Dragging acting strange after adding second eventhandler in JavaFX - java

guys! I am having an issue in my JavaFX Application. Images are able to be dragged and placed anywhere on the entire pane. This was working wonderfully, up until I added a second event handler. I added a second event handler to handle deletion. Right-clicking on the image gives you a pop up menu with deletion as an option. As soon as I added this, dragging stopped working properly. While it still works somewhat, the mouse is no longer ever on the image. As soon as you begin dragging, the image jumps over and down like 300 px? I am still able to drag, but I am not dragging on top of the image.
I understand this explanation isn't clear, and I can easily upload a gif of what happens if someone wants me to. Here's the code:
#FXML
private void handleImageButton()
{
System.out.println("Image Clicked");
closeMenus(6);
//contains the image path or image URL
String picURL = imageURL.getText();
if (!picURL.equals(""))
{
Image image = new Image(new File(picURL).toURI().toString());
ImageView iv = new ImageView();
iv.setImage(image);
iv.setCursor(Cursor.MOVE);
iv.setOnMousePressed(imageOnMousePressedEventHandler);
iv.setOnMouseDragged(imageOnMouseDraggedEventHandler);
workspace.getChildren().add(iv);
final ContextMenu contextMenu = new ContextMenu();
MenuItem delete = new MenuItem("Delete");
contextMenu.getItems().addAll(delete);
iv.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e){
if (e.isSecondaryButtonDown()){
contextMenu.show(workspace, e.getScreenX(), e.getScreenY());
delete.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event){
workspace.getChildren().remove(iv);
}
});
} //end if
}
});
}
}
EventHandler<MouseEvent> imageOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (ImageTools.isVisible())
{
originalX = t.getSceneX();
originalY = t.getSceneY();
newX = ((ImageView)(t.getSource())).getTranslateX();
newY = ((ImageView)(t.getSource())).getTranslateY();
System.out.println("(" + newX + ", " + newY + ")");
}
}
};
EventHandler<MouseEvent> imageOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (ImageTools.isVisible())
{
double offsetX = t.getSceneX() - originalX;
double offsetY = t.getSceneY() - originalY;
double newTranslateX = newX + offsetX;
double newTranslateY = newY + offsetY;
((ImageView)(t.getSource())).setTranslateX(newTranslateX);
((ImageView)(t.getSource())).setTranslateY(newTranslateY);
System.out.println("(" + newTranslateX + ", " + newTranslateY + ")");
}
}
};

The problem is that you are overriding your mouse listeners:
iv.setOnMousePressed(imageOnMousePressedEventHandler);
iv.setOnMouseDragged(imageOnMouseDraggedEventHandler);
iv.setOnMousePressed(new EventHandler() {});
setOnXYZ is a setter for a property, holding only one listener, so you are effectively overwriting your listeners in steps 1 and 3.
Use Node.addEventHandler() to add multiple handlers for the same EventType or in this case i would suggest using setOnContextMenuRequested to show the context menu because it is more convenient and system independent.

Related

JavaFX check what's under MouseClick

I have ArrayList<ImageView> imageViews = new ArrayList<>(); and so it contains images.
Images are created on button click, saved to that array and appears on scene from that array. Code is somewhat messy, but hey, #makeItWork at first #makeItRight later...
public void btnAddAndAction() throws FileNotFoundException {
algorithm.addElement(algorithm.calculateId(),"AND",null);
imageViews.add(new ImageView(algorithm.getLastElementImg()));
imageViews.get(imageViews.size()-1).setX(25); //default pos
imageViews.get(imageViews.size()-1).setY(85);
pane.getChildren().add( imageViews.get(imageViews.size()-1));
pane.requestLayout();
imageViews.get(imageViews.size()-1).setOnMousePressed(imgOnMousePressedEventHandler);
imageViews.get(imageViews.size()-1).setOnMouseDragged(imgOnMouseDraggedEventHandler);
}
//FIXME
EventHandler<MouseEvent> imgOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
orgTranslateX = ((imageViews.get(imageViews.size()-1)).getTranslateX());
orgTranslateY = ((imageViews.get(imageViews.size()-1)).getTranslateY());
}
};
//FIXME
EventHandler<MouseEvent> imgOnMouseDraggedEventHandler =
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;
(imageViews.get(imageViews.size()-1)).setTranslateX(newTranslateX);
(imageViews.get(imageViews.size()-1)).setTranslateY(newTranslateY);
}
};
I'm trying to do drag and drop feature to the images and I am struggling to detect what's under mouse...
I've fount getSource from MouseEvent, but it needs object...
so: how do I know, which element of array is pressed so I could drag it??
Java 1.8, JavaFX, IntelliJ.
Solution is to CAST ImageView... Answer is in the comment section.

How do I change the origin of the Rectangle drawn on JavaFX to be where I click my mouse?

I'm building a paint program using JavaFX. I'm trying to draw a Rectangle using GraphicsContext and have managed to get it to draw on the canvas. However, no matter where I click, it always starts from the origin. The rectangle stops being drawn when I release the mouse (this is done correctly) but isn't drawn from when I press down on the mouse. Here is my code:
public RectController(Canvas c, Scene s, BorderPane borderPane){
this.borderPane = borderPane;
this.scene = scene;
this.graphicsContext = canvas.getGraphicsContext2D();
rectangle = new Rectangle();
rectangle.setStrokeWidth(1.0);
rectangle.setManaged(true);
rectangle.setMouseTransparent(true);
//when you first press down on the mouse
pressMouse = event -> {
// borderPane.getChildren().add(rectangle); --> REMOVED
double startX = event.getX();
double startY = event.getY();
};
releaseMouse = event -> {
borderPane.getChildren().remove(rectangle);
double width = Math.abs(event.getX() - startX);
double height = Math.abs(event.getY() - startY);
graphicsContext.strokeRect(startX, startY, width, height);
borderPane.setOnMousePressed(null);
borderPane.setOnMouseDragged(null);
borderPane.setOnMouseReleased(null);
};
dragMouse = event -> {
rectangle.setWidth(event.getX());
rectangle.setHeight(event.getY());
};
}
Any help would be much appreciated.
Is this close to what you are asking?
#FXML AnchorPane apMain;
Rectangle rectangle;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML void handleOnMouseClick(MouseEvent event)
{
double x = event.getSceneX();
double y = event.getSceneY();
rectangle = new Rectangle();
rectangle.setX(x);
rectangle.setY(y);
rectangle.setWidth(25);
rectangle.setHeight(25);
apMain.getChildren().add(rectangle);
}
#FXML void handleOnMouseDrag(MouseEvent event)
{
rectangle.setWidth(event.getSceneX() - x);
rectangle.setHeight(event.getSceneY() - y);
}
#FXML void handleOnMouseRelease(MouseEvent event)
{
apMain.getChildren().remove(rectangle);
}
You will need to do work to this. It's crude.

JavaFX output to the SPI bus instead of HDMI on a RPi

I am experimenting with a JavaFX application on a raspberry. It works great on my HDMI monitor, and the rendered size seems all ok.
However, I now have added my 3.5 inch touch screen running on the SPI interface but I still get the JavaFX output on the HDMI monitor. It works ok for the graphical interface. The console appears on the 3.5 inch LCD, so I guess I need to set a setting somewhere for JavaFX to also output the 3.5 LCD, but I cannot remember how to do this.
[Edit:]
This is my (shortened) application code:
public class Main extends Application {
private static int width = 480;
private static int height = 320;
static final Text statusText = new Text(25, 300, "(no status update)");
static final Label messagesArea = new Label("");
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Registration UHF scanner");
final Group rootGroup = new Group();
final Scene scene = new Scene(rootGroup, width, height, Color.BLACK);
final Text title = new Text(25, 45, "Youth 2000 UHF Registration");
title.setFill(Color.WHITESMOKE);
title.setFont(Font.font(java.awt.Font.SERIF, FontWeight.BOLD, 25));
rootGroup.getChildren().add(title);
messagesArea.setWrapText(true);
messagesArea.setStyle("-fx-font-family: \"Comic Sans MS\"; -fx-font-size: 16; -fx-text-fill: white;");
messagesArea.setTranslateX(25);
messagesArea.setTranslateY(35);
rootGroup.getChildren().add(messagesArea);
statusText.setFill(Color.WHITESMOKE);
statusText.setFont(Font.font(java.awt.Font.SERIF, 16));
rootGroup.getChildren().add(statusText);
scene.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
System.out.println("Key pressed: " + keyEvent.getCode());
if (keyEvent.getCode() == KeyCode.ENTER) {
System.out.println("Test!");
}
}
});
Button shutDown = new Button("Shutdown");
shutDown.setPrefWidth(110);
shutDown.setPrefHeight(10);
shutDown.setTranslateX(width - 115);
shutDown.setTranslateY(height - 35);
rootGroup.getChildren().add(shutDown);
Button restart = new Button("Restart");
restart.setPrefWidth(110);
restart.setPrefHeight(10);
restart.setTranslateX(width - 115);
restart.setTranslateY(height - 75);
rootGroup.getChildren().add(restart);
shutDown.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
// Shutdown the computer.
try {
shutDown();
} catch (Exception ex){
System.out.println("Problem shutting down: " + ex.getMessage());
setStatusText("Problem shutting down!!!!");
}
}
});
restart.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
// Shutdown the computer.
try {
restartServer();
} catch (Exception ex){
System.out.println("Problem restarting: " + ex.getMessage());
setStatusText("Problem restarting!!!!");
}
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
System.out.println("Starting system...");
if (System.getProperty("screenWidth") != null) {
width = Integer.valueOf(System.getProperty("screenWidth"));
}
if (System.getProperty("screenHeight") != null) {
height = Integer.valueOf(System.getProperty("screenHeight"));
}
Application.launch(args);
}
}
There is a need to mirror the FrameBuffer to the SPI.
fbcp: https://github.com/tasanakorn/rpi-fbcp
The main display has to be configured correctly to have the same resolution. So in /boot/config.txt. In my case:
disable_overscan=1
framebuffer_width=480
framebuffer_height=320
#set specific CVT mode
hdmi_cvt 480 320 60 1 0 0 0
#set CVT as default
hdmi_group=2
hdmi_mode=87
hdmi_force_hotplug=1
That only left me with a little problem left with the touch screen as the xy are swapped. This should be able to be fixed by changing the
swap_xy=X
parameter in /etc/modules but for me that hasn't worked so far. I do want to use the screen in landscape, so rotating the image is not really an option.

Add touch gestures to legacy swing application

I have a legacy swing application that I need to add touch gestures to,specifically pinch to zoom and touch and drag.
I tried the SwingNode of JDK 8 and I can run the swing application there, but the display performance was cut by more than 50% which won't work. SwingTextureRenderer in MT4J has the same issue and that is without even trying to redispatch touch events as mouse events.
I thought about a glass pane approach using a JavaFX layer on top and capturing the touch events and attempting to dispatch them as mouse events to the Swing app underneath.
Does anyone have an alternative approach? The target platform is windows 8.
Bounty coming as soon as Stackoverflow opens it up. I need this one pretty rapidly.
EDIT:
Here is what I tried with SwingNode (the mouse redispatch didn't work). The SwingNode stuff might be a distraction from the best solution so ignore this if you have a better idea for getting touch into swing:
#Override
public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
createAndSetSwingContent(swingNode);
StackPane pane = new StackPane();
pane.getChildren().add(swingNode);
stage.setScene(new Scene(pane, 640, 480));
stage.show();
}
private void createAndSetSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
UILib.setPlatformLookAndFeel();
// create GraphViewTouch
String datafile = null;
String label = "label";
final JPanel frame = GraphView.demoFrameless(datafile, label);
swingNode.setContent(frame);
swingNode.setOnZoom(new EventHandler<ZoomEvent>() {
#Override public void handle(ZoomEvent event) {
MouseWheelEvent me = new MouseWheelEvent(frame, 1, System.currentTimeMillis(), 0, (int)Math.round(event.getSceneX()), (int)Math.round(event.getSceneY()), (int)Math.round(event.getScreenX()), (int)Math.round(event.getScreenY()), (int)Math.round(event.getZoomFactor()), false, MouseWheelEvent.WHEEL_UNIT_SCROLL, (int)Math.round(event.getZoomFactor()), (int)Math.round(event.getZoomFactor()), event.getZoomFactor());
frame.dispatchEvent(me);
System.out.println("GraphView: Zoom event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
}
});
}
You can use JNA to parse the messages from Windows.
Some documentation on the multi touch events from Microsoft:
https://learn.microsoft.com/en-us/windows/desktop/wintouch/wm-touchdown
Some documentation on how to do it in Java and sample code:
https://github.com/fmsbeekmans/jest/wiki/Native-Multitouch-for-AWT-component-(Windows)
hmm... this is a tough one, but there is a chance.
What I would do is make multiple MouseListener classes (relative to the number of mouse events you want to pick up), and than create some sort of system to detect certain adjustments, eg. (zoom)
1st listener:
public void mousePressed(MouseEvent e){
//Set click to true
clk = true;
//set first mouse position
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Second Listener
public void mousePressed(MouseEvent e){
//set clicked to true
clk = true;
//set first mouse position (listener 2)
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Main handler
if(Listener1.get1stMousePos() < Listener1.get2ndMousePos() && Listener2.get1stMousePos() < Listener2.get2ndMousePos() && Listener1.clk && Listener2.clk){
zoomMethod((Listener1.get1stMousePos - Listener1.get2ndMousePos()) + (Listener1.get1stListener2.get2ndMousePos());
}
And than just do this to add it to the window:
window.addMouseListener(Listener1);
window.addMouseListener(Listener2);
Hope you find a way.
For the pinch-to-zoom:
Try the GestureMagnificationListener by guigarage.com. It provides a method:
public void magnify(GestureMagnificationEvent me){...}

Getting Stage coordinates of Actor in Table in libGDX

I want to create a floating help bubble to introduce the basic functioning of my game. This bubble should float above the Actor I want it to explain, like shown in the picture below.
To accomplish this, I want the coordinates of the Actor, in this case the left button, and then I can add the bubble Actor to the Stage in front of everything else. The last part is easy enough, but I'm struggling with retrieving the actual coordinates of the button, as it is in a table.
The two buttons are added to a Table like this:
t.add(btnLab).expandX().center();
t.add(btnSea).expandX().center();
I've tried the most obvious approach:
Vector2 loc = new Vector2(a.getX(), a.getY());
System.out.println("Loc: " + loc);
a.localToStageCoordinates(loc);
System.out.println("Loc: " + loc);
This gives me (in the order of the sysouts): [0.0, 0.0] and [40.0, 130.0]. The last position is actually the position of the Table, which fills the blue area of the screen. So this location obviously misses something the Table does to place the Actor, and cannot be used (as I only end up with the location of the Table).
(I've also tried using t.localToStageCoordinates here, t being the Table. Same results.)
Another solution I tried was to recursively search through all Parents:
private static Vector2 getLoc(Actor a) {
return getLoc(new Vector2(a.getX(), a.getY()), a.getParent());
}
private static Vector2 getLoc(Vector2 loc, Actor g) {
System.out.println("Location: " + loc + ", Actor: " + g);
loc.x += g.getX();
loc.y += g.getY();
if(g.getParent() == null) return loc;
return getLoc(loc, g.getParent());
}
Unfortunately, this gives me the same. The sysouts gives the following:
Location: [0.0:0.0], Actor: Table 40.0,130.0 944.0x508.0
Location: [40.0:130.0], Actor: Group 0.0,0.0 0.0x0.0
Location: [40.0:130.0], Actor: Group 0.0,0.0 0.0x0.0
So I can't seem to get the actual positions of the Groups/Actors within the Table.
How am I supposed to do this?
I think I understand where you got confused. It is actually very simple to get the stage coordinates of an actor (assuming screen aligned, unrotated, unscaled actors).
All you need to do is this:
public static Vector2 getStageLocation(Actor actor) {
return actor.localToStageCoordinates(new Vector2(0, 0));
}
What you are doing wrong is trying to get the location of the actor relative to the table (by using actor.getX() and actor.getY()), and then transform that location to the stage. That will end up giving you the location of table because it will be adding the location of the actor. What you really what is to know where point new Vector2(0, 0) inside your actor is. This tells you where the bottom left corner of the actor is. If you want top left you would use new Vector2(0, actor.getHeight()), and the other corners in a similar manner.
Additionally, if you are working with events and want to know the stage location of an event you would simply do the following:
#Override public void touchUp(final InputEvent event, final float x, final float y, final int pointer, final int button) {
if (pointer == Buttons.LEFT) {
Gdx.app.log("Event", "x: " + event.getStageX() + " y: " + event.getStageY());
}
}
For a more complete example look at the following code:
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
public class ActorStage implements ApplicationListener {
private Stage stage;
private Skin skin;
#Override public void create() {
Gdx.app.log("CREATE", "App Opening");
this.stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
Gdx.input.setInputProcessor(this.stage);
this.skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
this.skin.hashCode();
final Table table = new Table();
table.setFillParent(true);
final Button btnLab = new TextButton("Lab", skin);
final Button btnSea = new TextButton("Sea", skin);
setupButton(table, btnLab);
setupButton(table, btnSea);
this.stage.addActor(table);
Gdx.gl20.glClearColor(0f, 0f, 0f, 1);
Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
}
private void setupButton(Table table, final Button button) {
table.add(button).expandX().center();
button.addListener(new ClickListener() {
#Override public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("XY", "[" + x + ", " + y + "]");
Gdx.app.log("Event", "[" + event.getStageX() + ", " + event.getStageY() + "]");
Gdx.app.log("Actor", "[" + button.getX() + ", " + button.getY() + "]");
Vector2 loc = new Vector2(button.getX(), button.getY());
Vector2 stageLoc = button.localToStageCoordinates(loc);
Gdx.app.log("ActorStage", "[" + stageLoc.x + ", " + stageLoc.y + "]");
Vector2 zeroLoc = button.localToStageCoordinates(new Vector2());
Gdx.app.log("ZeroStage", "[" + zeroLoc.x + ", " + zeroLoc.y + "]");
}
});
}
#Override public void render() {
this.stage.act();
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl20.glEnable(GL20.GL_BLEND);
this.stage.draw();
}
#Override public void dispose() {
Gdx.app.log("DISPOSE", "App Closing");
}
#Override public void resize(final int width, final int height) {
Gdx.app.log("RESIZE", width + "x" + height);
Gdx.gl20.glViewport(0, 0, width, height);
this.stage.setViewport(width, height, false);
}
#Override public void pause() {}
#Override public void resume() {}
}
When clicking btnLab once, clicking btnSea once and then closing the window, outputs:
CREATE: App Opening
RESIZE: 800x600
XY: [18.0, 13.0]
Event: [200.0, 296.0]
Actor: [182.0, 283.0]
ActorStage: [364.0, 566.0]
ZeroStage: [182.0, 283.0]
XY: [6.0, 23.0]
Event: [588.0, 306.0]
Actor: [582.0, 283.0]
ActorStage: [1164.0, 566.0]
ZeroStage: [582.0, 283.0]
DISPOSE: App Closing
As you can see you want the "ZeroStage" message because that gives you the location of the actor on the stage.

Categories