I got quite simple question but I can't find answer to my case.
My goal is to "allow" only visual overflow of node and prevent resize of parent node like this:
I want to allow visual overflow of child (right) but I want to prevent resize of parent and clip it as black rectangle (left).
I am aware of setClip method but this way I get situation (#1) where child is clipped visually.
Is it possible in JavaFX to allow visual overflow?
I need it becauase I can't deal with centering of node in StackPane when nested children overflow but would be good to avoid clipping them. I want to get first situation but now i get second one:
Is there other solution to my problem?
If you set the absulute position and size of the children yourself, you can make the parent layout ignore a child by setting the managed property to false. Unmanaged nodes are not repositioned/resized by the parent and are ignored when calculating the layout bounds of the parent.
child.setManaged(false);
This is standard behavior for JavaFX Regions:
Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside these bounds. The content area of a Region is the area which is occupied for the layout of its children. This area is, by default, the same as the layout bounds of the Region, but can be modified by either the properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can be negative, such that the content area of a Region might extend beyond the layout bounds of the Region, but does not affect the layout bounds.
Here's a short example:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
pane.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null)));
pane.setMaxSize(500, 300);
Rectangle rect = new Rectangle(100, 100, Color.FIREBRICK);
rect.setOnMousePressed(this::handleMousePressed);
rect.setOnMouseDragged(this::handleMouseDragged);
pane.getChildren().add(rect);
StackPane root = new StackPane(pane);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
private Point2D origin;
private void handleMousePressed(MouseEvent event) {
origin = new Point2D(event.getX(), event.getY());
event.consume();
}
private void handleMouseDragged(MouseEvent event) {
Rectangle rect = (Rectangle) event.getSource();
rect.setTranslateX(rect.getTranslateX() + event.getX() - origin.getX());
rect.setTranslateY(rect.getTranslateY() + event.getY() - origin.getY());
event.consume();
}
}
This has a Rectangle whose parent is a Pane but allows you to drag the Rectangle anywhere, even outside the bounds of the Pane.
Related
I'm trying to have two boxes with one of them half transparent and the other in orange. Somehow it always just fully replaces the pixels but still kinda applies the transparency to the color. What am I missing? Same happens with loaded Obj files which have d/Tr set to 0.5 for example.
import javafx.application.Application;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class HelloFX extends Application {
#Override
public void start(Stage primaryStage) {
boolean is3DSupported = Platform.isSupported(ConditionalFeature.SCENE3D);
if (!is3DSupported) {
System.out.println("Sorry, 3D is not supported in JavaFX on this platform.");
return;
}
Box boxForeground = new Box(100, 500, 100);
boxForeground.setTranslateX(250);
boxForeground.setTranslateY(100);
boxForeground.setTranslateZ(400);
boxForeground.setMaterial(new PhongMaterial(new Color(0, 0, 0, 0.3)));
Box boxBackground = new Box(100, 100, 100);
boxBackground.setMaterial(new PhongMaterial(Color.ORANGE));
boxBackground.setTranslateX(250);
boxBackground.setTranslateY(200);
boxBackground.setTranslateZ(800);
boolean fixedEyeAtCameraZero = false;
PerspectiveCamera camera = new PerspectiveCamera(fixedEyeAtCameraZero);
camera.setTranslateX(150);
camera.setTranslateY(-100);
camera.setTranslateZ(250);
Group root = new Group(boxForeground, boxBackground);
// root.setDepthTest(DepthTest.ENABLE); // no effect
root.setRotationAxis(Rotate.X_AXIS);
root.setRotate(30);
Scene scene = new Scene(root, 500, 300, true);
scene.setCamera(camera);
primaryStage.setScene(scene);
primaryStage.setTitle("3D Example");
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
I've played around with many options of depth buffer, depth test settings and colors but it seemed to have no good effect.
My expectation is that its possible to slightly see the orange box through the black box with strong transparency.
Actual result:
So based on the comments and my own investigation it seems to be a known bug thats hasn't been fixed in over 5 years. Basically transparency works like in the 2D space of JavaFx. The one last added to the graph paints over the already painted ones - potentially using transparency blending when your colors/pixels contain alpha information.
Bugtracker: Order-independent transparency for 3D objects
Workaround:
The current workaround is to reorder the graph so it matches the desired z-order. Depending on the use case you can group objects on a root level to get close to an ideal transparency handling. But for transparent objects that 'interact' with each other on the same level of the graph you gonna need constant reordering when the objects or the camera move.
When done right you get the expected result:
Group root = new Group(boxForeground, boxBackground); // bug shows
Group root = new Group(boxBackground, boxForeground); // workaround
Apparently JavaFx has a long history of transparency issues. Which are also discussed here: JavaFX 3D Transparency
I have a little problem I hope you can help me with:
In this Scene, That blue circle is a 128x128 ImageView, this ImageView is in an HBox, and the HBox is in a VBox, I then set the VBox alignment to Pos.CENTER;
Everything's ok, but when I print the layoutY of the ImageView, it says 0 instead of a 61 (Scene's height is 250, so the layoutY should be 125 - 64);
Does someone have an idea?
Thanks.
The layoutX and layoutY properties determine the layout position of a node within its parent: in this case, the layout position of the image in the HBox. Since there is nothing else in the HBox, the image view will just be at (0,0) in the coordinate system of the HBox, so you will just get 0 for the layoutY property.
(Note also that transforms, such as translations, are applied independently of the layout coordinates - if you like to think of it this way, the node is laid out, then transforms are applied which will alter its final position. So transforms do not modify the layoutX and layoutY properties.)
To get the location of a node in the scene, you can use the localToScene transform to convert a point in the node's own coordinate system to a point in the scene's coordinate system. So to get the location of the top left ((0,0)) of the image view in the scene, you can do
image.localToScene(new Point2D(0, 0))
Here is a complete SSCCE (just using a plain Region to stand in for the image view):
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class BoundsInSceneExample extends Application {
#Override
public void start(Stage primaryStage) {
HBox hbox = new HBox();
Node image = createImage();
hbox.getChildren().add(image);
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().add(hbox);
Scene scene = new Scene(root, 250, 250);
// force the layout, so layout computations are performed:
root.layout();
System.out.printf("Layout coordinates: [%.1f, %.1f]%n", image.getLayoutX(), image.getLayoutY());
Point2D sceneCoords = image.localToScene(new Point2D(0,0));
System.out.printf("Scene coordinates: [%.1f, %.1f]%n", sceneCoords.getX(), sceneCoords.getY());
primaryStage.setScene(scene);
primaryStage.show();
}
private Node createImage() {
Region region = new Region();
region.setMinSize(128, 128);
region.setPrefSize(128, 128);
region.setMaxSize(128, 128);
region.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));
return region ;
}
public static void main(String[] args) {
launch(args);
}
}
Output:
Layout coordinates: [0.0, 0.0]
Scene coordinates: [0.0, 61.0]
I have a group with two circles on it, when I move one of them with a translate transition I should see the stationary one remain at the center(which is in the middle of the scene graph) and the other one move. Instead what happens is the "camera" follows the moving circle making it seem like they are both moving apart.
Is there a way to center the camera on 0,0 so that it remains there instead of following the circle?
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application
{
public static void main(String[] args)
{
launch(args);
}
public void start(Stage stage)
{
BorderPane root = new BorderPane();
root.setStyle("-fx-background-color: Black");
Group graph = new Group();
root.setCenter(graph);
graph.setLayoutX(250);
graph.setLayoutY(250);
Circle circle = new Circle(0,0,5);
circle.setFill(Color.ORANGE);
graph.getChildren().add(circle);
Circle circle2 = new Circle(0, 0, 5);
circle2.setFill(Color.AQUA);
graph.getChildren().add(circle2);
TranslateTransition t = new TranslateTransition(Duration.millis(1000), circle);
t.setFromX(0);
t.setToX(100);
t.setFromY(0);
t.setToY(0);
t.setInterpolator(Interpolator.LINEAR);
t.play();
stage.setTitle("Circle Test");
stage.setScene((new Scene(root, 500, 500)));
stage.show();
}
}
To understand what is happening with the layout here, first note that the layout coordinates of the Group graph are ignored entirely, because you place graph in a layout container (a BorderPane). (Comment out the setLayoutX and setLayoutY lines and you will see they make no difference.) The layout container will size its child nodes according to 1. how much space it has for them, 2. the child nodes' min, preferred, and max sizes. Since the BorderPane doesn't have any other child nodes in this example, it wants to allocate all its available space to the graph. Since graph is in the center, if there is space it cannot allocate to it, it will center it, leaving the rest of the space unused.
Groups behave differently to Regions (which include Controls, Panes, and their subclasses): according to the documentation they are not resizable and take on the collective bounds of their children.
At the beginning of your animation, both circles are coincident, centered at (0,0) and with radius 5: so their bounding boxes (and consequently the bounding box of the Group) has top left corner at (-5,-5) and width and height of 10. This square 10x10 bounding box cannot be made bigger (since it's a Group, which is not resizable), and is centered on the screen. Since the BorderPane has 500 pixels of total width available, there are 490 pixels of unused width, which are divided equally on either side of the Group to center it: 245 to the left and 245 to the right. So the left edge of the Group, which is the left edge of both the circles, is at x=245 in the BorderPane coordinate system.
At the end of the animation, one circle remains at (-5,-5) with width 10x10, while the other has been translated 100 pixels to the right, so its bounding box extends from (95, -5) to (105, 5). Consequently, the bounding box of the Group, which takes on the collective bounds of its child nodes, has top left at (-5, -5), width 110 and height 10. This box cannot be resized, so the BorderPane's layout mechanism centers this box in the area it has available. Since the BorderPane has a width of 500 pixels available, there are 390 unused pixels in width which are divided equally on either side: 195 on the left of the Group and 195 on the right. So at this point, the left edge of the Group, which is the left edge of the untranslated circle, is at x=195 in the BorderPane coordinate system. Consequently, at the end of the animation, the untranslated circle has moved 50 pixels (half of the translation distance) to the left in the BorderPane's coordinate system.
A more natural thing to do here is to use a Pane instead of a Group. A Pane is resizable, so the BorderPane will simply expand it to fill all the available space. Thus it will sit in the top left of the BorderPane and fill the BorderPane. The bounds of the Pane start at (0,0) and extend to its width and height. Thus if you simply change Group to Pane, the untranslated circle will not move during the animation, as you want.
However, the circles will now both start in the top left of the pane instead of the center. If you want them to start in the center, you can change the coordinates of the circles themselves, so they start centered at (250, 250):
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
BorderPane root = new BorderPane();
root.setStyle("-fx-background-color: Black");
Pane graph = new Pane();
root.setCenter(graph);
// graph.setLayoutX(250);
// graph.setLayoutY(250);
Circle circle = new Circle(250, 250, 5);
circle.setFill(Color.ORANGE);
graph.getChildren().add(circle);
Circle circle2 = new Circle(250, 250, 5);
circle2.setFill(Color.AQUA);
graph.getChildren().add(circle2);
TranslateTransition t = new TranslateTransition(Duration.millis(1000), circle);
t.setFromX(0);
t.setToX(100);
t.setFromY(0);
t.setToY(0);
t.setInterpolator(Interpolator.LINEAR);
t.play();
stage.setTitle("Circle Test");
stage.setScene((new Scene(root, 500, 500)));
stage.show();
}
}
As an alternative, you could use a Pane as the root, instead of a BorderPane. A plain Pane doesn't do any layout, so in this case the layoutX and layoutY settings will take effect. Thus you can revert the centers of the circles to (0,0), and use the layout settings on graph to center it:
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
Pane root = new Pane();
root.setStyle("-fx-background-color: Black");
Pane graph = new Pane();
root.getChildren().add(graph);
graph.setLayoutX(250);
graph.setLayoutY(250);
Circle circle = new Circle(0, 0, 5);
circle.setFill(Color.ORANGE);
graph.getChildren().add(circle);
Circle circle2 = new Circle(0, 0, 5);
circle2.setFill(Color.AQUA);
graph.getChildren().add(circle2);
TranslateTransition t = new TranslateTransition(Duration.millis(1000), circle);
t.setFromX(0);
t.setToX(100);
t.setFromY(0);
t.setToY(0);
t.setInterpolator(Interpolator.LINEAR);
t.play();
stage.setTitle("Circle Test");
stage.setScene((new Scene(root, 500, 500)));
stage.show();
}
}
You can change the class name to whatever you want.
The problem you had was that you added it through the setCenter() method which automatically makes its center the center of the pane.
I hope this came in time.
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class NewClass extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
BorderPane root = new BorderPane();
root.setStyle("-fx-background-color: #efefef");
Group graph = new Group();
root.getChildren().add(graph);
graph.setLayoutX(250);
graph.setLayoutY(250);
Circle circle = new Circle(0, 0, 5);
circle.setFill(Color.ORANGE);
graph.getChildren().add(circle);
Circle circle2 = new Circle(0, 0, 5);
circle2.setFill(Color.AQUA);
graph.getChildren().add(circle2);
TranslateTransition t = new TranslateTransition(Duration.millis(1000), circle);
t.setFromX(0);
t.setToX(100);
t.setFromY(0);
t.setToY(0);
t.setInterpolator(Interpolator.LINEAR);
t.setCycleCount(5);
t.play();
stage.setTitle("Circle Test");
stage.setScene((new Scene(root, 500, 500)));
stage.show();
}
}
Is it possible so set the origin of a Pane to the bottom-left corner (instead of top-left)? I'm adding many shapes to my canvas which are defined within the "mathematical coordinate system".
I thought there is perhaps an easier way than always substract the height from the y-coordinate. Another advantage would be that I don't have to take care of resizing the pane.
If all you are doing is using shapes, you can apply a reflection to the pane. You can represent a reflection as a Scale with x=1 and y=-1. The only tricky part is that you must keep the pivot of the scale at the vertical center, which needs a binding in case the pane changes size.
If you're putting controls, or text, in the pane, then those will also be reflected, so they won't look correct. But this will work if all you are doing is using shapes.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
public class ReflectedPaneTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scale scale = new Scale();
scale.setX(1);
scale.setY(-1);
scale.pivotYProperty().bind(Bindings.createDoubleBinding(() ->
pane.getBoundsInLocal().getMinY() + pane.getBoundsInLocal().getHeight() /2,
pane.boundsInLocalProperty()));
pane.getTransforms().add(scale);
pane.setOnMouseClicked(e ->
System.out.printf("Mouse clicked at [%.1f, %.1f]%n", e.getX(), e.getY()));
primaryStage.setScene(new Scene(pane, 600, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I hava 3 tabs in a TabPane that each one has a text area with different texts and different length.
I want to autosize text area according to it's length in each tab.
I don't understand what should I do ? using scene builder ? css ?javaFX methods ?
Thank's in Advance ...
I think you are asking that the text areas grow or shrink according to the text that is displayed in them?
If so, see if this code helps:
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class AutosizingTextArea extends Application {
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setMinHeight(24);
textArea.setWrapText(true);
VBox root = new VBox(textArea);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
// This code can only be executed after the window is shown:
// Perform a lookup for an element with a css class of "text"
// This will give the Node that actually renders the text inside the
// TextArea
Node text = textArea.lookup(".text");
// Bind the preferred height of the text area to the actual height of the text
// This will make the text area the height of the text, plus some padding
// of 20 pixels, as long as that height is between the text area's minHeight
// and maxHeight. The minHeight we set to 24 pixels, the max height will be
// the height of its parent (usually).
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(new Callable<Double>(){
#Override
public Double call() throws Exception {
return text.getBoundsInLocal().getHeight();
}
}, text.boundsInLocalProperty()).add(20));
}
public static void main(String[] args) {
launch(args);
}
}
If you want to make this reusable, then you could consider subclassing TextArea. (In general, I dislike subclassing control classes.) The tricky part here would be to execute the code that makes the TextArea expand once it has been added to a live scene graph (this is necessary for the lookup to work). One way to do this (which is a bit of a hack, imho) is to use an AnimationTimer to do the lookup, which you can stop once the lookup is successful. I mocked this up here.