I'm animating some pictures. Animations of each of them are performed sequentially. The pictures are in the hbox, the coordinates of which I track through layoutX. At
the end of each animation, I do a check. If the check is successful, then I delete some pictures. Moreover, the coordinates of others are shifted. Since transition was originally set in a loop (before all animations), animations (and recalculation of x) occur during animations, the result is bad.
How can I solve this problem?
There are two hboxes (blue rectangles). They contain pictures (dark blue rectangles) that, when animated, beat a picture from another hbox. During the impact, some images may be deleted.
import javafx.animation.SequentialTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) {
stage.setTitle("Есока");
Pane pane = new Pane();
stage.setScene(new Scene(pane,350,300));
HBox h1 = new HBox();
HBox h2 = new HBox();
h2.setAlignment(Pos.CENTER);
h2.setLayoutY(150);
h2.setPrefWidth(330);
pane.getChildren().addAll(h1, h2);
Rectangle r1 = new Rectangle(100, 100, Color.BLUE);
Rectangle r2 = new Rectangle(100, 100, Color.RED);
Rectangle r3 = new Rectangle(100, 100, Color.GREEN);
Rectangle r4 = new Rectangle(100, 100, Color.BLACK);
Rectangle r5 = new Rectangle(100, 100, Color.BROWN);
h1.setSpacing(10);
h2.setSpacing(10);
h1.getChildren().addAll(r1, r2, r3);
h2.getChildren().addAll(r4, r5);
pane.layout();
SequentialTransition st=new SequentialTransition();
for (int i = 0; i < 5; i++) {
int j1= (int) (Math.random()*h1.getChildren().size());
int j2= (int) (Math.random()*h2.getChildren().size());
TranslateTransition translateTransition=new TranslateTransition();
double x=h2.getChildren().get(j2).getLayoutX()-
h1.getChildren().get(j1).getLayoutX();
translateTransition.setByX(x);
double y=150;
translateTransition.setByY(y);
translateTransition.setAutoReverse(true);
translateTransition.setCycleCount(2);
translateTransition.setNode(h1.getChildren().get(j1));
translateTransition.setOnFinished(actionEvent -> {
if(Math.random()>0.5)h2.getChildren().remove(j2);
});
st.getChildren().add(translateTransition);
if(h1.getChildren().size()<=0|h2.getChildren().size()<=0)break;
}
st.setDelay(Duration.seconds(1));
st.play();
stage.show();
}
}
In this code, x is considered in a loop, and some images are deleted after, which is why the animation is incorrect. The problem is visible when the first object is removed (the "blow" trace goes to false coordinates).
It's not at all clear what you're trying to do, but you probably want to compute the next transition when the previous one finishes, instead of trying to compute them all at once.
public class Main extends Application {
private final Random rng = new Random();
private Pane pane;
private HBox h1;
private HBox h2;
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) {
stage.setTitle("Есока");
pane = new Pane();
stage.setScene(new Scene(pane,350,300));
h1 = new HBox();
h2 = new HBox();
h2.setAlignment(Pos.CENTER);
h2.setLayoutY(150);
h2.setPrefWidth(330);
pane.getChildren().addAll(h1, h2);
Rectangle r1 = new Rectangle(100, 100, Color.BLUE);
Rectangle r2 = new Rectangle(100, 100, Color.RED);
Rectangle r3 = new Rectangle(100, 100, Color.GREEN);
Rectangle r4 = new Rectangle(100, 100, Color.BLACK);
Rectangle r5 = new Rectangle(100, 100, Color.BROWN);
h1.setSpacing(10);
h2.setSpacing(10);
h1.getChildren().addAll(r1, r2, r3);
h2.getChildren().addAll(r4, r5);
doAnimation();
stage.show();
}
private void doAnimation() {
pane.layout();
int j1= rng.nextInt(h1.getChildren().size());
int j2= rng.nextInt(h2.getChildren().size());
TranslateTransition translateTransition=new TranslateTransition();
double x=h2.getChildren().get(j2).getLayoutX()-
h1.getChildren().get(j1).getLayoutX();
translateTransition.setByX(x);
double y=150;
translateTransition.setByY(y);
translateTransition.setAutoReverse(true);
translateTransition.setCycleCount(2);
translateTransition.setNode(h1.getChildren().get(j1));
translateTransition.setOnFinished(actionEvent -> {
if(rng.nextBoolean()) {
h2.getChildren().remove(j2);
}
if (h2.getChildren().size() > 0) {
doAnimation();
}
});
translateTransition.play();
}
}
Related
I created a simple paint program which allows the user to choose between 4 shapes(line,circle,rectangle,ellipse) , the user can change the width height and stroke width , he can save the design he makes , undo and redo , the user can also choose the stroke type (solid or dashed) ,I'm almost done with the program but I'm facing one problem , the line is getting displayed not in the way I want it to be displayed as the photo below :
So the line here is of width 32 and height 32 , my line is getting printed in a different way.Is it correct the way I'm printing my line ?
NOTE : I'm using the line shape in the code to make the code small.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class paintLine extends Application {
#Override
public void start(Stage primaryStage) {
Image image1 = new
Image("C:\\Users\\Mhamd\\Desktop\\laol\\src\\resources\\Daco_70400.png",
100, 100, false, false);
ImageView view1 = new ImageView(image1);
view1.setFitHeight(40);
view1.setPreserveRatio(true);
ToggleButton linebtn = new ToggleButton();
linebtn.setGraphic(view1);
ToggleButton[] toolsArr = {linebtn};
ToggleGroup tools = new ToggleGroup();
for (ToggleButton tool : toolsArr) {
tool.setMinWidth(50);
tool.setToggleGroup(tools);
tool.setCursor(Cursor.HAND);
}
ColorPicker cpLine = new ColorPicker(Color.BLACK);
ColorPicker cpFill = new ColorPicker(Color.TRANSPARENT);
TextField textWidth = new TextField("32");
TextField textHeight = new TextField("32");
TextField contouring = new TextField("2");
Label line_color = new Label("Line Color");
Label fill_color = new Label("Fill Color");
Label line_width = new Label("3.0");
Label imgWidth = new Label("Width");
Label imgHeight = new Label("Height");
String week_days[] =
{"Solid", "Dotted"};
ChoiceBox choiceBox = new ChoiceBox(FXCollections
.observableArrayList(week_days));
VBox btns = new VBox(10);
btns.getChildren().addAll(linebtn, imgWidth, textWidth, imgHeight,
textHeight, line_color, cpLine,
fill_color, cpFill, line_width, contouring, choiceBox);
btns.setPadding(new Insets(5));
btns.setStyle("-fx-background-color: #999");
btns.setPrefWidth(100);
Canvas canvas = new Canvas(1080, 790);
GraphicsContext gc;
gc = canvas.getGraphicsContext2D();
gc.setLineWidth(1);
Line line = new Line();
canvas.setOnMouseClicked(e -> {
if (linebtn.isSelected()) {
// double widthSize =
Double.parseDouble(textWidth.getText());
// double heightSize =
Double.parseDouble(textHeight.getText());
double strokeWidth =
Double.parseDouble(contouring.getText());
// gc.setLineWidth(strokeWidth);
gc.setStroke(cpLine.getValue());
if (choiceBox.getSelectionModel().isSelected(0)) {
gc.setLineWidth(strokeWidth);
} else if (choiceBox.getSelectionModel().isSelected(1)) {
gc.setLineWidth(strokeWidth);
gc.setLineDashes(10);
}
gc.setFill(cpFill.getValue());
line.setStartX(e.getX());
line.setStartY(e.getY());
line.setEndX(e.getX() / 2);
line.setEndY(e.getY() / 2);
gc.strokeLine(line.getStartX(), line.getStartY(),
line.getEndX(), line.getEndY());
}
});
BorderPane pane = new BorderPane();
pane.setRight(btns);
pane.setCenter(canvas);
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setTitle("Paint");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Focusing on just drawing a line interactively, you need to at least do the following:
Record the initial point at which the mouse was pressed and then
Render the line when you know where the mouse was released:
private double startX;
private double startY;
…
canvas.setOnMousePressed(e -> {
startX = e.getX();
startY = e.getY();
});
canvas.setOnMouseReleased(e -> {
gc.strokeLine(startX, startY, e.getX(), e.getY());
});
This works as shown above, but the problem then becomes how to draw the line while dragging—without damaging previous work. One solution is to add each new line to a List<Line> and render the accumulated lines with each update:
private final List<Line> lines = new ArrayList<>();
…
canvas.setOnMouseReleased(e -> {
lines.add(new Line(startX, startY, e.getX(), e.getY()));
});
canvas.setOnMouseDragged(e -> {
if (lineButton.isSelected()) {
…
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
lines.forEach(l -> {
gc.strokeLine(l.getStartX(), l.getStartY(), l.getEndX(), l.getEndY());
});
gc.strokeLine(startX, startY, e.getX(), e.getY());
}
});
A more general solution, which illustrates undo() and redo(), is offered here.
I want to be able to click only the boundary of Arcs overlapping each other in a StackPane. Currently, my code below also picks the fill of the Arcs and I'm not able to fire the MOUSE_CLICKED event of the arc1 when I click point p3 in the below depiction. The explanation of my desired outcome and current outcome is also below.
When user clicks p1, code works as intended, prints "arc2"
When user clicks p2, code prints "arc2" whereas the intended operation is to do nothing
When user clicks p3, code prints "arc2" whereas the intended operation is to print "arc1"
This is because the fill area of the Arc2 gets picked before Arc1 even though the fill is transparent and PickOnBounds is set to false. I'm trying to find a way to not get the fill area to be picked. Failing that, I want to have the event handlers for both Arcs to be fired so I can handle the behavior myself.
Code representing my issue:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage primaryStage) {
Arc arc1 = new Arc(100, 100, 100, 100, 90, 90);
Arc arc2 = new Arc(0, 100, 100, 100, 0, 90);
arc1.setStroke(Color.BLACK);
arc2.setStroke(Color.BLACK);
arc1.setFill(Color.TRANSPARENT);
arc2.setFill(Color.TRANSPARENT);
arc1.setStrokeWidth(10);
arc2.setStrokeWidth(10);
arc1.setType(ArcType.OPEN);
arc2.setType(ArcType.OPEN);
arc1.setPickOnBounds(false);
arc2.setPickOnBounds(false);
arc1.setOnMouseClicked(event -> System.out.println("arc1"));
arc2.setOnMouseClicked(event -> System.out.println("arc2"));
StackPane root = new StackPane();
root.getChildren().add(arc1);
root.getChildren().add(arc2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Picking Issues");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
EDIT:
With c0der's help, I implemented my intended behavior as follows:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class Example extends Application {
private Arc arc1, arc2;
private StackPane root;
#Override
public void start(Stage primaryStage) {
arc1 = getArc(200, 200, 200, 200, 90, 90, "arc1");
arc2 = getArc(0, 200, 200, 200, 0, 90, "arc2");
root = new StackPane(arc1, arc2);
root.setOnMouseClicked(event -> onStackPaneClick(event.getX(), event.getY()));
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private Arc getArc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle, String userData){
Arc arc = new Arc(x,y, radiusX,radiusY,startAngle, endAngle);
arc.setStroke(Color.BLACK);
arc.setFill(Color.TRANSPARENT);
arc.setStrokeWidth(10);
arc.setType(ArcType.OPEN);
arc.setPickOnBounds(false);
arc.setUserData(userData);
return arc;
}
private void onStackPaneClick(double x, double y) {
for (Node itNode : root.getChildren()) {
if (itNode.contains(new Point2D(x, y))) {
if (itNode instanceof Arc) {
Arc itArc = (Arc) itNode;
double centerDistance = Math.sqrt(
(x - itArc.getCenterX()) * (x - itArc.getCenterX()) +
(y - itArc.getCenterY()) * (y - itArc.getCenterY()));
if (centerDistance >= itArc.getRadiusX() - itArc.getStrokeWidth() / 2) {
System.out.println(itArc.getUserData());
break;
}
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
However, the question still remains technically unanswered as the initial question needs either;
A way to exclude the "fill" area from click events, or,
A way to have the event handlers of all shapes containing the clicked point to be fired when a clicked point is contained within more than one shape
Simply check if Node contains the clicked point:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class FxmlTest extends Application {
private Arc arc1, arc2;
#Override
public void start(Stage primaryStage) {
arc1 = getArc(100, 100, 100, 100, 90, 90);
arc2 = getArc(0, 100, 100, 100, 0, 90);
StackPane root = new StackPane(arc1, arc2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private Arc getArc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle){
Arc arc = new Arc(x,y, radiusX,radiusY,startAngle, endAngle);
arc.setStroke(Color.BLACK);
arc.setFill(Color.TRANSPARENT);
arc.setStrokeWidth(10);
arc.setType(ArcType.OPEN);
arc.setPickOnBounds(false);
arc.setOnMouseClicked(event -> checkClickedPoint(event.getX(), event.getY()));
return arc;
}
private void checkClickedPoint(double x, double y) {
Point2D clickedPoint = new Point2D(x, y);
if(arc1.contains(clickedPoint)) {System.out.println("arc1");}
if(arc2.contains(clickedPoint)) {System.out.println("arc2");}
}
public static void main(String[] args) {
launch(null);
}
}
Edit:
To avoid mouse click events from the fill area of the arc (the yellow area in the image)
you could construct a shape of the contour only, without the fill area.
Let's call the arc in the image arc1.
Construct a second arc, arc2 which is smaller and enclosed in arc1.
arc2 is constructed so it covers the yellow area.
Subtracting arc2 from arc1 returns a shape which is the contour of arc1:
class ArcContour {
private final Shape arcContour;
private static final double STROKE = 10;
public ArcContour(double x, double y, double radiusX, double radiusY, double startAngle, double length) {
Arc arc1 = new Arc(x,y, radiusX,radiusY,startAngle, length);
arc1.setStroke(Color.BLACK);
arc1.setStrokeWidth(STROKE);
arc1.setStrokeType(StrokeType.INSIDE);
arc1.setType(ArcType.OPEN);
arc1.setPickOnBounds(false);
Arc arc2 = new Arc(x,y, radiusX - STROKE,radiusY - STROKE,startAngle, length);
arc2.setStrokeType(StrokeType.INSIDE);
arcContour = Shape.subtract(arc1, arc2);
}
Shape getShape() {
return arcContour;
}
}
Test it by:
public class FxmlTest extends Application {
private Shape shape;
private static final double RADIUS = 100;
#Override
public void start(Stage primaryStage) {
shape = new ArcContour(150, 150, RADIUS, RADIUS, 30, 150).getShape();
shape.setOnMouseClicked(event -> checkClickedPoint(event.getX(), event.getY()));
StackPane root = new StackPane(shape);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
//this functinality can be refactored to ArcContour
private void checkClickedPoint(double x, double y) {
if( shape.contains(new Point2D(x, y))) {System.out.println("Arc clicked");}
}
public static void main(String[] args) {
launch(null);
}
}
I have an ImageView which I add to a TilesPane layout. I'd like to animate the ImageView from a specified (x,y) position to the position it would be added to.
I tried something like this:
TilePane tp = new TilePane();
...
ImageView iv = new ImageView(img);
tp.getChildren().add(iv);
TranslateTransition tt = new TranslateTransition(Duration.seconds(5), iv);
tt.setFromX(0);
tt.setFromY(0);
tt.setToX(iv.x);
tt.setToY(iv.y);
tt.play();
But I get this error: error: x has private access in ImageView
What I tried to do:
Add the image to the TilePane layout.
Get the images position.
Animate the image from a specified (x,y) position to its current position.
If this isn't the correct/best way to do this, what are the alternatives? If it is, how can I get around this error?
This is what I came up with, it is similar to Fabian's answer, so doesn't add much. The starting points are calculated at random and are in scene co-ordinates, then converted to local co-ordinates via the sceneToLocal function.
For some reason (which I don't understand) the layout pass request on the tilePane did not seem to work until after a stage was shown (before the stage was shown the tilePane height and width would show as 0). So I show the stage first before trying to do any calculations and animation.
Fish at random starting positions:
Fish in a tiled school after animation:
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
import java.util.stream.Collectors;
public class Layup extends Application {
private final Random random = new Random(42);
// image license: linkware - backlink to http://www.fasticon.com
private static final String[] IMAGE_LOCS = {
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Blue-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Red-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Yellow-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Green-Fish-icon.png"
};
#Override
public void start(Stage stage) throws Exception {
TilePane tilePane = createTilePane(IMAGE_LOCS);
Scene scene = new Scene(new Group(tilePane));
stage.setScene(scene);
stage.show();
stage.setTitle("Click on the scene to animate");
animateIn(tilePane);
scene.setOnMouseClicked(event -> animateIn(tilePane));
}
private TilePane createTilePane(String[] imageLocs) {
TilePane tilePane = new TilePane(10, 10);
tilePane.setPrefColumns(2);
Arrays.stream(imageLocs).map(
loc -> new ImageView(new Image(
loc, 64, 0, true, true
))
).collect(Collectors.toCollection(tilePane::getChildren));
tilePane.setPadding(new Insets(200));
return tilePane;
}
private void animateIn(TilePane tilePane) {
// layout tilePane
tilePane.applyCss();
tilePane.layout();
double W = (int) tilePane.getScene().getWidth();
double H = (int) tilePane.getScene().getHeight();
ParallelTransition pt = new ParallelTransition();
for (int i = 0; i < IMAGE_LOCS.length; i++) {
Node child = tilePane.getChildren().get(i);
Point2D start = new Point2D(
random.nextInt((int) (W - child.getBoundsInLocal().getWidth())),
random.nextInt((int) (H - child.getBoundsInLocal().getHeight()))
);
Point2D startInLocal = child.sceneToLocal(start);
TranslateTransition tt = new TranslateTransition(Duration.seconds(.5), child);
tt.setFromX(startInLocal.getX());
tt.setFromY(startInLocal.getY());
tt.setToX(0);
tt.setToY(0);
pt.getChildren().add(tt);
}
pt.play();
}
public static void main(String[] args) {
launch(args);
}
}
The final position will be determined by the layoutX and layoutY properties after a layout pass. Furthermore any transition is relative to this position, so (0, 0) needs to be the end value of the transition.
Example
private static final double START_X = 50;
private static final double START_Y = 50;
#Override
public void start(Stage primaryStage) {
TilePane tilePane = new TilePane();
tilePane.setPrefSize(200, 200);
Image image = new Image("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png", 50, 50, true, false);
Button btn = new Button("add");
btn.setOnAction((ActionEvent event) -> {
ImageView img = new ImageView(image);
tilePane.getChildren().add(img);
// layout tilePane
tilePane.applyCss();
tilePane.layout();
// create transition from start position to final position
TranslateTransition transition = new TranslateTransition(Duration.seconds(4), img);
transition.setFromX(START_X - img.getLayoutX());
transition.setFromY(START_Y - img.getLayoutY());
transition.setToX(0);
transition.setToY(0);
transition.play();
});
VBox root = new VBox(btn, tilePane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I would want to add a drawing area to my tabs in JavaFX. I am new to the field, and tried adding a Canvas to each tab I create. However, this does not seem to be working. Relevant code included.
#FXML
void fileNewTabHandler(ActionEvent event) {
++indexTab;
Tab tab = new Tab("Untitled " + indexTab);
Canvas canvas = new Canvas(500, 285);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.BLUE);
gc.fillRect(250, 856, 50, 60);
tab.setContent(canvas);
tabPane.getTabs().add(tab);
}
Here tabPane and indexTab are defined and work as long as I do not use the canvas. Where am I going wrong. Should I use a different method to implement what I want to?
Quick Sample
There is no real trick to this, you just set the canvas as the content of the tab.
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class TabbedCanvas extends Application {
private int tabId = 0;
private double W = 200, H = 150;
private Random random = new Random(42);
#Override
public void start(Stage stage) {
TabPane tabPane = new TabPane();
Button newTabButton = new Button("New Tab");
newTabButton.setOnAction(
event -> addTab(tabPane)
);
newTabButton.fire();
ToolBar toolBar = new ToolBar(newTabButton);
toolBar.setMinHeight(ToolBar.USE_PREF_SIZE);
VBox layout = new VBox(toolBar, tabPane);
VBox.setVgrow(tabPane, Priority.ALWAYS);
stage.setScene(new Scene(layout));
stage.show();
}
private void addTab(TabPane tabPane) {
Tab tab = new Tab("Tab: " + tabId++);
tab.setContent(createTabContent());
tabPane.getTabs().add(tab);
tabPane.getSelectionModel().select(tab);
}
private Node createTabContent() {
Canvas canvas = new Canvas(W, H);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(randomColor());
gc.fillRect(0, 0, W, H);
return canvas;
}
private Color randomColor() {
return Color.rgb(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256)
);
}
public static void main(String[] args) {
launch(args);
}
}
I have this JavaFX application that lets you plot locations on a map and connect them.
I do this by drawing a map as a background image on a canvas and then drawing circles and lines on it. I have made the circles clickable by using the contains() method in the Circle class, but how can I make the lines clickable?
edit: Look at this example where I just draw a line and set an event handler:
Canvas canvas = new Canvas();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(5);
gc.strokeLine(100, 100, 200, 200);
canvas.setOnMouseClicked(event -> {
double x = event.getX(), y = event.getY();
});
My question is simply this: how do I finish the event handler so that it detects if the click is inside the line I just drew?
You should create a canvas and add the nodes (Circle, Line, etc) to it. Then you add mouse listeners to the nodes.
Example:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
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.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class DragNodes extends Application {
public static List<Circle> circles = new ArrayList<Circle>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Canvas canvas = new Canvas(300, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawShapes(gc);
Circle circle1 = new Circle(50);
circle1.setStroke(Color.GREEN);
circle1.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.7));
circle1.relocate(100, 100);
Circle circle2 = new Circle(50);
circle2.setStroke(Color.BLUE);
circle2.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.7));
circle2.relocate(200, 200);
Line line = new Line(circle1.getLayoutX(), circle1.getLayoutY(), circle2.getLayoutX(), circle2.getLayoutY());
line.setStrokeWidth(20);
Pane overlay = new Pane();
overlay.getChildren().addAll(circle1, circle2, line);
MouseGestures mg = new MouseGestures();
mg.makeDraggable(circle1);
mg.makeDraggable(circle2);
mg.makeDraggable(line);
root.getChildren().addAll(canvas, overlay);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
private void drawShapes(GraphicsContext gc) {
gc.setStroke(Color.RED);
gc.strokeRoundRect(10, 10, 230, 230, 10, 10);
}
public static class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable(Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
} else {
Node p = ((Node) (t.getSource()));
orgTranslateX = p.getTranslateX();
orgTranslateY = p.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;
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
} else {
Node p = ((Node) (t.getSource()));
p.setTranslateX(newTranslateX);
p.setTranslateY(newTranslateY);
}
}
};
}
}