JavaFX - resizing simple application causes "flickering" due to canvas clearing/repaint - java

I've got this dead simple project that basically draws a black circle in the middle of a JavaFX Scene Canvas and grows it every 50ms.
Here's my Controller:
public class PrimaryController {
public StackPane theRootPane;
public CanvasPane theCanvasPane;
int i = 1;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> UpdateCanvas(i++)));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void UpdateCanvas(int diameter) {
theCanvasPane.DrawCircleAtCenterOfCanvas(diameter);
}
Here's my CanvasPane class:
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener(observable -> RedrawCanvas());
theCanvas.heightProperty().addListener(observable -> RedrawCanvas());
}
private void RedrawCanvas() {
ClearCanvas();
}
private void ClearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void DrawCircleAtCenterOfCanvas(int diameter) {
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - diameter / 2.0, centreY - diameter / 2.0, diameter, diameter);
}
}
Finally, here's my App class and my .fxml
public class App extends Application {
private static Scene scene;
#Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
//stage.setResizable(false);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
primary.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane fx:id="theRootPane" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.xxx.PrimaryController" />
It works fine until I resize the window, at which point, the canvas is redrawn by clearing it and then painting the new larger circle on the canvas. This "clearing" of the canvas presents as flicker when the form is resized.
What is a better way of doing this? I'm fudging around with JavaFX after learning Java and getting into UI and animations. I'm thinking canvas is not the way to go...
Any advice would be much appreciated.

It's probably better to update the canvas in a ChangeListener rather than an InvalidationListener, which will result in fewer redraws. Either way you should either:
ensure you redraw the circle when the canvas size changes (with your current code, you clear the canvas as soon as the canvas changes size, but don't redraw the circle until the next keyframe, so you end up with some blank canvases in between):
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
private int currentDiameter ;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener((obs, oldWidth, newWidth) -> redrawCanvas());
theCanvas.heightProperty().addListener((obs, oldHeight, newHeight) -> redrawCanvas());
}
public void increaseDiameter() {
currentDiameter++;
redrawCanvas();
}
private void redrawCanvas() {
clearCanvas();
drawCircleAtCenterOfCanvas();
}
private void clearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void drawCircleAtCenterOfCanvas() {
currentDiameter = currentDiameter ;
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - currentDiameter / 2.0, centreY - currentDiameter / 2.0, currentDiameter, currentDiameter);
}
}
and
public class PrimaryController {
#FXML
private StackPane theRootPane;
#FXML
private CanvasPane theCanvasPane;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCanvas()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCanvas() {
theCanvasPane.increaseDiameter();
}
}
or (probably better, and far easier for this example) use a Circle instead of a canvas:
public class PrimaryController {
#FXML
private StackPane theRootPane;
private Circle circle ;
public void initialize() {
circle = new Circle();
theRootPane.getChildren().addAll(circle);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCircle()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCircle() {
circle.setRadius(circle.getRadius()+0.5);
}
}

Related

Java AnimationTimer content not showing

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();
}

Bidirectional property binding via expression or some approach to work around it

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();
}
}
}
};
}
}

JavaFX Cell Spacing?

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.

How to make a node move whenever mouse is move over the scene (in JavaFX)?

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;

Draw rectangles on top of a picture in JavaFX2

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.

Categories

Resources