JavaFX Transparent Cursor using WritableImage - java

Edit-Answer:
You can check Fabian's answer and also this library (https://github.com/goxr3plus/JFXCustomCursor)
Actual Question
I want to create a cursor which is fading out in JavaFX so for that i am using a WritableImage and i am continuously reading pixels from the original Image and writing them to a new WritableImage.Then i set a custom cursor to the Scene using ImageCursor(writableImage),below is the full code(give it a try).
The problem is that a get black pixels where transparent pixels are expected.
Note that all the below classes have to be in package sample.
Code(Main):
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Main extends Application {
FadingCursor fade = new FadingCursor();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setWidth(300);
primaryStage.setHeight(300);
Scene scene = new Scene(new FlowPane());
primaryStage.setScene(scene);
fade.startFade(scene,100);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Code(FadingCursor)(Edited):
package sample;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.scene.ImageCursor;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
public class FadingCursor {
private int counter;
private Image cursorImage;
/**
* Change the image of the Cursor
*
* #param image
*/
public void setCursorImage(Image image) {
this.cursorImage = image;
}
/**
* Start fading the Cursor
*
* #param scene
*/
public void startFade(Scene scene, int millisecondsDelay) {
// Create a Thread
new Thread(() -> {
// Keep the original image stored here
Image image = new Image(getClass().getResourceAsStream("fire.png"), 64, 64, true, true);
PixelReader pixelReader = image.getPixelReader();
// Let's go
counter = 10;
for (; counter >= 0; counter--) {
CountDownLatch count = new CountDownLatch(1);
Platform.runLater(() -> {
// Create the fading image
WritableImage writable = new WritableImage(64, 64);
PixelWriter pixelWriter = writable.getPixelWriter();
// Fade out the image
for (int readY = 0; readY < image.getHeight(); readY++) {
for (int readX = 0; readX < image.getWidth(); readX++) {
Color color = pixelReader.getColor(readX, readY);
// Now write a brighter color to the PixelWriter.
// -------------------------Here some way happens
// the problem------------------
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (counter / 10.00) * color.getOpacity());
pixelWriter.setColor(readX, readY, color);
}
}
System.out.println("With counter:"+counter+" opacity is:" + writable.getPixelReader().getColor(32, 32).getOpacity());
scene.setCursor(new ImageCursor(writable));
count.countDown();
});
try {
// Wait JavaFX Thread to change the cursor
count.await();
// Sleep some time
Thread.sleep(millisecondsDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
The image(needs to be downloaded)(Right Click ->Save Image as...):

You set the opacity of every pixel to a value only depending on the loop variable here:
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), counter / 10.00);
For transparent pixels (opacity = 0) you actually increase the opacity making the values stored in the other channels (in this case 0 / black) visible. You need to make sure transparent pixels remain transparent, which usually is done like this:
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (counter / 10.00) * color.getOpacity());
Alternatively you could use deriveColor:
color = color.deriveColor(0, 1, 1, counter / 10d);
Edit
For some reason ImageCursor doesn't seem to like a completely transparent image. You can check that this works, if at least one pixel is not completely transparent by adding
pixelWriter.setColor(0, 0, new Color(0, 0, 0, 0.01));
After the for loops writing the image.
To fix this you could simply use Cursor.NONE instead of an ImageCursor with a fully transparent image:
for (; counter >= 1; counter--) {
...
}
Platform.runLater(() -> scene.setCursor(Cursor.NONE));
 
Alternative without the need to recreate the image/cursor
You could simulate the cursor yourself by moving a image Across the root of the Scene. This won't make the image show up beyond the bounds of the Scene, but you can apply animations to the ImageView for fading instead of modifying the opacity of each pixel manually...
public class CursorSimulator {
private final FadeTransition fade;
public CursorSimulator(Image image, Scene scene, ObservableList<Node> rootChildrenWriteable, double hotspotX, double hotspotY) {
ImageView imageView = new ImageView(image);
imageView.setManaged(false);
imageView.setMouseTransparent(true);
fade = new FadeTransition(Duration.seconds(2), imageView);
fade.setFromValue(0);
fade.setToValue(1);
// keep image on top
rootChildrenWriteable.addListener((Observable o) -> {
if (imageView.getParent() != null
&& rootChildrenWriteable.get(rootChildrenWriteable.size() - 1) != imageView) {
// move image to top, after changes are done...
Platform.runLater(() -> imageView.toFront());
}
});
scene.addEventFilter(MouseEvent.MOUSE_ENTERED, evt -> {
rootChildrenWriteable.add(imageView);
});
scene.addEventFilter(MouseEvent.MOUSE_EXITED, evt -> {
rootChildrenWriteable.remove(imageView);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, evt -> {
imageView.setLayoutX(evt.getX() - hotspotX);
imageView.setLayoutY(evt.getY() - hotspotY);
});
scene.setCursor(Cursor.NONE);
}
public void fadeOut() {
fade.setRate(-1);
if (fade.getStatus() != Animation.Status.RUNNING) {
fade.playFrom(fade.getTotalDuration());
}
}
public void fadeIn() {
fade.setRate(1);
if (fade.getStatus() != Animation.Status.RUNNING) {
fade.playFromStart();
}
}
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
System.out.println("Hello World!");
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 500, 500);
Image image = new Image("http://i.stack.imgur.com/OHj1R.png");
CursorSimulator simulator = new CursorSimulator(image, scene, root.getChildren(), 32, 50);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
private boolean fadeOut = true;
#Override
public void handle(MouseEvent event) {
if (fadeOut) {
simulator.fadeOut();
} else {
simulator.fadeIn();
}
fadeOut = !fadeOut;
}
});
primaryStage.setScene(scene);
primaryStage.show();
}

The reason of this question was to create a kind of cursor that can be
modified.For example here i wanted to make it had a fade effect.For
future users who want to create custom cursors i have created a
library on github and i will show some code here:
https://github.com/goxr3plus/JFXCustomCursor
Code:
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
/**
* This class allows you to set as a Cursor in a JavaFX Scene,whatever you want
* ,even a video!. <br>
* <br>
* <b>What you have to do is create a basic layout,for example:</b><br>
* #-->A BorderPane which contains a MediaView,<br>
* #-->A StackPane which contains an animated ImageView,<br>
* #-->A Pane which contains an animated Rectangle or something more complex
* etc..)<br>
*
* <br>
* <br>
* The options are unlimited!
*
* #author GOXR3PLUS
* #param <T>
* #Version 1.0
*/
public class JFXCustomCursor {
private SimpleIntegerProperty hotSpotX = new SimpleIntegerProperty();
private SimpleIntegerProperty hotSpotY = new SimpleIntegerProperty();
private Scene scene;
private Pane sceneRoot;
private Pane content;
private EventHandler<MouseEvent> eventHandler1;
private EventHandler<MouseEvent> eventHandler2;
private EventHandler<MouseEvent> eventHandler3;
/**
* Constructor
*
* #param scene
* The Scene of your Stage
* #param sceneRoot
* The Root of your Stage Scene
* #param content
* The content of the JFXCustomCursor class
* #param hotspotX
* Represents the location of the cursor inside the content on X
* axis
* #param hotspotY
* Represents the location of the cursor inside the content on Y
* axis
*/
public JFXCustomCursor(Scene scene, Pane sceneRoot, Pane content, int hotspotX, int hotspotY) {
// Go
setRoot(scene, sceneRoot, content, hotspotX, hotspotY);
}
/**
* This method changes the content of the JFXCustomCursor
*
* #param scene
* The Scene of your Stage
* #param sceneRoot
* The Root of your Stage Scene
* #param content
* The content of the JFXCustomCursor class
* #param hotspotX
* Represents the location of the cursor inside the content on X
* axis
* #param hotspotY
* Represents the location of the cursor inside the content on Y
* axis
*/
public void setRoot(Scene scene, Pane sceneRoot, Pane content, int hotSpotX, int hotSpotY) {
// Keep them in case of unRegister-reRegister
unRegister(); // has to be called before the below happens
this.scene = scene;
this.sceneRoot = sceneRoot;
this.content = content;
// hot spots
this.hotSpotX.set(hotSpotX);
this.hotSpotX.set(hotSpotY);
// cursor container
content.setManaged(false);
content.setMouseTransparent(true);
// Keep the Content on the top of Scene
ObservableList<Node> observable = sceneRoot.getChildren();
observable.addListener((Observable osb) -> {
if (content.getParent() != null && observable.get(observable.size() - 1) != content) {
// move the cursor on the top
Platform.runLater(content::toFront);
}
});
if (!observable.contains(content))
observable.add(content);
// Add the event handlers
eventHandler1 = evt -> {
if (!sceneRoot.getChildren().contains(content))
observable.add(content);
};
eventHandler2 = evt -> observable.remove(content);
eventHandler3 = evt -> {
content.setLayoutX(evt.getX() - hotSpotX);
content.setLayoutY(evt.getY() - hotSpotY);
};
scene.addEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
scene.addEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
scene.addEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);
}
/**
* Unregisters the CustomCursor from the Scene completely
*/
public void unRegister() {
if (scene != null) {
sceneRoot.getChildren().remove(content);
scene.removeEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
scene.removeEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
scene.removeEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);
}
}
/**
* Re register the CustomCursor to the Scene,<b>this method is
* experimental(use with caution!)</b>
*/
public void reRegister() {
if (scene != null)
setRoot(scene, sceneRoot, content, hotSpotX.get(), hotSpotY.get());
}
public SimpleIntegerProperty hotSpotXProperty() {
return hotSpotX;
}
public SimpleIntegerProperty hotSpotYProperty() {
return hotSpotY;
}
}

Related

JavaFX -- when I create new instance of TextField whole window becomes white-ish [duplicate]

This question already has answers here:
MenuBar changes the background color of the scene (Java FX 8)
(1 answer)
JavaFX: Just Declaring Nodes changes Background of other Nodes
(1 answer)
Closed 1 year ago.
I created JavaFX application, made whole screen black. Then I decided to add new instance of TextField to get input from user, but noticed that whole screen now is white-ish. I didn't even added it to any pane or scene. I tried to change TextField's background color to empty, but nothing removes white-ish color.
Here is code, that makes black window:
package org.medianik.tictactoe;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Pane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import java.io.InputStream;
import static org.medianik.tictactoe.util.Constants.*;
/**
* JavaFX App
*/
public class TicTacToe extends Application{
private static TicTacToe instance;
public static TicTacToe getInstance(){
return instance;
}
private final int width;
private final int height;
final Pane pane;
public TicTacToe(){
width = calculateWidth();
height = calculateHeight();
pane = new StackPane();
instance = this;
}
public static void main(String[] args){
launch();
}
/**
* The entry point
*/
#Override
public void start(Stage stage){
//In Constants.java:
//public static final Color BACKGROUND_COLOR = Color.BLACK;
var scene = new Scene(pane, width, height, BACKGROUND_COLOR);
stage.setScene(scene);
stage.show();
setupIcon(stage);
// new TextField();
}
private int calculateWidth(){
var bounds = Screen.getPrimary().getBounds();
return Math.min((int) bounds.getWidth() - GLOBAL_OFFSET, MAX_WIDTH);
}
private int calculateHeight(){
var bounds = Screen.getPrimary().getBounds();
return Math.min((int) bounds.getHeight() - GLOBAL_OFFSET, MAX_HEIGHT);
}
private void setupIcon(Stage stage){
InputStream inputIcon = getClass().getResourceAsStream("/icon.png");
Image icon = new Image(inputIcon);
stage.getIcons().add(icon);
}
#Override
public void stop() throws Exception{
System.out.println("stop");
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
}
And here is window:
Picture of ordinary person
But if I uncomment one single line:
/**
* The entry point
*/
#Override
public void start(Stage stage){
//In Constants.java:
//public static final Color BACKGROUND_COLOR = Color.BLACK;
var scene = new Scene(pane, width, height, BACKGROUND_COLOR);
stage.setScene(scene);
stage.show();
setupIcon(stage);
new TextField();
}
Everything goes white-ish:
Picture of insane one
Even if I make background empty nothing changes:
/**
* The entry point
*/
#Override
public void start(Stage stage){
var scene = new Scene(pane, width, height, BACKGROUND_COLOR);
stage.setScene(scene);
stage.show();
setupIcon(stage);
TextField text = new TextField();
text.setBackground(Background.EMPTY);
}
What should I do to fix this?
Was a bit astonished to see this (and had no immediate answer ;) so did a bit of digging:
verified that I can reproduce the different behavior in the (slightly stripped-down) example
added debug print outs of pane's state before/after creating the TextField: noted that its background is null before and not-null after
followed field creation in a debugger: noted styleSheet init on classLoading of Control
So the reason for the difference is that the default styleSheet is loaded in static code block of class Control (there might be other paths):
static {
...
// Ensures that the default application user agent stylesheet is loaded
if (Application.getUserAgentStylesheet() == null) {
PlatformImpl.setDefaultPlatformUserAgentStylesheet();
}
}
Modified example:
public class TicTacToe extends Application{
public static void main(String[] args){
launch();
}
#Override
public void start(Stage stage){
Pane pane = new StackPane();
var scene = new Scene(pane, 500, 300, Color.BLACK);
stage.setScene(scene);
stage.show();
System.out.println("pane before styling: " + pane.getBackground());
new TextField();
Platform.runLater(() -> {
System.out.println("pane after styling: " + pane.getBackground());
});
}
}

Transparent JavaFX stage capture scrolling events if there is another window behind

Mouse events and scroll events behave in different ways
Mouse Events:
The event is captured by mainStage
The event is captured by mainStage
The event is not captured
Scroll Events:
The event is captured by mainStage
The event is captured by secondStage
The event is not captured
Is there any way that transparent secondStage does not capture scroll events?
My code:
Pane mainPane = new Pane(new Label("Main Stage"));
mainPane.setPrefSize(300, 300);
mainStage.setScene(new Scene(mainPane));
Stage secondStage = new Stage();
Pane secondPane = new Pane(new Label("Second Stage"));
secondPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
secondPane.setBorder(new Border(
new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2))));
secondPane.setPrefSize(300, 300);
secondStage.setScene(new Scene(secondPane, Color.TRANSPARENT));
secondStage.initStyle(StageStyle.TRANSPARENT);
mainStage.getScene().setOnScroll(event -> System.out.println("Scroll in main stage"));
secondStage.getScene().setOnScroll(event -> System.out.println("Scroll in second stage"));
mainStage.getScene().setOnMouseClicked(event -> System.out.println("Click in main stage"));
secondStage.getScene().setOnMouseClicked(event -> System.out.println("Click in second stage"));
mainStage.show();
secondStage.show();
Java version: 1.8.0_201 (64 bits), Windows 10
edit:
The example is a simplification with only two windows. Fire the event programmatically implies discovering which stage is immediately lower and that is another problem in itself.
It might be a great coincidence, that we also came with the same solution of transparent window because of not having the feature of managing z-index of stages. And We encountered the exact same issue as yours. ie, scroll events not propagating to underlying Stages. We used the below approach, not sure whether this can help you:
Firstly, We constructed a Singleton class that keeps a reference of Node that is currently hovered on.
Then, when we create any normal stage, we include the below handlers to the scene of that new stage. The key thing here is that, the mouse events are still able to pass through the transparent stage to the underlying window, keep track of node which sits under the mouse.
scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
hoverNode.set(null);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
hoverNode.set(e.getTarget());
});
In the scene of the transparent window, we included the below handlers to delegate the scroll events to the underlying node.
scene.addEventFilter(ScrollEvent.SCROLL, e -> {
if (hoverNode.get() != null) {
Event.fireEvent(hoverNode.get(), e);
}
});
scene.addEventHandler(ScrollEvent.SCROLL, e -> {
if (hoverNode.get() != null) {
Event.fireEvent(hoverNode.get(), e);
}
});
I am pretty sure this is not the most desired way. But this addressed our issue. :)
Below is the quick demo code of what I mean.
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.stream.IntStream;
public class ScrollThroughTransparentStage_Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Main Window");
VBox root = new VBox(buildScrollPane());
root.setStyle("-fx-background-color:#888888;");
root.setSpacing(10);
root.setPadding(new Insets(10));
Button normalStageBtn = new Button("Normal Stage");
normalStageBtn.setOnAction(e -> {
Stage normalStage = new Stage();
normalStage.initOwner(stage);
Scene normalScene = new Scene(buildScrollPane(), 300, 300);
addHandlers(normalScene);
normalStage.setScene(normalScene);
normalStage.show();
});
CheckBox allowScrollThrough = new CheckBox("Allow scroll through transparency");
allowScrollThrough.setSelected(true);
HBox buttons = new HBox(normalStageBtn);
buttons.setSpacing(20);
root.getChildren().addAll(allowScrollThrough,buttons);
Scene scene = new Scene(root, 600, 600);
addHandlers(scene);
stage.setScene(scene);
stage.show();
/* Transparent Stage */
Stage transparentStage = new Stage();
transparentStage.initOwner(stage);
transparentStage.initStyle(StageStyle.TRANSPARENT);
Pane mainRoot = new Pane();
Pane transparentRoot = new Pane(mainRoot);
transparentRoot.setStyle("-fx-background-color:transparent;");
Scene transparentScene = new Scene(transparentRoot, Color.TRANSPARENT);
transparentStage.setScene(transparentScene);
transparentScene.addEventFilter(ScrollEvent.SCROLL, e -> {
if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
}
});
transparentScene.addEventHandler(ScrollEvent.SCROLL, e -> {
if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
}
});
determineStageSize(transparentStage, mainRoot);
transparentStage.show();
Button transparentStageBtn = new Button("Transparent Stage");
transparentStageBtn.setOnAction(e -> {
MiniStage miniStage = new MiniStage(mainRoot);
ScrollPane scrollPane = buildScrollPane();
scrollPane.setPrefSize(300, 300);
miniStage.setContent(scrollPane);
miniStage.show();
});
buttons.getChildren().add(transparentStageBtn);
}
private static void determineStageSize(Stage stage, Node root) {
DoubleProperty width = new SimpleDoubleProperty();
DoubleProperty height = new SimpleDoubleProperty();
DoubleProperty shift = new SimpleDoubleProperty();
Screen.getScreens().forEach(screen -> {
Rectangle2D bounds = screen.getVisualBounds();
width.set(width.get() + bounds.getWidth());
if (bounds.getHeight() > height.get()) {
height.set(bounds.getHeight());
}
if (bounds.getMinX() < shift.get()) {
shift.set(bounds.getMinX());
}
});
stage.setX(shift.get());
stage.setY(0);
stage.setWidth(width.get());
stage.setHeight(height.get());
root.setTranslateX(-1 * shift.get());
}
private void addHandlers(Scene scene) {
scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
HoverNodeSingleton.getInstance().setHoverNode(null);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
HoverNodeSingleton.getInstance().setHoverNode(e.getTarget());
});
}
private ScrollPane buildScrollPane() {
VBox vb = new VBox();
vb.setSpacing(10);
vb.setPadding(new Insets(15));
IntStream.rangeClosed(1, 100).forEach(i -> vb.getChildren().add(new Label(i + "")));
ScrollPane scrollPane = new ScrollPane(vb);
return scrollPane;
}
class MiniStage extends Group {
private Pane parent;
double sceneX, sceneY, layoutX, layoutY;
protected BorderPane windowPane;
private BorderPane windowTitleBar;
private Label labelTitle;
private Button buttonClose;
public MiniStage(Pane parent) {
this.parent = parent;
buildRootNode();
getChildren().add(windowPane);
addEventHandler(MouseEvent.MOUSE_PRESSED, e -> toFront());
}
#Override
public void toFront() {
parent.getChildren().remove(this);
parent.getChildren().add(this);
}
public void setContent(Node content) {
// Computing the bounds of the content before rendering
Group grp = new Group(content);
new Scene(grp);
grp.applyCss();
grp.requestLayout();
double width = grp.getLayoutBounds().getWidth();
double height = grp.getLayoutBounds().getHeight() + 30; // 30 title bar height
grp.getChildren().clear();
windowPane.setCenter(content);
// Centering the stage
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
setX(screenBounds.getWidth() / 2 - width / 2);
setY(screenBounds.getHeight() / 2 - height / 2);
}
public Node getContent() {
return windowPane.getCenter();
}
public void setX(double x) {
setLayoutX(x);
}
public void setY(double y) {
setLayoutY(y);
}
public void show() {
if (!parent.getChildren().contains(this)) {
parent.getChildren().add(this);
}
}
public void hide() {
parent.getChildren().remove(this);
}
private void buildRootNode() {
windowPane = new BorderPane();
windowPane.setStyle("-fx-border-width:2px;-fx-border-color:#444444;");
labelTitle = new Label("Mini Stage");
labelTitle.setStyle("-fx-font-weight:bold;");
labelTitle.setMaxHeight(Double.MAX_VALUE);
buttonClose = new Button("X");
buttonClose.setFocusTraversable(false);
buttonClose.setStyle("-fx-background-color:red;-fx-background-radius:0;-fx-background-insets:0;");
buttonClose.setOnMouseClicked(evt -> hide());
windowTitleBar = new BorderPane();
windowTitleBar.setStyle("-fx-border-width: 0 0 2px 0;-fx-border-color:#444444;-fx-background-color:#BBBBBB");
windowTitleBar.setLeft(labelTitle);
windowTitleBar.setRight(buttonClose);
windowTitleBar.setPadding(new Insets(0, 0, 0, 10));
windowTitleBar.getStyleClass().add("nonfocus-title-bar");
windowPane.setTop(windowTitleBar);
assignTitleBarEvents();
}
private void assignTitleBarEvents() {
windowTitleBar.setOnMousePressed(this::recordWindowLocation);
windowTitleBar.setOnMouseDragged(this::moveWindow);
windowTitleBar.setOnMouseReleased(this::resetMousePointer);
}
private final void recordWindowLocation(final MouseEvent event) {
sceneX = event.getSceneX();
sceneY = event.getSceneY();
layoutX = getLayoutX();
layoutY = getLayoutY();
getScene().setCursor(Cursor.MOVE);
}
private final void resetMousePointer(final MouseEvent event) {
// Updating the new layout positions
setLayoutX(layoutX + getTranslateX());
setLayoutY(layoutY + getTranslateY());
// Resetting the translate positions
setTranslateX(0);
setTranslateY(0);
getScene().setCursor(Cursor.DEFAULT);
}
private final void moveWindow(final MouseEvent event) {
double offsetX = event.getSceneX() - sceneX;
double offsetY = event.getSceneY() - sceneY;
setTranslateX(offsetX);
setTranslateY(offsetY);
event.consume();
}
}
}
/**
* Singleton class.
*/
class HoverNodeSingleton {
private static HoverNodeSingleton INSTANCE = new HoverNodeSingleton();
private EventTarget hoverNode;
private HoverNodeSingleton() {
}
public static HoverNodeSingleton getInstance() {
return INSTANCE;
}
public EventTarget getHoverNode() {
return hoverNode;
}
public void setHoverNode(EventTarget hoverNode) {
this.hoverNode = hoverNode;
}
}
I don't know that's right or not, but you can bind properties:
secondStage.getScene().onScrollProperty().bind(mainStage.getScene().onScrollProperty());
You can create a custom event dispatcher that will ignore events you don't want:
public class CustomEventDispatcher extends BasicEventDispatcher {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if(event instanceof ScrollEvent) {
return null;
} else {
return super.dispatchEvent(event, tail);
}
}
}
Then set that on your stage:
secondStage.setEventDispatcher(new CustomEventDispatcher());
I don't know how this works in the context of stages but for simple shapes it makes a difference whether you set the fill color to Color.TRANSPARENT or just null. Using any Color catches events, whereas null does not.
You can do so by ignoring the event on the second stage using event dispatcher using this answer by #Slaw you can understand everything about EventDispatcher
https://stackoverflow.com/a/51015783/5303683
Then you can fire your own event using this answer by DVarga
https://stackoverflow.com/a/40042513/5303683
Sorry I don't have time to try and make a full example of it

How to implement an image like a cue stick that rotates around a point as you drag your mouse with JavaFX

I'm trying to implement a cue stick into my pool game but I can't figure out how to get an image of a cuestick to rotate around the cueball.
I'm currently set it up so you can click and drag from the cue ball to draw a line.
private void startDrag(CueBall node, Pane root) {
currentLine = new Line();
currentLine.setUserData(node);
currentLine.setStartX(node.getxPosition());
currentLine.setStartY(node.getyPosition());
currentLine.endXProperty().bind(mouseX);
currentLine.endYProperty().bind(mouseY);
/**
* colors in a cue stick
*/
currentLine.setStrokeWidth(10);
String path = "cuestick.png";
Image img = new Image(path);
currentLine.setStroke(new ImagePattern(img));
currentLine.setStrokeLineCap(StrokeLineCap.ROUND);
root.getChildren().add(currentLine);
}
But this doesn't make the image rotate with the mouse.
Here is an example. Using Math.atan2. This uses a Line instead of an ImageView, but the idea is the same. This approach is slightly different from the duplicate. It uses Math.atan2 to find the angle between the center and a point on the scene.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class QueAndStick extends Application
{
#Override
public void start(Stage primaryStage)
{
Circle cue = new Circle(300 / 2.0, 250 / 2.0, 5);
cue.setFill(Color.WHITE);
cue.setStroke(Color.BLACK);
Line stick = new Line(cue.getCenterX() + (cue.getRadius() + 5), cue.getCenterY(), cue.getCenterX() + (cue.getRadius() + 5 + 75), cue.getCenterY());
stick.setStrokeWidth(3);
stick.setFill(Color.BROWN);
Rotate rotate = new Rotate(45);
rotate.pivotXProperty().bind(cue.centerXProperty());
rotate.pivotYProperty().bind(cue.centerYProperty());
stick.getTransforms().add(rotate);
Pane root = new Pane(cue, stick);
root.setStyle("-fx-background-color: green");
Scene scene = new Scene(root, 300, 250);
scene.setOnMouseMoved((event) -> {
double newX = event.getSceneX();
double newY = event.getSceneY();
System.out.println(Math.toDegrees(Math.atan2(newY - cue.getCenterY(), newX - cue.getCenterX())));
rotate.setAngle(Math.toDegrees(Math.atan2(newY - cue.getCenterY(), newX - cue.getCenterX())));
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

why onMouseClick doesnt work in javafx circle shape?

I want to click on one of three button on title of my internal window to change its color to black. but it some times works and sometimes does`nt work.
please look at my code an tell me whats wrong with it!?
I used javac 1.8u20 to compile and jre 1.9 to run...
if we use to or three layer of Pane inside of each other how the events handle? is there problem?
package core.windowManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Screen;
import static javafx.scene.paint.Color.rgb;
/**
* Created by yn on 22/08/17.
* just declare a win not more ...
*/
public class win {
/**
* where we add the win to it in scene
*/
private final AnchorPane root;
/**
* just the title and a hashCode from instance will be the win ID
*/
private final String winId;
/**
* win pane contains title and content pane
*/
private final AnchorPane winPane = new AnchorPane();
/**
* title pane to add title label and some three [close-min-max] buttons maybe...
*/
private final AnchorPane titlePane = new AnchorPane();
/**
* where the content goes there ...
*/
private final AnchorPane content = new AnchorPane();
/**
* win title pane height
*/
private final int winTitlePaneH = 30;
/**
* three close-min-max buttons
*/
private final Circle closeShape = new Circle();
private final Circle minShape = new Circle();
private final Circle maxShape = new Circle();
/**
* initialize the win class with some important params
*
* #param root //where the win add to scene
* #param title // and String ID make by title+hashCode and used in windowManager
* #param winW //win width
* #param winH // win height
*/
public win(AnchorPane root, String title, int winW, int winH) {
//init some final vars
this.root = root;
this.winId = title + "--" + this.hashCode();
// make the winPane
winPane.setEffect(new DropShadow(20, rgb(0, 0, 0, 0.9)));
winPane.setPrefSize(winW, winH);
winPane.setStyle("-fx-background-color: #abcdef");
// put winPane center of scene
double screenW = Screen.getPrimary().getVisualBounds().getWidth();
double screenH = Screen.getPrimary().getVisualBounds().getHeight();
double deltaW = (screenW - winW) / 2;
double deltaH = (screenH - winH) / 2;
winPane.setLayoutX(deltaW);
winPane.setLayoutY(deltaH);
// put it to top on click
winPane.setOnMouseClicked(e -> {
winPane.toFront();
});
//make title and top of window ...
titlePane.setPrefHeight(winTitlePaneH);
AnchorPane.setTopAnchor(titlePane, 0.0);
AnchorPane.setLeftAnchor(titlePane, 0.0);
AnchorPane.setRightAnchor(titlePane, 0.0);
// make winPane draggable by titlePane
makeDragable(titlePane);
makeResizable(50);
// add close and min buttons to title pane
closeShape.setRadius(winTitlePaneH / 3);
closeShape.setFill(Color.RED); //red-yellow-green
closeShape.setOnMouseClicked(e -> {
closeShape.setFill(Color.BLACK);
e.consume();
});
//add min button to title pane
minShape.setRadius(winTitlePaneH / 3);
minShape.setFill(Color.YELLOW); //red-yellow-green
minShape.setOnMouseClicked(e -> {
minShape.setFill(Color.BLACK);
});
// add max button to title pane
maxShape.setRadius(winTitlePaneH / 3);
maxShape.setFill(Color.GREEN); //red-yellow-green
maxShape.setOnMouseClicked(e -> {
maxShape.setFill(Color.BLACK);
});
HBox bb = new HBox();
//bb.setBackground(new Background(new BackgroundFill(Color.BLACK, null, Insets.EMPTY)));
AnchorPane.setLeftAnchor(bb, 0d);
AnchorPane.setTopAnchor(bb, winTitlePaneH / 4.5);
AnchorPane.setBottomAnchor(bb, 0d);
bb.getChildren().addAll(closeShape, minShape, maxShape);
titlePane.getChildren().addAll(bb);
// add a label to show title
Label titleL = new Label(title);
titleL.setTextFill(Color.BLACK);
titleL.setAlignment(Pos.BASELINE_CENTER);
AnchorPane.setTopAnchor(titleL, 5.0);
AnchorPane.setLeftAnchor(titleL, 0.0);
AnchorPane.setRightAnchor(titleL, 0.0);
titlePane.getChildren().add(titleL);
titlePane.setBackground(new Background(new BackgroundFill(Color.web("#E2E0E2"), CornerRadii.EMPTY, Insets.EMPTY)));
winPane.getChildren().add(titlePane);
root.getChildren().add(winPane);
}
/**
* titlePane to drag and win move behavior
*
* #param what
*/
public void makeDragable(Node what) {
final Delta dragDelta = new Delta();
what.setOnMousePressed(mouseEvent -> {
dragDelta.x = winPane.getLayoutX() - mouseEvent.getScreenX();
dragDelta.y = winPane.getLayoutY() - mouseEvent.getScreenY();
//also bring to front when moving
winPane.toFront();
});
what.setOnMouseDragged(mouseEvent -> {
winPane.setLayoutX(mouseEvent.getScreenX() + dragDelta.x);
winPane.setLayoutY(mouseEvent.getScreenY() + dragDelta.y);
});
}
//current state
private boolean RESIZE_BOTTOM;
private boolean RESIZE_RIGHT;
public void makeResizable(double mouseBorderWidth) {
winPane.setOnMouseMoved(mouseEvent -> {
//local window's coordiantes
double mouseX = mouseEvent.getX();
double mouseY = mouseEvent.getY();
//window size
double width = winPane.boundsInLocalProperty().get().getWidth();
double height = winPane.boundsInLocalProperty().get().getHeight();
//if we on the edge, change state and cursor
if (Math.abs(mouseX - width) < mouseBorderWidth
&& Math.abs(mouseY - height) < mouseBorderWidth) {
RESIZE_RIGHT = true;
RESIZE_BOTTOM = true;
winPane.setCursor(Cursor.NW_RESIZE);
} else {
RESIZE_BOTTOM = false;
RESIZE_RIGHT = false;
winPane.setCursor(Cursor.DEFAULT);
}
});
winPane.setOnMouseDragged(mouseEvent -> {
//resize root
Region region = (Region) winPane;
//resize logic depends on state
if (RESIZE_BOTTOM && RESIZE_RIGHT) {
region.setPrefSize(mouseEvent.getX(), mouseEvent.getY());
} else if (RESIZE_RIGHT) {
region.setPrefWidth(mouseEvent.getX());
} else if (RESIZE_BOTTOM) {
region.setPrefHeight(mouseEvent.getY());
}
});
}
//just for encapsulation
private static class Delta {
double x, y;
}
}
Mouse click works perfectly fine on Circle in JavaFX. The problem with your code is that the label you add for the title is on top of your circles and catches the mouse clicks. You can only click the circles on the very bottom which made you think that it works "sometimes". Check the image here:
So you can solve it by making the label mouse transparent with:
titleL.setMouseTransparent(true);
or adding first the label and then the HBox with the circles.
For this kind of problems ScenicView comes really handy tool.
I use a HBox to group and layout the three buttons [close min max] and then toFront() it. and they goes over title pane and now events work correctly...

too many different javafx stages

I have 2 sides of the MVC. on the model side, is where i have my main class for the entire battleship program. It instantiates the view/controller side of things, which consists of 3 different windows(classes that extend Application): a PreBoard, which gets both players names, and then one player board each (P1Board, P2Board). In all 3 of these separate classes, they extend Application, and all have a start(Stage primaryStage) method.
Since ive been reading about javaFX threading for the last 48 hours i am still barely understanding where the javaFX application thread starts. Does the javaFX Application thread start the very first time that Application.launch() is called, even if you have 3 seperate classes that extend Application and have their own start methods?
My original intentions were to have a window where players can enter their names, then a separate window with their own board, and i have failed MISERABLY because im getting more and more exceptions the longer the whole program runs.
So the question is, where the hell does the javaFX Application thread start?
Main class, on the model side
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package battleship.model;
import battleship.viewcon.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
/**
*
* #author foolishklown
*/
public class MainApp {
Player player1;
Player player2;
BattleshipGame theGame;
PreBoard theGamePreBoard;
ViewCon viewConnector;
public void go() {
theGamePreBoard = new PreBoard();
theGamePreBoard.setMainAppConnection(this);
viewConnector = theGamePreBoard.getVcon();
}
public void startBsGame(String[] names) {
theGame = new BattleshipGame(names[0], names[1]);
viewConnector.setGame(theGame);
}
public BattleshipGame getGame() {
return theGame;
}
public void setConnection(ViewCon vc) {
this.viewConnector = vc;
}
public static void main(String[] args) {
MainApp app = new MainApp();
app.go();
}
}
PreBoard code, which instantiates 2 other classes that extend Application and have their own start methods......
package battleship.viewcon;
import battleship.model.*;
import javafx.geometry.Insets;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
/**
* PreBoard class, used for getting user input for players names
* #author Chris Wilson
* #author Bob McHenry
* #author Mario Rodriguez De la Raza en la casa!
* #author Jessy Bernoudi
*/
public class PreBoard extends Application {
private boolean turn; // field to determine which players name to put into which board
private String player;
private Button hideBtn;
private Button showBtn;
private TextField userText;
private ViewCon controller;
private P1Board p1B;
private P2Board p2B;
private BattleshipGame game;
private String[] playerNames;
private MainApp mainApp;
/**
* Application class override method, where javaFX stage starts
* #param primaryStage
*/
#Override
public void start(Stage primaryStage) {
playerNames = new String[2];
turn = false;
p1B = new P1Board();
p2B = new P2Board();
controller = new ViewCon();
controller.setPreB(this);
controller.setp1(p1B);
controller.setp2(p2B);
controller.setMain();
primaryStage.setTitle("Battleship setup"); //Main stage (window container)
//Gridpane for using rows/columns for child node placement
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER_LEFT);
grid.setHgap(10);
grid.setVgap(5);
grid.setPadding(new Insets(100, 25, 25, 25));
// label in window
Text sceneTitle = new Text("Setup");
sceneTitle.setId("setup-text");
grid.add(sceneTitle, 0, 0, 2, 1);
// label and textfield
Label userName = new Label("Enter Player1 UserName:");
userName.setId("user-name");
grid.add(userName, 0, 1);
TextField userTextField = new TextField();
userTextField.setId("text-field");
grid.add(userTextField, 0, 2);
// button for setup, with actionListener to save player name or default if its left blank
Button setupBtn = new Button("Setup Board");
HBox hbBtn = new HBox(10);
hbBtn.setAlignment(Pos.BOTTOM_LEFT);
hbBtn.getChildren().add(setupBtn);
grid.add(hbBtn, 0, 3);
setupBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
// determine which player name to use to pass into which player board
if(turn == false) {
String temp1 = userTextField.getText();
if(temp1.equals("")) {
player = "Player1";
} else {
player = temp1;
}
playerNames[0] = player;
controller.setPlayer1(player);
turn = true;
p1B.start(new Stage());
grid.getChildren().remove(userTextField);
userText = new TextField();
userText.setId("text-field");
grid.add(userText, 0, 2);
userName.setText("Enter Player2 username:");
} else {
String temp2 = userText.getText();
if(temp2.equals("")) {
player = "Player2";
} else {
player = temp2;
}
playerNames[1] = player;
controller.startGame(playerNames);
controller.setPlayer2(player);
p2B.start(new Stage());
p1B.primeShow();
}
}
});
hideBtn = new Button();
hideBtn.setId("hideBtn");
hideBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
primaryStage.hide();
}
});
showBtn = new Button();
showBtn.setId("showBtn");
showBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
primaryStage.show();
}
});
controller.setPreShowBtn(showBtn);
controller.setPreHideBtn(hideBtn);
// Add the entire scene into the main window(stage) after setting the scene dimensions
Scene scene = new Scene(grid, 580, 200);
primaryStage.setScene(scene);
// Attach css stylesheet
scene.getStylesheets().add(PreBoard.class.getResource("styles/PreBoardStyle.css").toExternalForm());
// Show this window(stage) upon instantiation
primaryStage.show();
}
/**
*
* #param v
*/
public void setLink(ViewCon v) {
this.controller = v;
}
/**
*
* #param b
*/
public void setBattleshipGame(BattleshipGame b) {
this.game = b;
}
/**
*
* #param main
*/
public void setMainAppConnection(MainApp main) {
this.mainApp = main;
}
/**
*
* #return
*/
public MainApp getMainConnection() {
return mainApp;
}
/**
*
* #return
*/
public ViewCon getVcon() {
return controller;
}
public void exitPre() {
Platform.exit();
}
public static void main(String[] args) {
Application.launch(args);
}
}

Categories