JavaFX - "Pointless" (CSS) Shadow Effect, Drastically decrices Graphics Performance - javafx

Hello, People [...]
🤔 Summary
Whenever i use Shadow-effect on my BorderPane or any Component/control/Element, the 3D Graphics performance (as seen, in the Preview section below) is getting way too low.
The "confusing" part is that, it even gets low performance when the effect is applied to something that really has nothing to do with my Tab, Subscene or even my moving Button, in a way [...]
I Use jdk-12.0.1.
👁️ Preview
⚠️ Recreating The Issue
Files Needed:
App.java | main.fxml | AnchorPane.css | MathUtils.java | SimpleFPSCamera.java
📝 General Code
(You can refer to Recreating The Issue Section for more Informations too)
AnchorPane.css
#BorderPane1 {
-fx-effect: dropshadow(three-pass-box, rgb(26, 26, 26), 50, 0.6, 0, 0); /* Comment it*/
}
App.java
public class App extends Application {
#FXML
public Parent root;
public TabPane TabPane1;
public BorderPane BorderPane1;
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
loader.setController(this);
root = loader.load();
Scene RootScene = new Scene(root, 1120, 540);
primaryStage.setScene(RootScene);
Thread t = new Thread() {
public void run() {
//Setting NewButton2
Button NewButton2 = new Button();
NewButton2.setId("Button2");
NewButton2.setText("test2");
NewButton2.setPrefWidth(150);
NewButton2.setPrefHeight(50);
NewButton2.setTranslateX(-75);
NewButton2.setTranslateY(-25);
NewButton2.setTranslateZ(900);
// Setting group
Group SubRootGroup = new Group(NewButton2);
SubRootGroup.setTranslateX(0);
SubRootGroup.setTranslateY(0);
SubRootGroup.setTranslateZ(0);
// Setting Scene
SubScene SubScene1 = new SubScene(SubRootGroup, 0, 0, true, SceneAntialiasing.BALANCED);
SubScene1.setId("SubScene1");
SubScene1.setFill(Color.WHITE);
SubScene1.heightProperty().bind(RootScene.heightProperty());
SubScene1.widthProperty().bind(RootScene.widthProperty());
// Initializing Camera
SimpleFPSCamera SimpleFPSCam = new SimpleFPSCamera();
// Setting Camera To The Scene
SubScene1.setCamera(SimpleFPSCam.getCamera());
// Adding Scene To Stage-TabPane.Tab(0)
TabPane1.getTabs().add(new Tab("Without Shadows"));
TabPane1.getTabs().get(0).setContent(SubScene1);
// Loading Mouse & Keyboard Events
SimpleFPSCam.loadControlsForSubScene(SubScene1);
}
};
t.setDaemon(true);
t.run();
primaryStage.show();
}
}
Things I 've Tried Until Now
javafx animation poor performance consumes all my cpu
setCache(true);
setCacheShape(true);
setCacheHint(CacheHint.SPEED);
(i have tried using it with all components without having any success [it might be my poor javaFX knowledge too , [using it in the wrong way?] ])
...
💛 Outro
Any Idea? Thanks In Advance, Any help will be highly appreciated, 💛 [...]
George.

Most probably, you will have figured this out by now, but since I was also banging my head about this same issue in the past, here is your answer:
The drop shadow effect is "expensive" and drawing it is slow. If you use it on a node with many descendants and move any of the descendants, it will cause the effect to be re-calculated on the parent, so the whole animation becomes slow (regardless if the parent itself is animated or not).
I solved this by using a StackPane as the top-most container, to which I added a Pane as a first child (which has the css drop-shadow effect) and the normal top-level container for the actual controls as a second child.
This way, the "shadow" pane is not updated when something is animated down the layout tree and, voila, you have a working drop-shadow effect without a performance hit :-)

Related

JavafX & CSS - Move Node to Front

As the title basically. I have a node in JavaFX which I want to be displayed in front of all other nodes according to certain CSS rules. I do not want this to change the ordering of the nodes in my VBox which .toFront() appears to do. See this question.
Is this even possible?
EDIT: To clarify. The situation is the following. I have a VBox containing a bunch of tightly packed ImageViews. When I hover over one I want it to grow slightly to give it the feel that the image is being lifted off of the screen. But since the ImageViews are so tightly packed only the top edge grows (visibly). The bottom edge grows but is below the following image and cannot be seen.
EDIT 2: Upon request here is a screenshot of what I am doing.
The different colour gradients are ImageViews and as I hover over one it should grow as the top edge of the top gradient has in this image (look closely at the top right corner next to the X). However as is also visible in this image the bottom edge of this ImageView has become hidden by the next gradient in this VBox and the grow is not visible.
This sounds like the perfect situation for using the viewOrder property of Node added in Java 9. The viewOrder controls how Nodes are drawn in relation to other Nodes of the same Parent without changing the order of the Nodes in the child list. Here's the Javadoc:
Defines the rendering and picking order of this Node within its parent.
This property is used to alter the rendering and picking order of a
node within its parent without reordering the parent's children list.
For example, this can be used as a more efficient way to implement
transparency sorting. To do this, an application can assign the
viewOrder value of each node to the computed distance between that
node and the viewer.
The parent will traverse its children in decreasing viewOrder order.
This means that a child with a lower viewOrder will be in front of a
child with a higher viewOrder. If two children have the same
viewOrder, the parent will traverse them in the order they appear in
the parent's children list.
However, viewOrder does not alter the layout and focus traversal order
of this Node within its parent. A parent always traverses its children
list in order when doing layout or focus traversal.
Here's an example using this property:
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var box = new HBox(createRectangles(Color.DARKBLUE, Color.FIREBRICK, 25));
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(50, 20, 50, 20));
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
private Rectangle[] createRectangles(Color start, Color end, int count) {
var list = new ArrayList<Rectangle>(count);
for (double i = 0; i < count; i++) {
var rect = new Rectangle(30, 60, start.interpolate(end, i / count));
var scaleTrans = new ScaleTransition(Duration.millis(250), rect);
scaleTrans.setFromX(1.0);
scaleTrans.setFromY(1.0);
scaleTrans.setToX(1.2);
scaleTrans.setToY(1.2);
rect.setOnMouseEntered(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(1.0);
rect.setViewOrder(-1.0);
scaleTrans.play();
});
rect.setOnMouseExited(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(-1.0);
rect.setViewOrder(0.0);
scaleTrans.play();
});
// *the "stop()"'s don't seem to be necessary. When I commented
// them out the animation still worked. In fact, the animation
// actually seems smoother in the situation where you move the
// mouse over and then away quickly (before the zoom-in completes).
list.add(rect);
}
return list.toArray(new Rectangle[0]);
}
}
It uses Rectangles instead of ImageViews but the concept is the same. When the mouse hovers over a Rectangle it sets the view order to be lower than the others and then plays a ScaleTransition to make it bigger. When the mouse exits it resets the view order back to 0 and then reverses the ScaleTransition.
Note: I used the var keyword which was added in Java 10.
And here is a GIF of the example in action:
Edit: Since you brought up CSS I went and checked if the view order could be set from a stylesheet. And it appears it can. Looking at the CSS Reference Guide there is a CSS property defined for Node named -fx-view-order.
Here is one such solution, creating a new stage to show the zoomed in image.
I do not set the proper coordinates in this sample, but this works as a proof of concept.
In a nutshell: capture the onMouseEntered and onMouseExited events and hide or show the new stage.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 0; i < 25; i++) {
root.getChildren().add(getImagePane());
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private VBox getImagePane() {
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(50);
setHeight(50);
setStyle("-fx-border-color: black");
}};
ImageView img = new ImageView("sample/imageGallery/cerulean.png") {{
setFitWidth(50);
setFitHeight(50);
setPreserveRatio(true);
Stage stage = zoomedStage(pane, this);
setOnMouseEntered(mouseEvent -> stage.show());
setOnMouseExited(mouseEvent -> stage.hide());
}};
pane.getChildren().add(img);
return pane;
}
private Stage zoomedStage(VBox parent, ImageView img) {
Stage stage = new Stage();
stage.setWidth(110);
stage.setHeight(110);
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(110);
setHeight(110);
setStyle("-fx-border-color: black");
}};
pane.setPickOnBounds(false);
ImageView zoomedImage = new ImageView(img.getImage());
zoomedImage.setFitHeight(100);
zoomedImage.setFitWidth(100);
pane.getChildren().add(zoomedImage);
stage.setScene(new Scene(pane));
stage.initStyle(StageStyle.UNDECORATED);
return stage;
}
}
From here, it should just be a matter or fixing the stage's coordinates to be centered over the image and then remove the stage's decoration.
Known Issues:
You will also need to handle the issue with the mouse cursor being blocked by the new stage. This will lead to a loop where the mouse is constantly entering and exiting the thumbnail of the image, causing the zoomed in stage to flicker.
From my point of view, you have a VBox with spacing 0 so each ImageView is tightly packed to each other so the glow effect is not well visible in every image. In that case you could just add a margin each time you want to select an ImageView in order to 'help' the glowing effect to appear.
For Java 8 :
Sadly, this can't be happened from a CSS cause the ImageView does not provide any rule for setting margin or padding. So you are more or less (in my opinion) bound to write that behaviour through code.
:
private Node createImageView(String imageLink) {
// Setting the image view
ImageView imageView = new ImageView(imageLink);
// setting the fit width of the image view
imageView.setFitWidth(400);
// Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
// Instantiating the Glow class
Glow glow = new Glow();
imageView.setOnMouseEntered(e -> {
// setting level of the glow effect
glow.setLevel(0.9);
// Adding a margin on TOP and Bottom side just to make the
// glowing effect visible
VBox.setMargin(imageView, new Insets(2,0,2,0));
});
imageView.setOnMouseExited(e -> {
// remove the glow effect
glow.setLevel(0.0);
// remove the margins
VBox.setMargin(imageView, new Insets(0));
});
// Applying bloom effect to text
imageView.setEffect(glow);
return imageView;
}

When full screen is applied in javaFx the controllers doesnt adjust to the screen

Im doing my 1st JavaFx project using scene builder 2.0. When I set the fullscreen method true, the anchor pane goes to full screen but the controllers inside the container doesn't adjust according to the adjusted pane size. It will be a great help if someone could point me out where I have messed up.
This is the code I have used in the main program.
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
javafx.geometry.Rectangle2D r = Screen.getPrimary().getBounds();
Scene scene = new Scene(root,r.getWidth(), r.getHeight());
String css = this.getClass().getResource("newCascadeStyleSheet.css").toExternalForm();
stage.setScene(scene);
scene.getStylesheets().add(css);
stage.show();
}
Ive set the sizes padding and everything using scene builder options.
Thanks in advance.
I think what you are missing is a Fit to parent setting in your view.
As you are using SceneBuilder, I suggest you to try the following: in the Hierarchy view (left side) right-click your component and select Fit to parent.
This is an example (taken from this awesome tutorial):

JavaFX: How can I horizontally center an ImageView inside a ScrollPane?

viewScroll.setContent(new ImageView(bigimg));
double w = viewScroll.getContent().getBoundsInLocal().getWidth();
double vw = viewScroll.getViewportBounds().getWidth();
viewScroll.getContent().setTranslateX((vw/2)-(w/2));
viewScroll.toFront();
I set an ImageView with some Image inside the ScrollPane but the ImageView always goes to the far left corner. Here I'm trying to manually offset the difference, but it doesn't work well. The ImageView goes too far to the right plus it only updates once because it's inside the eventhandler for a button.
Here is an example using a label without the need for listeners:
public void start(Stage primaryStage) throws Exception {
ScrollPane scrollPane = new ScrollPane();
Label label = new Label("Hello!");
label.translateXProperty().bind(scrollPane.widthProperty().subtract(label.widthProperty()).divide(2));
scrollPane.setContent(label);
Scene scene = new Scene(scrollPane);
primaryStage.setScene(scene);
primaryStage.setWidth(200);
primaryStage.setHeight(200);
primaryStage.show();
}
I am not sure if you are familiar with properties or not, but in the code above, when I bind the translateXProperty to the formula, every time one of the dependencies changes, such as the ScrollPane's widthProperty or the Label's widthProperty, the formula is recalculated and translateXProperty is set to the result of the formula.
I am not sure, but in your previous code, it appears that the calculation code would be in a resize listener. This is not required when dealing with properties as they update whenever dependencies changed (note the bind() and not set()).

UNIFIED StageStyle and Transparent Scene produce black background instead of transparent

JavaFX 8 has StageStyle.Unified, enabling to create OS X style, unified toolbars. I have tested the following code works correctly in JDK 8u5.(the scene background is transparent and the button appears against the stage background.)
However in JDK 8u25 and JDK 8u31 there is a weird behavior. the scene background becomes black.
changing the scene color to anything other than transparent works fine, it displays that color.
It is looking like a bug to me unless I am missing something,
Any ideas?
public class UnifiedTest extends Application {
#Override
public void start(Stage primaryStage) {
StackPane testPane = new StackPane();
testPane.setStyle("-fx-background-color:transparent;");
Label someText = new Label("TEXT AGAINST TRANSPARENT SCENE");
testPane.getChildren().add(someText);
Scene myScene = new Scene(testPane,500,500);
myScene.setFill(Color.TRANSPARENT);
primaryStage.setScene(myScene);
primaryStage.initStyle(StageStyle.UNIFIED);
primaryStage.setTitle("Application");
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Some more information:
By stage background I meant the background of the window that is provided by the OS.
I am trying to create a unified toolbar. According to JavaFX 8 API the way to do it is to use StageStyle.UNIFIED. this will give a window provided by the OSX.
It looks like this when there is no scene added:
http://imgur.com/iHEiVf0,fMbFr4e,bFSL8bA
However when I setFill(Color.TRANSPARENT) the scene background becomes black:
http://imgur.com/iHEiVf0,fMbFr4e,bFSL8bA#2
The desired result is to see the text against the background of the first link. It worked with JDK 8u5, but now I am using 8u31 and instead of that grey I get black. What is weird is that if I setFill(Color.RED) the background becomes red as expected
From the JavaFX 8 api:
public static final StageStyle UNIFIED
Defines a Stage style with platform decorations and eliminates the border between client area and decorations. The client area background is unified with the decorations. This is a conditional feature, to check if it is supported see Platform.isSupported(javafx.application.ConditionalFeature). If the feature is not supported by the platform, this style downgrades to StageStyle.DECORATED
` NOTE: To see the effect the Scene covering the Stage should have Color.TRANSPARENT
Looks like this is a bug, which is fixed for Java 8u60:
JDK-8095040 StageStyle.UNIFIED not working on OSX 10.9.5
Kevin Rushforth comments on the bug tracker:
It looks like the regression was introduced some time in 8u20.

JavaFX does not repaint when removing an object drawn on top of another object

In the code below, the green rectangle is not removed on mouse click. However, if you resize the stage after the mouse click, the scene is repainted and the green rect vanishes.
If you set the green rectangle's size to 150/150, then some of it is immediately on top of the pane, and it vanishes immediately on mouse click.
Is this a JavaFX bug, or am I overlooking something?
How can I make the rectangle disappear on mouse click?
My environment: Windows 7 / Java 1.8.0 64-Bit Server VM build 25.0-b70.
package xschach.client;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main3 extends Application {
public static void main(String[] args) {
Application.launch(Main3.class.getName());
}
public Main3() {}
#Override
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
Rectangle rect = new Rectangle(10, 10, 200, 200);
pane.getChildren().add(rect);
stage.setScene(new Scene(pane, 300, 300));
stage.show();
Rectangle rect2 = new Rectangle(100, 100, 50, 50);
rect2.setFill(Color.GREEN);
pane.getChildren().add(rect2);
pane.setOnMouseClicked(event -> pane.getChildren().remove(rect2));
}
}
It really seems to me a bug.
Doing some tests, what happens is the very last node (on top), no matter how many we have, that lays within the bounds of the first one, when it's removed, it is not visible to the scene graph, it's not marked as dirty, and no requestLayout() is called.
I've found also other workaround. Just allow some (minimal) transparency to the first child, and it will work...
rect.setFill(Color.web("000000FE"));
And you can always put this node behind the first one...
pane.setOnMouseClicked(event -> {
rect2.toBack();
pane.getChildren().remove(rect2);
});
Anyway, consider filing a bug to Jira.
I know it's a little bit late, but it might help someone.
If you use the method .clear() on the children list of the pane if forces the render of the view.
So this solution, quite extreme, I admit it, works :
pane.setOnMouseClicked(event -> {
pane.getChildren().clear();
pane.getChildren().add(rect);
});
I found another workaround. Move the node that is not erasing to the back with the toBack() method, and then move it back to its place with the toFront() method. This seems to activate the dirty mechanism and repaints the background node correctly.

Resources