JavaFX transparent window only receives mouse events over drawn pixels - java

I'd like a Stage that is the same size as the screen which is fully transparent and receives mouse events anywhere. In the example below I get mouse events only when the mouse is over the circle. I see this issue on both Windows XP and Windows 7 using Java 8u11
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class TransparentTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage ignored) throws Exception {
Stage stage = new Stage(StageStyle.TRANSPARENT);
stage.setTitle("Transparent app test");
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
stage.setX(0);
stage.setY(0);
stage.setWidth(screenBounds.getWidth());
stage.setHeight(screenBounds.getHeight());
Circle circle = new Circle(100);
circle.setFill(Color.RED);
Rectangle rectangle = new Rectangle(screenBounds.getWidth(),
screenBounds.getHeight());
rectangle.setFill(Color.TRANSPARENT);
Scene scene = new Scene(new StackPane(circle, rectangle));
scene.setFill(null);
stage.setScene(scene);
scene.setOnMouseMoved((e) -> {
System.out.println("Mouse over rectangle " + e);
});
stage.show();
}
}
Interestingly if I set the alpha part of the fill color to its absolute minimum then I get mouse events. However I'd prefer not to use this workaround and actually get to the bottom of the issue. My conclusion is somewhere in JavaFX or a Windows library there is some hit-detection code that filters mouse events based on the pixel value of the mouse event.
rectangle.setFill(Color.rgb(0, 0, 0, 1d / 255d)); // receives mouse events
rectangle.setFill(Color.rgb(0, 0, 0, 0)); // does not receive mouse events
Research
JavaFx Transparent window - yes please. Mouse transparent - no thanks describes a similar problem, however it does not address the issue of mouse events in completely transparent areas
Debugging - using a breakpoint in the setOnMouseMoved() I've examined the preceding stackframes to try to find the hit-detection code.
Used JNA to test different styles such as WS_EX_TRANSPARENT and WS_EX_LAYERED. Interestingly WS_EX_TRANSPARENT made the window fully mouse transparent - no mouse events over the painted pixels.
Tried putting the mouse listener on the rectangle/StackPane instead - no difference
MSDN article Layered Windows hints at this functionality being part of Windows rather than JavaFX. If this is true is there any workaround?
Hit testing of a layered window is based on the shape and transparency
of the window. This means that the areas of the window that are
color-keyed or whose alpha value is zero will let the mouse messages
through. If the layered window has the WS_EX_TRANSPARENT extended
window style, the shape of the layered window will be ignored and the
mouse events will be passed to the other windows underneath the
layered window.

In summary only known solution is to set the background to be "not quite" transparent to fool JavaFX into sending events.
rectangle.setFill(Color.rgb(0, 0, 0, 1d / 255d)); // receives mouse events

Related

How to convert an image into a shape in Java?

I'm currently working on a stock market program that teaches students how to interact with the stock market. An issue that I'm working on right now revolves around the issue of efficiency and memory. I make all of my 2D icons (such as a settings icon, portfolio icons, etc) in adobe illustrator, I export those files as png files, and throw them into my program. I currently use JavaFX, and one of the features of JavaFX is what's called imageView, which is a method that handles the opening and viewing of images.
So let's say that the user would like to press on the Settings icon in the game, I would like to change the settings icon to a darker or lighter color when the user hovers over that icon in order to provide the user with a better user experience (UX) at the moment, I use two different images and remove one from the frame, and replace it with another, which is highly inefficient.
I know that JavaFX has a Shape class that inherits many methods such as Fill, or setFill, but these methods can only affect those of the Shape class.
My question is, "How can I convert an image that is imported into the project, into something that I can use methods such as setFill and Fill on"
If you're only aiming for basic changes such as darkening or lightning your icons, you can look at the Effects part of javaFx, you can read about it here, or else you can import your images as SVG as suggested in the comments
If you're planning to do it using Effects, you can achieve a darken-on-hover effect using the ColorAdjust Effect by setting the brightness value to something negative (the brightness in ColorAdjust ranges between -1 and +1 where 0 is the default) as in the following example
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage s) {
ImageView image = new ImageView(new Image("img.png"));
ColorAdjust darker = new ColorAdjust();
darker.setBrightness(-.3);
image.setOnMouseEntered(e-> {
image.setEffect(darker);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});
s.setScene(new Scene(new StackPane(image)));
s.show();
}
public static void main(String[] args) {
launch(args);
}
}
Changing the color of an image might involve adjusting the hue using ColorAdjust by setting the hue value
ColorAdjust hueShift = new ColorAdjust();
hueShift.setHue(-.3);
image.setOnMouseEntered(e-> {
image.setEffect(hueShift);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});
You can combine effects by setting effects as input to other effects, so for example, if you want to darken a Node and blur it at the same time you can set the blur effect as input to the darkening colorAdjust
GaussianBlur blur = new GaussianBlur();
blur.setRadius(10);
ColorAdjust darker = new ColorAdjust();
darker.setBrightness(-.3);
darker.setInput(blur);
image.setOnMouseEntered(e-> {
image.setEffect(darker);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});

Why does instantiating a JavaFX scene control corrupt the layout

I'm trying to create a simple data entrty application with javafx and ran into a problem when adding a scene control. The display loses it's fill colour even BEFORE i've added the control to the scene! Simply instantiating the control breaks it.
I was running on Oracle java 8 on windows, but I;ve tried OpenJDK 8 on Windows and OpenJDk/OpenJFX 13 on linux and all behave identically. I stripped out the code to the bear minmium to recreate the problem.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class BasicTest extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle r = new Rectangle();
r.setWidth(200);
r.setHeight(50);
r.setFill(Color.BLUE);
r.setStroke(Color.WHITE);
r.setStrokeWidth(2);
Text t = new Text();
t.setText("Confirm");
t.setFill(Color.WHITE);
t.setFont(Font.font("null", 40));
StackPane sp = new StackPane(r);
sp.getChildren().add(t);
sp.setMaxWidth(200);
t.setTranslateY(-2);
Label b = new Label("Click me");//Comment this line out after first run
Scene scene = new Scene(sp, 300, 200);
scene.setFill(Color.BLUE);
primaryStage.setScene(scene);
primaryStage.show();
}
}
With the label commented out the scene background is blue so I get a white "Confirm" with white outline. Just adding the label constructor will make the scene background go grey.
Thanks for all the comments. Many apologies if my wording was confusing. I did add some images to show the difference but they seem to have disappeared. Anyway my thanks to #Matt for their very succinct description (which I wish I'd have thought of!). I like the mouse click idea too.
I was probably being very naughty asking this "question" on SO when I firmly believe this is a bug in javafx. Instantiating an object that goes no where near (yet) the scene graph should have no effect on it in my opinion - CSS or no CSS. I will raise a bug against javafx.
However I knew the power of SO would help me and it has!
Thanks to the clue by #jewelsea I simply replaced my scene.setFill() (which was only a test anyway) with CSS and the problem is circumvented. I can even add the control to the scene now and it works as expected. From the JavaFX CSS Reference:
"The Scene object has no settable CSS properties, nor does it have any pseudo-classes. However, the root node of the scene is assigned the style class "root" (in addition to style classes already assigned to the node). "
So I set up in my css:
.root {
-fx-background-color: blue;
}
Another way to circumvent this is to simply set the background of the StackPane to BLUE:
sp.setBackground(new Background(new BackgroundFill(Color.BLUE, null, null)));

JFXPanel in JFrame causing frame drops upon mouse movement

I am working on embedding a JavaFX login screen inside a Swing application.
I added a JFXPanel to a JPanel's content frame. Upon loading the application, all is well and smooth until I move my mouse inside the content pane (see link below for a video of this happening).
While my mouse is actively moving in the pane, the rendering becomes very laggy:
Turns laggy when mouse enters pane
Desired behaviour:
Use JavaFX designed UI in Swing through means of a JFXPanel without suffering a significant decrease in performance
The code below is a minimal, reproducible example of this problem.
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.*;
import javax.swing.*;
import java.io.IOException;
import java.net.URL;
public class TestJFXPanel {
public static void main(String[] args) throws IOException {
/*
Create pane with as background an animated image (GIF)
*/
final Pane pane = new Pane();
final URL url = new URL("https://i.stack.imgur.com/AvkzQ.gif");
pane.setBackground(new Background(new BackgroundImage(
new Image(url.openStream()),
BackgroundRepeat.NO_REPEAT,
BackgroundRepeat.NO_REPEAT,
BackgroundPosition.CENTER,
BackgroundSize.DEFAULT)));
/*
Set the pane as root of a Scene
*/
final JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(pane, 500, 500));
/*
Invoke JFrame on AWT thread
*/
SwingUtilities.invokeLater(() -> {
final JFrame jFrame = new JFrame();
// Add JFXPanel to content pane of JFrame
jFrame.getContentPane().add(panel);
jFrame.pack();
jFrame.setVisible(true);
});
}
}
My thoughts:
I suspect this has something to do with the mouse motion being redirected to Swing from JavaFX causing a delay in communication between the FX and Swing threads. I think this is especially costly because I use a GIF as background image of the login screen.
I am not sure whether this is caused by a delay in communication of FX and Swing, if it is the case, would handling all my FX UI in Swing resolve this (assuming this is more feasible than porting the entire design to Swing)?
However, if someone is more familiar with integrating JavaFX with Swing and thinks my suspicions are wrong. Some advice on how to proceed are much appreciated!
Update:
When I override the processMouseMotionEvent method of the JFXPanel class, so that no MouseMotionEvents get consumed, the problem does not occur. I think this might be a compatibility issue between JavaFX and MacOS.

Javafx AlwaysOnTop windows priority on macos full screen application

I can't find anything on this and i am not sure if this is even possible but i will try. I have a JavaFX application showing a stage when you start typing text (it listen to os inputs). Everything is working fine except when the application is set to full screen on macOS. The stage will not show over the application, even if alwaysontop is true.
Only way i got this to work was using a JDialog in Type.POPUP but this is causing other issues in my code. I tried playing with the window level but it doesn't work.
Here is a test i ran to test the issue. This will go over any application except when it is in full screen.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ontopTest extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage stage){
Scene scene = new Scene(new Pane(new Label("test application")));
stage.setTitle("test");
stage.initStyle(StageStyle.UNDECORATED);
stage.setWidth(300);
stage.setHeight(300);
stage.setScene(scene);
// stage.setAlwaysOnTop unneeded as com.sun.glass.ui sets window to top
stage.setY(0); // Set Y Axis to 0
// Need to show window first
stage.show();
com.sun.glass.ui.Window.getWindows().get(0).setLevel(3);
// Set window level to 3 (Maximum)
}
}
I want to know if this is possible with JavaFX or any other workaround. I want my view to be over any application, even full screen.

How to execute code during a drag and drop operation?

I have a node I want to implement drag and drop for (this object is the source not the target). I also want the object to move along with the mouse cursor. I managed to do both of these but not at the same time.
It appears that setOnDragDetected and setOnMouseDragged don't work well together. Consider a node with the following handlers:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(20, 20);
rect.setOnMousePressed(e -> System.out.println("Pressed"));
rect.setOnMouseDragged(e -> System.out.println("Dragged"));
rect.setOnDragDetected(e -> {
System.out.println("Detected");
ClipboardContent content = new ClipboardContent();
content.putString("something");
Dragboard db = rect.startDragAndDrop(TransferMode.ANY);
db.setContent(content);
});
Group subGroup = new Group(rect);
Scene scene = new Scene(subGroup, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Example.launch(args);
}
}
Now press the mouse on the node and move the mouse. This is the output:
Pressed
Dragged
Dragged
Dragged
Dragged
Dragged
Dragged
Detected
Once the drag is detected the MouseDragged handler stops.
How do I achieve what I described? One thing i noticed was maybe that I can use a onDragOver for the parent but i want the behavior to be in the node because that's where it really should be.
You are mixing two things up here. In short, when you call the startDragAndDrop method the system switches to drag and drop mode and Java stops delivering MouseEvent to rect.
The MouseEvent documentation has a section "Dragging gestures" which explains the three types of dragging gestures. Here is just a short summary:
simple press-drag-release - when a drag is detected Java continues to deliver MouseEvents to the node where the drag was detected.
full press-drag-release - you can call startFullDrag inside the handler you set with setOnDragDetected. Then Java also starts to deliver MouseDragEvents to other nodes (potential gesture targets).
platform-supported drag-and-drop - if you call startDragAndDrop inside the OnDragDetected handler, Java will stop to deliver MouseEvents and start to deliver DragEvents instead. This is used for drag and drop interaction with other applications.
It is not clear to me what you want to achieve, but as long as you do not want to drag something outside of your application, try using startFullDrag instead.
Also, it might be helpful to have a further look at the DragEvent and MouseDragEvent documentation.

Categories

Resources