Lineargradient for Line in javafx - javafx

How can I use lineargradient for Line in Javafx?
I tried this:
LinearGradient lg = new LinearGradient(...);
line.setFill(lg);
....
It doesn't work.

Based on this answer, you need to specify the gradient on absolute coordinates.
Something like this will work for any given ones:
LinearGradient linearGradient = new LinearGradient(x1, y1, x2, y2, false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
Based on this answer, you can create a simple app to test it how it works when you move the anchor nodes around the scene:
private final DoubleProperty x1 = new SimpleDoubleProperty();
public final double getX1() { return x1.get(); }
public final void setX1(double value) { x1.set(value); }
public final DoubleProperty x1Property() { return x1; }
private final DoubleProperty x2 = new SimpleDoubleProperty();
public final double getX2() { return x2.get(); }
public final void setX2(double value) { x2.set(value); }
public final DoubleProperty x2Property() { return x2; }
private final DoubleProperty y1 = new SimpleDoubleProperty();
public final double getY1() { return y1.get(); }
public final void setY1(double value) { y1.set(value); }
public final DoubleProperty y1Property() { return y1; }
private final DoubleProperty y2 = new SimpleDoubleProperty();
public final double getY2() { return y2.get(); }
public final void setY2(double value) { y2.set(value); }
public final DoubleProperty y2Property() { return y2; }
private Line line;
#Override
public void start(Stage primaryStage) {
Group group = new Group();
primaryStage.setScene(new Scene(group, 400, 400));
x1.set(100);
y1.set(50);
x2.set(200);
y2.set(300);
line = new Line(x1.get(), y1.get(), x2.get(), y2.get());
line.startXProperty().bind(x1);
line.startYProperty().bind(y1);
line.endXProperty().bind(x2);
line.endYProperty().bind(y2);
line.setStrokeWidth(12);
line.setMouseTransparent(true);
Anchor start = new Anchor(Color.BLUE, x1, y1);
Anchor end = new Anchor(Color.YELLOW, x2, y2);
group.getChildren().setAll(line, start, end);
x1Property().addListener(o -> updateLine());
x2Property().addListener(o -> updateLine());
y1Property().addListener(o -> updateLine());
y2Property().addListener(o -> updateLine());
updateLine();
primaryStage.show();
}
private void updateLine() {
LinearGradient linearGradient = new LinearGradient(x1.get(), y1.get(), x2.get(), y2.get(), false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
}
private class Anchor extends Circle {
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(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(mouseEvent -> {
getScene().setCursor(Cursor.HAND);
});
setOnMouseDragged(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(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
});
}
// records relative x and y co-ordinates.
private class Delta { double x, y; }
}

Use this
line.setStyle("-fx-background-color: linear-gradient(to right, #color_1, #color_2);");

Related

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException while adding cloned object to Pane

I have the following class in which I am trying to implement the prototype pattern:
public class Element extends Group implements Cloneable{
private final double ELEMENT_WIDTH = 50;
private final double ELEMENT_HEIGHT = 70;
private final double CIRCLE_RADIUS = 1;
private int minimalNumberOfInputs;
private Shape body = new Rectangle(ELEMENT_WIDTH, ELEMENT_HEIGHT, Color.WHITE);
private Circle output = new Circle(CIRCLE_RADIUS);
private Line outputLine = new Line(ELEMENT_WIDTH, ELEMENT_HEIGHT / 2, ELEMENT_WIDTH + 15, ELEMENT_HEIGHT / 2);
private ArrayList<Circle> inputs;
private ArrayList<Line> inputLines;
private Circle inversionDesignation;
private Text symbol;
private Integer identifier;
private double bodyCorX;
private double bodyCorY;
private double corX = 0;
private double corY = 0;
private double mouseX = 0;
private double mouseY = 0;
private boolean dragging = false;
public Integer getIdentifier() {
return identifier;
}
public ArrayList<Circle> getInputs() {
return inputs;
}
public Circle getOutput() {
return output;
}
public Circle getInversionDesignation() {
return inversionDesignation;
}
public double getELEMENT_WIDTH() {
return ELEMENT_WIDTH;
}
public double getELEMENT_HEIGHT() {
return ELEMENT_HEIGHT;
}
public double getCIRCLE_RADIUS() {
return CIRCLE_RADIUS;
}
public int getMinimalNumberOfInputs() {
return minimalNumberOfInputs;
}
public Shape getBody() {
return body;
}
public Line getOutputLine() {
return outputLine;
}
public ArrayList<Line> getInputLines() {
return inputLines;
}
public Text getSymbol() {
return symbol;
}
public double getBodyCorX() {
return bodyCorX;
}
public double getBodyCorY() {
return bodyCorY;
}
public double getCorX() {
return corX;
}
public double getCorY() {
return corY;
}
public double getMouseX() {
return mouseX;
}
public double getMouseY() {
return mouseY;
}
public boolean isDragging() {
return dragging;
}
public Element(){
}
public Element(Circle inversionDesignation, Text symbol, int minimalNumberOfInputs) {
this.minimalNumberOfInputs = minimalNumberOfInputs;
this.body.setStroke(Color.BLACK);
this.body.setStrokeType(StrokeType.INSIDE);
this.body.setStrokeWidth(2.5);
this.output.setFill(Color.BLACK);
this.output.toFront();
this.inversionDesignation = inversionDesignation;
this.symbol = symbol;
this.inputs = new ArrayList<>();
this.inputLines = new ArrayList<>();
this.identifier = this.hashCode();
this.outputLine.setStrokeWidth(2);
this.createStartInputs();
this.configureInputPoints();
this.bindGraphicalElements();
elementMovementEvents();
elementEnteredEvents();
}
private void bindGraphicalElements() {
this.getChildren().add(body);
this.getChildren().add(output);
this.getChildren().add(outputLine);
this.getChildren().addAll(inputs);
this.getChildren().addAll(inputLines);
if (this.symbol != null) {
this.getChildren().add(symbol);
symbol.relocate((ELEMENT_WIDTH / 2) - symbol.getTabSize() / 2, ELEMENT_HEIGHT / 8);
symbol.setFont(new Font("Consolas", 14));
}
if (this.inversionDesignation != null) {
this.getChildren().add(inversionDesignation);
this.inversionDesignation.setStrokeType(StrokeType.INSIDE);
this.inversionDesignation.setStrokeWidth(1);
this.inversionDesignation.setStroke(Color.BLACK);
this.inversionDesignation.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (this.inversionDesignation.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - this.inversionDesignation.getRadius());
inversionDesignation.toFront();
}
}
private void addGraphicalElement(Shape shape) {
this.getChildren().add(shape);
}
private void createStartInputs() {
for (int i = 0; i < this.minimalNumberOfInputs; i++) {
inputs.add(new Circle(CIRCLE_RADIUS));
inputs.get(i).setFill(Color.BLACK);
inputs.get(i).toFront();
}
configureInputPoints();
for (int i = 0; i < inputs.size(); i++) {
Line line = new Line(inputs.get(i).getLayoutX() - 15, inputs.get(i).getLayoutY(), inputs.get(i).getLayoutX(), inputs.get(i).getLayoutY());
line.setStrokeWidth(2);
inputLines.add(line);
}
}
private void addNewInput() {
Circle newCircle = new Circle(CIRCLE_RADIUS);
newCircle.setFill(Color.BLACK);
this.inputs.add(newCircle);
this.configureInputPoints();
this.addGraphicalElement(newCircle);
}
private void configureInputPoints() {
this.output.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (output.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - output.getRadius());
int distance = (int) ELEMENT_HEIGHT / (inputs.size() + 1); //Растояние между точками входа.
for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).relocate(this.bodyCorX - (CIRCLE_RADIUS - 1), this.bodyCorY + (distance * (i + 1) - CIRCLE_RADIUS));
}
}
private void elementMovementEvents() {
onMousePressedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
corX = getLayoutX();
corY = getLayoutY();
}
});
onMouseDraggedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getSceneX() - mouseX; //смещение по X
double offsetY = event.getSceneY() - mouseY;
corX += offsetX;
corY += offsetY;
double scaledX = corX;
double scaledY = corY;
setLayoutX(scaledX);
setLayoutY(scaledY);
dragging = true;
mouseX = event.getSceneX();
mouseY = event.getSceneY();
event.consume();
}
});
onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
dragging = false;
}
});
}
private void testEvent() {
this.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
this.addNewInput();
});
}
private void elementEnteredEvents() {
onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getTarget() instanceof Circle) {
System.out.println("Circle!");
}
}
});
}
#Override
public Element clone() throws CloneNotSupportedException{
return (Element)super.clone();
}
#Override
public String toString() {
return "Element " + this.hashCode() + ": location = " + this.getLayoutX() + ": output = " + this.minimalNumberOfInputs;
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof Element)) return false;
Element element = (Element) obj;
return element.getBodyCorX() == bodyCorX && element.getBodyCorY() == bodyCorY && element.getBody() == body;
}
}
I am trying to implement a pattern using the following method:
#Override
public Element clone() throws CloneNotSupportedException{
return (Element)super.clone();
}
I have a controller like this:
public class FXMLController {
#FXML
private AnchorPane anchorPane;
#FXML
private AnchorPane workPane;
//prototypes
private Element AndPrototype = new Element(null, new Text("&"), 2);
private Element OrPrototype = new Element(null, new Text("1"), 2);
private Element NotPrototype = new Element(new Circle(5, Color.WHITE), null, 1);
private Element AndNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("&"), 2);
private Element OrNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("1"), 2);
#FXML
public void initialize() {
}
#FXML
private void method() throws CloneNotSupportedException {
workPane.getChildren().add(AndPrototype.clone());
}
}
In this method, I am trying to make a clone and add it to the AnchorPane
#FXML
private void method() throws CloneNotSupportedException {
workPane.getChildren().add(AndPrototype.clone());
}
As a result, when I click on the button, I get an error of the following content:
Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 8
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
at java.base/java.util.Objects.checkIndex(Objects.java:359)
at java.base/java.util.ArrayList.get(ArrayList.java:427)
at javafx.base/com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:305)
at javafx.graphics/javafx.scene.Parent.updateCachedBounds(Parent.java:1704)
at javafx.graphics/javafx.scene.Parent.recomputeBounds(Parent.java:1648)
at javafx.graphics/javafx.scene.Parent.doComputeGeomBounds(Parent.java:1501)
at javafx.graphics/javafx.scene.Parent$1.doComputeGeomBounds(Parent.java:115)
at javafx.graphics/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl(ParentHelper.java:84)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeGeomBounds(NodeHelper.java:115)
at javafx.graphics/javafx.scene.Node.updateGeomBounds(Node.java:3847)
at javafx.graphics/javafx.scene.Node.getGeomBounds(Node.java:3809)
at javafx.graphics/javafx.scene.Node.doComputeLayoutBounds(Node.java:3657)
at javafx.graphics/javafx.scene.Node$1.doComputeLayoutBounds(Node.java:449)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBoundsImpl(NodeHelper.java:166)
at javafx.graphics/com.sun.javafx.scene.GroupHelper.computeLayoutBoundsImpl(GroupHelper.java:63)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBounds(NodeHelper.java:106)
at javafx.graphics/javafx.scene.Node$13.computeBounds(Node.java:3509)
at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9782)
at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9752)
at javafx.graphics/javafx.scene.Node.getLayoutBounds(Node.java:3524)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeWidth(AnchorPane.java:272)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeMinWidth(AnchorPane.java:248)
at javafx.graphics/javafx.scene.Parent.minWidth(Parent.java:1048)
at javafx.graphics/javafx.scene.layout.Region.minWidth(Region.java:1553)
at javafx.graphics/javafx.scene.layout.Region.computeChildPrefAreaWidth(Region.java:2012)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeChildWidth(AnchorPane.java:315)
at javafx.graphics/javafx.scene.layout.AnchorPane.layoutChildren(AnchorPane.java:353)
at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1207)
at javafx.graphics/javafx.scene.Scene.doLayoutPass(Scene.java:576)
at javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2476)
at javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:413)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:412)
at javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:439)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:563)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:543)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:536)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:342)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:831)
I have absolutely no idea what this error may be connected with and in which direction they are moving.
Usually this is caused because you modified the scene graph or an attribute of a scene graph node off of the JavaFX thread.
Similar stack traces all caused by threading errors:
javafx vlcj play mutiple video get IndexOutOfBoundsException error
Exception on JavaFX when moving Labels around their container.(IndexOutOfBoundsException)
How to fix IndexOutOfBounds exception when javafx recomputes Parent/Node Bounds
How do I find out what's causing this Java FX Application Thread exception?
If it is a multi-threading issue, usually it can be fixed by either removing unnecessary threading. Or if multi-threading is unavoidable, using tools like the javafx.concurrent package or Platform.runLater to ensure nodes in the active scene graph are only modified on the JavaFX thread.
However, if you don’t have any multi-threading going on, it might be down to the weird cloning stuff you have going on which may be ill-advised. JavaFX nodes can only occur once in the scene and the clones may cause glitches in the framework.

Can't rotate a shape in Javafx

I'm having trouble rotating a shape in JavaFX. Here's some code in which I want the arrowhead to point in the same direction as the line (yes, I will be adding a translation later, to put it on the end of the line, but one thing at a time).
The println statement is showing me that theta is updating correctly as I move the start and end points around, but the arrowhead stays stubbornly at the original angle. Is there another step I have to do in order to get shape to rotate, or should I be doing it a different way?
package application.view;
public class Connector extends Group {
private final DoubleProperty startX;
private final DoubleProperty startY;
private final DoubleProperty endX;
private final DoubleProperty endY;
public Connector(Line line, Polygon arrowhead, Preferences prefs) {
arrowhead.setStroke(Color.BLACK);
arrowhead.setStrokeWidth(1);
arrowhead.setFill(Color.CORNFLOWERBLUE);
startX = new SimpleDoubleProperty();
startY = new SimpleDoubleProperty();
endX = new SimpleDoubleProperty();
endY = new SimpleDoubleProperty();
thetaBinding = new DoubleBinding() {
{
super.bind(startX, startY, endX, endY);
}
#Override
protected double computeValue() {
return Math.atan2(getEndY() - getStartY(), getEndX() - getStartX());
}
};
theta = new SimpleDoubleProperty();
theta.bind(thetaBinding);
theta.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
System.out.println(newValue);
arrowhead.rotateProperty().set((Double)newValue);
}
});
lineLength = new DoubleBinding() {
{
super.bind(startX, startY, endX, endY);
}
#Override
protected double computeValue() {
return Math.hypot(getEndX() - getStartX(), getEndY() - getStartY()) - prefs.getDouble("CONNECTOR_ARROW_LENGTH", Defaults.CONNECTOR_ARROW_LENGTH);
}
};
lineEndX = new DoubleBinding() {
{
super.bind(startX, lineLength, theta);
}
#Override
protected double computeValue() {
return startX.get() + lineLength.get() * Math.cos(theta.get());
}
};
lineEndY = new DoubleBinding() {
{
super.bind(startY, lineLength, theta);
}
#Override
protected double computeValue() {
return startY.get() + lineLength.get() * Math.sin(theta.get());
}
};
arrowhead.getPoints().setAll(new Double[] {
0.0, 0.0,
0.0, prefs.getDouble("CONNECTOR_ARROW_HALF_WIDTH", Defaults.CONNECTOR_ARROW_HALF_WIDTH),
prefs.getDouble("CONNECTOR_ARROW_LENGTH", Defaults.CONNECTOR_ARROW_LENGTH), 0.0,
0.0, -prefs.getDouble("CONNECTOR_ARROW_HALF_WIDTH", Defaults.CONNECTOR_ARROW_HALF_WIDTH),
});
line.startXProperty().bind(startX);
line.startYProperty().bind(startY);
line.endXProperty().bind(lineEndX);
line.endYProperty().bind(lineEndY);
getChildren().addAll(line, arrowhead);
}
public final DoubleBinding thetaBinding;
public final DoubleProperty theta;
public final DoubleBinding lineLength;
public final DoubleBinding lineEndX;
public final DoubleBinding lineEndY;
public double getStartX() {
return startX.get();
}
public void setStartX(double value) {
startX.set(value);
}
public DoubleProperty startXProperty() {
return startX;
}
public double getStartY() {
return startY.get();
}
public void setStartY(double value) {
startY.set(value);
}
public DoubleProperty startYProperty() {
return startY;
}
public double getEndX() {
return endX.get();
}
public void setEndX(double value) {
endX.set(value);
}
public DoubleProperty endXProperty() {
return endX;
}
public double getEndY() {
return endY.get();
}
public void setEndY(double value) {
endY.set(value);
}
public DoubleProperty endYProperty() {
return endY;
}
}

Zooming and panning image in scrollpane

I have a problem with zooming and panning image in ScrollPane. So far I have code like this:
Image image = imageView.getImage();
scrollPane.setPrefViewportWidth(0d);
scrollPane.setPrefViewportHeight(0d);
imageView.setFitWidth(0d);
imageView.setFitHeight(0d);
Bounds viewportBounds = scrollPane.getViewportBounds();
boolean vertical = image.getWidth() > image.getHeight();
if (imageView.getRotate() == 90 || imageView.getRotate() == 270d) {
vertical = !vertical;
}
imageView.setPreserveRatio(true);
double propX = viewportBounds.getWidth() / image.getWidth();
double propY = viewportBounds.getHeight() / image.getHeight();
boolean xLead = !(propX > propY);
imageView.setScaleX((xLead) ? propX : propY);
imageView.setScaleY((xLead) ? propX : propY);
scrollPane.setContent(imageView);
scrollPane.setPannable(true);
scrollPane.setHvalue(scrollPane.getHmin() + (scrollPane.getHmax() - scrollPane.getHmin()) / 2); // center the scroll contents.
scrollPane.setVvalue(scrollPane.getVmin() + (scrollPane.getVmax() - scrollPane.getVmin()) / 2);
zoom(imageView);
private void zoom(ImageView imagePannable) {
imagePannable.setOnScroll(
new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double zoomFactor = 1.20;
double deltaY = event.getDeltaY();
if (deltaY < 0) {
zoomFactor = 0.80;
}
imagePannable.setScaleX(imagePannable.getScaleX() * zoomFactor);
imagePannable.setScaleY(imagePannable.getScaleY() * zoomFactor);
event.consume();
}
});
}
What I want to do is align image relative to mouse pointer not to center of image everytime.
I also have a problem with large images (like maps which are 8*A4 size for example). When I zooming this maps pannable function stop working. What is wrong with this code? Thanks for helps!
Several people (including me) have had this same question. I got my answer here.
In the interest of clarity, here is a working example of a panning & zooming pane using a rectangle as the zoomed node. I have implemented this in a slightly more complex way with an ImageView.
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 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.NAVY);
rect.setStrokeType(StrokeType.INSIDE);
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();
}
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = scale - oldScale;
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 - Resize Rectangle [duplicate]

This question already has an answer here:
How to change a shape property using its border in JavaFX?
(1 answer)
Closed 5 years ago.
I am try to allow users to drag/resize a rectangle in javafx.
Rectangle in javafx uses TopLeft-X, Y, height and width to draw.
What I am trying to do is to have 4 handles at each corner of the rectangle to allow users to drag the handle. Which will result in resizing the rectangle.
My application throws a stackoverflow exception which I know is caused by the recursive call of the listener.
Did i forget something? Or is there some listener that is unnecessary?
public class Handler extends Circle
{
private double deltaX;
private double deltaY;
private static final double DEFAULT_RADIUS = 10;
private static final Color DEFAULT_COLOR = Color.GOLD;
private DoubleProperty xProperty;
private DoubleProperty yProperty;
public Handler(DoubleProperty xProperty, DoubleProperty yProperty)
{
super(DEFAULT_RADIUS, DEFAULT_COLOR);
this.xProperty = xProperty;
this.yProperty = yProperty;
init();
}
private void init()
{
setFill(DEFAULT_COLOR.deriveColor(1, 1, 1, 0.5));
setStroke(DEFAULT_COLOR);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
centerXProperty().bind(xProperty);
centerYProperty().bind(yProperty);
setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
mouseEvent.consume();
deltaX = getCenterX() - mouseEvent.getX();
deltaY = getCenterY() - mouseEvent.getY();
getScene().setCursor(javafx.scene.Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
getScene().setCursor(javafx.scene.Cursor.HAND);
}
});
setOnMouseEntered(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
if (!mouseEvent.isPrimaryButtonDown())
{
getScene().setCursor(javafx.scene.Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
if (!mouseEvent.isPrimaryButtonDown())
{
getScene().setCursor(javafx.scene.Cursor.DEFAULT);
}
}
});
setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
double newX = mouseEvent.getX() + deltaX;
double newY = mouseEvent.getY() + deltaY;
if (newX > 0 && newX < getScene().getWidth())
{
xProperty.set(newX);
}
if (newY > 0 && newY < getScene().getHeight())
{
yProperty.set(newY);
}
}
});
}
//JavaFx Accessor and mutator
}
public class CustomRectangle extends Rectangle
{
private DoubleProperty topRightX;
private DoubleProperty topRightY;
private DoubleProperty btmLeftX;
private DoubleProperty btmLeftY;
private DoubleProperty btmRightX;
private DoubleProperty btmRightY;
private DoubleProperty customeWidth;
private DoubleProperty customHeight;
public CustomRectangle()
{
super();
init();
}
public CustomRectangle(double x, double y, double width, double height)
{
super(x, y, width, height);
init();
}
public CustomRectangle(double width, double height, Paint fill)
{
super(width, height, fill);
init();
}
public CustomRectangle(double width, double height)
{
super(width, height);
init();
}
private void init()
{
topRightX = new SimpleDoubleProperty();
topRightY = new SimpleDoubleProperty();
btmLeftX = new SimpleDoubleProperty();
btmLeftY = new SimpleDoubleProperty();
btmRightX = new SimpleDoubleProperty();
btmRightY = new SimpleDoubleProperty();
topRightX.addListener((observable, oldValue, newValue) ->
{
this.setWidth(this.getWidth() + (newValue.doubleValue() - oldValue.doubleValue()));
});
topRightY.addListener((observable, oldValue, newValue) ->
{
this.setY(newValue.doubleValue());
this.setHeight(this.getHeight() - (newValue.doubleValue() - oldValue.doubleValue()));
});
btmLeftX.addListener((observable, oldValue, newValue) ->
{
this.setX(newValue.doubleValue());
this.setWidth(this.getWidth() - (newValue.doubleValue() - oldValue.doubleValue()));
});
btmLeftY.addListener((observable, oldValue, newValue) ->
{
this.setY(newValue.doubleValue());
this.setHeight(this.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
});
btmRightX.addListener((observable, oldValue, newValue) ->
{
this.setWidth(this.getWidth() + (newValue.doubleValue() - oldValue.doubleValue()));
});
btmRightY.addListener((observable, oldValue, newValue) ->
{
this.setHeight(this.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
});
this.xProperty().addListener((observable, oldValue, newValue) ->
{
btmLeftX.set(newValue.doubleValue());
topRightX.set(newValue.doubleValue() + this.getWidth());
btmRightX.set(newValue.doubleValue() + this.getWidth());
});
this.yProperty().addListener((observable, oldValue, newValue) ->
{
btmLeftY.set(newValue.doubleValue() + this.getHeight());
topRightY.set(newValue.doubleValue());
btmRightY.set(newValue.doubleValue() + this.getHeight());
});
this.widthProperty().addListener((observable, oldValue, newValue) ->
{
topRightX.set(this.getX() + (newValue.doubleValue() - oldValue.doubleValue()));
btmRightX.set(this.getX() + (newValue.doubleValue() - oldValue.doubleValue()));
});
this.heightProperty().addListener((observable, oldValue, newValue) ->
{
btmLeftY.set(this.getY() + (newValue.doubleValue() - oldValue.doubleValue()));
btmRightY.set(this.getY() + (newValue.doubleValue() - oldValue.doubleValue()));
});
}
//JavaFx Accessor and Mutator
}
public class FeatureHelper
{
private static double orgSceneX;
private static double orgSceneY;
private static double orgTranslateX;
private static double orgTranslateY;
public static CustomRectangle createDraggableRectangle(double x, double y, double width, double height,
boolean resizeImage)
{
CustomRectangle rect = new CustomRectangle(x, y, width, height);
// top left resize handle:
Handler topLeftHandle = new Handler(rect.topLeftXProperty(), rect.topLeftYProperty());
// top right resize handle:
Handler topRightHandle = new Handler(rect.topRightXProperty(), rect.topRightYProperty());
// bottom left resize handle:
Handler btmLeftHandle = new Handler(rect.btmLeftXProperty(), rect.btmLeftYProperty());
// bottom right resize handle:
Handler btmRightHandle = new Handler(rect.btmRightXProperty(), rect.btmRightYProperty());
// force circles to live in same parent as rectangle:
rect.parentProperty().addListener((obs, oldParent, newParent) ->
{
for (Circle c : Arrays.asList(topLeftHandle, topRightHandle, btmLeftHandle, btmRightHandle))
{
if (newParent != null)
{
((Pane) newParent).getChildren().add(c);
}
}
});
rect.setOnMousePressed(event ->
{
event.consume();
orgSceneX = event.getSceneX();
orgSceneY = event.getSceneY();
Node p = ((Node) (event.getSource()));
orgTranslateX = p.getTranslateX();
orgTranslateY = p.getTranslateY();
});
rect.setOnMouseDragged(event ->
{
double offsetX = event.getSceneX() - orgSceneX;
double offsetY = event.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Node p = ((Node) (event.getSource()));
p.setTranslateX(newTranslateX);
p.setTranslateY(newTranslateY);
for(Circle circle : Arrays.asList(topLeftHandle, topRightHandle, btmLeftHandle, btmRightHandle))
{
circle.setTranslateX(newTranslateX);
circle.setTranslateY(newTranslateY);
}
});
return rect;
}
}
You should have a look at this nice Example from karakullukcuhuseyin -->
https://github.com/karakullukcuhuseyin/JavaFX-ImageCropper
He extended the Rectangle Class as well and is using his custom Rectangle to select an area in an Image.

How to select multiple components of Canvas using ctrl key in javafx?

I want to select few images available on my Canvas. How to select multiple of them using control key?
You create a model, e. g.
List<Node> selectionModel = new ArrayList<Node>();
and add a listener to each node, e. g.
imageView.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
if( event.isControlDown()) {
selectionModel.add( (Node) event.getSource());
// logging
System.out.println("Items in model:");
selectionModel.forEach(n -> System.out.println(n));
}
});
Of course you need to provide an option to clear the selection model, e. g. by clicking outside on the scene instead of an ImageView.
Here's a more sophisticated example. Supports rubberband selection, ctrl for selection toggle, shift for adding to selection and click on background to clear the selection.
public class ImageSelection extends Application {
Image image = new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg");
SelectionModel selectionModel = new SelectionModel();
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
double width = image.getWidth();
double height = image.getHeight();
double padding = 20;
for( int row=0; row < 4; row++) {
for( int col=0; col < 4; col++) {
ImageView imageView = new ImageView( image);
imageView.relocate( padding * (col+1) + width * col, padding * (row + 1) + height * row);
pane.getChildren().add(imageView);
}
}
Scene scene = new Scene( pane, 1800, 1200);
primaryStage.setScene( scene);
primaryStage.show();
new RubberBandSelection( pane);
}
public static void main(String[] args) {
launch(args);
}
private class SelectionModel {
Set<Node> selection = new HashSet<>();
public void add( Node node) {
node.setStyle("-fx-effect: dropshadow(three-pass-box, red, 10, 10, 0, 0);");
selection.add( node);
}
public void remove( Node node) {
node.setStyle("-fx-effect: null");
selection.remove( node);
}
public void clear() {
while( !selection.isEmpty()) {
remove( selection.iterator().next());
}
}
public boolean contains( Node node) {
return selection.contains(node);
}
public void log() {
System.out.println( "Items in model: " + Arrays.asList( selection.toArray()));
}
}
private class RubberBandSelection {
final DragContext dragContext = new DragContext();
Rectangle rect;
Pane group;
public RubberBandSelection( Pane group) {
this.group = group;
rect = new Rectangle( 0,0,0,0);
rect.setStroke(Color.BLUE);
rect.setStrokeWidth(1);
rect.setStrokeLineCap(StrokeLineCap.ROUND);
rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));
group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
dragContext.mouseAnchorX = event.getSceneX();
dragContext.mouseAnchorY = event.getSceneY();
rect.setX(dragContext.mouseAnchorX);
rect.setY(dragContext.mouseAnchorY);
rect.setWidth(0);
rect.setHeight(0);
group.getChildren().add( rect);
event.consume();
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( !event.isShiftDown() && !event.isControlDown()) {
selectionModel.clear();
}
for( Node node: group.getChildren()) {
if( node instanceof ImageView) {
if( node.getBoundsInParent().intersects( rect.getBoundsInParent())) {
if( event.isShiftDown()) {
selectionModel.add( node);
} else if( event.isControlDown()) {
if( selectionModel.contains( node)) {
selectionModel.remove( node);
} else {
selectionModel.add( node);
}
} else {
selectionModel.add( node);
}
}
}
}
selectionModel.log();
rect.setX(0);
rect.setY(0);
rect.setWidth(0);
rect.setHeight(0);
group.getChildren().remove( rect);
event.consume();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getSceneX() - dragContext.mouseAnchorX;
double offsetY = event.getSceneY() - dragContext.mouseAnchorY;
if( offsetX > 0)
rect.setWidth( offsetX);
else {
rect.setX(event.getSceneX());
rect.setWidth(dragContext.mouseAnchorX - rect.getX());
}
if( offsetY > 0) {
rect.setHeight( offsetY);
} else {
rect.setY(event.getSceneY());
rect.setHeight(dragContext.mouseAnchorY - rect.getY());
}
event.consume();
}
};
private final class DragContext {
public double mouseAnchorX;
public double mouseAnchorY;
}
}
}

Resources