JavaFX - Clickable line on canvas - java

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

Related

Why wont my ball object continously move up when i press space?

I am trying to recreate the mini game shooting gallery from Zelda: A link to the past with a triangle polygon that acts as the player and a circle that acts as the projectile and when the player presses space the projectile moves up at a quick speed, but my setOnKeyPressed method does not work, what am i missing?
Method spoken about
scene.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands.get(event.getCode());
if (movement != null){
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null){
shoot.execute(ball);
}
}
});
This is my main method
package videogameclass;
import java.util.HashMap;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import static javafx.scene.paint.Color.color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author
*/
public class VideoGameClass extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
launch(args);
}
#Override
public void start(Stage stage) throws Exception
{
stage.setTitle("Target practice");
Group root = new Group();
Scene scene = new Scene(root, 320, 240);
stage.setScene(scene);
Circle circle1 = new Circle(20,20,10);
Sprite c = new Sprite(circle1);
circle1.setFill(Color.RED);
root.getChildren().add(c.getNode());
Circle circle2 = new Circle(80, 20, 10);
Sprite c2 = new Sprite(circle2);
circle2.setFill(Color.RED);
root.getChildren().add(c2.getNode());
Circle circle3 = new Circle(140, 20, 10);
Sprite c3 = new Sprite(circle3);
circle3.setFill(Color.RED);
root.getChildren().add(c3.getNode());
Rectangle rectangle = new Rectangle(15,40,25,10);
Sprite rec = new Sprite(rectangle);
rectangle.setFill(Color.BLACK);
root.getChildren().add(rec.getNode());
Rectangle rectangle2 = new Rectangle(75,40,25,10);
Sprite rec2 = new Sprite(rectangle2);
rectangle2.setFill(Color.BLACK);
root.getChildren().add(rec2.getNode());
Rectangle rectangle3 = new Rectangle(135,40,25,10);
Sprite rec3 = new Sprite(rectangle3);
rectangle3.setFill(Color.BLACK);
root.getChildren().add(rec3.getNode());
Rectangle rectangle4 = new Rectangle(195,40,25,10);
Sprite rec4 = new Sprite(rectangle4);
rectangle4.setFill(Color.BLACK);
root.getChildren().add(rec4.getNode());
Polygon t = new Polygon();
Sprite player = new Sprite(t);
root.getChildren().add(player.getNode());
t.getPoints().addAll(new Double[]{
25.0, 20.0,
45.0, 20.0,
35.0, 5.0,
25.0,20.0,
});
t.setTranslateX(125);
t.setTranslateY(200);
Circle circle4 = new Circle(t.getTranslateX()+35,t.getTranslateY(),5);
Sprite ball = new Sprite(circle4);
root.getChildren().add(ball.getNode());
Command moveLeft = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(-2, 0);
//sprite.update();
}
};
Command moveRight = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(2, 0);
//sprite.update();
}
};
Command moveUp = new Command(){
#Override
public void execute(Sprite sprite){
sprite.setVelocity(0,-2);
}
};
Command stopMoving = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(0,0);
}
};
Map<KeyCode,Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode,Command> keyCommandsBall = new HashMap<>();
keyCommands.put(KeyCode.SPACE, moveUp);
Map<KeyCode,Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F19, moveLeft);
keyCommands.put(KeyCode.F18, moveRight);
AnimationTimer animation = new AnimationTimer()
{
#Override
public void handle(long now){
c.setVelocity(1.0, 0);
c2.setVelocity(1.0,0);
c3.setVelocity(1.0,0);
ball.setVelocity(0, 0);
rec.setVelocity(-2.0,0);
rec2.setVelocity(-2.0,0);
rec3.setVelocity(-2.0,0);
rec4.setVelocity(-2.0,0);
c.update();
c2.update();
c3.update();
ball.update();//projectile
rec.update();
rec2.update();
rec3.update();
rec4.update();
player.update();
//move sprites
//check and handle collisions
if(c3.getNode().getTranslateX() > scene.getWidth() - 120){
c3.getNode().setTranslateX(-140);
}
if(c2.getNode().getTranslateX() > scene.getWidth() - 75){
c2.getNode().setTranslateX(c3.getNode().getTranslateX()-20);
}
if(c.getNode().getTranslateX() > scene.getWidth() - 10){
c.getNode().setTranslateX(c2.getNode().getTranslateX()-20);
}
if (rec.getNode().getTranslateX() < -40){
rec.getNode().setTranslateX(300);
}
if(rec2.getNode().getTranslateX() < -100){
rec2.getNode().setTranslateX(rec.getNode().getTranslateX()+30);
}
if(rec3.getNode().getTranslateX() < -135){
rec3.getNode().setTranslateX(rec2.getNode().getTranslateX()+30);
}
if(rec4.getNode().getTranslateX() < -225){
rec4.getNode().setTranslateX(rec3.getNode().getTranslateX()+30);
}
if(ball.getNode().getBoundsInParent().intersects(rec.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec2.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec3.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec4.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c2.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c3.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
}//end of handle
};
scene.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands.get(event.getCode());
if (movement != null){
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null){
shoot.execute(ball);
}
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands2.get(event.getCode());
Command stop = stopMoving;
if (movement == null){
stop.execute(player);
}
}
});
animation.start();
stage.show();
}
}
This is my Sprite class
package videogameclass;
import javafx.scene.Node;
/**
*
* #author
*/
public class Sprite {
private final Node node;
private double dx, dy;
public Sprite(Node node){
this.node = node;
}
public Node getNode(){
return node;
}
public void setVelocity(double dx, double dy){
this.dx = dx;
this.dy = dy;
}
public double getVelocityX(){
return dx;
}
public double getVelocityY(){
return dy;
}
public void update(){
this.node.setTranslateX(this.node.getTranslateX()+dx);
this.node.setTranslateY(this.node.getTranslateY()+dy);
}
}
This is my Command method
package videogameclass;
public interface Command
{
public void execute(Sprite sprite);
}
Because there is nothing added to keyCommandsBall map.
I am not sure if this is a typo mistake, but I can sense this part of the code is wrong. All the commands added to keyCommands map only.
Map<KeyCode,Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode,Command> keyCommandsBall = new HashMap<>();
keyCommands.put(KeyCode.SPACE, moveUp);
Map<KeyCode,Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F19, moveLeft);
keyCommands.put(KeyCode.F18, moveRight);
Update:
I felt a bit interesting in this game so I tried to put my hands in this. Indeed this was quite fun when playing ;)
But few suggestions for you from your code:
Try to reuse to code by either keeping things in loop or by calling a common method. That way you can reduce the code drastically.
Try to create constants for the fixed sizes and positions. That way you accidentally don't get messed up with some wrong values. (Though I have not included that in my example)
When providing a minimal example, try to keep all the code in a single class with all imports (even if it has multiple classes or interfaces). The main aim should be that others should directly copy the class and should be able to run the program.
Below is the output of the code that I worked on. (There is still so much scope to optimize this)
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class VideoGameClass extends Application {
private List<Sprite> circles = new ArrayList<>();
private List<Sprite> rectangles = new ArrayList<>();
private List<Sprite> all = new ArrayList<>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Group root = new Group();
Scene scene = new Scene(root, 320, 240, Color.BLACK);
stage.setTitle("Target practice");
stage.setScene(scene);
Sprite c1 = createCircle(20, 20);
Sprite c2 = createCircle(80, 20);
Sprite c3 = createCircle(140, 20);
Sprite rec1 = createRectangle(15);
Sprite rec2 = createRectangle(75);
Sprite rec3 = createRectangle(135);
Sprite rec4 = createRectangle(195);
Polygon t = new Polygon();
t.getPoints().addAll(new Double[]{
25.0, 20.0,
45.0, 20.0,
35.0, 5.0,
25.0, 20.0,
});
t.setFill(Color.WHITE);
t.setTranslateX(125);
t.setTranslateY(200);
Sprite player = new Sprite(t);
all.add(player);
Sprite ball = new Sprite(new Circle(t.getTranslateX() + 35, t.getTranslateY(), 5, Color.WHITE));
all.add(ball);
all.stream().map(Sprite::getNode).forEach(n -> root.getChildren().add(n));
Command moveLeft = sprite -> sprite.setVelocity(-2, 0);
Command moveRight = sprite -> sprite.setVelocity(2, 0);
Command moveUp = sprite -> sprite.setVelocity(0, -2);
Command stopMoving = sprite -> sprite.setVelocity(0, 0);
Map<KeyCode, Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode, Command> keyCommandsBall = new HashMap<>();
keyCommandsBall.put(KeyCode.SPACE, moveUp);
Map<KeyCode, Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F11, moveLeft);
keyCommands.put(KeyCode.F12, moveRight);
AnimationTimer animation = new AnimationTimer() {
#Override
public void handle(long now) {
circles.forEach(c -> c.setVelocity(1.0, 0));
rectangles.forEach(r -> r.setVelocity(-2.0, 0));
all.forEach(Sprite::update);
//check and handle collisions
if (c3.getNode().getTranslateX() > scene.getWidth() - 120) {
c3.getNode().setTranslateX(-140);
}
if (c2.getNode().getTranslateX() > scene.getWidth() - 75) {
c2.getNode().setTranslateX(c3.getNode().getTranslateX() - 20);
}
if (c1.getNode().getTranslateX() > scene.getWidth() - 10) {
c1.getNode().setTranslateX(c2.getNode().getTranslateX() - 20);
}
if (rec1.getNode().getTranslateX() < -40) {
rec1.getNode().setTranslateX(300);
}
if (rec2.getNode().getTranslateX() < -100) {
rec2.getNode().setTranslateX(rec1.getNode().getTranslateX() + 30);
}
if (rec3.getNode().getTranslateX() < -135) {
rec3.getNode().setTranslateX(rec2.getNode().getTranslateX() + 30);
}
if (rec4.getNode().getTranslateX() < -225) {
rec4.getNode().setTranslateX(rec3.getNode().getTranslateX() + 30);
}
// Check for collision and stop the ball
Stream.of(circles, rectangles).flatMap(List::stream).forEach(sprite -> {
if (ball.getNode().getBoundsInParent().intersects(sprite.getNode().getBoundsInParent())) {
ball.setVelocity(0, 0);
}
});
}//end of handle
};
scene.setOnKeyPressed(event -> {
Command movement = keyCommands.get(event.getCode());
if (movement != null) {
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null) {
shoot.execute(ball);
}
});
scene.setOnKeyReleased(event -> {
Command movement = keyCommands2.get(event.getCode());
Command stop = stopMoving;
if (movement == null) {
stop.execute(player);
}
// Bring back the ball to the player
if(event.getCode() == KeyCode.Z){
ball.setVelocity(0, 0);
Circle ballNode = (Circle) ball.getNode();
ballNode.setTranslateX(0);
ballNode.setTranslateY(0);
ballNode.setCenterX(player.getNode().getTranslateX() + 35);
ballNode.setCenterY(player.getNode().getTranslateY());
}
});
animation.start();
stage.show();
}
private Sprite createCircle(double centerX, double centerY) {
Sprite circle = new Sprite(new Circle(centerX, centerY, 10, Color.RED));
circles.add(circle);
all.add(circle);
return circle;
}
private Sprite createRectangle(double x) {
Rectangle r = new Rectangle(x, 40, 25, 10);
r.setFill(Color.YELLOW);
Sprite rectangle = new Sprite(r);
rectangles.add(rectangle);
all.add(rectangle);
return rectangle;
}
class Sprite {
private final Node node;
private double dx, dy;
public Sprite(Node node) {
this.node = node;
}
public Node getNode() {
return node;
}
public void setVelocity(double dx, double dy) {
this.dx = dx;
this.dy = dy;
}
public void update() {
this.node.setTranslateX(this.node.getTranslateX() + dx);
this.node.setTranslateY(this.node.getTranslateY() + dy);
}
}
interface Command {
void execute(Sprite sprite);
}
}

How can I create a feathered brush with JavaFX?

So I'm trying to create a very basic photo editor program in Java, using JavaFX. I got a brush and eraser working pretty well so far the following way:
package application;
import java.io.File;
import javax.imageio.ImageIO;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
public class EditorController {
private boolean eraser = false;
#FXML
private Canvas canvas;
#FXML
private ColorPicker colorPicker;
#FXML
private TextField brushSize;
#FXML
private TextField selectedTool;
private Point2D last = null;
public void initialize() {
GraphicsContext gc = canvas.getGraphicsContext2D();
canvas.setOnMouseReleased(e -> {last = null;});
canvas.setOnMouseClicked(e -> {
if (!eraser) {
double size = Double.parseDouble(brushSize.getText());
float mouseX = (float) e.getX();
float mouseY = (float) e.getY();
gc.fillOval(mouseX-(size/2), mouseY-(size/2), size, size);
}
});
canvas.setOnMouseDragged(e -> {
System.out.println(eraser);
double size = Double.parseDouble(brushSize.getText());
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineWidth(size);
float mouseX = (float) e.getX();
float mouseY = (float) e.getY();
if (last != null && !eraser) {
gc.strokeLine(last.getX(), last.getY(), mouseX, mouseY);
} else if (eraser) {
gc.clearRect(mouseX, mouseY, size, size);
}
last = new Point2D(mouseX, mouseY);
});
}
public void onSave() {
try {
Image snapshot = canvas.snapshot(null, null);
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File("paint.png"));
} catch (Exception e) {
System.out.println("Failed to save image: " + e);
}
}
public void onLoad() {
// not implemented yet
}
// not implemented yet
public void onUndo() { }
public void onRedo() { }
public void onSmaller() { }
public void onBigger() { }
public void onResetView() { }
public void onFitView() { }
public void onFillView() { }
public void onNewLayer() { }
public void onDeleteLayer() { }
public void onDuplicateLayer() { }
public void onGroupLayers() { }
public void onMergeLayers() { }
public void onAddMask() { }
public void onBrush() { eraser = false; selectedTool.setText("Brush"); }
public void onEraser() { eraser = true; selectedTool.setText("Eraser"); }
public void onExit() {
Platform.exit();
}
}
Now I want to have a feather/hardness value for the brush (like in photoshop) where I can draw a softer-looking line, but I'm not sure how to achieve it with JavaFX? Are there any tools within it for things like this?
So with a visual example: the brush on the left would be a feathered brush, the one on the right isn't (and that's what I have currently)
Simple drawing app.
It uses a radial gradient rendered to an image that is drawn on the canvas, but you could just draw the gradient directly onto the canvas. A gradient is a paint so you can set it directly as an argument to setFill on a graphics context.
The solution in my example probably won't exactly give you the solution you are looking for, but perhaps you could tweak it for what you need.
It was a quick app I put together for demo purposes, it could be structured better if a more functional drawing app was required.
The code which creates the "feathered brush" is based on a gradient:
private RadialGradient createSoftBrushGradient(Color primaryColor) {
return new RadialGradient(
0, 0,
.5, .5,
.5,
true,
CycleMethod.NO_CYCLE,
new Stop(0, primaryColor),
new Stop(1, Color.TRANSPARENT)
);
}
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class SprayPaint extends Application {
private final IntegerProperty brushDiameter = new SimpleIntegerProperty();
private final ObjectProperty<Image> brushImage = new SimpleObjectProperty<>();
private final ToggleGroup brushHardnessSelection = new ToggleGroup();
private final RadioButton hardBrushSelection = new RadioButton();
private final RadioButton softBrushSelection = new RadioButton();
private final Circle hardBrush = new Circle();
private final Circle softBrush = new Circle();
private final SnapshotParameters snapshotParams = new SnapshotParameters();
private final Canvas canvas = new Canvas(600, 450);
#Override
public void start(Stage stage) {
snapshotParams.setFill(Color.TRANSPARENT);
Pane controls = createControls();
StackPane canvasHolder = new StackPane(canvas);
canvasHolder.setStyle("-fx-border-color: gray;");
canvasHolder.setMaxSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
VBox layout = new VBox(
10,
controls,
canvasHolder
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
enableDrawing(canvas);
}
private void enableDrawing(Canvas canvas) {
EventHandler<MouseEvent> drawHandler = event -> {
Image brush = snapshotBrushImage();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(
brush,
event.getX() - brushDiameter.doubleValue() / 2,
event.getY() - brushDiameter.doubleValue() / 2
);
};
canvas.setOnMousePressed(drawHandler);
canvas.setOnMouseDragged(drawHandler);
}
private Image snapshotBrushImage() {
if (brushImage.get() == null) {
if (hardBrushSelection == brushHardnessSelection.getSelectedToggle()) {
brushImage.set(snapshot(hardBrush));
} else { // soft brush selected
brushImage.set(snapshot(softBrush));
}
}
return brushImage.get();
}
private Image snapshot(Circle brushNode) {
return brushNode.snapshot(snapshotParams, null);
}
private Pane createControls() {
hardBrush.radiusProperty().bind(
brushDiameter.divide(2.0)
);
softBrush.radiusProperty().bind(
brushDiameter.divide(2.0)
);
hardBrushSelection.getStyleClass().addAll("toggle-button", "left-pill");
hardBrushSelection.getStyleClass().remove( "radio-button");
StackPane hardBrushGraphic = new StackPane(hardBrush);
hardBrushGraphic.setMinSize(40, 40);
hardBrushSelection.setGraphic(hardBrushGraphic);
hardBrushSelection.setToggleGroup(brushHardnessSelection);
softBrushSelection.getStyleClass().addAll( "toggle-button", "right-pill");
softBrushSelection.getStyleClass().remove( "radio-button");
StackPane softBrushGraphic = new StackPane(softBrush);
softBrushGraphic.setMinSize(40, 40);
softBrushSelection.setGraphic(softBrushGraphic);
softBrushSelection.setToggleGroup(brushHardnessSelection);
hardBrushSelection.setSelected(true);
HBox brushSelectionPanel = new HBox(hardBrushSelection, softBrushSelection);
Slider brushDiameterSlider = new Slider(8, 40, 20);
brushDiameterSlider.setMajorTickUnit(4);
brushDiameterSlider.setMinorTickCount(0);
brushDiameterSlider.setShowTickMarks(true);
brushDiameter.setValue((int) Math.round(brushDiameterSlider.getValue()));
brushDiameterSlider.valueProperty().addListener((observable, oldValue, newValue) ->
brushDiameter.setValue((int) Math.round(newValue.doubleValue()))
);
Label diameterLabel = new Label();
diameterLabel.textProperty().bind(
brushDiameter.asString()
);
ColorPicker colorPicker = new ColorPicker();
hardBrush.fillProperty().bind(
colorPicker.valueProperty()
);
colorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
softBrush.setFill(
createSoftBrushGradient(newColor)
)
);
colorPicker.setValue(Color.NAVY);
brushDiameter.addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
colorPicker.valueProperty().addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
brushHardnessSelection.selectedToggleProperty().addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
Button clear = new Button("Clear");
clear.setOnAction(e ->
canvas.getGraphicsContext2D().clearRect(
0, 0, canvas.getWidth(), canvas.getHeight()
)
);
HBox controlPanel = new HBox(
10,
colorPicker,
brushSelectionPanel,
new Label("Diameter: "),
brushDiameterSlider,
diameterLabel,
clear
);
controlPanel.setMinWidth(450);
controlPanel.setMinHeight(Pane.USE_PREF_SIZE);
return controlPanel;
}
private RadialGradient createSoftBrushGradient(Color primaryColor) {
return new RadialGradient(
0, 0,
.5, .5,
.5,
true,
CycleMethod.NO_CYCLE,
new Stop(0, primaryColor),
new Stop(1, Color.TRANSPARENT)
);
}
}

Line painting in painting program JavaFX

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.

javafx code to pick only the boundary of an arc on MOUSE_CLICKED event

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

Add constraints to lines in javaFX

How would I go about adding a constraint to how much a line can be dragged? I have a stick man and you can drag all his arms and legs, head and back about but I want them to stay the same length as they started off, so you can't stretch them longer or shorter than they should be, just move them up and down, side to side, in a circle etc. I guess i have to do something with the start/end x and y but im not sure how to set a set constraint to it and also still have it be draggable and stay the same length
private Line connectLines(Line line, Circle startNode, Circle endNode) {
line.startXProperty().bind(startNode.centerXProperty().add(startNode.translateXProperty()));
line.startYProperty().bind(startNode.centerYProperty().add(startNode.translateYProperty()));
line.endXProperty().bind(endNode.centerXProperty().add(endNode.translateXProperty()));
line.endYProperty().bind(endNode.centerYProperty().add(endNode.translateYProperty()));
return line;
}
//mouse pressed event
EventHandler<MouseEvent> mousePressed = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("pressed");
sceneX = e.getSceneX();
sceneY = e.getSceneY();
translateCircleX = ((Circle)(e.getSource())).getTranslateX();
translateCircleY = ((Circle)(e.getSource())).getTranslateY();
}
};
//mouse dragged event
EventHandler<MouseEvent> mouseDragged = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("dragged");
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
double newTranslateCircleX = translateCircleX + offsetX;
double newTranslateCircleY = translateCircleY + offsetY;
((Circle)(e.getSource())).setTranslateX(newTranslateCircleX);
((Circle)(e.getSource())).setTranslateY(newTranslateCircleY);
}
};
Here is an example. This example does not use Circle.setTranslate#. It uses Circle.setCenter#. It also uses Math.hypot to keep track of the Line length. If the line length becomes greater than or equal to 100, the change in the shape movements is subtracted.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class TableViewDemo2 extends Application
{
double sceneX, sceneY;
Circle circle = new Circle(15, Color.RED);
Circle circle2 = new Circle(15, Color.BLUE);
Line line = new Line();
private Line connectLines(Line line, Circle startNode, Circle endNode)
{
line.startXProperty().bind(startNode.centerXProperty());
line.startYProperty().bind(startNode.centerYProperty());
line.endXProperty().bind(endNode.centerXProperty());
line.endYProperty().bind(endNode.centerYProperty());
return line;
}
//mouse pressed event
EventHandler<MouseEvent> mousePressed = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
System.out.println("pressed");
sceneX = e.getSceneX();
sceneY = e.getSceneY();
Circle tempCircle = ((Circle) e.getSource());
tempCircle.toFront();
}
};
//mouse dragged event
EventHandler<MouseEvent> mouseDragged = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
System.out.println(Math.hypot(line.getBoundsInLocal().getWidth(), line.getBoundsInLocal().getHeight()));
System.out.println("dragged");
double offSetX = e.getSceneX() - sceneX;
double offSetY = e.getSceneY() - sceneY;
Circle tempCircle = ((Circle) (e.getSource()));
tempCircle.setCenterX(tempCircle.getCenterX() + offSetX);
tempCircle.setCenterY(tempCircle.getCenterY() + offSetY);
if (Math.hypot(line.getBoundsInLocal().getWidth(), line.getBoundsInLocal().getHeight()) >= 100) {
tempCircle.setCenterX(tempCircle.getCenterX() - offSetX);
tempCircle.setCenterY(tempCircle.getCenterY() - offSetY);
}
sceneX = e.getSceneX();
sceneY = e.getSceneY();
}
};
#Override
public void start(Stage stage)
{
circle.setOnMouseDragged(mouseDragged);
circle2.setOnMouseDragged(mouseDragged);
Line returnLine = connectLines(line, circle, circle2);
StackPane root = new StackPane(new Pane(circle, circle2, returnLine));
stage.setTitle("TableView (o7planning.org)");
Scene scene = new Scene(root, 450, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}

Categories

Resources