Draw a polygon in javafx - java

I'm trying to draw a polygon in javafx adding to its array of point the mouse coordinates. My problem is that I don't know how to make it stop when I click something else from the mouse left click.
Polygon polygon = new Polygon();
rootPane.setOnMouseClicked((MouseEvent mouseEvent) -> {
do {
polygon.getPoints().addAll(mouseEvent.getX(),mouseEvent.getY());
} while(mouseEvent.getButton().equals(mouseEvent.isSecondaryButtonDown()));
});
rootPane.getChildren().add(polygon);

You can create a reference to a polygon. If it's the first click, then the polygon will be null, so create a new one and add it to your pane. Then keep adding points until you get a right click, at which point you just set the polygon back to null, so that the next left click starts a new polygon again.
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class DrawPolygon extends Application {
private Polygon currentPolygon ;
#Override
public void start(Stage primaryStage) {
Pane rootPane = new Pane();
rootPane.setMinSize(600, 600);
rootPane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
if (currentPolygon == null) {
currentPolygon = new Polygon();
currentPolygon.setStroke(Color.BLACK);
rootPane.getChildren().add(currentPolygon);
}
currentPolygon.getPoints().addAll(e.getX(), e.getY());
} else {
currentPolygon = null ;
}
});
Scene scene = new Scene(rootPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can play with various ideas around this to get different user experiences, e.g.
rootPane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
if (currentPolygon == null) {
currentPolygon = new Polygon();
currentPolygon.getPoints().addAll(e.getX(), e.getY());
currentPolygon.setStroke(Color.BLACK);
rootPane.getChildren().add(currentPolygon);
}
currentPolygon.getPoints().addAll(e.getX(), e.getY());
} else {
currentPolygon = null ;
}
});
rootPane.setOnMouseMoved(e -> {
if (currentPolygon != null) {
currentPolygon.getPoints().set(currentPolygon.getPoints().size()-2, e.getX());
currentPolygon.getPoints().set(currentPolygon.getPoints().size()-1, e.getY());
}
});

The problem is that your code gets stuck in one single event. Even when you move your mouse, or release the mouse button, the values of that event instance you are working with, are not going to change.
Think of the event as a single state. When something important happens (in your case, a mousebutton is clicked), javafx will call your mouseEventHandler using a MouseEvent instance with the state of the mouse at that verry moment. When you click again javafx will create a new instance with new values, and call the eventHandler again.
In order to make this work, you need a different mouse event (or modify it slightly so its only setting a point on a single mouse click). You need to lose the (infinite) while-loop as it is both blocking the EventThread, and not working for the stuff you need it to do. So something like this might work a little better.
// this will add a point for every (secondary)mousebutton click
rootPane.setOnMouseClicked((MouseEvent me) -> {
if(me.isSecondaryButtonDown())
polygon.getPoints().addAll(me.getX(),me.getY());
});
// this will add a point for every mousemovement while the secondary mousebutton is down.
rootPane.setOnMouseMoved((MouseEvent) me -> {
if(me.isSecondaryButtonDown())
polygon.getPoints().addAll(me.getX(),me.getY());
});
Now there is a MouseDragEvent, but thats mostly for moving data (like images and files), but I wouldnt recommend it. In your case it isnt much usefull, and its behavior is quit buggy still.
I hope this helped you in the right direction.

Related

Mouse release event doesn't happen on Ubuntu when target Node changes scene?

This question deals with mouse behavior across operating systems; specifically, my code works on Windows and Mac OS X, but not on Ubuntu.
Ultimately what I am trying to do is make a special Pane subclass ("ConvertiblePane") that exists within a parent Pane on a main stage/scene, but is magically transferred into its own temporary stage/scene when dragged, thus becoming independent and able to be placed anywhere on the screen. When the user releases the mouse button, the ConvertiblePane should drop back onto its original parent Pane and lose the temporary stage. (In my full program, the original parent Stage resizes/repositions itself to accommodate the ConvertiblePane wherever it is dropped.)
This brings me to my issue. When I press the mouse on the ConvertiblePane, it triggers a MousePress within the main scene as expected, at which point the ConvertiblePane is moved to the temporary stage. As I drag the mouse, it triggers MouseDrag within the temporary scene and moves the temp stage. Ok, great.
When I release the mouse button, however, I experience different behavior on different operating systems. On Windows (7) and Mac OS X (10.12.6), a MouseRelease occurs in the temp scene, sending the pane back to its original parent in the main stage as expected. On Ubuntu, however, no MouseRelease seems to be generated in either the main scene or the temp scene.
Here's the relevant code as an MCV example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ConvertibleTest extends Application {
#Override
public void start(Stage primaryStage) {
// Set up the main stage and scene with a
// Pane as root and a ConvertiblePane child:
primaryStage.initStyle(StageStyle.TRANSPARENT);
Pane root = new Pane();
ConvertiblePane conv = new ConvertiblePane();
root.getChildren().add(conv);
Scene scene = new Scene(root, 400, 400, Color.PINK);
primaryStage.setTitle("Convertible Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class ConvertiblePane extends Pane
{
private final Group TEMP_ROOT = new Group();
private final Stage TEMP_STAGE = new Stage(StageStyle.TRANSPARENT);
private final Scene TEMP_SCENE = new Scene(TEMP_ROOT);
private Pane originalParent = null;
private double deltaX = 0.0;
private double deltaY = 0.0;
private String name = null;
public void onMousePress(MouseEvent event)
{
// Save deltaX/Y for later:
Point2D delta = this.sceneToLocal(event.getX(), event.getY());
deltaX = delta.getX();
deltaY = delta.getY();
if (!isIndependent())
{
makeIndependent();
}
}
public void onMouseDrag(MouseEvent event)
{
// Keep the TEMP_STAGE relative to the original click point:
TEMP_STAGE.setX(event.getScreenX()-deltaX);
TEMP_STAGE.setY(event.getScreenY()-deltaY);
}
public void onMouseRelease(MouseEvent event)
{
if (isIndependent())
{
returnToParent();
}
}
public ConvertiblePane()
{
this.setPrefSize(100, 100);
this.setBackground(new Background(new BackgroundFill(Color.GREEN, new CornerRadii(10), Insets.EMPTY)));
this.setVisible(true);
// Attach event code and report to System.out what is happening:
this.setOnMousePressed((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Pressed as Independent");
else
System.out.println("Pressed as Child");
onMousePress(event);
});
this.setOnMouseDragged((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Dragged as Independent");
else
System.out.println("Dragged as Child");
onMouseDrag(event);
});
this.setOnMouseReleased((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Released as Independent");
else
System.out.println("Released as Child");
onMouseRelease(event);
});
}
public boolean isIndependent()
{
// Return whether this ConvertiblePane is "independent" (exists in its own temp scene)
return this.getScene() == TEMP_SCENE;
}
public void makeIndependent()
{
// Get the point where this ConvertiblePane appears on screen:
Point2D screenPt = this.localToScreen(0, 0);
// Save the originaParent of this ConvertiblePane; we will return to it later:
originalParent = (Pane)getParent();
// Remove this ConvertiblePane from its originalParent:
originalParent.getChildren().remove(this);
// Set this ConvertiblePane as the root of the TEMP_SCENE on the TEMP_STAGE:
TEMP_SCENE.setRoot(this);
TEMP_STAGE.setScene(TEMP_SCENE);
System.out.println("Transferred to TEMP.");
this.relocate(0, 0);
// Show the TEMP_STAGE in the same location on screen where this ConvertiblePane originally was:
TEMP_STAGE.setX(screenPt.getX());
TEMP_STAGE.setY(screenPt.getY());
TEMP_STAGE.show();
}
public void returnToParent()
{
// Reset deltas:
deltaX = 0;
deltaY = 0;
// Get the location of this ConvertiblePane on screen:
Point2D screenPt = this.localToScreen(0, 0);
// Set TEMP_ROOT as the root of TEMP_SCENE; this will allow us to detach
// this ConvertiblePane from being the scene root (since root cannot == null).
TEMP_SCENE.setRoot(TEMP_ROOT);
// Hide the TEMP_STAGE:
TEMP_STAGE.hide();
// Add this ConvertiblePane back to the originalParent:
originalParent.getChildren().add(this);
System.out.println("Transferred to MAIN.");
// Relocate this ConvertiblePane within the originalParent to maintain its position on screen
Point2D parentPt = originalParent.screenToLocal(screenPt);
this.relocate(parentPt.getX(), parentPt.getY());
}
}
As you can see, there is some basic reporting in the event handling methods; and the makeIndependent() and returnToParent() methods output "Transferred to TEMP." and "Transferred to MAIN." respectively.
If I click the mouse on the ConvertiblePane, drag a few pixels, and release it, this is the output:
(on Windows or Mac OS X)
Pressed as Child
Transferred to TEMP.
Dragged as Independent
Dragged as Independent
Dragged as Independent
Released as Independent
Transferred to MAIN.
(on Ubuntu)
Pressed as Child
Transferred to TEMP.
Dragged as Independent
Dragged as Independent
Dragged as Independent
I have also tried adding event filters to the two Scenes; but the result is the same: MouseRelease occurs on Win/Mac, but not Ubuntu.
If anyone can explain this behavior, or suggest something, that would be great. Alternatively... is there any "global" (pre-scene) creation of MouseEvents that I could catch? I mean, I don't really care about the details of the mouse release; I just want an event to tell me when to add the ConvertiblePane back to the main stage.
Thanks!
After spending several weeks on this, I couldn't find a way to fire a proper MouseReleased event on Ubuntu for this situation; however, I did come up with a hack that does the job well enough. Basically, instead of being notified when MouseReleased occurs, I'm checking every 10 milliseconds to see whether the mouse button is no longer down.
Explanation: When the node is transferred to the temporary Scene, the Timeline is started to "move" the mouse pointer in place every 10 milliseconds. This triggers either a MouseDragged event (if the mouse button is still down) or a MouseMoved event (if the mouse button is up); so I can then simulate the MouseReleased event and call my procedure for adding the node back to the main stage. At that point, of course, I also stop the Timeline.
Here's the relevant code to demonstrate this; perhaps it will be of use to someone else as well.
// The robot is needed to "move" the mouse in place,
// triggering a MOUSE_MOVED event.
private static Robot robot = null;
static {
try {
robot = new Robot();
} catch (AWTException ex) {
Logger.getLogger(ConvertiblePane.class.getName()).log(Level.SEVERE, null, ex);
}
}
// clickWaiter will move the mouse in place every 10 milliseconds,
// triggering a MOUSE_MOVED event if the mouse is no longer pressed.
private final Timeline clickWaiter = new Timeline(new KeyFrame(Duration.millis(10), (ActionEvent event) -> {
// Get the mouse position from MouseInfo class.
Point mouse = MouseInfo.getPointerInfo().getLocation();
// "Move" the mouse in place to trigger a mouseMove or mouseDrag event.
robot.mouseMove(mouse.x, mouse.y);
}));
public ConvertiblePane()
{
...
// MOUSE_MOVED will be triggered by the robot when the mouse button is no longer pressed:
TEMP_SCENE.addEventFilter(MouseEvent.MOUSE_MOVED, (MouseEvent event) ->
{
if (!event.isPrimaryButtonDown())
{
System.out.println("Temp.release (sim)");
clickWaiter.stop();
// Simulate MOUSE_RELEASED event.
}
});
}
public void makeIndependent()
{
...
// Start the clickWaiter, part of the Linux hack:
clickWaiter.playFromStart();
}

JavaFX - Notify when user drags mouse from one Node to another?

I have the following basic GUI for demonstration:
I'm trying to achieve the following functionality but I've exhausted all avenues that I've attempted.
User can left click on any of the ImageView's and it will create an
arrow that follows the user's cursor around until the user let's go of
the mouse button. (arrow start x,y is where he clicked and end x,y is
where his mouse currently is) If the user clicked on the Red
ImageView and dragged it over the Blue ImageView and then let go,
the program would print User just clicked from R to B
If the user clicked on the Red ImageView and let go of the mouse but
was not over a different ImageView, the program would print User
just clicked from R but did not target a different ImageView.
Under all circumstances, the arrow will appear when the user clicks on
the ImageView and will disappear the second he lets go of the mouse.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.util.HashMap;
public class Test extends Application
{
public static int HEIGHT = 500, WIDTH = 600;
#Override
public void start(Stage primaryStage) throws Exception
{
ImageView blue = new ImageView(new Image("blue.png")),
red = new ImageView(new Image("red.png")),
dark = new ImageView(new Image("dark.png"));
// Final array as to bypass the `final` requirement of event handler inner classes.
final ImageView[] hoveredOver = new ImageView[1];
final Line[] linePtr = new Line[1];
linePtr[0] = new Line();
linePtr[0].setStrokeWidth(10);
HashMap<ImageView, Character> lookup = new HashMap<ImageView, Character>(3)
{{
put(blue, 'B');
put(red, 'R');
put(dark, 'D');
}};
for (ImageView i : new ImageView[] { blue, red, dark })
{
i.setFitWidth(150);
i.setFitHeight(150);
// Set the anchor points of the click and display the arrow.
i.setOnMousePressed(e -> {
linePtr[0].setStartX(e.getX());
linePtr[0].setStartY(e.getY());
linePtr[0].setVisible(true);
});
// Move the arrow as the mouse moves.
i.setOnMouseDragged(e -> {
linePtr[0].setEndX(e.getX());
linePtr[0].setEndY(e.getY());
});
i.setOnMouseReleased(e -> {
// Not null means that the user WAS actually just now hovering over an imageview.
if (hoveredOver[0] != null)
System.out.printf("The user clicked from %c to %c!\n", lookup.get(i), lookup.get(hoveredOver[0]));
// Null means the user is not over an ImageView.
else
System.out.printf("The user initially clicked %c but did not drag to another Imageview.\n", lookup.get(i));
linePtr[0].setVisible(false);
});
// If the user enters ANY of the ImageViews,
// Set a variable so that the drag release listener
// can know about it!
i.setOnMouseDragOver(e -> hoveredOver[0] = i);
i.setOnMouseDragExited(e -> hoveredOver[0] = null);
}
blue.setX(400);
blue.setY(250);
red.setY(300);
red.setX(50);
/*
In this example I'm using a Pane but in my real program
I might be using a VBOX HBOX etc where I cannot freely move stuff around as I'd like.
This makes things extremely difficult and without using a 'Pane'
I don't know how this can even be done. Suggestions?
*/
Pane pneRoot = new Pane(blue, red, dark, linePtr[0]);
Scene scene = new Scene(pneRoot, WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
This was my best attempt and it's not even close. It moves a line (not an arrow, and ideally I want my arrow to curve as it moves much like this example image from a popular video game) but does not suit my needs. It cannot detect however when I let go while 'dragging over' an ImageView.
Is there a better way to do this? I feel like I can't simply the code I have down any further but there MUST be another way.
Java is an object-oriented language. The basic idea is that you create classes to represent the data you are modeling and then create objects from those classes. If you are tying things together with arbitrary maps to look things up, and arrays kicking around for no apparent reason, you are starting in the wrong place.
JavaFX has a system of observable properties. These wrap objects in a mutable way and can be observed so you can respond to changes.
Make sure you read and understand the documentation on MouseEvents and MouseDragEvents. There are three different modes for handling dragging. For events (mouse drag events) to be sent to nodes other than the one on which the drag was initiated during a mouse drag, you need to be in full "press-drag-release gesture" mode. You can activate this mode by calling startFullDrag() on the node when responding to a dragDetected event.
I would start with something like
public class NamedDragAwareImageView {
private final ObjectProperty<NamedDragAwareImageView> source ;
private final ObjectProperty<NamedDragAwareImageView> destination ;
private final String name ;
private final ImageView imageView ;
public NamedDragAwareImageView(ObjectProperty<NamedDragAwareImageView> source,
ObjectProperty<NamedDragAwareImageView> destination,
String name, String resource) {
this.source = source ;
this.destination = destination ;
this.name = name ;
this.imageView = new ImageView(new Image(resource));
imageView.setOnDragDetected(e -> {
source.set(this);
destination.set(null);
imageView.startFullDrag();
});
imageView.setOnMouseDragReleased(e -> {
if (source.get() != null && source.get() != this) {
destination.set(this);
}
});
// other image view config...
}
public ImageView getView() {
return imageView ;
}
public String getName() {
return name ;
}
}
Then you can do things like:
// observable properties to represent start and end nodes for drag:
ObjectProperty<NamedDragAwareImageView> source = new SimpleObjectProperty<>();
ObjectProperty<NamedDragAwareImageView> destination = new SimpleObjectProperty<>();
Pane root = new Pane();
// create your named image views, referencing the source and destination
// and add their image views to root, e.g.
NamedDragAwareImageView red = new NamedDragAwareImageView(source, destination, "Red", "red.png");
root.getChildren().add(red.getView());
// recommend using SVG paths (i.e. javafx.scene.shape.Path) for the arrow
// easy to draw programmatically, easy to manipulate elements etc:
Path arrowHead = new Path();
MoveTo arrowHeadStart = new MoveTo();
arrowHead.getElements().add(arrowHeadStart);
arrowHead.getElements().addAll(/* draw an arrow head with relative path elements... */);
arrowHead.setVisible(false);
// avoid arrowHead interfering with dragging:
arrowHead.setMouseTransparent(true);
// this will contain a MoveTo and a bunch of LineTo to follow the mouse:
Path arrowLine = new Path();
arrowLine.setMouseTransparent(true);
root.getChildren().addAll(arrowHead, arrowLine);
// change listener for source. source is set when drag starts:
source.addListener((obs, oldSource, newSource) -> {
if (newSource == null) return ;
arrowHeadStart.setX(/* x coord based on newSource */);
arrowHeadStart.setY(/* similarly */);
arrowHead.setVisible(true);
});
// change listener for destination. destination is only set
// when drag complete:
destination.addListener((obs, oldDestination, newDestination) -> {
if (newDestination != null) {
System.out.println("User dragged from "+source.get().getName()+
" to "+destination.get().getName());
}
});
root.setOnMouseDragOver(e -> {
if (source.get()==null && destination.get()!=null) {
// update arrowStart position
// add line element to arrowLine
}
});
root.setOnMouseReleased(e -> {
// clear arrow:
arrowHead.setVisible(false);
arrowLine.getElements().clear();
});

JavaFX Container Draggable

i have a draggable container in JavaFX. This Container is implemented in a PopUp. I can drag the Container, but if i drag it, the mouse-event hasn't a constant coordinate. There switchs the mouse position very fast between 2 fix Positions.
Thats my code:
container.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
// TODO Auto-generated method stub
if(dragAct==true){
//Set the Position of the PopUp to the Position of the Mouse
setX(me.getX());
setY(me.getY());
}
}
});
The containe is a VBox. The Main-Class is a extended Version of the PopUp-Class.
JavaFX Container Draggable
The setX and setY methods you call set the position of the Popup in screen coordinates. The calls to me.getX() and me.getY() give you the coordinates of the mouse relative to the container. When you move the popup, the container also moves, so the position of the mouse has changed relative to the container. So your calculations are not going to be consistent from one dragging event to the next.
The fix is to compute the positions relative to something that is fixed. Since you are moving the popup, which is a window, the fixed coordinate system is the screen coordinate system. MouseEvent has getScreenX and getScreenY methods you can use to easily get these.
I like to implement dragging by saving the last mouse location and then computing the distance moved on drag. There are other (possibly less verbose) ways to do this but to me this is clearest:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class DraggingPopup extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Show popup");
button.setOnAction(event -> showDraggablePopup(primaryStage));
StackPane root = new StackPane(button);
Scene scene = new Scene(root, 250, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
private void showDraggablePopup(Stage owner) {
Popup popup = new Popup();
Button closeButton = new Button("Close");
closeButton.setOnAction(event -> popup.hide());
StackPane container = new StackPane(closeButton);
container.setStyle("-fx-background-color: steelblue;");
container.setMinWidth(300);
container.setMinHeight(125);
// Dragging implementation:
ObjectProperty<Point2D> mouseLocation = new SimpleObjectProperty<>();
container.setOnMousePressed(event ->
mouseLocation.set(new Point2D(event.getScreenX(), event.getScreenY())));
container.setOnMouseDragged(event -> {
if (mouseLocation.get() != null) {
double x = event.getScreenX();
double deltaX = x - mouseLocation.get().getX() ;
double y = event.getScreenY();
double deltaY = y - mouseLocation.get().getY() ;
//in case of 2 or more computer screens this help me to avoid get stuck on 1 screen
if(Math.abs(popup.getX()-x)>popup.getWidth()){
popup.setX(x);
popup.setY(y);
}else {
popup.setX(popup.getX() + deltaX);
popup.setY(popup.getY() + deltaY);
}
mouseLocation.set(new Point2D(x, y));
}
});
container.setOnMouseReleased(event -> mouseLocation.set(null));
popup.getScene().setRoot(container);
popup.show(owner);
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX - Sequential Transition - playing back and forth (step by step)

I'm struggling with making an animation showing the searchin in Binary Search Tree in JavaFX.
The goal is to make a visualization of comparing the value of tree node with possibility to:
pause and play it any time
being able to play the animation backwards (to go at least one step back),
give a user an ability to play the animation step-by-step or whole at once.
The preview of visualization
My vision was to make a series of TranslateTransitions(TT) added in one SequentialTransition(ST). If the animation is marked as "step-by-step" the each TT pauses the whole ST in their OnFinished handler. However this kinda works only for going one-way.
My question is. What is the best approach to maintain going fluent and step-by-step animation in reverse direction ?
I was thinking about:
maybe making another sequence of inverse transitions (but how to tell
it from which step to continue ?)
somehow work with rate property ? is it possible to change it while the ST is running ?
Thank you very much for your answers.
In general, you can change the rate property of an Animation while it is in progress. The idea of using a SequentialTransition is appealing, but it doesn't work as easily as you might think. The problem arises when the sequential transition is paused at the boundary between two individual transitions: you don't have any way to tell which of the individual transitions is considered the current one (i.e. the next one or the previous one). So when you try to reverse the rate and play, the sequential transition can get confused and immediately think it's at the end of the one it's trying to play.
You might be able to hack this a little by using Animation.getCurrentTime() and Animation.jumpTo(...) to "nudge" the sequential transition a tiny amount in the correct direction before starting to play any step, but I think it's probably easier just to manage the individual transitions on their own instead of using a SequentialTransition.
Here's a simple example of using this technique to move a rectangle around:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ReverseSequentialTransitionTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rect = new Rectangle(50, 50, 250, 150);
rect.setFill(Color.color(.5, .5, .1));
pane.getChildren().add(rect);
TranslateTransition ttForward = new TranslateTransition(Duration.seconds(1), rect);
ttForward.setFromX(0);
ttForward.setToX(400);
TranslateTransition ttDown = new TranslateTransition(Duration.seconds(1), rect);
ttDown.setFromY(0);
ttDown.setToY(100);
TranslateTransition ttBackward = new TranslateTransition(Duration.seconds(1), rect);
ttBackward.setFromX(400);
ttBackward.setToX(0);
TranslateTransition ttUp = new TranslateTransition(Duration.seconds(1), rect);
ttUp.setFromY(100);
ttUp.setToY(0);
List<Animation> transitions = Arrays.asList(ttForward, ttDown, ttBackward, ttUp);
IntegerProperty nextTransitionIndex = new SimpleIntegerProperty();
Button playButton = new Button("Play Forward");
playButton.setOnAction(event -> {
int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index+1));
anim.setRate(1);
anim.play();
});
Button reverseButton = new Button("Play backward");
reverseButton.setOnAction(event -> {
int index = nextTransitionIndex.get()-1;
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index));
anim.setRate(-1);
anim.play();
});
// This is not really part of the answer to the current question, but the
// next three statements just disable the buttons when appropriate.
// This is a binding which is true if and only if any of the transitions are
// currently running:
BooleanBinding anyPlaying = createAnyPlayingBinding(transitions);
// Disable playButton if we are at the end of the last transition, or if
// any transitions are playing:
playButton.disableProperty().bind(
nextTransitionIndex.greaterThanOrEqualTo(transitions.size())
.or(anyPlaying)
);
// Disable reverseButton if we are at the beginning of the first transition,
// or if any transitions are currently playing:
reverseButton.disableProperty().bind(
nextTransitionIndex.lessThanOrEqualTo(0)
.or(anyPlaying));
HBox controls = new HBox(5);
controls.setAlignment(Pos.CENTER);
controls.getChildren().addAll(playButton, reverseButton);
BorderPane root = new BorderPane();
root.setCenter(pane);
root.setBottom(controls);
primaryStage.setScene(new Scene(root, 800, 400));
primaryStage.show();
}
private BooleanBinding createAnyPlayingBinding(List<Animation> transitions) {
return new BooleanBinding() {
{ // Anonymous constructor
// bind to the status properties of all the transitions
// (i.e. mark this binding as invalid if any of the status properties change)
transitions.stream()
.map(Animation::statusProperty)
.forEach(this::bind);
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
return transitions.stream()
.anyMatch(anim -> anim.getStatus()==Status.RUNNING);
}
};
}
public static void main(String[] args) {
launch(args);
}
}
In JDK 7, the event handler for the playButton looks like this:
playButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
nextTransitionIndex.set(index + 1) ;
}
});
anim.setRate(1);
anim.play();
}
});
and similarly for reverseButton. You will need to declare a couple of things as final as well. The createAnyPlayingBinding method is something like
private BooleanBinding createAnyPlayingBinding(final List<Animation> transitions) {
return new BooleanBinding() {
{
for (Animation transition : transitions) {
this.bind(transition.statusProperty();
}
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
for (Animation anim : transitions) {
if (anim.getStatus() == Status.RUNNING) {
return true ;
}
}
return false ;
}
};
}

How to know if the resizing of a window is done from the left or from the right?

I am using JavaFX to build a GUI and I'm having problems on knowing the mouse location when resized. The idea is that if a mouse gets on the very edge of the GUI it changes to a double sided arrow indicating that you can now press the mouse and resize the window.
I need the location of the mouse pointer on this edge, but I don't know how to do that. I need to know in which direction the window is resized.
Updated Added new options and discussed pros and cons.
This is tricky. The issue is that the window keeps track of its top, left, width, and height. When it is resized from the right or bottom, things are easy enough: the width or height change. But when it is resized from the left, both x and width must change. These two changes do not happen atomically, as x and width are stored as two independent properties.
The first approach to this is to keep track of the mouse coordinates, and just see if it's in the left half or the right half. (Obviously you can do the same with the height.) This approach is independent of any implementation details, but the user can cause it to fail by being extremely careful with the mouse. If you move the mouse to the right edge of the window to the exact pixel of the window boundary, then resize, you can see incorrect output.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
class MouseLocation {
double x,y ;
}
MouseLocation mouseLocation = new MouseLocation();
scene.setOnMouseMoved(event -> {
mouseLocation.x = event.getX();
mouseLocation.y = event.getY();
});
primaryStage.widthProperty().addListener((obs, oldWidth, newWidth) -> {
if (mouseLocation.x < primaryStage.getWidth() / 2) {
System.out.println("Resized from left");
} else {
System.out.println("Resized from right");
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The second approach is to keep track of the last known horizontal range of the window (minX and maxX), and update that range when the width changes. Then you can check to see whether the minX or maxX has changed. The problem with this approach is that it's dependent on undocumented implementation details. It appears (on my system, using the current version, etc) that when the window is resized from the left, x is changed first, then the width is changed. If that were to change in a subsequent release, the following would break:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
class MutableDouble {
double value ;
}
MutableDouble windowLeftEdge = new MutableDouble();
primaryStage.widthProperty().addListener((obs, oldWidth, newWidth) -> {
if (primaryStage.getX() == windowLeftEdge.value) {
System.out.println("Resized from right");
} else {
System.out.println("Resized from left");
}
windowLeftEdge.value = primaryStage.getX() ;
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The third approach (that I can think of) is to coalesce changes that happen quickly into a single change. This is a bit tricky to program correctly, so instead of doing it from scratch, I used the third party ReactFX framework which models "event streams" and has a built-in mechanism for combining events that happen in quick succession. This is probably the most robust of the three solutions presented here, but at the cost of either a degree of complexity, or the inclusion of an external framework.
import java.time.Duration;
import org.reactfx.Change;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ReactFXVersion extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
ObservableValue<Bounds> windowBounds = Bindings.createObjectBinding(() ->
new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()),
primaryStage.xProperty(), primaryStage.yProperty(), primaryStage.widthProperty(), primaryStage.heightProperty());
EventStream<Change<Bounds>> bounds = EventStreams.changesOf(windowBounds)
.reduceSuccessions((previousChange, nextChange) ->
new Change<>(previousChange.getOldValue(), nextChange.getNewValue()),
Duration.ofMillis(10));
bounds.subscribe(boundsChange -> {
Bounds newBounds = boundsChange.getNewValue();
Bounds oldBounds = boundsChange.getOldValue();
if (newBounds.getWidth() != oldBounds.getWidth()) {
if (newBounds.getMinX() != oldBounds.getMinX()) {
System.out.println("Resized from left");
} else if (newBounds.getMaxX() != oldBounds.getMaxX()) {
System.out.println("Resized from right");
}
}
if (newBounds.getHeight() != oldBounds.getHeight()) {
if (newBounds.getMinY() != oldBounds.getMinY()) {
System.out.println("Resized from top");
} else if (newBounds.getMaxY() != oldBounds.getMaxY()) {
System.out.println("Resized from bottom");
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories