I'm trying to learn how JavaFX animations work, so I've tried to create an animation with Earth moving in a circle, based on this tutorial: https://gamedevelopment.tutsplus.com/tutorials/introduction-to-javafx-for-game-development--cms-23835.
For some reason, the scene I want to show is set in the stage, but the graphics or animations aren't. I'm pretty sure this one is trivial, I just can't seem to find the reason. Here's my Main class code:
public class Main extends Application {
private static Stage stage;
private static Window window;
private static Scene scWindow;
#Override
public void start(Stage primaryStage) {
stage = primaryStage;
stage.setTitle("Animation");
showWindow();
stage.show();
}
public static void showWindow() {
window = new Window();
scWindow = new Scene(window, 400, 400);
stage.setScene(scWindow);
}
public static void main(String[] args) {
launch(args);
}
}
and this is the Window class code:
public class Window extends BorderPane {
final long startNanoTime = System.nanoTime();
Image earth = new Image("http://icdn.pro/images/en/g/o/google-earth-icone-8927-128.png");
Image space = new Image("https://space-facts.com/wp-content/uploads/magellanic-clouds.png");
GraphicsContext gc;
public Window() {
Group root = new Group();
Canvas canvas = new Canvas(512, 512);
gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
gc.setFill(Color.BLACK);
Anim a = new Anim();
a.start();
}
private class Anim extends AnimationTimer {
public void handle(long currentNanoTime)
{
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
double x = 232 + 128 * Math.cos(t);
double y = 232 + 128 * Math.sin(t);
gc.drawImage( space, 0, 0 );
gc.drawImage( earth, x, y );
}
}
}
There's no exception thrown or anything, I have no idea what's wrong.
EDIT:
I know that I haven't put anything in the setCenter/Left etc. in the constructor, so actually if it is the reason, how should I put the animation e.g. on the center of the border pane?
You forgot to add root to the scene. Add something like
setCenter(root);
to the constructor of Window.
Or simply use the Canvas as center:
public Window() {
Canvas canvas = new Canvas(512, 512);
gc = canvas.getGraphicsContext2D();
setCenter(canvas);
gc.setFill(Color.BLACK);
Anim a = new Anim();
a.start();
}
I am relatively new to property bindings and I am looking for some high-level advice on how to approach a design problem, which I will try to describe a simple example of here.
Problem description
The goal in this example is to allow the user to specify a box/rectangular region interactively in a pannable and zoomable 2D space. The 2D screen-space in which the box is depicted, maps to a 2D "real-space" (e.g. voltage vs time cartesian space, or GPS, or whatever). The user should be able to zoom/pan his viewport vertically/horizontally at any time, thereby changing the mapping between these two spaces.
screen-space <-------- user-adjustable mapping --------> real-space
The user specifies the rectangle in his viewport by dragging borders/corners, as in this demo:
class InteractiveHandle extends Rectangle {
private final Cursor hoverCursor;
private final Cursor activeCursor;
private final DoubleProperty centerXProperty = new SimpleDoubleProperty();
private final DoubleProperty centerYProperty = new SimpleDoubleProperty();
InteractiveHandle(DoubleProperty x, DoubleProperty y, double w, double h) {
super();
centerXProperty.bindBidirectional(x);
centerYProperty.bindBidirectional(y);
widthProperty().set(w);
heightProperty().set(h);
hoverCursor = Cursor.MOVE;
activeCursor = Cursor.MOVE;
bindRect();
enableDrag(true,true);
}
InteractiveHandle(DoubleProperty x, ObservableDoubleValue y, double w, ObservableDoubleValue h) {
super();
centerXProperty.bindBidirectional(x);
centerYProperty.bind(y);
widthProperty().set(w);
heightProperty().bind(h);
hoverCursor = Cursor.H_RESIZE;
activeCursor = Cursor.H_RESIZE;
bindRect();
enableDrag(true,false);
}
InteractiveHandle(ObservableDoubleValue x, DoubleProperty y, ObservableDoubleValue w, double h) {
super();
centerXProperty.bind(x);
centerYProperty.bindBidirectional(y);
widthProperty().bind(w);
heightProperty().set(h);
hoverCursor = Cursor.V_RESIZE;
activeCursor = Cursor.V_RESIZE;
bindRect();
enableDrag(false,true);
}
InteractiveHandle(ObservableDoubleValue x, ObservableDoubleValue y, ObservableDoubleValue w, ObservableDoubleValue h) {
super();
centerXProperty.bind(x);
centerYProperty.bind(y);
widthProperty().bind(w);
heightProperty().bind(h);
hoverCursor = Cursor.DEFAULT;
activeCursor = Cursor.DEFAULT;
bindRect();
enableDrag(false,false);
}
private void bindRect(){
xProperty().bind(centerXProperty.subtract(widthProperty().divide(2)));
yProperty().bind(centerYProperty.subtract(heightProperty().divide(2)));
}
//make a node movable by dragging it around with the mouse.
private void enableDrag(boolean xDraggable, boolean yDraggable) {
final Delta dragDelta = new Delta();
setOnMousePressed((MouseEvent mouseEvent) -> {
// record a delta distance for the drag and drop operation.
dragDelta.x = centerXProperty.get() - mouseEvent.getX();
dragDelta.y = centerYProperty.get() - mouseEvent.getY();
getScene().setCursor(activeCursor);
});
setOnMouseReleased((MouseEvent mouseEvent) -> {
getScene().setCursor(hoverCursor);
});
setOnMouseDragged((MouseEvent mouseEvent) -> {
if(xDraggable){
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
centerXProperty.set(newX);
}
}
if(yDraggable){
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
centerYProperty.set(newY);
}
}
});
setOnMouseEntered((MouseEvent mouseEvent) -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(hoverCursor);
}
});
setOnMouseExited((MouseEvent mouseEvent) -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
});
}
//records relative x and y co-ordinates.
private class Delta { double x, y; }
}
public class InteractiveBox extends Group {
private static final double sideHandleWidth = 2;
private static final double cornerHandleSize = 4;
private static final double minHandleFraction = 0.5;
private static final double maxCornerClearance = 6;
private static final double handleInset = 2;
private final Rectangle rectangle;
private final InteractiveHandle ihLeft;
private final InteractiveHandle ihTop;
private final InteractiveHandle ihRight;
private final InteractiveHandle ihBottom;
private final InteractiveHandle ihTopLeft;
private final InteractiveHandle ihTopRight;
private final InteractiveHandle ihBottomLeft;
private final InteractiveHandle ihBottomRight;
InteractiveBox(DoubleProperty xMin, DoubleProperty yMin, DoubleProperty xMax, DoubleProperty yMax){
super();
rectangle = new Rectangle();
rectangle.widthProperty().bind(xMax.subtract(xMin));
rectangle.heightProperty().bind(yMax.subtract(yMin));
rectangle.xProperty().bind(xMin);
rectangle.yProperty().bind(yMin);
DoubleBinding xMid = xMin.add(xMax).divide(2);
DoubleBinding yMid = yMin.add(yMax).divide(2);
DoubleBinding hx = (DoubleBinding) Bindings.max(
rectangle.widthProperty().multiply(minHandleFraction)
,rectangle.widthProperty().subtract(maxCornerClearance*2)
);
DoubleBinding vx = (DoubleBinding) Bindings.max(
rectangle.heightProperty().multiply(minHandleFraction)
,rectangle.heightProperty().subtract(maxCornerClearance*2)
);
ihTopLeft = new InteractiveHandle(xMin,yMax,cornerHandleSize,cornerHandleSize);
ihTopRight = new InteractiveHandle(xMax,yMax,cornerHandleSize,cornerHandleSize);
ihBottomLeft = new InteractiveHandle(xMin,yMin,cornerHandleSize,cornerHandleSize);
ihBottomRight = new InteractiveHandle(xMax,yMin,cornerHandleSize,cornerHandleSize);
ihLeft = new InteractiveHandle(xMin,yMid,sideHandleWidth,vx);
ihTop = new InteractiveHandle(xMid,yMax,hx,sideHandleWidth);
ihRight = new InteractiveHandle(xMax,yMid,sideHandleWidth,vx);
ihBottom = new InteractiveHandle(xMid,yMin,hx,sideHandleWidth);
style(ihLeft);
style(ihTop);
style(ihRight);
style(ihBottom);
style(ihTopLeft);
style(ihTopRight);
style(ihBottomLeft);
style(ihBottomRight);
getChildren().addAll(rectangle
,ihTopLeft, ihTopRight, ihBottomLeft, ihBottomRight
,ihLeft, ihTop, ihRight, ihBottom
);
rectangle.setFill(Color.ALICEBLUE);
rectangle.setStroke(Color.LIGHTGRAY);
rectangle.setStrokeWidth(2);
rectangle.setStrokeType(StrokeType.CENTERED);
}
private void style(InteractiveHandle ih){
ih.setStroke(Color.TRANSPARENT);
ih.setStrokeWidth(handleInset);
ih.setStrokeType(StrokeType.OUTSIDE);
}
}
public class Summoner extends Application {
DoubleProperty x = new SimpleDoubleProperty(50);
DoubleProperty y = new SimpleDoubleProperty(50);
DoubleProperty xMax = new SimpleDoubleProperty(100);
DoubleProperty yMax = new SimpleDoubleProperty(100);
#Override
public void start(Stage primaryStage) {
InteractiveBox box = new InteractiveBox(x,y,xMax,yMax);
Pane root = new Pane();
root.getChildren().add(box);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
After the rectangle has been specified by the user, its coordinates (in real-space) are passed on to or read by a different part of the program.
My rationale
My first instinct was to use the built-in scale/translate properties in JavaFX nodes to implement the mapping, but we want borders and handles to have a consistent size/appearance regardless of zoom-state; zooming should only embiggen the conceptual rectangle itself, not thicken the borders or corner-handles.
(In the following, arrows represent causality/influence/dependency. For example, A ---> B could mean property B is bound to property A (or it could mean that event-handler A sets property B), and <-----> could represent a bidirectional binding. A multi-tailed arrow such as --+--> could represent a binding that depends on multiple input observables.)
So my question became: which of the following should I do?
real-space-properties ---+--> screen-space-properties
real-space-properties <--+--- screen-space properties
or something different, using <---->
On the one hand, we have mouse events and the rendered rectangle itself in screen-space. This argues for a self-contained interactive rectangle (whose screen-space position/dimension properties we can observe (as well as manipulate, if we wanted to) externally) as per the demo above.
mouse events -----> screen-space properties ------> depicted rectangle
|
|
--------> real-space properties -----> API
On the other hand, when the user adjusts pan/zoom, we want the rectangle's properties in real-space (not screen-space) to be preserved. This argues for binding the screen-space properties to real-space properties using pan&zoom-state properties:
pan/zoom properties
|
|
real-space properties ---+--> screen-space properties ------> depicted rectangle
|
|
-------> API
If I try to put together both approaches above, I run into a problem:
mouse events
|
pan/zoom properties |
| |
| v
real-space properties <--+--> screen-space properties ------> depicted rectangle
| *
|
-------> API
This diagram makes a lot of sense to me, but I don't think the kind of "bidirectional" 3-way binding at * is possible, directly. But is there perhaps a simple way to emulate/work around it? Or should I take an entirely different approach?
Here's an example of a rectangle on a zoom & pannable pane with a constant stroke width. You just have to define the scale factor as a property of the pane, bind a it to a property in the calling class and divide that into a property that's bound to the strokewidth of the rectangle.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ZoomAndPanExample extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty strokeWidthProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
AnchorPane.setTopAnchor(scrollPane, 10.0d);
AnchorPane.setRightAnchor(scrollPane, 10.0d);
AnchorPane.setBottomAnchor(scrollPane, 10.0d);
AnchorPane.setLeftAnchor(scrollPane, 10.0d);
AnchorPane root = new AnchorPane();
Rectangle rect = new Rectangle(80, 60);
rect.setStroke(Color.NAVY);
rect.setFill(Color.web("#000080", 0.2));
rect.setStrokeType(StrokeType.INSIDE);
rect.strokeWidthProperty().bind(strokeWidthProperty.divide(zoomProperty));
group.getChildren().add(rect);
// create canvas
PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.3d;
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(60);
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
);
timeline.play();
}
/**
* fit the rectangle to the width of the window
*/
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = (scale / oldScale)-1;
double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
double newX = f*dx + getBoundsInParent().getMinX();
double newY = f*dy + getBoundsInParent().getMinY();
setPivot(newX, newY, scale);
}
public void resetZoom () {
double scale = 1.0d;
double x = getTranslateX();
double y = getTranslateY();
setPivot(x, y, scale);
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.resetZoom();
}
}
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.fitWidth();
}
}
}
};
}
}
Here is my code for my game of life i am creating, I want to try to add spacing between the alive cells but can't figure it out.
Can somebody help to do this? or is it possible to make a grid in the background?
public class ClickFX extends Application
{
public LifeGrid lg;
public int x;
public int y;
public static void main(String[] args)
{
launch(args);
}
GraphicsContext gc;
ClickData clickData;
int squareSize;
Stage primaryStage;
Color colours[] = { Color.WHITE, Color.RED, Color.GREEN, Color.CYAN,
Color.BLUE, Color.BLACK };
public void start(Stage primaryStage) throws FileNotFoundException {
squareSize = 6;
int x = 80, y = 80;
clickData = new ClickData(x, y);
lg = new LifeGrid(x,y,"seed.txt");
VBox root = new VBox(8);
HBox buttons = new HBox(5);
buttons.setAlignment(Pos.CENTER);
Button ngButton = new Button("Next Gen");
ngButton.setOnAction(new ngButtonHandler());
ngButton.setTooltip(new Tooltip("Click to Start!\nKeep clicking to Evolve!"));
Button clearButton = new Button("Clear");
clearButton.setOnAction(new clearButtonHandler());
clearButton.setTooltip(new Tooltip("Click to Clear"));
Button randomButton = new Button("Random Generate");
randomButton.setOnAction(new randomButtonHandler());
randomButton.setTooltip(new Tooltip("Click to create Random Generation"));
Button closeButton = new Button("Close");
closeButton.setOnAction(new closeButtonHandler());
buttons.getChildren().addAll(ngButton, randomButton, clearButton, closeButton);
Canvas canvas = new Canvas(x * squareSize, y * squareSize);
gc = canvas.getGraphicsContext2D();
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new mouseClickHandler());
this.primaryStage = primaryStage;
primaryStage.setTitle("Game Of Life");
root.getChildren().addAll(canvas, buttons);
primaryStage.setScene(new Scene(root));![enter image description here][1]
primaryStage.setResizable(false);
primaryStage.show();
}
public void init() throws Exception
{
super.init();
Parameters parameters = getParameters();
Map<String, String> namedParameters = parameters.getNamed();
for(Map.Entry<String, String> entry : namedParameters.entrySet())
{
if("width".equals(entry.getKey()))
{
x = Integer.parseInt(entry.getValue());
}
else if("height".equals(entry.getKey()))
{
y = Integer.parseInt(entry.getValue());
}
}
}
The action for the buttons are created below.
class clearButtonHandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent e) {
clickData.clear();
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, clickData.getX() * squareSize, clickData.getY()
* squareSize);
}
}
class closeButtonHandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent e) {
primaryStage.close();
}
}
class randomButtonHandler implements EventHandler<ActionEvent>
{
public void handle(ActionEvent e)
{
lg.RandomGeneration();
drawNG();
}
}
class ngButtonHandler implements EventHandler<ActionEvent>
{
public void handle(ActionEvent e)
{
drawNG();
lg.Run();
}
}
Here I draw onto the canvas the alive cells.
public void drawNG()
{
for(int i=0; i<lg.CG.length; i++)
{
for(int k=0; k<lg.CG[0].length; k++)
{
if(lg.CG[i][k] == 1)
{
gc.setFill(Color.BLACK);
gc.setStroke(Color.RED);
gc.fillRect(k*squareSize, i*squareSize, squareSize, squareSize);
}
else
{
gc.setFill(Color.WHITE);
gc.fillRect(k*squareSize, i*squareSize, squareSize, squareSize);
}
}
}
}
Change
gc.fillRect(k*squareSize, i*squareSize, squareSize, squareSize)
to...
gc.fillRect(k*squareSize + offset, i*squareSize + offset, squareSize, squareSize)
where offset is the distance you want between them.
I have written a small JavaFX program in which a Rectangle node is made to moved whenever mouse cursor is moved over the scene containing the rectangle. Here is my code:
public class MovedObjectWhenMouseMoved extends Application{
double nodeX;
double currentMousePos;
double oldMousePos = 0.0;
public static void main(String[] arg){
launch(arg);
}
#Override
public void start(Stage stage) throws Exception {
final Rectangle rect = new Rectangle(50, 50, Color.RED);
rect.setX(20);
rect.setY(20);
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().add(rect);
Scene scene = new Scene(anchorPane,500,500,Color.GREEN);
stage.setScene(scene);
stage.show();
scene.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
currentMousePos = mouseEvent.getX();
if(currentMousePos>oldMousePos){
rect.setX(rect.getX()+1); // Move right
}else if(currentMousePos<oldMousePos){
rect.setX(rect.getX()-1); // Move Left
}
oldMousePos = currentMousePos;
}
});
}
}
But here the problem is that the node speed is not same as the mouse speed.
How can I rectify this problem? Also please let me know if there is any better approach.
Mouse can change position on more then 1 pixel.
Try this code for handle:
currentMousePos = mouseEvent.getX();
double dX = currentMousePosition - oldMousePos;
rect.setX(rect.getX() + dX);
oldMousePos = currentMousePos;
I'm trying to draw rectangles on a picture using mouse events in JavaFX2.
Right now, I have an ImageView in a StackPane and I add Rectangles over it. The problem is even if I set the Rectangles' X and Y to the MouseEvent X and Y, the Rectangles' stay centered in the StackPane.
I guess it's the StackPane that centers every child by default, but I can't find a decent solution to the problem. Could you guys please point me in the right direction?
Here is my code:
#FXML
private StackPane stack_pane;
private final ImageView image_view = new ImageView();
private final Set<Rectangle> rectangles = new HashSet<Rectangle>();
private final SimpleDoubleProperty selectionRectInitialX = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectInitialY = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectCurrentX = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectCurrentY = new SimpleDoubleProperty();
private Rectangle selectionRect;
#Override
public void initialize(final URL fxmlFileLocation, final ResourceBundle resources)
{
this.stack_pane.getChildren().add(this.image_view);
this.selectionRect = this.getRectangle();
this.selectionRect.widthProperty().bind(this.selectionRectCurrentX.subtract(this.selectionRectInitialX));
this.selectionRect.heightProperty().bind(this.selectionRectCurrentY.subtract(this.selectionRectInitialY));
this.stack_pane.setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
MainWindowController.this.selectionRect.xProperty().set(event.getX());
MainWindowController.this.selectionRect.yProperty().set(event.getY());
MainWindowController.this.selectionRectInitialX.set(event.getX());
MainWindowController.this.selectionRectInitialY.set(event.getY());
}
});
this.stack_pane.setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
MainWindowController.this.selectionRectCurrentX.set(event.getX());
MainWindowController.this.selectionRectCurrentY.set(event.getY());
MainWindowController.this.repaint();
}
});
this.stack_pane.setOnMouseReleased(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
final Rectangle newRect = MainWindowController.this.getRectangle();
newRect.setWidth(MainWindowController.this.selectionRect.getWidth());
newRect.setHeight(MainWindowController.this.selectionRect.getHeight());
newRect.setX(MainWindowController.this.selectionRect.getX());
newRect.setY(MainWindowController.this.selectionRect.getY());
MainWindowController.this.selectionRectCurrentX.set(0);
MainWindowController.this.selectionRectCurrentY.set(0);
MainWindowController.this.rectangles.add(newRect);
MainWindowController.this.repaint();
}
});
}
public Rectangle getRectangle()
{
final Rectangle rect = new Rectangle();
rect.setFill(Color.web("firebrick", 0.4));
rect.setStroke(Color.web("firebrick", 0.4));
return rect;
}
public void repaint()
{
this.stack_pane.getChildren().clear();
this.stack_pane.getChildren().add(this.image_view);
this.stack_pane.getChildren().add(this.selectionRect);
for (final Rectangle rect : this.rectangles)
{
this.stack_pane.getChildren().add(rect);
}
}
Change StackPane to AnchorPane. AnchorPane allows you to set the X, Y of each child relative to the Pane. http://docs.oracle.com/javafx/2/api/javafx/scene/layout/AnchorPane.html
Try to set Alignment of StackPane:
StackPane.setAlignment(selectionRect, Pos.CENTER_LEFT);
Refer here for the value reference of Pos class.