Issues with JavaFX Translate - javafx

I have this sample:
package bit.fxtest2;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TransformTest2 extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TransformTest2");
var bp = new BorderPane();
bp.setCenter(new DragPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private static class DragPane extends Pane {
ObjectProperty<Transform> xform = new SimpleObjectProperty<>(new Translate(0, 0));
public DragPane() {
ObjectProperty<Point2D> mouseDown = new SimpleObjectProperty<>();
setOnMousePressed(e -> {
var mousePress = new Point2D(e.getX(), e.getY());
mouseDown.set(mousePress);
});
setOnMouseDragged(e -> {
var dragPoint = new Point2D(e.getX(), e.getY());
var delta = dragPoint.subtract(mouseDown.get());
var t = new Translate(delta.getX(), delta.getY());
xform.set(xform.get().createConcatenation(t));
mouseDown.set(dragPoint);
System.out.println("mp = " + mouseDown);
updateTransform();
});
populate();
updateTransform();
}
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
children.add(new Line(0, 0, 200, 0));
children.add(new Line(200, 0, 200, 200));
children.add(new Line(200, 200, 0, 200));
children.add(new Line(0, 200, 0, 0));
}
private void updateTransform() {
ObservableList<Transform> transforms = getTransforms();
transforms.clear();
transforms.add(xform.get());
}
}
}
If you run the code, two things happen.
First, as you start dragging, the box drags, but it starts getting very jerky, and bounces back and forth. If you print out the mouse motions they move back and forth.
Second, after you've dragged the box, say, down and to the right, you'll notice that you can no longer drag it in the upper left area of the window.
This is because the Translate is affecting the Pane itself, not necessarily the contents of the Pane. Since the OnMouse handlers are on the Pane itself, and the Pane is no longer in the upper left area, no handlers are called.
So, two questions.
First, why the jerky behavior?
Second, how can I apply Transforms (not just translate) to the children of a pane, and not the pane itself?

The answer to the first question (the jerkiness) is that it's because your calculations for the transform are incorrect.
When the dragging is processed, the pane is translated by the amount that was dragged. This leaves the coordinates of the mouse relative to the pane as being the same as they were when the mouse was first pressed.
For example, suppose you click on the pane at (100,100), so mouseDown contains the value (100,100). You then drag it, so suppose when the drag event is processed the mouse has moved to (102,101) in the pane's coordinate system. Then delta will be (2,1), so the pane will be translated by (an additional) (2,1), after which the mouse will again be over the point (100,100) in the pane's coordinate system.
Therefore, the correct thing to do here is not to change the value of mouseDown.
Simply removing the line
mouseDown.set(dragPoint);
fixes that issue.
For the second issue: As long as the user starts the drag inside the actual pane, then it all works fine; this seems to be the natural thing to do.
But if you really want to be able to drag from anywhere in the window, you can place the nodes to be dragged in a Group and apply the translation to the group. Note that this time, because the Pane is not moving, you do need to update the mouseDown value:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TransformTest2 extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TransformTest2");
var bp = new BorderPane();
bp.setCenter(new DragPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private static class DragPane extends Pane {
ObjectProperty<Transform> xform = new SimpleObjectProperty<>(new Translate(0, 0));
Group group ;
public DragPane() {
ObjectProperty<Point2D> mouseDown = new SimpleObjectProperty<>();
group = new Group();
getChildren().add(group);
setOnMousePressed(e -> {
var mousePress = new Point2D(e.getX(), e.getY());
mouseDown.set(mousePress);
});
setOnMouseDragged(e -> {
var dragPoint = new Point2D(e.getX(), e.getY());
var delta = dragPoint.subtract(mouseDown.get());
var t = new Translate(delta.getX(), delta.getY());
xform.set(xform.get().createConcatenation(t));
mouseDown.set(dragPoint);
System.out.println("mp = " + mouseDown);
updateTransform();
});
populate();
updateTransform();
}
private void populate() {
ObservableList<Node> children = group.getChildren();
children.clear();
children.add(new Line(0, 0, 200, 0));
children.add(new Line(200, 0, 200, 200));
children.add(new Line(200, 200, 0, 200));
children.add(new Line(0, 200, 0, 0));
}
private void updateTransform() {
ObservableList<Transform> transforms = group.getTransforms();
transforms.clear();
transforms.add(xform.get());
}
}
}
If you don't want the additional node, you can achieve the same effect by handling the mouse events on the scene, and update the transforms for the pane.

Related

Updating the Width of TextField and VBox when Full screened JavaFX

whenever I try to full screen my application, it doesn't scale. I've made multiple copies of this application trying different methods but none seem to work right.
First attempt: Application was a Parent, it would scale the background but the elements inside wouldn't scale to screen size.
As an update: here is the actual Parent that was made. The layout is the original one I wrote and has no issues when it's windowed. It has a preset WIDTH and HEIGHT but when full screened, The first example picture is what it looks like where the WIDTH of the the TextField doesn't update (since it's preset and not updating to the highest WIDTH of the screen it's running on). There are two parts to this that CAN be fixed when only one is fixed. The displayed Text has a set wrapping length of the console, though it is set by using WIDTH.
Here's what the console looks like when it's windowed:
If I could find a way to change the WIDTH, I'm thinking this can be fixed for both the TextField and the setWrappingWidth().
package application.console;
import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class Ce extends Region {
public static boolean fullscreen = false;
public static double WIDTH = 990;
// 990;
// Screen.getPrimary().getBounds().getMaxX();
public static double HEIGHT = 525;
// 525;
// Screen.getPrimary().getBounds().getMaxY();
public static Font Cinzel = (Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16));
public static VBox console = new VBox(2);
public static TextField input = new TextField();
public static ScrollPane scroll = new ScrollPane();
public static BorderPane root = new BorderPane();
public static String s;
public static Parent Window() {
root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
root.setStyle("-fx-background-color: #232323;");
scroll.setContent(console);
root.setCenter(scroll);
scroll.setStyle("-fx-background: #232323;"
+ "-fx-background-color: transparent;"
+ "-fx-border-color: #232323;"
+ "-fx-focus-color: #232323;"
);
scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
console.setStyle("-fx-background-color: #232323;"
+ "-fx-focus-color: #232323;");
console.heightProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
scroll.setVvalue((Double)newValue);
}
});
HBox hbox = new HBox();
hbox.setPrefSize(WIDTH, 16);
root.setBottom(hbox);
Text carrot = new Text(" >");
carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 26));
carrot.setFill(Color.WHITE);
input.setStyle("-fx-background-color: transparent;"
+ "-fx-text-fill: #FFFFFF;"
+ "-fx-highlight-fill: #FFFFFF;"
+ "-fx-highlight-text-fill: #232323;"
// + "-fx-border-color: #FFFFFF;"
// + "-fx-border-width: .5;"
);
input.setFont(Cinzel);
input.setMinWidth(console.getWidth());
input.setOnAction(e -> {
String s = (input.getText()).stripTrailing();
input.clear();
});
Pane pane = new Pane();
root.getChildren().add(pane);
hbox.getChildren().addAll(carrot, input);
return root;
}
This isn't the main issue as I've stated, once getting the scaled width for the TextField the process of for setWrappingWidth() for displaying the text should be the if a solution is found, here's how it goes:
#SuppressWarnings("static-access")
public void print(String s, Color c) {
Ce Ce = new Ce();
HBox text1 = new HBox();
text1.setMinWidth(Ce.WIDTH);
text1.setMaxWidth(Ce.WIDTH);
Text tCarrot = new Text(" > ");
tCarrot.setFont(Ce.Cinzel);
tCarrot.setFill(c);
Text text2 = new Text();
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline tl = new Timeline();
KeyFrame kf = new KeyFrame(
Duration.seconds(textSpeed(fastText)),
e1 -> {
if(i.get() > s.length()) {
tl.stop();
} else {
text2.setText(s.substring(0, i.get()));
i.set(i.get() + 1);
}
});
tl.getKeyFrames().add(kf);
tl.setCycleCount(Animation.INDEFINITE);
tl.play();
text2.setFill(c);
text2.setFont(Ce.Cinzel);
text2.setWrappingWidth(Ce.WIDTH - 40);
text1.getChildren().addAll(tCarrot, text2);
Ce.console.getChildren().add(text1);
Ce.console.setMargin(text1, new Insets(5, 0, 0, 3));
}
Lastly, the HEIGHT of the VBox for the displayed Text works just as intended, it's just the setting/updating the WIDTH to set it to the size of the window whether Windowed of Full screened that is the main issue here.
Try this app. It will not be exactly what you want but may provide some useful help for you if you study it, if not just ignore it, tears can keep you blind, and sometimes, that is ok.
The implementation follows the suggestions you have received in the comments on your questions which together explain what is being done and why, so I won't provide much commentary on the solution here.
Type text in the input bar, press enter and it will appear in the listview for the console log. Use the Toggle full-screen button to toggle full-screen mode on or off.
Console.java
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Console extends VBox {
private final ObservableList<String> consoleLog = FXCollections.observableArrayList();
private final ListView<String> logView = new ListView<>(consoleLog);
public Console(Stage stage) {
VBox.setVgrow(logView, Priority.ALWAYS);
HBox ribbon = createRibbon(
createFullScreenToggle(stage)
);
ribbon.setMinHeight(HBox.USE_PREF_SIZE);
getChildren().addAll(
ribbon,
logView
);
}
private ToggleButton createFullScreenToggle(Stage stage) {
ToggleButton fullScreenToggle = new ToggleButton("Toggle full screen");
fullScreenToggle.setOnAction(e ->
stage.setFullScreen(
fullScreenToggle.isSelected()
)
);
return fullScreenToggle;
}
private HBox createRibbon(ToggleButton fullscreenToggle) {
Text prompt = new Text(">");
TextField input = new TextField();
input.setOnAction(e -> {
consoleLog.add(0, input.getText());
logView.scrollTo(0);
input.clear();
});
HBox.setHgrow(input, Priority.ALWAYS);
HBox ribbon = new HBox(10,
prompt,
input,
fullscreenToggle
);
ribbon.setAlignment(Pos.BASELINE_LEFT);
return ribbon;
}
public ObservableList<String> getConsoleLog() {
return consoleLog;
}
}
ConsoleApplication.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ConsoleApplication extends Application {
#Override
public void start(Stage stage) {
Console console = new Console(stage);
console.getConsoleLog().addAll(
TEXT.lines().toList()
);
stage.setScene(
new Scene(
console
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private static final String TEXT = """
W. Shakespeare - Sonnet 148
O me, what eyes hath Love put in my head,
Which have no correspondence with true sight!
Or, if the have, where is my judgement fled,
That censures falsely what they see aright?
If that be fair whereon my false eyes dote,
What means the world to say it is not so?
If it be not, then love doth well denote
Love’s eye is not so true as all men’s ‘No.’
How can it? O, how can Love’s eye be true,
That is so vex’d with watching and with tears?
No marvel then, though I mistake my view;
The sun itself sees not till heaven clears.
O cunning Love! with tears thou keep’st me blind.
Lest eyes well-seeing thy foul faults should find.
""";
}
If you want to increase the nodes height/width according to the viewport, then this's not the best practice, because every user will have the same font size at the end. What you can do is to make the font resizable by either GUI buttons or keyboard/mouse keys.
Here is a modification on your code, that will allow users to use ctrl + mouse wheel to increase/decrease the font (like any browser or terminal):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ConsoleTest extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new GameWindow().Console(), 600, 600);
stage.setTitle("Console");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class GameWindow {
public static Console c = new Console();
public Parent Console() {
for (int i = 0; i < 100; i++) c.addText(new Text("Test" + i));
return c;
}
}
class Console extends BorderPane {
private final SimpleDoubleProperty fontSize = new SimpleDoubleProperty(20);
private final ObjectBinding<Font> fontBinding = Bindings.createObjectBinding(() -> Font.font(fontSize.get()), fontSize);
private final VBox console;
public Console() {
console = new VBox();
console.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
ScrollPane scroll = new ScrollPane(console);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
scroll.setPadding(Insets.EMPTY);
Text caret = new Text(" >");
caret.fontProperty().bind(fontBinding);
caret.setFill(Color.WHITE);
TextField input = new TextField();
input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;");
input.fontProperty().bind(fontBinding);
HBox inputBar = new HBox(2, caret, input);
inputBar.setStyle("-fx-background-color: #232323;");
inputBar.setAlignment(Pos.CENTER_LEFT);
setCenter(scroll);
setBottom(inputBar);
EventHandler<ScrollEvent> scrollEvent = e -> {
if (e.isControlDown()) {
if (e.getDeltaY() > 0) {
fontSize.set(fontSize.doubleValue() + 2);
} else {
double old;
fontSize.set((old = fontSize.doubleValue()) < 10 ? old : old - 2);
}
e.consume();
}
};
inputBar.setOnScroll(scrollEvent);
console.setOnScroll(scrollEvent);
}
public void addText(Text text) {
text.fontProperty().bind(fontBinding);
text.setFill(Color.WHITE);
console.getChildren().add(text);
}
}

JavaFX bounds issue?

So, using JAVAfx, which I painfully downloaded, I need to be able to move this ball, but not let it leave the bounds. Right now, I have it all set up, it just leaves the area. What am i doing wrong? Any tutors don't know anything about JavaFX, so I'm having trouble getting past this. Right now I tried to work it out, but keep getting this error Cannot Make a Static Reference to Bound
'''
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.geometry.Bounds;
public class MoveBall extends Application {
// create buttons
Button left = new Button();
Button right = new Button();
Button up = new Button();
Button down = new Button();
Circle ball = new Circle(30, 30, 30);
// action event class
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
// handle them
#Override
public void handle(ActionEvent event) {
// movement of ball
if (event.getSource().equals(left) && ball.getLayoutX() >= (Bounds.getMaxX() + ball.getRadius())) {
ball.setCenterX(ball.getCenterX() - 5);
}
if (event.getSource().equals(right)) {
ball.setCenterX(ball.getCenterX() + 5);
}
if (event.getSource().equals(up)) {
ball.setCenterY(ball.getCenterY() - 5);
}
if (event.getSource().equals(down) && ball.getCenterY() < 400)) {
ball.setCenterY(ball.getCenterY() + 5);
}
}
};
// main method
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Ball Movement");
ball.setStroke(Color.BLACK);
ball.setFill(null);
// button location
left.setLayoutX(100);
left.setLayoutY(210);
left.setText("Left");
// button event
left.setOnAction(event);
// similar for all button
right.setLayoutX(150);
right.setLayoutY(210);
right.setText("Right");
right.setOnAction(event);
// button location
up.setLayoutX(200);
up.setLayoutY(210);
up.setText("Up");
// button event
up.setOnAction(event);
// similar for all button
down.setLayoutX(250);
down.setLayoutY(210);
down.setText("Down");
down.setOnAction(event);
Group root = new Group();
// add to a group
root.getChildren().add(left);
root.getChildren().add(right);
root.getChildren().add(up);
root.getChildren().add(down);
root.getChildren().add(ball);
primaryStage.setScene(new Scene(root, 400, 250, Color.WHITE));
// show the scene
primaryStage.show();
}
}
'''

How do I add a Tooltip to a rectangular region of a JavaFX Canvas

In my JavaFX app I have a TableView with multiple columns, one of which displays data in a graphical form. To do this I have created a CanvasCell object that creates and manages its own Canvas to deal with the drawing. The drawing part works just fine.
I'd now like to put Tooltips over some regions within the Canvas/Cell. There may be multiple Tooltips per Cell (which prevents me from adding the Tooltip at the Cell level) and they should only trigger in specific regions of the graph. However, I'm not managing to get it functioning at all. I don't seem to understand the interactions of Display Node hierarchy well enough (read "at all") to be able to place the Tooltip anywhere where it will actually work.
Documentation for JavaFX is sparse and Google + SO has come up blank for all searches that I've tried. Is there anyone who knows how to do this sort of thing or should I just write it off as "not an option" for now.
For info, the CanvasCell calls a draw() function inside an extended Canvas object on updateItem(). The code in which I've tried to create a Tooltip sits inside that draw() function and looks like:
Rectangle rect = new Rectangle(leftVal, topVal, width, height);
gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
Tooltip tooltip = new Tooltip("Tooltip Text");
Tooltip.install(rect, tooltip);
but that code was written more in hope than anything else and doesn't generate anything useful in the interface.
If someone can point me in the right direction, I will be very grateful.
If you don't need the timing control illustrated here, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains to condition the text as shown below.
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
As suggested here, Java 9 and later provide control over Tooltip timing via the properties showDelay and showDuration.
A similar approach is illustrated here for Swing.
import javafx.application.Application;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
/**
* #see https://stackoverflow.com/a/53785468/230513
* #see https://stackoverflow.com/a/53753537/230513
*/
public class CanvasTooltipDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 400, 400);
stage.setScene(sc);
Canvas canvas = new Canvas(200, 200);
root.getChildren().add(canvas);
Map<Color, Rectangle> tooltips = new HashMap<>();
tooltips.put(Color.RED, new Rectangle(0, 0, 100, 100));
tooltips.put(Color.BLUE, new Rectangle(100, 0, 100, 100));
tooltips.put(Color.YELLOW, new Rectangle(0, 100, 100, 100));
tooltips.put(Color.GREEN, new Rectangle(100, 100, 100, 100));
GraphicsContext gc = canvas.getGraphicsContext2D();
tooltips.forEach((color, bounds) -> {
gc.setFill(color);
gc.fillRect(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
});
setToolTips(canvas, tooltips);
stage.show();
}
private void setToolTips(Node node, Map<Color, Rectangle> tooltips) {
Tooltip tooltip = new Tooltip();
Tooltip.install(node, tooltip);
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
node.setOnMouseExited(e -> {
tooltip.hide();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
I have the same solution as per #Slaw suggested. My idea is to make it more centralized so that you can pass your node and its regions you want to show the tooltips.
In the below demo, you can use the setToolTips() as static utitlity method for multiple nodes.
Note: some part of the logic is referred from Tooltip core implementation ;)
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.Map;
public class MultiTooltipDemo extends Application {
private double lastMouseX;
private double lastMouseY;
private static int TOOLTIP_XOFFSET = 10;
private static int TOOLTIP_YOFFSET = 7;
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
StackPane box1 = new StackPane();
box1.setMaxSize(200, 200);
box1.setStyle("-fx-background-color:red, blue, yellow, green; -fx-background-insets: 0 100 100 0, 0 0 100 100, 100 100 0 0, 100 0 0 100;");
root.getChildren().add(box1);
Map<String, Rectangle2D> tooltips = new HashMap<>();
tooltips.put("I am red", new Rectangle2D(0, 0, 100, 100));
tooltips.put("I am blue", new Rectangle2D(100, 0, 100, 100));
tooltips.put("I am yellow", new Rectangle2D(0, 100, 100, 100));
tooltips.put("I am green", new Rectangle2D(100, 100, 100, 100));
setToolTips(box1, tooltips);
}
private void setToolTips(Node node, Map<String, Rectangle2D> tooltips) {
Duration openDelay = Duration.millis(1000);
Duration hideDelay = Duration.millis(5000);
Tooltip toolTip = new Tooltip();
Timeline hideTimer = new Timeline();
hideTimer.getKeyFrames().add(new KeyFrame(hideDelay));
hideTimer.setOnFinished(event -> {
toolTip.hide();
});
Timeline activationTimer = new Timeline();
activationTimer.getKeyFrames().add(new KeyFrame(openDelay));
activationTimer.setOnFinished(event -> {
Bounds nodeScreenBounds = node.localToScreen(node.getBoundsInLocal());
double nMx = nodeScreenBounds.getMinX();
double nMy = nodeScreenBounds.getMinY();
toolTip.setText("");
tooltips.forEach((str, bounds) -> {
double mnX = nMx + bounds.getMinX();
double mnY = nMy + bounds.getMinY();
double mxX = mnX + bounds.getWidth();
double mxY = mnY + bounds.getHeight();
if (lastMouseX >= mnX && lastMouseX <= mxX && lastMouseY >= mnY && lastMouseY <= mxY) {
toolTip.setText(str);
}
});
if (!toolTip.getText().isEmpty()) {
toolTip.show(node.getScene().getWindow(), lastMouseX + TOOLTIP_XOFFSET, lastMouseY + TOOLTIP_YOFFSET);
hideTimer.playFromStart();
}
});
node.setOnMouseMoved(e -> {
double buffPx = 2;
double eX = e.getScreenX();
double eY = e.getScreenY();
// Not hiding for slight mouse movements while tooltip is showing
if (hideTimer.getStatus() == Animation.Status.RUNNING) {
if (lastMouseX - buffPx <= eX && lastMouseX + buffPx >= eX && lastMouseY - buffPx <= eY && lastMouseY + buffPx >= eY) {
return;
}
}
lastMouseX = e.getScreenX();
lastMouseY = e.getScreenY();
toolTip.hide();
hideTimer.stop();
activationTimer.playFromStart();
});
node.setOnMouseExited(e -> {
toolTip.hide();
activationTimer.stop();
hideTimer.stop();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}

How can I stop a MouseEvent in JavaFX?

(Sorry for my poor English)
I don't know how I can stop a Mouse Event in JavaFX.
This code generates a small image into a large rectangle when I press a button and then pressed the large rectangle, but if I press again the big rectangle is rebuilt a new image.
I dont want to generate a new image, how Can I do that?
button.setOnAction((ActionEvent t) -> {
rectangle.setOnMouseClicked((MouseEvent me) -> {
Rectangle asdf = new Rectangle(48, 48, Color.TRANSPARENT);
StackPane imageContainer = new StackPane();
ImageView image = new ImageView("firefox-icono-8422-48.png");
imageContainer.getChildren().addAll(asdf, image);
imageContainer.setTranslateX(me.getX());
imageContainer.setTranslateY(me.getY());
enableDragging(imageContainer);
rootGroup.getChildren().add(imageContainer);
myList2.add(imageContainer);
});
});
Thanks
PS: t.consume() and me.consume(); don't anything.
I'm not sure I have interpreted your question correctly, but if you want to "turn off" the mouse click handler on the rectangle, you can just call
rectangle.setOnMouseClicked(null);
Complete example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ActivateRectangleWithButton extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle border = new Rectangle(100, 100, Color.TRANSPARENT);
Rectangle rect = new Rectangle(80, 80, Color.CORNFLOWERBLUE);
StackPane stack = new StackPane(border, rect);
Button button = new Button("Activate");
button.setOnAction(evt -> {
border.setFill(Color.BLUE);
rect.setOnMouseClicked(me -> {
System.out.println("Active rectangle was clicked!");
// de-activate:
border.setFill(Color.TRANSPARENT);
rect.setOnMouseClicked(null);
});
});
VBox root = new VBox(20, stack, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX 8 Changing the opacity of the stage does not work with StageStyle.TRANSPARENT (bug or my fault?)

I'm trying to build a FadeIn/Out animation on a window (stage). If the mouse moves into the stage it should fade in and if the mouse leaves it should fade out.
I created a Timeline that modifies the stage.opacityProperty() to achieve this. I ran into problems when I set the stage style transparent like this stage.initStyle(StageStyle.TRANSPARENT);. If I do so, the fading will not be visible. The Timeline plays the animation, but the opacity change will not be rendered by JavaFX. When setting the stageStyle to default, everything works fine and the window plus its decoration will fade in and out.
I want this effect to work in TRANSPARENT stage style so i tried the following:
I put a label onto the scene and change its textproperty in another Timeline. I now update the label text every 400msecs. If i do so, the opacity change will be rendered on every label-change.
This brings me to the conclusion, that modifying the opacity in TRANSPARENT stage style, will not result in a repaint of the stage.
Modifying the label text will result in repaint. Does this mean, that i cannot fade a stage in TRANSPARENT stage style, if the content does not change?
Is this a bug or am I doing something wrong?
I've made an SSCCE that reproduces the problem. If you remove the line stage.initStyle(StageStyle.TRANSPARENT); the fadeIn/out animation will run smoothly.
package de.schuette.jfx.stage_opacity_bug;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class FadeApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
private Label label;
#Override
public void start(Stage stage) {
if (stage == null)
throw new IllegalArgumentException("No stage was set.");
this.label = new Label("HALLO WELT");
Scene scene = new Scene(label, 300, 300);
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
stage.close();
}
});
stage.setScene(scene);
stage.setOpacity(1);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setTitle("Opacity change does result in repaint when stage style is transparent.");
stage.setAlwaysOnTop(true);
stage.show();
Platform.runLater(() -> {
Timeline t = new Timeline(new KeyFrame(Duration.millis(0),
new KeyValue(stage.opacityProperty(), 1)), new KeyFrame(
Duration.millis(500), new KeyValue(stage.opacityProperty(),
0)));
t.setAutoReverse(true);
t.setCycleCount(Timeline.INDEFINITE);
t.playFromStart();
});
Platform.runLater(() -> {
Timeline t = new Timeline(new KeyFrame(Duration.millis(400), e -> {
label.textProperty().set(String.valueOf(Math.random()));
}));
t.setCycleCount(Timeline.INDEFINITE);
t.playFromStart();
});
}
}
I'm currtently working with
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Windows 7 x64 Professional
With the help of the JavaFX developer team I was able to find a workaround for this problem. Using a custom linear interpolator that changes the scene's fill property and immediately change it back to its original value will cause a repaint on the stage. This is done by the "bugFixInterpolator" in the code below:
package de.schuette.jfx.stage_opacity_bug;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class FadeApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
private Label label;
/*
* (non-Javadoc)
*
* #see javafx.application.Application#start(javafx.stage.Stage)
*/
#Override
public void start(Stage stage) {
if (stage == null)
throw new IllegalArgumentException("No stage was set.");
this.label = new Label("HELLO WORLD");
Scene scene = new Scene(label, 300, 300);
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
stage.close();
}
});
stage.setScene(scene);
stage.setOpacity(1);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setTitle("Opacity change does result in repaint when stage style is transparent.");
stage.setAlwaysOnTop(true);
stage.show();
Interpolator bugFixInterpolator = new Interpolator() {
#Override
protected double curve(double t) {
Paint fill = scene.getFill();
scene.setFill(Color.RED);
scene.setFill(fill);
return t;
}
#Override
public String toString() {
return "Interpolator.LINEAR";
}
};
Timeline t = new Timeline(new KeyFrame(Duration.millis(0),
new KeyValue(stage.opacityProperty(), 1, bugFixInterpolator)),
new KeyFrame(Duration.millis(500), new KeyValue(stage
.opacityProperty(), 0, bugFixInterpolator)));
t.setAutoReverse(true);
t.setCycleCount(Timeline.INDEFINITE);
t.playFromStart();
t = new Timeline(new KeyFrame(Duration.millis(400), e -> {
label.textProperty().set(String.valueOf(Math.random()));
}));
t.setCycleCount(Timeline.INDEFINITE);
t.playFromStart();
}
}

Resources