How to create a centerXProperty in a Rectangle to bind to? - javafx

I am developing a program for video annotation. Objects seen in a video may be marked as objects of interest and if they are interacting, the user may draw a line between two annotations. On the visible level, bject annotations are basically transparent Rectangles and relations between them are Lines. At this point it is easy to compute the center of a rectangle, but I am not able to bind the start and end of a Line to the center of the corresponding Rectangle. I have tried the following approaches:
Creating two DoubleBindings inside the rectangle class that computes the center x and y:
private DoubleBinding centerXBinding = new DoubleBinding() {
#Override
protected double computeValue() {
return getX() + getWidth() / 2;
}
};
and then bound it to the newly created Line:
`currentRelation.startXProperty().bind(startShape.centerXBinding());`
in the controller…
The result is ok at first, the line start and end points are exactly where I want to haven them, but when a Rectangle gets dragged to another position, the line end does not move anywhere!
Does anyone see the problem?
UPDATE:
The movement of a Rectangle is done by computing an offset and updating the translation values like translateX:
public class MyRectangle extends Rectangle {
private double orgSceneX;
private double orgSceneY;
private double orgTranslateX;
private double orgTranslateY;
private void initEventHandling() {
this.setOnMousePressed(mousePress -> {
if (mousePress.getButton() == MouseButton.PRIMARY) {
orgSceneX = mousePress.getSceneX();
orgSceneY = mousePress.getSceneY();
orgTranslateX = ((MyRectangle) mousePress.getSource()).getTranslateX();
orgTranslateY = ((MyRectangle) mousePress.getSource()).getTranslateY();
mousePress.consume();
} else if (mousePress.getButton() == MouseButton.SECONDARY) {
System.out.println(LOG_TAG + ": right mouse button PRESS on " + this.getId() + ", event not consumed");
}
});
this.setOnMouseDragged(mouseDrag -> {
if (mouseDrag.getButton() == MouseButton.PRIMARY) {
double offsetX = mouseDrag.getSceneX() - orgSceneX;
double offsetY = mouseDrag.getSceneY() - orgSceneY;
double updateTranslateX = orgTranslateX + offsetX;
double updateTranslateY = orgTranslateY + offsetY;
this.setTranslateX(updateTranslateX);
this.setTranslateY(updateTranslateY);
mouseDrag.consume();
}
});
}
}

Your binding needs to invalidate when either the xProperty or widthProperty are invalidated (so that anything bound to it knows to recompute). You can do this by calling the bind method in the custom binding's constructor:
private DoubleBinding centerXBinding = new DoubleBinding() {
{
bind(xProperty(), widthProperty());
}
#Override
protected double computeValue() {
return getX() + getWidth() / 2;
}
};
Note you can also do
private DoubleBinding centerXBinding = xProperty().add(widthProperty().divide(2));
The choice between the two is really just a matter of which style you prefer.
Binding to the x and width properties assumes, obviously, that you are moving the rectangle by changing one or both of those properties. If you are moving the rectangle by some other means (e.g. by changing one of its translateX or translateY properties, or by altering its list of transformations), then you need to observe the boundsInParentProperty instead:
private DoubleBinding centerXBinding = new DoubleBinding() {
{
bind(boundsInParentProperty());
}
#Override
protected double computeValue() {
Bounds bounds = getBoundsInParent();
return (bounds.getMinX() + bounds.getMaxX()) / 2 ;
}
}
This binding will give the x-coordinate of the center of the rectangle in the parent's coordinate system (which is usually the coordinate system you want).

Related

Bindable Property destroying SkiaSharp Rectangle

I have a very strange issue. In the following code at 261 in the TouchActioNTypeCancelled i want to call a Method which should set the CroppingRect.Rect to my bindable Property testImage. The Strange thing: When testIamge is a normal Property, not bindable, everything works fine. I can move the croppign rectangle with all corners in the view. If i now change the Proeprty to a bindable property when i start movign the rectangle on one corner in the app i can move the rectangle only from the opposite corners again and if i press the opposite corner, the rectangle buggs back into its original position. I don't understand how this can happen. I did not yet consume the Property in the view since i wanted to check first if it works without consuming it.
This is the way it works with a npormal property. In fact, this is, how i want it to work with a bindable proeprty as well:
Workign example(only with normal property)
This, however, is how it works with the bindable property currently:
Not working example(Bindable property)
public static readonly BindableProperty testImageProperty =
BindableProperty.Create(nameof(testImage), typeof(SKRect), typeof(SKRect));
public SKRect testImage
{
get
{
return (SKRect)GetValue(testImageProperty);
}
set
{
SetValue(testImageProperty, value);
}
}
//public SKRect testImage { get; set; }
void OnTouchEffectTouchAction(object sender, TouchActionEventArgs args)
{
int i = 0;
SKPoint pixelLocation = ConvertToPixel(args.Location);
SKPoint bitmapLocation = inverseBitmapMatrix.MapPoint(pixelLocation);
switch (args.Type)
{
case TouchActionType.Pressed:
// Convert radius to bitmap/cropping scale
float radius = inverseBitmapMatrix.ScaleX * RADIUS;
// Find corner that the finger is touching
int cornerIndex = croppingRect.HitTest(bitmapLocation, radius);
if (cornerIndex != -1 && !touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = new TouchPoint
{
CornerIndex = cornerIndex,
Offset = bitmapLocation - croppingRect.Corners[cornerIndex]
};
touchPoints.Add(args.Id, touchPoint);
}
break;
case TouchActionType.Moved:
if (touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = touchPoints[args.Id];
croppingRect.MoveCorner(touchPoint.CornerIndex,
bitmapLocation - touchPoint.Offset);
InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchPoints.ContainsKey(args.Id))
{
SetMap();
touchPoints.Remove(args.Id);
}
break;
}
}
private void SetMap()
{
testImage = croppingRect.Rect;
Console.WriteLine("test");
}

Preventing overlapping shapes while dragging on a Pane

I've looked at similar questions but they all are concerned with collision detection rather than preventing overlap. I've gotten most of it to work with the below code:
private final EventHandler<MouseEvent> onPress = mouseEvent -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : getAllShapes()) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
However, the problem is, once there is a tiniest bit of overlap, the Shape is no longer draggable at all. Meaning, if I drag a shape to another, once they become essentially tangent, neither of them are draggable anymore. What I want to happen is just that, for example, if you try to drag a circle onto another, the circle won't follow the mouse position as long as the future position of the drag will cause an overlap.
I can't figure out exactly how to accomplish this.
EDIT: Minimum Reproducible Example:
Main.java
public class Main extends Application {
static Circle circle1 = new DraggableCircle(100, 200);
static Circle circle2 = new DraggableCircle(200, 300);
static Circle[] circleList = new Circle[]{circle1, circle2};
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World");
Pane pane = new Pane();
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
pane.getChildren().addAll(circle1, circle2);
}
public static void main(String[] args) {
launch(args);
}
}
DraggableCircle.java
public class DraggableCircle extends Circle {
private double xDrag, yDrag;
public DraggableCircle(double x, double y) {
super(x, y, 30);
this.setFill(Color.WHITE);
this.setStroke(Color.BLACK);
this.setStrokeWidth(1.5);
this.addEventHandler(MouseEvent.MOUSE_PRESSED, onPress);
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDrag);
}
private final EventHandler<MouseEvent> onPress = (mouseEvent) -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
}
This also has an issue where dragging too quickly causes a noticeable overlap between the circles, before the drag detection ends.
A simple (imperfect) solution
The following algorithm will allow a node to continue to be dragged after an intersection has occurred:
Record the current draggable shape position.
Set the new position.
Check the intersection.
If an intersection is detected, reset the position to the original position.
An implementation replaces the drag handler in the supplied minimal example code from the question.
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
this.setCenterX(priorCenterX);
this.setCenterY(priorCenterY);
return;
}
}
}
};
This handler does work better than what you had, it does at least allow you to continue dragging after the intersection.
But, yes, if you drag quickly it will leave a visible space between nodes when it has detected that the drag operation would cause an intersection, which isn't ideal.
Also, the additional requirement you added in your comment about having the dragged shape glide along a border would require a more sophisticated solution.
Other potential solutions
I don't offer code for these more sophisticated solutions here.
One potential brute force solution is to interpolate the prior center with the new center and then, in a loop, slowly move the dragged object along the interpolated line until an intersection is detected, then just back it out to the last interpolated value to prevent the intersection. You can do this by calculating and applying a normalized (1 unit distance) movement vector. That might fix space between intersected nodes.
Similarly to get the gliding, on the intersection, you could just update either the interpolated x or y value rather than both.
There may be more sophisticated methods with geometry math applied, especially if you know shape geometry along with movement vectors and surface normals.
+1 for #jewelsea answer.
On top of #jewelsea answer, I would like to provide a fix for the "space between nodes" issue.
So you might have already observed that when you drag fast, it will not cover each and every pixel in the drag path. It varies with the speed of the drag. So when you decide to move it to the previous recorded point, we will do a quick math, to see if there is any gap between the two nodes, if yes:
We will do a math "to determine a point along a line which is at distance d" and move the drag circle to that point. Here..
start point of line is : previous recorded point
end point of line is : the intersected shape center
d is : the gap between the two shapes.
So the updated code to the #jewelsea answer is as below:
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Circle shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
Point2D cx = new Point2D(priorCenterX, priorCenterY);
Point2D px = new Point2D(shape.getCenterX(), shape.getCenterY());
double d = cx.distance(px);
if (d > getRadius() + shape.getRadius()) {
cx = pointAtDistance(cx, px, d - getRadius() - shape.getRadius());
}
this.setCenterX(cx.getX());
this.setCenterY(cx.getY());
return;
}
}
}
};
private Point2D pointAtDistance(Point2D p1, Point2D p2, double distance) {
double lineLength = p1.distance(p2);
double t = distance / lineLength;
double dx = ((1 - t) * p1.getX()) + (t * p2.getX());
double dy = ((1 - t) * p1.getY()) + (t * p2.getY());
return new Point2D(dx, dy);
}

How can I center the virtual keyboard on bottom before it is shown - javafx

How can I center the keyboard on the bottom of the screen?
I just have the sizes of the keyboard/PopupWindow after the board is already shown. Before calling show() every function returns 0.0 for the asked width. I could set the position correctly if I just knew the width before.
The keyboard size might change later, that's why I can't do it with a set size.
I am using the fx-onscreen-keyboard
My little service:
public class KeyboardService {
private double screenWidth = Screen.getPrimary().getVisualBounds().getWidth();
private double screenHight = Screen.getPrimary().getVisualBounds().getHeight();
private double keyboardPosX = 0.0;
private double keyboardPosY = 0.0;
private KeyBoardPopup keyboardPopup;
public KeyboardService() {
keyboardPopup = KeyBoardPopupBuilder.create().initLocale(Locale.GERMAN).build();
keyboardPopup.setAutoHide(true);
keyboardPopup.setConsumeAutoHidingEvents(false);
keyboardPopup.getKeyBoard().setScale(2.5);
keyboardPopup.getKeyBoard().setLayer(DefaultLayer.DEFAULT);
keyboardPopup.getKeyBoard().setOnKeyboardCloseButton((e) -> {
keyboardPopup.hide();
});
}
public void showKeyboard(Node node){
keyboardPosX = (screenWidth - keyboardPopup.getWidth())/2;
//keyboardPosX = (screenWidth - keyboardPopup.getKeyBoard().getWidth())/2;
keyboardPosY = screenHight;
keyboardPopup.show(node, keyboardPosX, keyboardPosY);
}}
The width of the keyboard is defined when the popup that holds it is laid out, what happens right after you call show.
The easy way to do this is by listening to the widthProperty of the KeyBoardPopup, to get the new value, and then moving the window of the popup accordingly.
This will do:
public KeyboardService() {
keyboardPopup = KeyBoardPopupBuilder.create().initLocale(Locale.GERMAN).build();
keyboardPopup.setAutoHide(true);
keyboardPopup.setConsumeAutoHidingEvents(false);
keyboardPopup.getKeyBoard().setScale(2.5);
keyboardPopup.getKeyBoard().setLayer(DefaultLayer.DEFAULT);
keyboardPopup.getKeyBoard().setOnKeyboardCloseButton((e) -> {
keyboardPopup.hide();
});
// listen to width changes and center
keyboardPopup.widthProperty().addListener((obs, ov, nv) -> {
keyboardPosX = (screenWidth - nv.doubleValue()) / 2d;
keyboardPopup.getScene().getWindow().setX(keyboardPosX);
});
}
public void showKeyboard(Node node) {
keyboardPosX = (screenWidth - keyboardPopup.getWidth())/2;
keyboardPosY = screenHeight;
keyboardPopup.show(node, keyboardPosX, keyboardPosY);
}

JavaFx Can't obtain stackPane position on Scene

I have a class which extends StackPane. Another one which makes a 20x20 2d Array of this class. I then translated their position somewhere on the scene. Right now, I can't obtain the position of a cell relative on the scene.
I tried Bounds, Point2D.zero and getMin..
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("mouse click detected! " + mouseEvent.getSource());
// boundsInScene = cell.localToScene(cell.getLayoutBounds());
// System.out.println("Cell width: " + boundsInScene.getMinX());
// System.out.println("Cell height: " + boundsInScene.getMinY());
// cell.localToScene(Point2D.ZERO);
int x = (int) cell.getTranslateX();
int y = (int) cell.getTranslateY();
System.out.println("X: " + x + "y :" + y);
}
});
Part of my code:
private Parent createContent() {
root = new Pane();
root.setPrefSize(640, 480);
for (int col = 0; col < y_Tiles; col++) {
for (int row = 0; row < x_Tiles; row++) {
cell = new Cell(row, col);
grid_Array[row][col] = cell;
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {});
root.getChildren().add(cell);
}
}
return root;
}
Bounds is resturning me 480 and 400. Another thing is that all cells return the same output for any of the methods I tried. Any idea how to cell the position of a cell on the scene?
PS:Sorry for bad english. Not native language
You shouldn't be using translateX/Y to set the location of your items. translateX/Y should just be used for temporary movements of an item, such as a bounce effect or a transition. layoutX/Y should be used to set the location of items. Normally layoutX/Y will be set automatically by a layout pane. If you aren't using a layout pane and are manually performing the layout in an unmanaged parent (such as a Pane or a Group), then you can manually set the layoutX/Y values as needed (relocate will do this).
Once you switch to setting the layout values rather than the translate values, then you determine the location of any node in a scene using the localToScene function.
Bounds boundsInScene = node.localToScene(node.getLayoutBounds());

Getting Coordinates after Rotate Transition JavaFX

I'm writing a game like Wheel of Fortune, with the Wheel is actually an JavaFX ImageView lay outed to the right most of window. This wheel is made up of 15 piece (24 degree for each).
Basically, after RotateTransition finish I want to get the coordinate, or bounding box of something like this of the ImageView in order to calculate the angle between that coordinate, the center of the image, and the needle. From that angle I can determine the score returned.
However, I'm confused on how to do this. I've tried to play around with bounding box, but it just doesn't solve the problem.
The code for this is as follow :
public class FXMLRoundSceneController {
#FXML
private AnchorPane backgroundAnchorPane;
#FXML
private ImageView wheel;
/* Wheel background Image*/
private Image img = new Image(getClass().getResource("/resources/WOF.png").toExternalForm());
#FXML
public void initialize() {
wheel.setImage(img);
}
#FXML
private void rotateWheel(MouseEvent mv) {
rotate(1355,5); // Just an example, the rotate angle should be randomly calculated on each MouseClicked event.
}
public void rotate(double angle, double duration) {
RotateTransition rt = new RotateTransition();
rt.setNode(wheel);
rt.setByAngle(angle);
rt.setDuration(Duration.seconds(duration));
rt.setCycleCount(1);
rt.setAutoReverse(false);
rt.setInterpolator(Interpolator.EASE_OUT);
rt.play();
rt.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ae) {
wheelBoundingBox();
}
});
}
public void wheelBoundingBox() {
double MX = wheel.getBoundsInParent().getMaxX();
double MY = wheel.getBoundsInParent().getMaxY();
double mX = wheel.getBoundsInParent().getMinX();
double mY = wheel.getBoundsInParent().getMinY();
double width = MX - mX;
double height = MY - mY;
Rectangle bb = new Rectangle(mX, mY, Color.TRANSPARENT);
bb.setWidth(width);
bb.setHeight(height);
bb.setStroke(Color.WHITE);
bb.setStrokeWidth(1);
backgroundAnchorPane.getChildren().add(bb);
}
}
Can anyone tell me a proper way to determine the coordinate or something like that to help you determine the score.
public static void main(String[] args) {
String[] prizes = new String[] {"100","200","300","400","500","600"};
int slotSize = 360 / prizes.length;
int angle = 359;
for (int i = 0; i < 3600; i++) // 10 spins
{
angle++;
if (angle>359) {
angle = 0;
}
// based on the current angle, determine the entry:
int entry = angle / slotSize;
System.out.println("angle "+angle+" entry "+entry+" prize "+prizes[entry]);
}
}

Resources