Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java FX rubberband resize bug

So, I am having a bug in my rubberband class and I can't seem to fix it.

What I am basicly doing is: I have a borderpane which is the outside pane of the node I want to resize. I assign this borderpane a border with a width of 1 pixel (view css). I also assign this borderpane 4 rectangles, each one in a corner (NE, SE, SW, NW). In this borderpane I have the so called 'contentPane'. This pane is containing all the content (rectangles, imageviews, etc.).

It works pretty well, but I can't seem to fix a bug.

Bug:

If I resize one pixel, the width and height/x and y are being adjusted with unknown values. After that, the resizing works just fine. enter image description here

RubberBand2.java

package org.displee.javafx.mod;

import javafx.scene.Cursor;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

/**
 * A class representing a rubberband that can be used to resize panes.
 * @author Displee
 */
public class RubberBand2 {

    /**
     * The parent of this rectangle.
     */
    private final Pane parent;

    /**
     * The rectangle.
     */
    private final Pane node;

    /**
     * The corner rectangles.
     */
    private final Rectangle[] rectangles = new Rectangle[4];

    /**
     * The last known node width.
     */
    private double nodeWidth;

    /**
     * The last known node height.
     */
    private double nodeHeight;

    /**
     * The last known node x-coordinate.
     */
    private double nodeX;

    /**
     * The last known node y-coordinate.
     */
    private double nodeY;

    /**
     * The current selected component.
     */
    public static RubberBand2 SELECTED;

    /**
     * The size of the corners of a rectangle.
     */
    public static final double RECTANGLE_CORNER_SIZE = 5.0;

    /**
     * The minimum width of the {@code node}.
     */
    private static final double MIN_WIDTH = 10.0;

    /**
     * The minimum height of the {@code node}
     */
    private static final double MIN_HEIGHT = 10.0;

    public RubberBand2(Pane node) {
        this.node = node;
        this.parent = (Pane) node.getParent();
        parent.getStyleClass().add("transparent_border");
        createCorners();
        bind();
    }

    /**
     * Create the corners.
     */
    public void createCorners() {
        final Pane inheritPane = node;
        for (int i = 0; i < rectangles.length; i++) {
            final Rectangle rectangle = rectangles[i] = new Rectangle();
            rectangle.setWidth(RECTANGLE_CORNER_SIZE);
            rectangle.setHeight(RECTANGLE_CORNER_SIZE);
            rectangle.setFill(Color.BLACK);
            rectangle.getStyleClass().add("rectangle_corner");
            rectangle.setArcHeight(4);
            rectangle.setArcWidth(4);
            if (i == 0) {
                rectangle.xProperty().bind(inheritPane.layoutXProperty().subtract(RECTANGLE_CORNER_SIZE));
                rectangle.yProperty().bind(inheritPane.layoutYProperty().subtract(RECTANGLE_CORNER_SIZE));
                rectangle.setOnMouseEntered((e) -> {
                    rectangle.setCursor(Cursor.NW_RESIZE);
                });
                rectangle.setOnMouseDragged((event) -> {
                    resize(event, 0);
                });
            } else if (i == 1) {
                rectangle.xProperty().bind(inheritPane.layoutXProperty().add(inheritPane.widthProperty()));
                rectangle.yProperty().bind(inheritPane.layoutYProperty().subtract(RECTANGLE_CORNER_SIZE));
                rectangle.setOnMouseEntered((e) -> {
                    rectangle.setCursor(Cursor.NE_RESIZE);
                });
                rectangle.setOnMouseDragged((event) -> {
                    resize(event, 1);
                });
            } else if (i == 2) {
                rectangle.xProperty().bind(inheritPane.layoutXProperty().add(inheritPane.widthProperty()));
                rectangle.yProperty().bind(inheritPane.layoutYProperty().add(inheritPane.heightProperty()));
                rectangle.setOnMouseEntered((e) -> {
                    rectangle.setCursor(Cursor.SE_RESIZE);
                });
                rectangle.setOnMouseDragged((event) -> {
                    resize(event, 2);
                });
            } else {
                rectangle.xProperty().bind(inheritPane.layoutXProperty().subtract(RECTANGLE_CORNER_SIZE));
                rectangle.yProperty().bind(inheritPane.layoutYProperty().add(inheritPane.heightProperty()));
                rectangle.setOnMouseEntered((e) -> {
                    rectangle.setCursor(Cursor.SW_RESIZE);
                });
                rectangle.setOnMouseDragged((event) -> {
                    resize(event, 3);
                });
            }
            rectangle.setOnMousePressed((e) -> {
                setDefaults();
                e.consume();
            });
            rectangle.setVisible(false);
            parent.getChildren().add(rectangle);
        }
    }

    /**
     * Bind the mouse events.
     */
    public void bind() {
        node.setOnMouseEntered((e) -> {
            node.setCursor(Cursor.MOVE);
        });
        parent.setOnMouseClicked((e) -> {
            if (SELECTED != null) {
                SELECTED.setRubberBandSelection(false);
            }
            SELECTED = this;
            setRubberBandSelection(true);
            e.consume();
        });
        node.setOnMouseClicked((e) -> {
            if (SELECTED != null) {
                SELECTED.setRubberBandSelection(false);
            }
            SELECTED = this;
            setRubberBandSelection(true);
            e.consume();
        });
        node.setOnMousePressed((e) -> {
            setDefaults();
            e.consume();
        });
        node.setOnMouseMoved((e) -> {

        });
        node.setOnMouseReleased((e) -> {

        });
    }

    /**
     * Resize the argued resize type.
     * @param event The mouse event
     * @param type The type (0 = NW, 1 = NE, 2 = SE, 3 = SW);
     */
    public void resize(MouseEvent event, int type) {
        final double mouseX = parent.getBoundsInParent().getMinX() + event.getX();
        final double mouseY = parent.getBoundsInParent().getMinY() + event.getY();
        double newX = nodeX;
        double newY = nodeY;
        double newW = nodeWidth;
        double newH = nodeHeight;
        switch (type) {
        case 0:
            newX = mouseX;
            newY = mouseY;
            newW = nodeWidth + nodeX - newX;
            newH = nodeHeight + nodeY - newY;
            break;
        case 1:
            newY = mouseY;
            newW = mouseX - nodeX;
            newH = nodeHeight + nodeY - newY;
            break;
        case 2:
            newW = mouseX - nodeX;
            newH = mouseY - nodeY;
            break;
        case 3:
            newX = mouseX;
            newW = nodeWidth + nodeX - newX;
            newH = mouseY - nodeY;
            break;
        }
        parent.setLayoutX(newX);
        parent.setLayoutY(newY);
        node.setPrefSize(newW, newH);
    }

    /**
     * Set the defaults before we resize anything.
     */
    public void setDefaults() {
        nodeX = node.getBoundsInParent().getMinX();
        nodeY = node.getBoundsInParent().getMinY();
        nodeHeight = node.getBoundsInParent().getHeight();
        nodeWidth = node.getBoundsInParent().getWidth();
    }

    /**
     * Set the rubber band selection for the rectangle.
     * @param show If we have to show the corner rectangles.
     */
    public void setRubberBandSelection(boolean show) {
        if (show) {
            parent.getStyleClass().remove("transparent_border");
            parent.getStyleClass().add("dotted_pane");
        } else {
            parent.getStyleClass().remove("dotted_pane");
            parent.getStyleClass().add("transparent_border");
        }
        for (Rectangle rectangle : rectangles) {
            rectangle.setVisible(show);
        }
    }

}

style.css

.dotted_pane {
    -fx-background-insets: 0;
    -fx-border-color: white;
    -fx-border-width: 1;
    -fx-border-style: dashed;
}
.transparent_border {
    -fx-background-insets: 0;
    -fx-border-color: transparent;
    -fx-border-width: 1;
    -fx-border-style: solid;
}

Test2.java

package org.test;

import org.displee.javafx.mod.RubberBand2;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Test2 extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Group root = new Group();
        Scene scene = new Scene(root, 900, 600);
        Pane inner = new Pane();
        inner.setBackground(new Background(new BackgroundFill(Color.web("red"), CornerRadii.EMPTY, Insets.EMPTY)));
        BorderPane border = new BorderPane(inner);
        inner.setPrefSize(100, 100);
        border.setBorder(new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
        Pane outer = new Pane(border);
        outer.layoutXProperty().bind(scene.widthProperty().divide(4));
        outer.layoutYProperty().bind(scene.heightProperty().divide(4));
        root.getChildren().addAll(outer);
        primaryStage.setScene(scene);
        primaryStage.show();
        new RubberBand2(inner);
    }

}

Any suggestions and improvements are of course much appreciated.

Edit: Sorry, my documentation is pretty outdated xD.

Thanks.

like image 835
Displee Avatar asked Sep 25 '22 19:09

Displee


2 Answers

Well, I have found a cheap fix for my problem.

First of all, I am moving the borderpane and not the node it self, so I changed this

nodeX = node.getBoundsInParent().getMinX();
nodeY = node.getBoundsInParent().getMinY();

To this

nodeX = parent.getBoundsInParent().getMinX();
nodeY = parent.getBoundsInParent().getMinY();

The cheap fix was the following:

parent.setLayoutX(newX);
parent.setLayoutY(newY);

I just increase the newX and newY depending on the corner size. This made everything flawless.

final double offset = RECTANGLE_CORNER_SIZE - 1;
parent.setLayoutX(newX + offset);
parent.setLayoutY(newY + offset);

I guess you can add moving and the other resize rectangles (N, E, S, W) your self, if not, I can help you with it.

like image 85
Displee Avatar answered Oct 11 '22 12:10

Displee


Here's an alternate version if you are interested. I did this some time ago, maybe its useful for you. I'm using a layer system, i. e. the resizable objects are on a layer and the selection handles are on a separate layer on top of the object layer.

The code:

Main.java: create nodes, regions, shapes and make them draggable and resizable

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Main extends Application {

    SelectionModel selectionModel;
    MouseHandler mouseHandler;

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();

        // object layer
        Group objectLayer = new Group();
        root.getChildren().add( objectLayer);

        // selection layer on top of object layer
        Group selectionLayer = new Group();
        root.getChildren().add( selectionLayer);

        selectionModel = new SelectionModel( selectionLayer);
        mouseHandler = new MouseHandler( selectionModel);

        Rectangle rect1 = new Rectangle(200,100);
        rect1.setFill(Color.RED.deriveColor(1, 1, 1, 0.2));
        rect1.relocate(100,100);
        mouseHandler.makeDraggable(rect1);

        Rectangle rect2 = new Rectangle(200,100);
        rect2.setFill(Color.AQUA.deriveColor(1, 1, 1, 0.2));
        rect2.relocate(300,300);
        mouseHandler.makeDraggable(rect2);

        TitledPane sampleNode = new TitledPane();
        sampleNode.setPrefHeight(100);
        sampleNode.setPrefWidth(200);
        sampleNode.relocate(400,200);
        mouseHandler.makeDraggable(sampleNode);

        StackPane sampleNode2 = new StackPane();
        sampleNode2.getChildren().add( new Label( "I'm a StackPane"));
        sampleNode2.setStyle( "-fx-background-color: lightblue");
        sampleNode2.setPrefHeight(100);
        sampleNode2.setPrefWidth(200);
        sampleNode2.relocate(600,300);
        mouseHandler.makeDraggable(sampleNode2);

        objectLayer.getChildren().addAll( rect1, rect2, sampleNode, sampleNode2);

        Scene scene = new Scene( root, 1600, 900);

        // clear selection when user clicks outside of cell
        scene.setOnMousePressed(mouseEvent -> selectionModel.clear());

        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

        primaryStage.setScene( scene);
        primaryStage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }

}

MouseHandler.java: add nodes to the selection model, allow dragging

import javafx.scene.Node;

public class MouseHandler {

    private SelectionModel selectionModel;

    public MouseHandler( SelectionModel selectionModel) {
        this.selectionModel = selectionModel;
    }

    private class DragContext {
        double x;
        double y;
    }

    public void makeDraggable( final Node node) {

        final DragContext dragDelta = new DragContext();

        node.setOnMousePressed(mouseEvent -> {

            // TODO: add shift & ctrl check to add/remove nodes to selection
            selectionModel.clear();

            // add to selection model, create drag handles
            selectionModel.add(node);

            dragDelta.x = node.getTranslateX() - mouseEvent.getSceneX();
            dragDelta.y = node.getTranslateY() - mouseEvent.getSceneY();

            // consume event, so that scene won't get it (which clears selection)
            mouseEvent.consume();
        });

        node.setOnMouseDragged(mouseEvent -> {

            node.setTranslateX(mouseEvent.getSceneX() + dragDelta.x);
            node.setTranslateY(mouseEvent.getSceneY() + dragDelta.y);

        });

        node.setOnMouseReleased(mouseEvent -> {

            fixPosition(node);

        });
    }

    private void fixPosition( Node node) {

        double x = node.getTranslateX();
        double y = node.getTranslateY();

        node.relocate(node.getLayoutX() + x, node.getLayoutY() + y);

        node.setTranslateX(0);
        node.setTranslateY(0);

    }

}

SelectionModel.java: add selected nodes to the selection model, create the selection overlay for the nodes

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.scene.Group;
import javafx.scene.Node;

public class SelectionModel {

    Group selectionLayer;
    Map<Node, SelectionOverlay> map = new HashMap<Node, SelectionOverlay>();

    List<Node> selection = new ArrayList<>();

    public SelectionModel( Group layoutBoundsOverlay) {
        this.selectionLayer = layoutBoundsOverlay;
    }

    public void add( Node cell) {

        // don't add duplicates or else duplicates might be added to the javafx scene graph which causes exceptions
        if( selection.contains(cell))
            return;

        SelectionOverlay selectionOverlay = new SelectionOverlay( cell);

        // register
        map.put( cell, selectionOverlay);

        // add component
        selectionLayer.getChildren().add(selectionOverlay);

        selection.add( cell);
    }

    public void remove( Node cell) {

        removeOverlay( cell);

        selection.remove( cell);

    }

    private void removeOverlay( Node cell) {
        SelectionOverlay boundsDisplay = map.get( cell);
        if( boundsDisplay != null) {
            selectionLayer.getChildren().remove(boundsDisplay);
        }
    }

    public void clear() {

        // remove style
        // we can't call remove here because we'd get a ConcurrentModificationException (or we could use an iterator)
        for( Node cell: selection) {
            removeOverlay( cell);
        }

        // clear selection list
        selection.clear();
    }

    public boolean isEmpty()  {
        return selection.isEmpty();
    }

    public boolean contains( Node cell) {
        return selection.contains( cell);
    }


    public List<Node> getSelection() {
        return selection;
    }

    public void log() {
        for( Node task: selection) {
            System.out.println( "In selection: " + task);
        }
    }
}

SelectionOverlay.java: create drag handles, show cursors for each drag handle, allow dragging and resizing using drag handles

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;

public class SelectionOverlay extends Region {

    /**
     * Selection rectangle visibility
     */
    boolean selectionRectangleVisible = true;

    /**
     * Drag handles visibility. In addition to this boolean the cell must implement ResizableI
     */
    boolean dragHandlesVisible = true;

    /**
     * The shape (cell) to which the overlay has been assigned.
     */
    final Node monitoredShape;

    private ChangeListener<Bounds> boundsChangeListener;

    /**
     * Drag handle size
     */
    double diameter = 6;

    /**
     * Drag handle half size, just to avoid / 2.0 of radius everywhere
     */
    double radius = diameter / 2.0;

    /**
     * Selection rectangle around the shape / cell
     */
    Rectangle selectionRectangle = new Rectangle();

    // Drag handles
    DragHandle dragHandleNW;
    DragHandle dragHandleNE;
    DragHandle dragHandleSE;
    DragHandle dragHandleSW;
    DragHandle dragHandleN;
    DragHandle dragHandleS;
    DragHandle dragHandleE;
    DragHandle dragHandleW;

    Node cell;

    public SelectionOverlay(final Node shape) {

        this.cell = shape;


        // mouse events only on our drag objects, but not on this node itself
        // note that the selection rectangle is only for visuals and is set to being mouse transparent
        setPickOnBounds(false);

        // the rectangle is only for visuals, we don't want any mouse events on it
        selectionRectangle.setMouseTransparent(true);

        // drag handles: drag handles must be enabled AND the cell must implement ResizableI
        dragHandlesVisible = dragHandlesVisible && (shape instanceof Node);

        if( selectionRectangleVisible) {

            // set style
            selectionRectangle.getStyleClass().add("selection_rectangle");

            getChildren().add(selectionRectangle);

        }

        if( dragHandlesVisible) {
            dragHandleNW = new DragHandle( diameter, Cursor.NW_RESIZE);
            dragHandleNE = new DragHandle( diameter, Cursor.NE_RESIZE);
            dragHandleSE = new DragHandle( diameter, Cursor.SE_RESIZE);
            dragHandleSW = new DragHandle( diameter, Cursor.SW_RESIZE);

            dragHandleN = new DragHandle( diameter, Cursor.N_RESIZE);
            dragHandleS = new DragHandle( diameter, Cursor.S_RESIZE);
            dragHandleE = new DragHandle( diameter, Cursor.E_RESIZE);
            dragHandleW = new DragHandle( diameter, Cursor.W_RESIZE);

            getChildren().addAll(dragHandleNW, dragHandleNE, dragHandleSE, dragHandleSW, dragHandleN, dragHandleS, dragHandleE, dragHandleW);
        }

        monitoredShape = shape;

        monitorBounds();

    }

    /**
     * Set bounds listener for the overlay.
     */
    private void monitorBounds() {

      // determine the shape's
      final ReadOnlyObjectProperty<Bounds> bounds = monitoredShape.boundsInParentProperty();

      // set the overlay based upon the new bounds and keep it in sync
      updateBoundsDisplay(bounds.get());

      // keep the overlay based upon the new bounds in sync
      boundsChangeListener = new ChangeListener<Bounds>() {
        @Override public void changed(ObservableValue<? extends Bounds> observableValue, Bounds oldBounds, Bounds newBounds) {
          updateBoundsDisplay(newBounds);
        }
      };

      bounds.addListener(boundsChangeListener);
    }

    /**
     * Update this overlay to match a new set of bounds.
     * @param newBounds
     */
    private void updateBoundsDisplay(Bounds newBounds) {

        if( selectionRectangleVisible) {
            selectionRectangle.setX(newBounds.getMinX());
            selectionRectangle.setY(newBounds.getMinY());
            selectionRectangle.setWidth(newBounds.getWidth());
            selectionRectangle.setHeight(newBounds.getHeight());
        }

        if( dragHandlesVisible) {
            dragHandleNW.setX(newBounds.getMinX() - radius);
            dragHandleNW.setY(newBounds.getMinY() - radius);

            dragHandleNE.setX(newBounds.getMaxX() - radius);
            dragHandleNE.setY(newBounds.getMinY() - radius);

            dragHandleSE.setX(newBounds.getMaxX() - radius);
            dragHandleSE.setY(newBounds.getMaxY() - radius);

            dragHandleSW.setX(newBounds.getMinX() - radius);
            dragHandleSW.setY(newBounds.getMaxY() - radius);

            dragHandleN.setX(newBounds.getMinX() + newBounds.getWidth() / 2.0 - radius);
            dragHandleN.setY(newBounds.getMinY() - radius);

            dragHandleS.setX(newBounds.getMinX() + newBounds.getWidth() / 2.0 - radius);
            dragHandleS.setY(newBounds.getMaxY() - radius);

            dragHandleE.setX(newBounds.getMaxX() - radius);
            dragHandleE.setY(newBounds.getMinY() + newBounds.getHeight() / 2.0 - radius);

            dragHandleW.setX(newBounds.getMinX() - radius);
            dragHandleW.setY(newBounds.getMinY() + newBounds.getHeight() / 2.0 - radius);

        }
    }

    // make a node movable by dragging it around with the mouse.
    private void enableDrag( final DragHandle dragHandle) {

        final Delta dragDelta = new Delta();

        dragHandle.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {

                // record a delta distance for the drag and drop operation.
                dragDelta.x = dragHandle.getX() - mouseEvent.getX();
                dragDelta.y = dragHandle.getY() - mouseEvent.getY();

                dragDelta.minX = cell.getBoundsInParent().getMinX();
                dragDelta.maxX = cell.getBoundsInParent().getMaxX();
                dragDelta.minY = cell.getBoundsInParent().getMinY();
                dragDelta.maxY = cell.getBoundsInParent().getMaxY();

                getScene().setCursor(dragHandle.getDragCursor());

                mouseEvent.consume();
            }
        });
        dragHandle.setOnMouseReleased(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                getScene().setCursor(Cursor.DEFAULT);

                mouseEvent.consume();
            }
        });
        dragHandle.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {

                Node rCell = (Node) cell;

                double newX = mouseEvent.getX() + dragDelta.x;
                double newY = mouseEvent.getY() + dragDelta.y;

                dragHandle.setX(newX);
                dragHandle.setY(newY);

                if( dragHandle == dragHandleN) {

                    setHeight( rCell, dragDelta.maxY - newY - radius);
                    rCell.relocate( dragDelta.minX, newY + radius);

                }
                else if( dragHandle == dragHandleNE) {

                    setWidth( rCell, newX - dragDelta.minX + radius);
                    setHeight( rCell, dragDelta.maxY - newY - radius);
                    rCell.relocate( dragDelta.minX, newY + radius);

                }
                else if( dragHandle == dragHandleE) {

                    setWidth( rCell, newX - dragDelta.minX + radius);

                }
                else if( dragHandle == dragHandleSE) {

                    setWidth( rCell, newX - dragDelta.minX + radius);
                    setHeight( rCell, newY - dragDelta.minY + radius);

                }
                else if( dragHandle == dragHandleS) {

                    setHeight( rCell, newY - dragDelta.minY + radius);

                }
                else if( dragHandle == dragHandleSW) {

                    setWidth( rCell, dragDelta.maxX - newX - radius);
                    setHeight( rCell, newY - dragDelta.minY + radius);
                    rCell.relocate( newX + radius, dragDelta.minY);
                }
                else if( dragHandle == dragHandleW) {

                    setWidth( rCell, dragDelta.maxX - newX - radius);
                    rCell.relocate( newX + radius, dragDelta.minY);

                }
                else if( dragHandle == dragHandleNW) {

                    setWidth( rCell, dragDelta.maxX - newX - radius);
                    setHeight( rCell, dragDelta.maxY - newY - radius);
                    rCell.relocate( newX + radius, newY + radius);

                }

                mouseEvent.consume();
            }
        });
        dragHandle.setOnMouseEntered(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                if (mouseEvent.isPrimaryButtonDown()) {
                    return;
                }

                getScene().setCursor(dragHandle.getDragCursor());

            }
        });
        dragHandle.setOnMouseExited(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                if (!mouseEvent.isPrimaryButtonDown()) {
                    getScene().setCursor(Cursor.DEFAULT);
                }
            }
        });

    }

    private void setWidth( Node node, double value) {

        if( node instanceof Rectangle) {

            Rectangle shape = (Rectangle) node;
            shape.setWidth(value);

        } else if (node instanceof Control) {

            Control control = (Control) node;
            control.setPrefWidth(value);

        } else if (node instanceof Region) {

            Region region = (Region) node;
            region.setPrefWidth(value);

        }

    }

    private void setHeight( Node node, double value) {

        if( node instanceof Rectangle) {

            Rectangle shape = (Rectangle) node;
            shape.setHeight(value);

        } else if (node instanceof Control) {

            Control control = (Control) node;
            control.setPrefHeight(value);

        } else if (node instanceof Region) {

            Region region = (Region) node;
            region.setPrefHeight(value);

        }

    }

    /**
     * Drag handle
     */
    private class DragHandle extends Rectangle {

        Cursor dragCursor;

        public DragHandle( double size, Cursor dragCursor) {

            this.dragCursor = dragCursor;

            setWidth(size);
            setHeight(size);

            getStyleClass().add("selection_drag_handle");

            enableDrag( this);
        }

        public Cursor getDragCursor() {
            return dragCursor;
        }
    }

    // records relative x and y co-ordinates.
    private class Delta {
        double x;
        double y;
        double minX;
        double maxX;
        double minY;
        double maxY;
    }

  }

application.css

.selection_rectangle {
    -fx-fill: transparent;
    -fx-stroke: green;
    -fx-stroke-width: 1;
    -fx-stroke-dash-array: 2 4;
    -fx-stroke-type: outside;
}
.selection_drag_handle {
    -fx-fill: rgba( 0, 128, 0, 0.6); /*green = #008000 */
    -fx-stroke: green;
    -fx-stroke-width: 1;
    -fx-stroke-type: outside;
}

And a screenshot:

enter image description here

ps: in case someone finds the time to refactor the code, please do share

like image 41
Roland Avatar answered Oct 11 '22 13:10

Roland