I'm trying to draw rectangles on a picture using mouse events in JavaFX2.
Right now, I have an ImageView in a StackPane and I add Rectangles over it. The problem is even if I set the Rectangles' X and Y to the MouseEvent X and Y, the Rectangles' stay centered in the StackPane.
I guess it's the StackPane that centers every child by default, but I can't find a decent solution to the problem. Could you guys please point me in the right direction?
Here is my code:
#FXML
private StackPane stack_pane;
private final ImageView image_view = new ImageView();
private final Set<Rectangle> rectangles = new HashSet<Rectangle>();
private final SimpleDoubleProperty selectionRectInitialX = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectInitialY = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectCurrentX = new SimpleDoubleProperty();
private final SimpleDoubleProperty selectionRectCurrentY = new SimpleDoubleProperty();
private Rectangle selectionRect;
#Override
public void initialize(final URL fxmlFileLocation, final ResourceBundle resources)
{
this.stack_pane.getChildren().add(this.image_view);
this.selectionRect = this.getRectangle();
this.selectionRect.widthProperty().bind(this.selectionRectCurrentX.subtract(this.selectionRectInitialX));
this.selectionRect.heightProperty().bind(this.selectionRectCurrentY.subtract(this.selectionRectInitialY));
this.stack_pane.setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
MainWindowController.this.selectionRect.xProperty().set(event.getX());
MainWindowController.this.selectionRect.yProperty().set(event.getY());
MainWindowController.this.selectionRectInitialX.set(event.getX());
MainWindowController.this.selectionRectInitialY.set(event.getY());
}
});
this.stack_pane.setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
MainWindowController.this.selectionRectCurrentX.set(event.getX());
MainWindowController.this.selectionRectCurrentY.set(event.getY());
MainWindowController.this.repaint();
}
});
this.stack_pane.setOnMouseReleased(new EventHandler<MouseEvent>()
{
#Override
public void handle(final MouseEvent event)
{
final Rectangle newRect = MainWindowController.this.getRectangle();
newRect.setWidth(MainWindowController.this.selectionRect.getWidth());
newRect.setHeight(MainWindowController.this.selectionRect.getHeight());
newRect.setX(MainWindowController.this.selectionRect.getX());
newRect.setY(MainWindowController.this.selectionRect.getY());
MainWindowController.this.selectionRectCurrentX.set(0);
MainWindowController.this.selectionRectCurrentY.set(0);
MainWindowController.this.rectangles.add(newRect);
MainWindowController.this.repaint();
}
});
}
public Rectangle getRectangle()
{
final Rectangle rect = new Rectangle();
rect.setFill(Color.web("firebrick", 0.4));
rect.setStroke(Color.web("firebrick", 0.4));
return rect;
}
public void repaint()
{
this.stack_pane.getChildren().clear();
this.stack_pane.getChildren().add(this.image_view);
this.stack_pane.getChildren().add(this.selectionRect);
for (final Rectangle rect : this.rectangles)
{
this.stack_pane.getChildren().add(rect);
}
}
Change StackPane to AnchorPane. AnchorPane allows you to set the X, Y of each child relative to the Pane. http://docs.oracle.com/javafx/2/api/javafx/scene/layout/AnchorPane.html
Try to set Alignment of StackPane:
StackPane.setAlignment(selectionRect, Pos.CENTER_LEFT);
Refer here for the value reference of Pos class.
Related
I've got this dead simple project that basically draws a black circle in the middle of a JavaFX Scene Canvas and grows it every 50ms.
Here's my Controller:
public class PrimaryController {
public StackPane theRootPane;
public CanvasPane theCanvasPane;
int i = 1;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> UpdateCanvas(i++)));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void UpdateCanvas(int diameter) {
theCanvasPane.DrawCircleAtCenterOfCanvas(diameter);
}
Here's my CanvasPane class:
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener(observable -> RedrawCanvas());
theCanvas.heightProperty().addListener(observable -> RedrawCanvas());
}
private void RedrawCanvas() {
ClearCanvas();
}
private void ClearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void DrawCircleAtCenterOfCanvas(int diameter) {
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - diameter / 2.0, centreY - diameter / 2.0, diameter, diameter);
}
}
Finally, here's my App class and my .fxml
public class App extends Application {
private static Scene scene;
#Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
//stage.setResizable(false);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
primary.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane fx:id="theRootPane" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.xxx.PrimaryController" />
It works fine until I resize the window, at which point, the canvas is redrawn by clearing it and then painting the new larger circle on the canvas. This "clearing" of the canvas presents as flicker when the form is resized.
What is a better way of doing this? I'm fudging around with JavaFX after learning Java and getting into UI and animations. I'm thinking canvas is not the way to go...
Any advice would be much appreciated.
It's probably better to update the canvas in a ChangeListener rather than an InvalidationListener, which will result in fewer redraws. Either way you should either:
ensure you redraw the circle when the canvas size changes (with your current code, you clear the canvas as soon as the canvas changes size, but don't redraw the circle until the next keyframe, so you end up with some blank canvases in between):
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
private int currentDiameter ;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener((obs, oldWidth, newWidth) -> redrawCanvas());
theCanvas.heightProperty().addListener((obs, oldHeight, newHeight) -> redrawCanvas());
}
public void increaseDiameter() {
currentDiameter++;
redrawCanvas();
}
private void redrawCanvas() {
clearCanvas();
drawCircleAtCenterOfCanvas();
}
private void clearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void drawCircleAtCenterOfCanvas() {
currentDiameter = currentDiameter ;
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - currentDiameter / 2.0, centreY - currentDiameter / 2.0, currentDiameter, currentDiameter);
}
}
and
public class PrimaryController {
#FXML
private StackPane theRootPane;
#FXML
private CanvasPane theCanvasPane;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCanvas()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCanvas() {
theCanvasPane.increaseDiameter();
}
}
or (probably better, and far easier for this example) use a Circle instead of a canvas:
public class PrimaryController {
#FXML
private StackPane theRootPane;
private Circle circle ;
public void initialize() {
circle = new Circle();
theRootPane.getChildren().addAll(circle);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCircle()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCircle() {
circle.setRadius(circle.getRadius()+0.5);
}
}
I am having issues with libgdx sound inside a ClickListener for my button.
I am getting the error Syntax error on token "PlayButtonSound", Identifier expected after this token
SoundManager:
import utils.Constants;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
public class SoundManager {
private static Sound buttonSound = Gdx.audio.newSound(Constants.buttonSound);
private static Music song = Gdx.audio.newMusic(Constants.song);
public static void PlayMusic() {
song.setLooping(true);
song.setVolume(0.2f);
song.play();
}
public static void PlayButtonSound() {
buttonSound.play();
}
public void DestryoAudio() {
buttonSound.dispose();
song.dispose();
}
}
And MainMenu:
public class MainMenu implements Screen {
private Stage stage;
private Sprite sprite;
private TextureRegion menuBackgroundImg;
private TextureAtlas menuButton;
private Skin skin;
private BitmapFont font;
private Table table;
private TextButton playButton;
#Override
public void show() {
// Set stage
stage = new Stage();
Gdx.input.setInputProcessor(stage);
SoundManager.PlayMusic();
// Set menu baggrund
menuBackgroundImg = new TextureRegion(new Texture(Gdx.files.internal(Constants.menuBackgroundImg)));
sprite = new Sprite(menuBackgroundImg);
sprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// Set menuknapper
menuButton = new TextureAtlas(Constants.menuButton);
skin = new Skin(menuButton);
// Set font
font = new BitmapFont(Constants.font, false);
// Set table
table = new Table(skin);
table.setBounds(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// Create button styling
TextButtonStyle textButtonStyle = new TextButtonStyle();
textButtonStyle.up = skin.getDrawable("menuButtonUp");
textButtonStyle.down = skin.getDrawable("menuButtonPressed");
textButtonStyle.pressedOffsetX = 1;
textButtonStyle.pressedOffsetY = -1;
textButtonStyle.font = font;
textButtonStyle.fontColor = Color.BLACK;
// Create buttons
playButton = new TextButton(Constants.play, textButtonStyle);
playButton.addListener(new InputListener() {
SoundManager.PlayButtonSound(); // This is the error
});
// Button padding
playButton.pad(20, 100, 20, 100);
// Setting the table
table.add(playButton).row();
// Setting stage actor
stage.addActor(table);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Tegn baggrund
stage.getBatch().begin();
sprite.draw(stage.getBatch());
stage.getBatch().end();
// Tegn resten
stage.act(delta);
stage.draw();
}
...
#Override
public void dispose() {
stage.dispose();
menuButton.dispose();
skin.dispose();
font.dispose();
}
}
I can't really grasp what I am doing wrong and a search for the error gives vague answers that dosen't really solves my issue.
P.S. I have imported SoundManager but left it out due to length of the code snippet.
You need to implement one of the InputListener interface methods, most likely touchDown(InputEvent event, float x, float y, int pointer, int button).
Check out the API for InputListener. It lists all the methods and gives a pretty good example.
playButton.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
SoundManager.PlayButtonSound();
return false;
}
});
I'm trying to create a basic UML diagramming tool with JavaFX.
First I did this:
public class Asd extends Application {
Double WINDOW_HEIGHT = (double) 400;
Double WINDOW_WIDTH = (double) 800;
Double RECTANGLE_DEFAULT_HEIGHT = (double) 50;
Double RECTANGLE_DEFAULT_WIDTH = (double) 100;
#Override
public void start(Stage stage) throws Exception {
final Pane root = new Pane();
Rectangle background = new Rectangle(WINDOW_WIDTH, WINDOW_HEIGHT,
Color.WHITE);
root.getChildren().add(background);
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
// On window click
root.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent mouseEvent) {
Rectangle rect = new Rectangle();
rect.setX(mouseEvent.getX());
rect.setY(mouseEvent.getY());
rect.setWidth(RECTANGLE_DEFAULT_WIDTH);
rect.setHeight(RECTANGLE_DEFAULT_HEIGHT);
rect.setFill(null);
rect.setStroke(Color.BLACK);
root.getChildren().add(rect);
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
With that, I can draw a rectangle (that would represent a UML Class) in the exact point I click.
Now I want to add text. If instead of
root.getChildren().add(rect);
I do
Text text = new Text("mockText");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(rect, text);
root.getChildren().add(stackPane);
the text is placed in the center of the rectangle, but all rectangles are positioned at coordinates (0,0)
So, any way to change the position of each StackPane? Or any alternative to add text to the rectangles directly without using StackPane?
OK, got it. Layout elements have methods setLayoutX & setLayoutY that can be used in cases like this one, so I don't need anymore Rectangle.setX/setY & finally the handle method should be:
public void handle(MouseEvent mouseEvent) {
Rectangle rect = new Rectangle();
rect.setWidth(RECTANGLE_DEFAULT_WIDTH);
rect.setHeight(RECTANGLE_DEFAULT_HEIGHT);
rect.setFill(null);
rect.setStroke(Color.BLACK);
Text text = new Text("mockText");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(rect, text);
stackPane.setLayoutX(mouseEvent.getX());
stackPane.setLayoutY(mouseEvent.getY());
root.getChildren().add(stackPane);
}
I have written a small JavaFX program in which a Rectangle node is made to moved whenever mouse cursor is moved over the scene containing the rectangle. Here is my code:
public class MovedObjectWhenMouseMoved extends Application{
double nodeX;
double currentMousePos;
double oldMousePos = 0.0;
public static void main(String[] arg){
launch(arg);
}
#Override
public void start(Stage stage) throws Exception {
final Rectangle rect = new Rectangle(50, 50, Color.RED);
rect.setX(20);
rect.setY(20);
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().add(rect);
Scene scene = new Scene(anchorPane,500,500,Color.GREEN);
stage.setScene(scene);
stage.show();
scene.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
currentMousePos = mouseEvent.getX();
if(currentMousePos>oldMousePos){
rect.setX(rect.getX()+1); // Move right
}else if(currentMousePos<oldMousePos){
rect.setX(rect.getX()-1); // Move Left
}
oldMousePos = currentMousePos;
}
});
}
}
But here the problem is that the node speed is not same as the mouse speed.
How can I rectify this problem? Also please let me know if there is any better approach.
Mouse can change position on more then 1 pixel.
Try this code for handle:
currentMousePos = mouseEvent.getX();
double dX = currentMousePosition - oldMousePos;
rect.setX(rect.getX() + dX);
oldMousePos = currentMousePos;
So basically I'm writing a program where the user clicks and drags the mouse to a size he/she wants, and lets go, filling in a rectangle based on the choice from the JComboBox.
What I have implemented is MouseListener and MouseMotionListener to track the location of the mouse, and draw a rectangle based on where the user first clicked, to where it was let go.
When the user clicks and drags (but does not let go), there is a drawRect() but not a fillRect() (as in, the rectangle does not fill - only when the user releases the mouse does the rectangle fill with the color).
This class creates a Rect object that has another part in the constructor, which is the color that is selected (which is determined in the ColorListener class below).
This is where I included my instance variables, and everything is instantiated in the constructor below:
private ArrayList<Rect> rectList;
private Color currentColor;
private Canvas canvas;
private JPanel controlPanel;
private JButton undo, erase;
private JComboBox comboBox;
private int xStart, yStart, xEnd, yEnd;
private Graphics page;
private Point pt = null;
private PointListener pointListener;
private ColorListener colorListener;
public WholePanel()
{
// here we use black to draw a rectangle
currentColor = Color.black;
pointListener = new PointListener();
addMouseListener(pointListener);
addMouseMotionListener(pointListener);
String[] listOfColors = {"black", "red", "blue", "green", "orange"};
comboBox = new JComboBox(listOfColors);
comboBox.setSelectedIndex(0);
rectList = new ArrayList<Rect>();
controlPanel = new JPanel(new GridLayout(1,3));
undo = new JButton("Undo");
erase = new JButton("Erase");
controlPanel.add(comboBox);
controlPanel.add(undo);
controlPanel.add(erase);
undo.addActionListener(new ButtonListener());
erase.addActionListener(new ButtonListener());
canvas = new Canvas();
JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlPanel, canvas);
setLayout(new BorderLayout());
add(sp);
}
The Rect class can create a Rect object used later.
public class Rect
{
private int x1, y1, width1, height1;
private Color color1;
public Rect(int x, int y, int width, int height, Color color)
{
x1 = x;
y1 = y;
width1 = width;
height1 = height;
color1 = color;
}
public void draw(Graphics page)
{
page.setColor(color1);
page.drawRect(x1,y1,width1,height1);
}
}
The Canvas class creates the space that allows for drawing of an object.
private class Canvas extends JPanel
{
public void paintComponent(Graphics page)
{
super.paintComponent(page);
setBackground(Color.white);
if (pt != null)
{
Rect rect = new Rect(xStart, yStart, xEnd-xStart, yEnd-yStart, currentColor);
rect.draw(page);
}
}
}
The PointListener class finds all the points that are there, as in where the user clicks, to where the user drags, and also to where the user releases.
private class PointListener implements MouseListener, MouseMotionListener
{
public void mousePressed(MouseEvent event)
{
pt = event.getPoint();
xStart = pt.x;
yStart = pt.y;
}
public void mouseReleased(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
page.fillRect(xStart, yStart, xEnd-xStart, yEnd-yStart);
}
}
public void mouseClicked(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseDragged(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
Rect rect = new Rect(xStart, yStart, xEnd-xStart, yEnd-yStart, currentColor);
rect.draw(page);
}
repaint();
}
public void mouseMoved(MouseEvent event) {}
}
ColorListener finds the type of object that is selected in the JComboBox determined in the main() method, and sets the currentColor to it (it is put back as the color in the Rect constructor above).
private class ColorListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if (event.getSource().equals("black"))
{
currentColor = Color.black;
comboBox.setSelectedIndex(0);
}
else if (event.getSource().equals("red"))
{
currentColor = Color.red;
comboBox.setSelectedIndex(1);
}
else if (event.getSource().equals("blue"))
{
currentColor = Color.blue;
comboBox.setSelectedIndex(2);
}
else if (event.getSource().equals("green"))
{
currentColor = Color.green;
comboBox.setSelectedIndex(3);
}
else if (event.getSource().equals("orange"))
{
currentColor = Color.orange;
comboBox.setSelectedIndex(4);
}
}
}
So what I am having trouble with is being able to draw it. I don't see any flaws in logic in the program so far above, and nothing is being able to be drawn onto the Canvas (don't worry about the JButtons Undo and Erase, as they are easy to work with with an ArrayList and remove() and clear() and things like that).
Your program should have no Graphics field that is a class field (your page class field). I'm surprised your not seeing a NullPointException with the way you're attempting to use it above in say your Mouse Listener class:
public void mouseReleased(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
// !!!! don't do this !!!!
page.fillRect(xStart, yStart, xEnd-xStart, yEnd-yStart);
}
}
Instead the Graphics object should only be obtained from the JVM, should only exist within the paintComponent method and should only be used within the same paintComponent method (exceptions being any methods called from with paintComponent that are passed this object and of course a Graphics object obtained from a BufferedImage). The MouseListener/MouseMotionListener should fill in your x-start, y-start, x-end, and y-end variables during mouseDragged, and then on mouseRelease these variables should be used to create a new Rect object that is placed in the rectList, and then repaint called.
Then paintComponent should iterate through rectList, filling each Rect object it finds there.