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

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()).

Related

JavaFX 3D boxes overlapping each other

I am having a problem with JavaFX 3D, the problem is as follows:
When I turn my perspective camera around, the last added box (blue box) overlaps the first added box (red box), here is a screenshot:
can anyone tell me why is this happening? And is there a way to fix it? (the boxes are literally 2 box classes with a width, height, depth, position and color)
Minimal reproducible example since somebody asked for it:
Box box1 = new Box();
Box box2 = new Box();
box1.setWidth(300);
box2.setWidth(300);
box1.setHeight(300);
box2.setHeight(300);
box1.setDepth(300);
box2.setDepth(300);
box1.setTranslateX(300);
box2.setTranslateX(300);
box1.setTranslateY(300);
box2.setTranslateX(300);
Group root = new Group();
root.getChildren().addAll(box, box2);
PerspectiveCamera cam = new PerspectiveCamera();
Scene scene = new Scene(root);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
where stage is the stage inside public void start(Stage stage), JavaFX's default run method (any class that extends Application should implement it)
You probably have to add a subscene with the depth buffer enabled. See: https://openjfx.io/javadoc/15/javafx.graphics/javafx/scene/SubScene.html#%3Cinit%3E(javafx.scene.Parent,double,double,boolean,javafx.scene.SceneAntialiasing)
Solution:
I used the following constructor:
new Scene(root, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
instead of:
new Scene(root);

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

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 :-)

JavaFX binding only applied after resizing the window

When I run the following code in the start method of my Main (JavaFX) class I get weird results. The window gets displayed but pane (with a green border) has a width of 0. It is supposed to have the same width as the container's height since I binded prefWidth to the height property.
Then, when I resize the window, the binding comes into effect and the pane becomes a square. Notice that if I maximize the window it also doesn't apply the bindings.
Thank you!
//Create a pane with a min width of 10 and a green border to be able to see it
Pane pane = new Pane();
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
The problem you are running into here is that when the container is laid out, it has no reasonable information as to the order in which it should compute the width and the height of the pane. So essentially what happens is it computes the width, which (since it's empty), is zero; then computes the height (which fills the container, since you told the VBox to do that). After that, the prefWidth property is changed, but by then the actual width has already been set, so it's essentially too late. The next time a layout pass occurs, the new pref width is taken into account.
I haven't checked the actual layout code, but (since the default content bias is null) most likely the layout code for the vbox is going to do something equivalent to the following pseudocode:
protected void layoutChildren() {
// content bias is null:
double prefWidth = pane.prefWidth(-1);
double prefHeight = pane.prefHeight(-1);
// no fill width:
double paneWidth = Math.max(this.getWidth(), prefWidth);
// vgrow, so ignore preferred height and size to height of the vbox:
double paneHeight = this.getHeight();
pane.resizeRelocate(0, 0, paneWidth, paneHeight);
}
The last call actually causes the height of the pane to change, which then causes the prefWidth to change via the binding. Of course, that's too late for the current layout pass, which has already set the width based on the previous preferred width calculation.
Basically, relying on bindings to manage layout like this is not a reliable way of doing things, because you are changing properties (such as prefWidth in this example) during the layout pass, when it may be already too late to resize the component.
The reliable way to manage layout for a pane like this is to override the appropriate layout methods, which are invoked by the layout pass in order to size the component.
For this example, since the width depends on the height, you should return VERTICAL for the contentBias, and you should override computePrefWidth(double height) to return the height (so the width is set to the height):
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane() {
#Override
public Orientation getContentBias() {
return Orientation.VERTICAL ;
}
#Override
public double computePrefWidth(double height) {
return height ;
}
};
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
// pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
}

How to select a 2D node in a 3D scene?

Here is my code. You can copy-paste and follow what I write bellow to see the problem yourself.
public class MyApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(new MyView(), 100, 150);
stage.setScene(scene);
stage.show();
}
private class MyView extends BorderPane {
MyView() {
GridPane board = new GridPane();
int size = 3;
for (int i = 0; i < size*size; i++) {
BorderPane pane = new BorderPane();
pane.setMinSize(30, 30);
pane.setBackground(new Background(new BackgroundFill(Color.RED, null, null)));
pane.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID,
null, null, null)));
pane.setOnMousePressed(e -> {
PickResult pick = e.getPickResult();
Pane selectedNode = (Pane) pick.getIntersectedNode();
selectedNode.setBackground(new Background(new BackgroundFill(Color.GREEN, null, null)));
});
board.add(pane, i / size, i % size);
}
Box box = new Box(20d, 20d, 20d);
BorderPane boardPane = new BorderPane(box, null, null, board, null);
Group root = new Group(boardPane);
SubScene scene = new SubScene(root, USE_PREF_SIZE, USE_PREF_SIZE, true, SceneAntialiasing.BALANCED);
scene.widthProperty().bind(widthProperty());
scene.heightProperty().bind(heightProperty());
setCenter(scene);
}
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
I create a subscene with a grid of squares. When i press on a square I want its background to change color. This works in 2 situations:
if I don't add the Box to the boardPane
if I don't set the scene with a depth buffer
or both. But if i both add the box and set a depth buffer, the squares don't receive the event. instead the boardPane receives it. i guess it's something to do with 2D nodes in a 3D scene.
I tried setting combinations of these methods :setPickOnBounds, setDepthTest, setMouseTransparent but nothing worked.
What's the solution?
Once you have a 3D scene, there aren't really such things as 2D nodes, everything in the scene graph has an x, y and z co-ordinate; even things that were previously treated as 2D nodes when the depth buffer was set to false.
When you place a box in your root border pane, that root border pane assumes the 3D co-ordinates of the box. For picking purposes, the box you have defined is represented in 3D space from z co-ordinates -10 to 10, so the root border pane is defined as -10 for picking purposes. The border panes that you place inside the root border pane have no z co-ordinate defined for them, so they end up at a z co-ordinate of 0, which, from the viewers perspective is behind the root border pane.
So, the root border pane is receiving clicks, but because it is now on a different z plane than the rest of its contents, the other 2D square contents you have defined do not receive clicks. One could argue that the root border pane is not rendered at all as it has no color, so it should be treated as transparent and the clicks just go through to the child nodes, but it seems that is just not how the 3D picking algorithm for JavaFX works.
For your example, to get everything in the same Z plane and picking working correctly, add the following line inside your for loop: pane.setTranslateZ(-10);.
Note: I debugged this by adding the following line to your source code (which reported to me the target of the mouse clicks and the x,y,z pick result co-ordinate for each click):
root.setOnMouseClicked(System.out::println);
My advice is to avoid using layout panes that are designed for 2D purposes (e.g. border panes) to attempt laying out elements in 3D space. The JavaFX layout panes are really only meant for 2D layout. To handle positioning in 3D space you should manage co-ordinates yourself, by just using Groups rather than anything that derives from Pane. At the very least don't try adding 3D elements into 2D layout panes as the results can be confusing (as you have discovered).
You can further separate 2D and 3D items by placing them in different sub scenes (that is really what the intention of the sub scene notion in JavaFX is I think). An example of an application with multiple sub scenes is shown in the following answer: How to create custom 3d model in JavaFX 8?

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):

Resources