How to set draggable bounds for a polygon - JavaFX - java

I have a polygon that can be resized as required and dragged/moved around the scene as desired. However, my question is how can I stop it from being dragged over buttons or my treeview list? Here is my code:
public Polygon cfp(ActionEvent event) throws IOException {
Polygon fp = new Polygon();
ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
//Set the anchor points for the template layout
fp.getPoints().setAll(
350d, 50d,
700d, 50d,
1050d, 50d,
1050d, 350d,
1050d, 650d,
700d, 650d,
350d, 650d,
350d, 350d
);
//Allow the Floor plan to be draggable around the screen
fp.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
}
});
fp.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
fp.setLayoutX(fp.getLayoutX()+deltaX);
fp.setLayoutY(fp.getLayoutY()+deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
}
});
//Set the colour and properties of the template layout
fp.setStroke(Color.DARKRED);
fp.setStrokeWidth(4);
fp.setStrokeLineCap(StrokeLineCap.ROUND);
fp.setFill(Color.MINTCREAM);
container.getChildren().add(fp);
container.getChildren().addAll(createAnchors(fp, fp.getPoints()));
return fp;
}
private ObservableList<Anchor> createAnchors(Polygon polygon, final ObservableList<Double> points) {
ObservableList<Anchor> anchors = FXCollections.observableArrayList();
for (int i = 0; i < points.size(); i += 2) {
final int idx = i;
DoubleProperty xProperty = new ListWriteDoubleProperty(points, i);
DoubleProperty yProperty = new ListWriteDoubleProperty(points, i + 1);
//Bind the anchors to the polygon, so if its moved so are they
Anchor anchor = new Anchor(Color.BLACK, xProperty, yProperty);
anchor.layoutXProperty().bindBidirectional(polygon.layoutXProperty());
anchor.layoutYProperty().bindBidirectional(polygon.layoutYProperty());
xProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldX, Number x) {
points.set(idx, (double) x);
}
});
yProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldY, Number y) {
points.set(idx + 1, (double) y);
}
});
anchors.add(anchor);
}
return anchors;
}
//Creating circles to mark the anchor points to help users know where to modify from
class Anchor extends Circle {
private final DoubleProperty x, y;
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 5);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
this.x = x;
this.y = y;
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
//Make the circle node movable with mouse drag
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records the x and y co-ordinates.
private class Delta {
double x, y;
}
}
Here is my problem:
the scene where the polygon generates has an Anchor pane, the treeview is in a HBox and so are the buttons if that helps anyone.

What is Happening
Your check for the drag points for the anchor are based upon the scene dimensions rather than the dimensions of the parent container of the polygon.
How to Fix it
Change your checks to be based upon the parent container dimensions.
From:
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
To:
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getParent().getLayoutBounds().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getParent().getLayoutBounds().getHeight()) {
setCenterY(newY);
}
}
});
Ensure that your parent is a resizable parent (e.g. a Pane and not a Group), otherwise the parent won't automatically expand to fill the available area in which the polygon can be placed.
Additional Concerns
If you resize the scene so that the area in which the polygon can be rendered is smaller than the size of the polygon, then the polygon will still overflow the available bounds (as they have now shrunk smaller than the size of the polygon). There a couple of ways you could handle that situation.
You could place the polygon in a ScrollPane, so that the user can scroll around if the currently viewable area gets too small. This is probably the preferred solution, but is a bit more complicated to implement well (and it is isn't really the question you asked). I won't provide example code for this at the moment.
You can apply a clip to the parent container so that it does not draw outside the visible area. For example if your container Pane for the polygon is named polyPane:
Rectangle clip = new Rectangle();
clip.widthProperty().bind(polyPane.widthProperty());
clip.heightProperty().bind(polyPane.heightProperty());
polyPane.setClip(clip);
Content sourced from StackOverflow requires attribution.

Related

Finding the original (x,y) coordinates after zooming

I am using the below code for zooming effect, this code will be applied to a group consisting of a map image which allows the user to click on a city to get information about it, but the problem is when the user zooms I need to know the original (x,y) coordinates in order to display the proper information. How can I solve this problem? what I have tried is to get the zoom ratio and then multiple (zoom out) or divide (zoom in) new coordinate by it.
private Parent createZoomPane(final Group group) {
final double SCALE_DELTA = 1.1;
final StackPane zoomPane = new StackPane();
zoomPane.getChildren().add(group);
zoomPane.setOnScroll(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
event.consume();
if (event.getDeltaY() == 0) {
return;
}
double scaleFactor =
(event.getDeltaY() > 0)
? SCALE_DELTA
: 1/SCALE_DELTA;
group.setScaleX(group.getScaleX() * scaleFactor);
group.setScaleY(group.getScaleY() * scaleFactor);
}
});
return zoomPane;
}
root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
// root is the map parent.
public void handle(MouseEvent mouseEvent) {
double x = mouseEvent.getX();
double y = mouseEvent.getY();
// Then I use x,y to find the city which the user wants, but when i apply zoom
// effect the x,y changes fron the orignal values.
}
});

Javafx: keep a figure bounded in to the scene during movement

first of all, I've just started with JavaFX and Java in general, so be patient with me :) I was wondering how can I keep a shape , in my case a Circle,bounded into the scene in JavaFX during the movement?
This is my Circle class
public class NewCircle extends Circle {
public NewCircle (double x, double y , double radius, Color colore){
super(x,y,radius);
this.setFill(colore);
this.setOnMousePressed(circleOnMousePressedEventHandler);
this.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
double centerX = this.getCenterX();
double centerY = this.getCenterY();
double radius = this.getRadius();
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t){
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
orgTranslateX = ((Circle) (t.getSource())).getTranslateX();
orgTranslateY = ((Circle) (t.getSource())).getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Circle)(t.getSource())).setTranslateX(newTranslateX);
((Circle)(t.getSource())).setTranslateY(newTranslateY);
}
};
}
Make sure the none of the bounds of the dragged node crosses the scene bounds:
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Node source = (Node) t.getSource();
orgTranslateX = source.getTranslateX();
orgTranslateY = source.getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler
= new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
Node source = (Node) t.getSource();
Bounds sceneBounds = source.getScene().getRoot().getLayoutBounds();
Bounds localBounds = source.getBoundsInLocal();
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
// restirct x movement to scene bounds
if (offsetX >= 0) {
if (localBounds.getMaxX() + newTranslateX > sceneBounds.getMaxX()) {
newTranslateX = sceneBounds.getMaxX() - localBounds.getMaxX();
}
} else {
if (localBounds.getMinX() + newTranslateX < 0) {
newTranslateX = -localBounds.getMinX();
}
}
// restrict y movement to scene bounds
if (offsetY >= 0) {
if (localBounds.getMaxY() + newTranslateY > sceneBounds.getMaxY()) {
newTranslateY = sceneBounds.getMaxY() - localBounds.getMaxY();
}
} else {
if (localBounds.getMinY() + newTranslateY < 0) {
newTranslateY = -localBounds.getMinY();
}
}
source.setTranslateX(newTranslateX);
source.setTranslateY(newTranslateY);
}
};
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(100, 100, 50);
circle.setOnMousePressed(circleOnMousePressedEventHandler);
circle.setOnMouseDragged(circleOnMouseDraggedEventHandler);
Pane root = new Pane(circle);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}

Drag and Drop a shape in Java awt applet

I'm trying to create an awt applet in which the user can drag around a rectangle that has been drawn on the applet. I can't figure out why the rectangle in my applet only drags diagonally and in the positive x direction, I can't move it up or down or to the left, it only drags diagonally to the right. Can someone please tell me what's wrong with my logic ?
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class DragRectangle extends Applet implements MouseMotionListener {
boolean clicked_in_rectangle = false;
int rectangleX, rectangleY, mouseX, mouseY;
int rectangle_width = 80, rectangle_height = 50;
public void init() {
rectangleX = 0;
rectangleY = 0;
mouseX = 0;
mouseY = 0;
addMouseMotionListener(this);
}
public void paint(Graphics g) {
g.drawRect(rectangleX, rectangleY, rectangle_width, rectangle_height);
if(clicked_in_rectangle) {
rectangleX = mouseX;
rectangleY = mouseY;
g.drawRect(rectangleX, rectangleY, rectangle_width, rectangle_height);
}
}
public void mouseDragged(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
clicked_in_rectangle = mouseX > rectangleX && mouseY > rectangleY &&
mouseX < rectangleX + rectangle_width && mouseY < rectangleY + rectangle_height;
repaint();
}
public void mouseMoved(MouseEvent e) {
}
}
I think the problem is that you're setting the rectangle's position to the mouse's pointer position and when you try to drag the rectangle up or to the left, the pointer will be outside of the rectangle's bounds and clicked_in_rectangle will be false.
You can try this:
if (clicked_in_rectangle) {
rectangleX = mouseX - 3;
rectangleY = mouseY - 3;
g.drawRect(rectangleX, rectangleY, rectangle_width, rectangle_height);
}
But this is still not an ideal solution, it stops working if you try to drag the rectangle (up or to the left) quickly, that's because the rectangleX and rectangleY don't get a chance to be updated to the new position.
If you want something more reliable, you can do this:
public class DragRectangle extends Applet implements MouseMotionListener, MouseListener {
boolean clicked_in_rectangle = false, dragStarted = false;
int x, y, px, py;
Rectangle rect = new Rectangle(0, 0, 80, 50);
public void init() {
addMouseMotionListener(this);
addMouseListener(this);
}
public void paint(Graphics g) {
if (clicked_in_rectangle) {
int nx = rect.x + (x - px);
int ny = rect.y + (y - py);
Rectangle bounds = g.getClipBounds();
boolean reset = false;
if (nx + rect.width > bounds.width || nx < 0) {
nx = rect.x;
reset = true;
}
if (ny + rect.height > bounds.height || ny < 0) {
ny = rect.y;
reset = true;
}
if (reset) {
px = 0;
py = 0;
} else {
px = x;
py = y;
}
rect = new Rectangle(nx, ny, rect.width, rect.height);
}
g.drawRect(rect.x, rect.y, rect.width, rect.height);
}
public void mouseDragged(MouseEvent e) {
if (dragStarted) {
x = e.getX();
y = e.getY();
if (px == 0 && py == 0) {
px = x;
py = y;
} else {
clicked_in_rectangle = new Rectangle(x, y, 1, 1).intersects(rect);
if (clicked_in_rectangle) {
repaint();
}
}
}
}
public void mouseMoved(MouseEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
dragStarted = new Rectangle(e.getX(), e.getY(), 1, 1).intersects(rect);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
px = 0;
py = 0;
}
}
}
I may have made this more complicated then it has to be, in any case, the idea is to change the shape's position based on the difference between consecutive pointer coordinates instead of setting its position to the same position as the pointer.

Pick and move a node in a pannable/zoomable Pane

I am trying to place a few components inside a ScrollPane. These components should have the ability to be moved across this pane by mouse (click and drag). The ScrollPane itself is pannable and zoomable.
Now if I pick one of them and drag it to a new position the mouse is faster than the component if I have zoomed out. When zoomed in, the component gets moved faster than the mouse movement.
If not zoomed it works until I reach a certain position where the ScrollPane automatically pans.
It must have to do something with the determined coordinates of the nodes. Does anyone have an idea what I have to add to make it work correctly?
My controller class:
public class MainWindowController implements Initializable {
private final double SCALE_DELTA = 1.1;
private final StackPane zoomPane = new StackPane();
private Group group = new Group();
#FXML
private ScrollPane scrollPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
Node node1 = new Node("Test");
Node node2 = new Node("Test2", 100, 200);
group.getChildren().addAll(node1, node2);
zoomPane.getChildren().add(group);
Group scrollContent = new Group(zoomPane);
scrollPane.setContent(scrollContent);
scrollPane.viewportBoundsProperty().addListener((ObservableValue<? extends Bounds> observable,
Bounds oldValue, Bounds newValue) -> {
zoomPane.setMinSize(newValue.getWidth(), newValue.getHeight());
});
zoomPane.setOnScroll(
(ScrollEvent event) -> {
event.consume();
if (event.getDeltaY() == 0) {
return;
}
double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA;
Point2D scrollOffset = figureScrollOffset(scrollContent, scrollPane);
group.setScaleX(group.getScaleX() * scaleFactor);
group.setScaleY(group.getScaleY() * scaleFactor);
repositionScroller(scrollContent, scrollPane, scaleFactor, scrollOffset);
}
);
group.getChildren()
.add(new Node("Test3", 500, 500));
}
private Point2D figureScrollOffset(javafx.scene.Node scrollContent, ScrollPane scroller) {
double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth();
double hScrollProportion = (scroller.getHvalue() - scroller.getHmin()) / (scroller.getHmax() - scroller.getHmin());
double scrollXOffset = hScrollProportion * Math.max(0, extraWidth);
double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight();
double vScrollProportion = (scroller.getVvalue() - scroller.getVmin()) / (scroller.getVmax() - scroller.getVmin());
double scrollYOffset = vScrollProportion * Math.max(0, extraHeight);
return new Point2D(scrollXOffset, scrollYOffset);
}
private void repositionScroller(javafx.scene.Node scrollContent, ScrollPane scroller, double scaleFactor, Point2D scrollOffset) {
double scrollXOffset = scrollOffset.getX();
double scrollYOffset = scrollOffset.getY();
double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth();
if (extraWidth > 0) {
double halfWidth = scroller.getViewportBounds().getWidth() / 2;
double newScrollXOffset = (scaleFactor - 1) * halfWidth + scaleFactor * scrollXOffset;
scroller.setHvalue(scroller.getHmin() + newScrollXOffset * (scroller.getHmax() - scroller.getHmin()) / extraWidth);
} else {
scroller.setHvalue(scroller.getHmin());
}
double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight();
if (extraHeight > 0) {
double halfHeight = scroller.getViewportBounds().getHeight() / 2;
double newScrollYOffset = (scaleFactor - 1) * halfHeight + scaleFactor * scrollYOffset;
scroller.setVvalue(scroller.getVmin() + newScrollYOffset * (scroller.getVmax() - scroller.getVmin()) / extraHeight);
} else {
scroller.setHvalue(scroller.getHmin());
}
}
}
The node class:
public class Node extends Parent {
private NodeStatus status = NodeStatus.OK;
private final Image okImage = new Image(getClass().getResourceAsStream("/images/MasterOK.png"));
private ImageView image = new ImageView(okImage);
private final Text label = new Text();
private final Font font = Font.font("Courier", 20);
double orgSceneX, orgSceneY;
double layoutX, layoutY;
public Node(String labelText) {
this(labelText, 0, 0);
}
public Node(String labelText, double x, double y) {
label.setText(labelText);
label.setFont(font);
label.setLayoutX(okImage.getWidth() + 10);
label.setLayoutY(okImage.getHeight() / 2 + 10);
getChildren().add(image);
getChildren().add(label);
setLayoutX(x);
setLayoutY(y);
setCursor(Cursor.MOVE);
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
layoutX = getLayoutX();
layoutY = getLayoutY();
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
setLayoutX(layoutX + t.getSceneX() - orgSceneX);
setLayoutY(layoutY + t.getSceneY() - orgSceneY);
}
});
}
public NodeStatus getStatus() {
return status;
}
public void setStatus(NodeStatus status) {
this.status = status;
}
}
class Delta {
double x, y;
}
and the fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import nodes.*?>
<AnchorPane id="AnchorPane" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="cqsmonitor.MainWindowController">
<children>
<Pane layoutX="666.0" layoutY="14.0" prefHeight="572.0" prefWidth="114.0" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0">
<children>
<TextField layoutY="30.0" prefHeight="25.0" prefWidth="114.0" />
<Label layoutY="12.0" text="Search:" />
<ChoiceBox layoutY="90.0" prefHeight="25.0" prefWidth="114.0" />
<Label layoutY="73.0" text="View:" />
</children>
</Pane>
<ScrollPane fx:id="scrollPane" layoutX="14.0" layoutY="14.0" pannable="true" prefHeight="571.0" prefWidth="644.0" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="142.0" AnchorPane.topAnchor="14.0">
</ScrollPane>
</children>
</AnchorPane>
Since nobody answered yet, here's some code. I don't want to dig into yours and re-invent the wheel.
You can move nodes by dragging with the left mouse button, scale the pane with the mouse wheel, pan the pane with the right mouse button. No ScrollPane needed. However, if you want ScrollBars, you can always add them if you prefer them.
The code:
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.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* The canvas which holds all of the nodes of the application.
*/
class PannableCanvas extends Pane {
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public PannableCanvas() {
setPrefSize(600, 600);
setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
// logging
addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
System.out.println(
"canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ", scale: " + getScale())
);
System.out.println( "canvas bounds: " + getBoundsInParent());
});
}
/**
* Add a grid to the canvas, send it to back
*/
public void addGrid() {
double w = getBoundsInLocal().getWidth();
double h = getBoundsInLocal().getHeight();
// add grid
Canvas grid = new Canvas(w, h);
// don't catch mouse events
grid.setMouseTransparent(true);
GraphicsContext gc = grid.getGraphicsContext2D();
gc.setStroke(Color.GRAY);
gc.setLineWidth(1);
// draw grid lines
double offset = 50;
for( double i=offset; i < w; i+=offset) {
// vertical
gc.strokeLine( i, 0, i, h);
// horizontal
gc.strokeLine( 0, i, w, i);
}
getChildren().add( grid);
grid.toBack();
}
public double getScale() {
return myScale.get();
}
/**
* Set x/y scale
* #param myScale
*/
public void setScale( double scale) {
myScale.set(scale);
}
/**
* Set x/y pivot points
* #param x
* #param y
*/
public void setPivot( double x, double y) {
setTranslateX(getTranslateX()-x);
setTranslateY(getTranslateY()-y);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
*/
class NodeGestures {
private DragContext nodeDragContext = new DragContext();
PannableCanvas canvas;
public NodeGestures( PannableCanvas canvas) {
this.canvas = canvas;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// left mouse button => dragging
if( !event.isPrimaryButtonDown())
return;
nodeDragContext.mouseAnchorX = event.getSceneX();
nodeDragContext.mouseAnchorY = event.getSceneY();
Node node = (Node) event.getSource();
nodeDragContext.translateAnchorX = node.getTranslateX();
nodeDragContext.translateAnchorY = node.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// left mouse button => dragging
if( !event.isPrimaryButtonDown())
return;
double scale = canvas.getScale();
Node node = (Node) event.getSource();
node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));
event.consume();
}
};
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
class SceneGestures {
private static final double MAX_SCALE = 10.0d;
private static final double MIN_SCALE = .1d;
private DragContext sceneDragContext = new DragContext();
PannableCanvas canvas;
public SceneGestures( PannableCanvas canvas) {
this.canvas = canvas;
}
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) {
// right mouse button => panning
if( !event.isSecondaryButtonDown())
return;
sceneDragContext.mouseAnchorX = event.getSceneX();
sceneDragContext.mouseAnchorY = event.getSceneY();
sceneDragContext.translateAnchorX = canvas.getTranslateX();
sceneDragContext.translateAnchorY = canvas.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// right mouse button => panning
if( !event.isSecondaryButtonDown())
return;
canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - 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 = 1.2;
double scale = canvas.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
if (event.getDeltaY() < 0)
scale /= delta;
else
scale *= delta;
scale = clamp( scale, MIN_SCALE, MAX_SCALE);
double f = (scale / oldScale)-1;
double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));
canvas.setScale( scale);
// note: pivot value must be untransformed, i. e. without scaling
canvas.setPivot(f*dx, f*dy);
event.consume();
}
};
public static double clamp( double value, double min, double max) {
if( Double.compare(value, min) < 0)
return min;
if( Double.compare(value, max) > 0)
return max;
return value;
}
}
/**
* An application with a zoomable and pannable canvas.
*/
public class ZoomAndScrollApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Group group = new Group();
// create canvas
PannableCanvas canvas = new PannableCanvas();
// we don't want the canvas on the top/left in this example => just
// translate it a bit
canvas.setTranslateX(100);
canvas.setTranslateY(100);
// create sample nodes which can be dragged
NodeGestures nodeGestures = new NodeGestures( canvas);
Label label1 = new Label("Draggable node 1");
label1.setTranslateX(10);
label1.setTranslateY(10);
label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Label label2 = new Label("Draggable node 2");
label2.setTranslateX(100);
label2.setTranslateY(100);
label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Label label3 = new Label("Draggable node 3");
label3.setTranslateX(200);
label3.setTranslateY(200);
label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Circle circle1 = new Circle( 300, 300, 50);
circle1.setStroke(Color.ORANGE);
circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5));
circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Rectangle rect1 = new Rectangle(100,100);
rect1.setTranslateX(450);
rect1.setTranslateY(450);
rect1.setStroke(Color.BLUE);
rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5));
rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
canvas.getChildren().addAll(label1, label2, label3, circle1, rect1);
group.getChildren().add(canvas);
// create scene which can be dragged and zoomed
Scene scene = new Scene(group, 1024, 768);
SceneGestures sceneGestures = new SceneGestures(canvas);
scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
stage.setScene(scene);
stage.show();
canvas.addGrid();
}
}
Let me know if this doesn't help you at all, then I'll delete the post. I'm not interested in the bounty.
I'll just post an answer to summarize the solution to my problem:
The problem why my nodes where lagging behind was because the had no clue about the scaling that might have occured in the pane one level above. So as a final solution I mixed my code with Roland's answer. if anyone has further optimizations in mind, please post them.
Controller class:
public class MainWindowController implements Initializable {
private final Group group = new Group();
private static final double MAX_SCALE = 10.0d;
private static final double MIN_SCALE = .1d;
#FXML
private ScrollPane scrollPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
ZoomableCanvas canvas = new ZoomableCanvas();
MasterNode node1 = new MasterNode("Test");
MasterNode node2 = new MasterNode("Test", 100, 200);
canvas.getChildren().add(node1);
canvas.getChildren().add(node2);
group.getChildren().add(canvas);
scrollPane.setContent(group);
scrollPane.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = canvas.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
if (event.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
scale = clamp(scale, MIN_SCALE, MAX_SCALE);
double f = (scale / oldScale) - 1;
double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth() / 2 + canvas.getBoundsInParent().getMinX()));
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight() / 2 + canvas.getBoundsInParent().getMinY()));
canvas.setScale(scale);
// note: pivot value must be untransformed, i. e. without scaling
canvas.setPivot(f * dx, f * dy);
event.consume();
}
});
}
private double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0) {
return min;
}
if (Double.compare(value, max) > 0) {
return max;
}
return value;
}
}
The canvas:
public class ZoomableCanvas extends Pane {
DoubleProperty scale = new SimpleDoubleProperty(1.0);
public ZoomableCanvas() {
scaleXProperty().bind(scale);
scaleYProperty().bind(scale);
getChildren().addListener((Change<? extends javafx.scene.Node> c) -> {
while (c.next()) {
if (c.wasAdded()) {
for (Node child : c.getAddedSubList()) {
((MasterNode) child).scaleProperty.bind(scale);
}
}
if (c.wasRemoved()) {
for (Node child : c.getRemoved()) {
((MasterNode) child).scaleProperty.unbind();
}
}
}
});
}
public double getScale() {
return scale.get();
}
public void setScale(double scale) {
this.scale.set(scale);
}
public void setPivot(double x, double y) {
setTranslateX(getTranslateX() - x);
setTranslateY(getTranslateY() - y);
}
}
Node:
public class MasterNode extends Parent {
private NodeStatus status = NodeStatus.OK;
private final Image okImage = new Image(getClass().getResourceAsStream("/images/MasterOK.png"));
private ImageView image = new ImageView(okImage);
private final Text label = new Text();
private final Font font = Font.font("Courier", 20);
private DragContext nodeDragContext = new DragContext();
public DoubleProperty scaleProperty = new SimpleDoubleProperty(1.0);
double orgSceneX, orgSceneY;
double layoutX, layoutY;
public MasterNode(String labelText) {
this(labelText, 0, 0);
}
public MasterNode(String labelText, double x, double y) {
scaleXProperty().bind(scaleProperty);
scaleYProperty().bind(scaleProperty);
label.setText(labelText);
label.setFont(font);
label.setLayoutX(okImage.getWidth() + 10);
label.setLayoutY(okImage.getHeight() / 2 + 10);
getChildren().add(image);
getChildren().add(label);
setLayoutX(x);
setLayoutY(y);
setCursor(Cursor.MOVE);
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// left mouse button => dragging
if (!event.isPrimaryButtonDown()) {
return;
}
nodeDragContext.setMouseAnchorX(event.getSceneX());
nodeDragContext.setMouseAnchorY(event.getSceneY());
Node node = (Node) event.getSource();
nodeDragContext.setTranslateAnchorX(node.getTranslateX());
nodeDragContext.setTranslateAnchorY(node.getTranslateY());
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// left mouse button => dragging
if (!event.isPrimaryButtonDown()) {
return;
}
Node node = (Node) event.getSource();
node.setTranslateX(nodeDragContext.getTranslateAnchorX() + ((event.getSceneX() - nodeDragContext.getMouseAnchorX()) / getScale()));
node.setTranslateY(nodeDragContext.getTranslateAnchorY() + ((event.getSceneY() - nodeDragContext.getMouseAnchorY()) / getScale()));
event.consume();
}
});
}
public double getScale() {
return scaleProperty.get();
}
public void setScale(double scale) {
scaleProperty.set(scale);
}
public NodeStatus getStatus() {
return status;
}
public void setStatus(NodeStatus status) {
this.status = status;
}
}

javafx get width and height of a scaled Pane to resize canvas and redraw inside canvas

I'm using javafx for developing an application. My objective is scaling the Pane (which is zooming - although I still have doubts if scaling is same as zooming - please clarify if wrong).
I'm able to scale and translate the pane inside the ScrollPane, but now how can I get its new scaled width and height??
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
pane.setScaleX(newValue.doubleValue());
pane.setScaleY(newValue.doubleValue());
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
pressedX = event.getX();
pressedY = event.getY();
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
pane.setTranslateX(pane.getTranslateX() + event.getX() - pressedX);
pane.setTranslateY(pane.getTranslateY() + event.getY() - pressedY);
event.consume();
}
});
if(newValue.intValue()==1) {
pane.setScaleX(1.0);
pane.setScaleY(1.0);
pane.setTranslateX(0.0);
pane.setTranslateY(0.0);
pane.setTranslateZ(0.0);
}
Platform.runLater(new Runnable() {
#Override
public void run() {
scrollPane.setPrefWidth(pane.getWidth()*pane.getScaleX());
scrollPane.setPrefHeight(pane.getHeight()*pane.getScaleY());
pane.resize(scrollPane.getPrefWidth(),
scrollPane.getPrefHeight());//Here once pane is resized, ResizableCanvas also draws
for(int i=0;i<canvas.length;i++){
canvas[i].setScaledWidth(scrollPane.getPrefWidth());
canvas[i].setScaledHeight(scrollPane.getPrefHeight());
}
mChart.redrawAllAgain();//redraw whole chart again with resized canvas
}
});
}
});
I need this new width and height to resize a canvas and redraw inside it.
Please note that in my start of the application, I'm binding some properties.
resizableCanvas1.widthProperty().bind(pane.widthProperty());
resizableCanvas1.heightProperty().bind(pane.heightProperty());
I'm using a ResizableCanvas.java
public class ResizableCanvas extends Canvas {
private double scaledWidth;
private double scaledHeight;
private boolean once = true;
public ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
System.out.println("Canvas: "+scaledWidth+","+scaledHeight);
}
private void draw() {
double width = getWidth();
double height = getHeight();
if(once) {
scaledWidth = width;
scaledHeight = height;
}
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
gc.setFill(Color.BLACK);
gc.fillRect(0,0,width, height);
gc.setStroke(Color.RED);
gc.strokeLine(0, 0, width, height);
gc.strokeLine(0, height, width, 0);
}
#Override
public boolean isResizable() {
return true;
}
#Override
public double prefWidth(double height) {
return getWidth();
}
#Override
public double prefHeight(double width) {
return getHeight();
}
public void setScaledDimension(boolean is) {
this.once = is;
}
public void setScaledWidth(double sWidth) {
this.scaledWidth = sWidth;
}
public void setScaledHeight(double sHeight) {
this.scaledHeight = sHeight;
}
public double getScaledWidth() {
return this.scaledWidth;
}
public double getScaledHeight() {
return this.scaledHeight;
}
}

Categories

Resources