Fill/Padding space between 2 Labels with dots in GridPane (JavaFX) - css

Starting point:
GridPane with 2 Columns
each Column has a Label
Like-to-have output:
space between the labels filled by dots
So far I have only come across String solutions in which the target length of the combined String is known. But this case doesn't do it for me because I need a solution which can also works when screen size changes and therefore the space between Labels do change dynamically. Could you guys please point me to the right direction?

You could put the 2 Labels in a HBox with a Region in between them, set hgrow for the labels and the Region to NEVER and ALWAYS respectively and use a linear gradient as background for the region that draws half of it's size black and the other half transparent.
Example
// 20 px wide horizontal gradient alternating between black and transparent with immediate color switch
private static final Paint FILL = new LinearGradient(
0, 0,
10, 0,
false,
CycleMethod.REPEAT,
new Stop(0, Color.BLACK),
new Stop(0.5, Color.BLACK),
new Stop(0.5, Color.TRANSPARENT)
);
// create background for regions
private static final Background BACKGROUND = new Background(new BackgroundFill(FILL, CornerRadii.EMPTY, Insets.EMPTY));
private static void addRow(Pane parent, String s1, String s2) {
// create labels
Label label1 = new Label(s2);
Label label2 = new Label('['+s2+']');
// create filler region with "stroke width" 2
Region filler = new Region();
filler.setPrefHeight(2);
filler.setBackground(BACKGROUND);
HBox hbox = new HBox(5, label1, filler, label2);
hbox.setAlignment(Pos.CENTER);
HBox.setHgrow(label1, Priority.NEVER);
HBox.setHgrow(label2, Priority.NEVER);
HBox.setHgrow(filler, Priority.ALWAYS);
hbox.setFillHeight(false);
parent.getChildren().add(hbox);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
addRow(root, "JBoss", "DOWN");
addRow(root, "GlassFish", "UP");
addRow(root, "verylongprocessname", "UP");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
You could also use a border (top only) on the Region instead of using a background and do not set prefHeight. This would allow you to actually use dots instead of strokes, but since your picture shows strokes, I posted the background approach instead...

Related

JavaFX Layout issue within hbox

I want to add an statusbar to my application (the root pane is a vbox and the statusbar is a hbox with a fix height). On this statusbar I have a label with something like "2 processes running". As soon as the mouse hoovers this label, I want to add a Pane above this label with some details about the processes (like IntelliJ or Eclipse).
My problem in the moment is how to create this pane and position this pane above the label.
I create a simple example of my problem
The green area is a StackPane on the root VBox with VGow = Always. The red area is the Hbox with a fix height of 30 pixel. Then I added the yellow VBox the the HBox and put the minHeight to 300.
The problem is, that the yellow area should be above the red area (over the green area) and not outside the window.
What is the best way to achive something like that?
The only way I figured out was using a negative top margin amount (- max/min height). But then the window gets stretched because the yellow pane (or the green bordered pane is not on top of the other elements). The red crossed area shouldn't be there. The green boxed area should be above the other content. Probably I can't use a vbox as my root element?
Update 1
Here is an example - strange thing is, that it is working in this standalone example. But is that the way I should do something like that?
VBox rootBox = new VBox();
rootBox.setMaxHeight(500);
rootBox.setStyle("-fx-background-color: lightgreen");
StackPane contentPane = new StackPane();
contentPane.getChildren().add(new Button("Dont click me"));
contentPane.setStyle("-fx-background-color: lightblue");
VBox.setVgrow(contentPane, Priority.ALWAYS);
HBox statusbar = new HBox();
statusbar.setMinHeight(30);
statusbar.setMaxHeight(30);
statusbar.setStyle("-fx-background-color: red");
VBox processIndicatorBox = new VBox();
processIndicatorBox.setMinHeight(30);
processIndicatorBox.setMaxHeight(30);
HBox.setMargin(processIndicatorBox, new Insets(-300, 0, 0, 0));
StackPane processListPane = new StackPane();
processListPane.setStyle("-fx-background-color: yellow");
processListPane.setMinHeight(300);
processListPane.setMaxHeight(300);
processListPane.setMinWidth(150);
processListPane.setMaxWidth(150);
processListPane.setVisible(false);
Label label = new Label("Show processes");
label.setOnMouseEntered(mouseEvent -> processListPane.setVisible(true));
label.setOnMouseExited(mouseEvent -> processListPane.setVisible(false));
processIndicatorBox.getChildren().addAll(processListPane, label);
statusbar.getChildren().add(processIndicatorBox);
rootBox.getChildren().addAll(contentPane, statusbar);
Scene scene = new Scene(rootBox, 600, 500);
primaryStage.setScene(scene);
primaryStage.show();
Many greetings
Hauke
Did you consider using GridPane? ( https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/GridPane.html ). Gives you more control to layout elements with different sizes. VBox and HBox are from my experience good when all elements have similar sizes.
Here a pseudo-pseudo code
GridPane grid = new GridPane();
Pane greenBox = new Pane(); //your green box
Pane redBox = new Pane(); //your red box
Pane yellowBox = new Pane(); //your yellow box
// add the panes to the grid pane and define where they are
grid.add(greenBox, 0, 0); set the green box in column 0 and row 0
grid.add(redBox, 0, 1); set the red box in column 0 and row 1
grid.add(yellowBox, 1, 1); set the yellowbox in column 1 and row 1
// size of the boxes
GridPane.setColumnSpan(greenBox,2); //The green box should span over 2 columns
GridPane.setRowSpan(yellowBox,2); //The red box should span over 2 rows
The boxes are now only empty panes, which will have a minimum size without content. Replace the Pane() boxes with the content you want to put in or put the content in the Pane() objects.

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

Region width not refreshing automatically after modifying Grid Pane to which the region width is bound - JavaFX

I'm making a simple Java GUI app using JavaFX that has a Border Pane as the root node.
In the top section of the Border Pane, there is a Grid Pane with three columns (top Grid Pane from now on).
In the first column of the top Grid Pane, there is a Home Button, in the second column, there is an empty Region that only serves as spacer between the first and third column of the top Grid Pane, and in the third column, there is another GridPane (right Grid Pane from now on).
The right Grid Pane contains one Button (Log In Button) on start. However, when a user successfully logs into the app, two other Buttons and a Label are added to the right Grid Pane as part of the Log In Button click event.
The spacer maxWidthProperty and minWidthProperty are bound to the top Grid Pane (tgp) widthProperty and the right Grid Pane(rgp) widthProperty like this:
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
which makes the right Grid Pane move nicely with its buttons staying on the right side of the scene when a user resizes the main stage.
However, a problem occurs when the user logs in and additional buttons are added to the right Grid Pane. The spacer somehow misses this change and its width stays the same, which makes the additional Buttons appear outside of the current stage width. The only way to refresh the spacer width is to interact with the stage somehow, by clicking minimize/maximize/restore or by clicking any button on the scene.
Is there a way to automatically refresh Region width after the nodes to which its width is bound to are modified? Or, is there a better approach to making a top Grid Pane with one button on the left and modifiable number of buttons (nodes) on the right?
Edit: Here is a demonstration of the problem with several screenshots stacked on one another:
Minimal reproducible example:
BorderPane root = new BorderPane();
GridPane tgp = new GridPane();
tgp.minWidthProperty().bind(root.widthProperty());
tgp.maxWidthProperty().bind(root.widthProperty());
tgp.setStyle("-fx-background-color: WHITE; -fx-border-color: LIGHTGREY;");
tgp.setMinHeight(37);
tgp.setMaxHeight(37);
root.setTop(tgp);
Button homeButton = new Button("Home"));
homeButton.setMinHeight(35);
homeButton.setMaxHeight(35);
homeButton.setMinWidth(80);
homeButton.setMaxWidth(80);
tgp.add(homeButton, 0, 0);
GridPane rgp = new GridPane(); // Right Grid Pane - holds User related nodes
rgp.setHgap(5);
tgp.add(rgp, 2, 0);
Label unl = new Label("My Profile");
unl.setFont(new Font("Calibri", 15));
unl.setTextFill(Color.RED);
unl.setMinWidth(Region.USE_PREF_SIZE);
Button wlButton = new Button("Watchlist");
wlButton.setMinHeight(35);
wlButton.setMaxHeight(35);
wlButton.setMinWidth(80);
wlButton.setMaxWidth(80);
Button cartButton = new Button("Cart");
cartButton.setMinHeight(35);
cartButton.setMaxHeight(35);
cartButton.setMinWidth(60);
cartButton.setMaxWidth(60);
Button logInOutButton = new Button("Log In");
logInOutButton.setMinHeight(35);
logInOutButton.setMaxHeight(35);
logInOutButton.setMinWidth(60);
logInOutButton.setMaxWidth(60);
rgp.add(logInOutButton, 3, 0);
logInOutButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (logInOutButton.getText().equals("Log In")) {
LogInStage lis = new LogInStage();
lis.initStage();
if (lis.username != null) {
logInOutButton.setText("Log Out");
rgp.add(unl, 0, 0);
rgp.add(wlButton, 1, 0);
rgp.add(cartButton, 2, 0);
}
} else if (logInOutButton.getText().equals("Log Out")) {
logInOutButton.setText("Log In");
rgp.getChildren().remove(unl);
rgp.getChildren().remove(wlButton);
rgp.getChildren().remove(cartButton);
}
}
});
Region spacer = new Region();
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
tgp.add(spacer, 1, 0)
It's always a bad idea to use bindings, if you can avoid it. Any changes to the size constraints can lead to a new layout pass being scheduled, but during the layout pass they are assumed to be constant. If you now introduce a binding the following sequence of events could happen:
A layout pass is requested for the GridPane, setting a flag to indicate layout is required
A layout pass happens. During the layout pass the children are resized. This triggers an update of the constraints of the children with the bindings.
The flag is cleared, but the changes to the contraints already happened. The layout won't reflect this. The GridPane gets another reason to do a layout.
I don't know, how your scene is set up in detail, but I recommend using column constraints: Set the grow priorities for the outer ones to SOMETIMES and the one for the center to ALWAYS. If you require some spacing around the children, you could use GridPane.setMargin (or the padding of the GridPane itself, if you require the a distance to the edges for all children).
#Override
public void start(Stage primaryStage) {
Button[] rightContent = new Button[3];
for (int i = 0; i < rightContent.length; i++) {
Button btn = new Button(Integer.toString(i));
GridPane.setColumnIndex(btn, i);
rightContent[i] = btn;
}
Button cycle = new Button("cycle");
GridPane rgp = new GridPane(); // I would usually use a HBox here
// don't grow larger than needed
rgp.setMaxWidth(Region.USE_PREF_SIZE);
// cycle though 0 to 3 buttons on the right
cycle.setOnAction(new EventHandler<ActionEvent>() {
int nextIndex = 0;
#Override
public void handle(ActionEvent event) {
if (nextIndex >= rightContent.length) {
rgp.getChildren().clear();
nextIndex = 0;
} else {
rgp.getChildren().add(rightContent[nextIndex]);
nextIndex++;
}
}
});
ColumnConstraints sideConstraints = new ColumnConstraints();
sideConstraints.setHgrow(Priority.SOMETIMES);
ColumnConstraints centerConstraints = new ColumnConstraints();
centerConstraints.setHgrow(Priority.ALWAYS);
//prefer to grow the center part of the GridPane
GridPane root = new GridPane();
root.getColumnConstraints().addAll(sideConstraints, centerConstraints, sideConstraints);
root.add(cycle, 0, 0);
root.add(rgp, 2, 0);
// add something to visualize the center part
// you could simply leave this part out
Region center = new Region();
center.setStyle("-fx-border-radius: 10;-fx-border-width: 1;-fx-border-color:black;");
root.add(center, 1, 0);
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
As mentioned in the comments, the center region is not actually needed.

Add a node in the middle of a line java fx

I have a line and I want a square with text inside of the square to be placed in the middle of this line.
I have created the square with text using a stack pane. This line is draggable so I want the square to stay in the middle of this line when it is being dragged.
I tried:
weightSquare.layoutXProperty().bind((line.startXProperty().add(line.endXProperty())).divide(2).add(line.translateXProperty()));
weightSquare.layoutYProperty().bind((line.startYProperty().add(line.endYProperty())).divide(2).add(line.translateXProperty()));
where weightSquare is a StackPane containing a rectangle and text.
Currently, the weightSquare is near the middle of the line but not perfectly in the middle. When the line moves around the weightSquare stays relatively near the middle of the line but sometimes goes off the line slightly.
I want something like this:
Example of what I want
Thank you.
Assuming no transformations have been applied to the line or the StackPane, you can calculate the position of the StackPane based on the line properties like this
stackPane.layoutX = (line.startX + line.endX - stackPane.width) / 2;
(Procede accordingly for y coordinates.)
transformX and transformY could simply be added, but general transforms would require you to
Listen to changes of the transforms
Use localToParent on the start/end coordinates of the line to get the location in the parent.
I recommend using Bindings.createDoubleBindings for complicate double bindings btw, since this makes the formula for calculating the values much easier to read.
Example
I use a Label, since this provides background/border functionality too.
#Override
public void start(Stage primaryStage) throws IOException {
Label label = new Label();
label.setStyle("-fx-background-color: white; -fx-border-color: black;");
label.setPadding(new Insets(2, 4, 2, 4));
Line line = new Line(300, 300, 300, 100);
label.layoutXProperty().bind(Bindings.createDoubleBinding(
() -> (line.getStartX() + line.getEndX() - label.getWidth()) / 2,
line.startXProperty(), line.endXProperty(), label.widthProperty()));
label.layoutYProperty().bind(Bindings.createDoubleBinding(
() -> (line.getStartY() + line.getEndY() - label.getHeight()) / 2,
line.startYProperty(), line.endYProperty(), label.heightProperty()));
DoubleProperty angle = new SimpleDoubleProperty();
line.endXProperty().bind(Bindings.createDoubleBinding(() -> 300 + 200 * Math.sin(angle.get()), angle));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> 300 + 200 * Math.cos(angle.get()), angle));
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(angle, 0d, Interpolator.LINEAR)),
new KeyFrame(Duration.seconds(10), new KeyValue(angle, Math.PI * 2, Interpolator.LINEAR)));
timeline.setCycleCount(Animation.INDEFINITE);
label.textProperty().bind(timeline.currentTimeProperty().asString());
timeline.play();
Scene scene = new Scene(new Pane(line, label), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX - Get Coordinates of Node Relative to its Parent

I am making a simple graphical interface for saving previously generated images. All images come to me square but I want to allow for some cropping functionality (more precisely cutting off equal parts from the bottom and top of the image). I want to do this by allowing the user to drag a shaded region over the image which will tell the user that this region will be cropped out. See the below image for details. To enable this drag functionality I have added small triangles that I want the user to drag which in turn will move the shaded regions about. However the coordinates for the triangles are all weird and seem nonsensical. Therefor I was wondering what the best way is to get the coordinates of the triangles in relation to the ImageView (or their first common parent node) in terms of ImageView-side-lengths. So if the triangle is in the center its coordinates are [0.5, 0.5] for instance.
The Image view will be moving around inside the scene and will also be changing size so it is vital that I can get the coordinates relative to not only the ImageView but also to the size of the ImageView.
Here is also the surrounding hierarchy of nodes if that helps. The Polygons are the triangles and the regions are the rectangles.
Thanks for all forms of help!
Node.getBoundsInParent returns the bounds of a node in it's parent coordinates. E.g. polygon.getBoundsInParent() would return the bounds in the VBox.
If you need to "go up" one additional step, you can use parent.localToParent to do this. vBox.localToParent(boundsInVbox) returns the bounds in the coordinate system of the AnchorPane.
To get values relative to the size of the image, you simply need to divide by it's size.
The following example only allows you to move the cover regions to in one direction and does not check, if the regions intersect, but it should be sufficient to demonstrate the approach.
The interesting part is the event handler of the button. It restricts the viewport of the second image to the part of the first image that isn't covered.
private static void setSideAnchors(Node node) {
AnchorPane.setLeftAnchor(node, 0d);
AnchorPane.setRightAnchor(node, 0d);
}
#Override
public void start(Stage primaryStage) {
// create covering area
Region topRegion = new Region();
topRegion.setStyle("-fx-background-color: white;");
Polygon topArrow = new Polygon(0, 0, 20, 0, 10, 20);
topArrow.setFill(Color.WHITE);
VBox top = new VBox(topRegion, topArrow);
top.setAlignment(Pos.TOP_CENTER);
topArrow.setOnMouseClicked(evt -> {
topRegion.setPrefHeight(topRegion.getPrefHeight() + 10);
});
// create bottom covering area
Region bottomRegion = new Region();
bottomRegion.setStyle("-fx-background-color: white;");
Polygon bottomArrow = new Polygon(0, 20, 20, 20, 10, 0);
bottomArrow.setFill(Color.WHITE);
VBox bottom = new VBox(bottomArrow, bottomRegion);
bottom.setAlignment(Pos.BOTTOM_CENTER);
bottomArrow.setOnMouseClicked(evt -> {
bottomRegion.setPrefHeight(bottomRegion.getPrefHeight() + 10);
});
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/402px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg");
ImageView imageView = new ImageView(image);
setSideAnchors(top);
setSideAnchors(bottom);
setSideAnchors(imageView);
AnchorPane.setTopAnchor(top, 0d);
AnchorPane.setBottomAnchor(bottom, 0d);
AnchorPane.setTopAnchor(imageView, 0d);
AnchorPane.setBottomAnchor(imageView, 0d);
AnchorPane container = new AnchorPane(imageView, top, bottom);
ImageView imageViewRestricted = new ImageView(image);
Button button = new Button("restrict");
button.setOnAction(evt -> {
// determine bouns of Regions in AnchorPane
Bounds topBounds = top.localToParent(topRegion.getBoundsInParent());
Bounds bottomBounds = bottom.localToParent(bottomRegion.getBoundsInParent());
// set viewport accordingly
imageViewRestricted.setViewport(new Rectangle2D(
0,
topBounds.getMaxY(),
image.getWidth(),
bottomBounds.getMinY() - topBounds.getMaxY()));
});
HBox root = new HBox(container, button, imageViewRestricted);
root.setFillHeight(false);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Resources