In JavaFX 8 I would like to specify the css to rotate a Label so that instead of the text going from left to right, it goes from bottom to top.
How can I do that?
Any node can have it's rotation styled via CSS using the -fx-rotate css attribute.
This is the angle of the rotation in degrees. Zero degrees is at 3 o'clock (directly to the right). Angle values are positive clockwise. Rotation is about the center.
So in your code or FXML you can have:
label.setStyle("vertical");
And in your css stylesheet you can define:
.vertical { -fx-rotate: -90; }
Also note James_D's answer suggestion of wrapping the label in a Group to account for the rotation when performing layout bounds calculations.
Call setRotate on the label to rotate it about its center.
To allow layout panes to properly measure the bounds of the label after rotation, wrap it in a Group:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class RotatedLabelTest extends Application {
#Override
public void start(Stage primaryStage) {
Label label1 = new Label("Hello");
Label label2 = new Label("World");
label1.setRotate(-90);
Group labelHolder = new Group(label1);
HBox root = new HBox(5, labelHolder, label2);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 250, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can easily set a label's rotation by specifying the degrees of rotation as per the code below:
label.setRotate(45)
for 45 degrees, for example.
if you want to set it after some operations, that is, after the label has been displayed in the User Interface, you can use
Platform.runLater() lambda expression.
i.e
Platform.runLater(()->
label.setRotate(45);
)
Related
I have a simple FX example with a simple component.
package fxtest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane();
var r = new Rectangle(0, 0, 200, 200);
r.setFill(Color.GREEN);
var sp = new StackPane(r);
bp.setCenter(sp);
bp.setTop(new XPane());
bp.setBottom(new XPane());
bp.setLeft(new XPane());
bp.setRight(new XPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
package fxtest;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setMinSize(100, 100);
setPrefSize(100, 100);
widthProperty().addListener((o) -> {
populate();
});
heightProperty().addListener((o) -> {
populate();
});
populate();
}
private void populate() {
ObservableList<Node> children = getChildren();
Rectangle r = new Rectangle(getWidth(), getHeight());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
children.add(r);
Line line = new Line(0, 0, getWidth(), getHeight());
children.add(line);
line = new Line(0, getHeight(), getWidth(), 0);
children.add(line);
}
}
When run, it does what I expect:
When I grow the window, the X's grow.
But when I shrink the window, I get artifacts of the side panels.
I would have thought erasing the backgrounds would have fixed this, but I guess there's some ordering issue. But even still, when you drag the corner, all of the XPanes change size, and they all get repainted, but the artifacts remain.
I tried wrapping the XPanes in to a StackPane, but that didn't do anything (I didn't think it would, but tried it anyway).
How do I remedy this? This is JavaFX 13 on JDK 16 on macOS Big Sur.
Why you get artifacts
I think a different approach should be used rather than fixing the approach you have, but you could fix it if you want.
You are adding new rectangles and lines to your XPane in listeners. Every time the height or width changes, you add a new set of nodes, but the old set of nodes at the old height and widths remains. Eventually, if you resize enough, performance will drop or you will run out of memory or resources, making the program unusable.
A BorderPane paints its children (the center and the XPanes) in the order they were added without clipping, so these old lines will remain and the renderer will paint them over some panes as you resize. Similarly, some panes will paint over some lines because you are building up potentially lots of filled rectangles in the panes and they are partially overlapping lots of lines created.
To fix this, clear() the child node list in your populate() method before you add any new nodes.
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
// now you can add new nodes...
}
Alternate Solution
Change listeners on widths and heights aren't really the place to add content to a custom region, IMO.
I think that it is best to take advantage of the scene graph and let it handle the repainting and updating of existing nodes after you change the attributes of those nodes, instead of creating new nodes all the time.
Here is an example that subclasses Region and paints fine when a resize occurs.
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
super();
Rectangle border = new Rectangle();
Line topLeftToBottomRight = new Line();
Line bottomLeftToTopRight = new Line();
getChildren().addAll(
border,
topLeftToBottomRight,
bottomLeftToTopRight
);
border.setStroke(Color.BLACK);
border.setFill(Color.WHITE);
border.widthProperty().bind(
widthProperty()
);
border.heightProperty().bind(
heightProperty()
);
topLeftToBottomRight.endXProperty().bind(
widthProperty()
);
topLeftToBottomRight.endYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.startYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.endXProperty().bind(
widthProperty()
);
setMinSize(100, 100);
setPrefSize(100, 100);
}
}
On Region vs Pane
I'm not sure if you should be subclassing Pane or Region, the main difference between the two is that a Pane has a public accessor for a modifiable child list, but a Region does not. So it would depend on what you are trying to do. If it is just drawing X's like the example, then Region is appropriate.
On layoutChildren() vs binding
The Region documentation states:
By default a Region inherits the layout behavior of its superclass,
Parent, which means that it will resize any resizable child nodes to
their preferred size, but will not reposition them. If an application
needs more specific layout behavior, then it should use one of the
Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane,
BorderPane, GridPane, or AnchorPane.
To implement a more custom layout, a Region subclass must override
computePrefWidth, computePrefHeight, and layoutChildren. Note that
layoutChildren is called automatically by the scene graph while
executing a top-down layout pass and it should not be invoked directly
by the region subclass.
Region subclasses which layout their children will position nodes by
setting layoutX/layoutY and do not alter translateX/translateY, which
are reserved for adjustments and animation.
I am not actually doing that here, instead, I am binding in the constructor rather than overriding layoutChildren(). You could implement an alternate solution that operates as the documentation discusses, overriding layoutChildren() rather than using binding, but it is more complicated and less well documented on how to do that.
It is uncommon to subclass Region and override layoutChildren(). Instead, usually, a combination of standard layout Panes will be used and constraints set on the panes and nodes to get the desired layout. This lets the layout engine do a lot of the work such as snapping to pixels, calculating margins and insets, respecting constraints, repositioning content, etc, a lot of which would need to be done manually for a layoutChildren() implementation.
One common approach is to bind the relevant geometric properties to the desired properties of the enclosing container. A related example is examined here, and others are collected here.
The variation below binds the vertices of several Shape instances to the Pane width and height properties. Resize the enclosing stage to see how the BorderPane children conform to entries in the BorderPane Resize Table. The example also adds a red Circle, which stays centered in each child, growing and shrinking in the center to fill the smaller of the width or height. The approach relies on the fluent arithmetic API available to properties that implement NumberExpression or methods defined in Bindings.
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
/**
* #see https://stackoverflow.com/q/70311488/230513
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane(new XPane(), new XPane(),
new XPane(), new XPane(), new XPane());
stage.setScene(new Scene(bp, 640, 480));
stage.show();
}
private static class XPane extends Pane {
private final Rectangle r = new Rectangle();
private final Circle c = new Circle(8, Color.RED);
private final Line line1 = new Line();
private final Line line2 = new Line();
public XPane() {
setPrefSize(100, 100);
r.widthProperty().bind(this.widthProperty());
r.heightProperty().bind(this.heightProperty());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
getChildren().add(r);
line1.endXProperty().bind(widthProperty());
line1.endYProperty().bind(heightProperty());
getChildren().add(line1);
line2.startXProperty().bind(widthProperty());
line2.endYProperty().bind(heightProperty());
getChildren().add(line2);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
getChildren().add(c);
}
}
public static void main(String[] args) {
launch();
}
}
I have encountered yet another problem with javaFX light effects. When using perspective camera to follow player in 2D world, everything works fine, until I add light effects!
The CYAN color represents the background.. When light effects are ON, the level pane does not render at the edges of the scene and the background can be seen instead. This makes the bottom and right side of my levels invisible and therefore unplayable. In the full screen mode it is even worse!
here is my demonstration code:
import javafx.application.Application;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class dd extends Application {
PerspectiveCamera cam = new PerspectiveCamera(false);
public void start(Stage alku) throws Exception {
Pane testpane = new Pane();
Rectangle background = new Rectangle(500, 500);
background.setFill(Color.CYAN);
Rectangle rec = new Rectangle(500, 300);
Lighting lig = new Lighting();
Light.Point l = new Light.Point();
l.setX(200);
l.setY(150);
l.setZ(20);
l.setColor(Color.WHITE);
lig.setLight(l);
rec.setFill(Color.RED);
testpane.getChildren().addAll(background, rec);
Scene scene = new Scene(testpane, 300, 300);
cam.setTranslateX(30);
cam.setTranslateY(30);
alku.setTitle("TEST");
alku.setScene(scene);
rec.setEffect(lig);
scene.setCamera(cam);
alku.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you remove the camera or the light, the "rec" renders normally again. Just adjust the window size to see the problem better.
So the main question I have:
Is this a bug? If not, how can this rendering issue be solved without sacrificing lights and camera?
can I somehow only style the bottom border of an textfield?
I already tried
textfield.setStyle("-fx-border-bottom-color: #FF0000");
but it hasn't worked.
Is there an possibility to color the bottom border??
Greetings
MatsG23
Here is a quick and dirty example of how that can be done.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldStyleTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
root.setCenter(vBox);
HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(hBox);
TextField textField = new TextField("Hello World");
textField.setAlignment(Pos.BASELINE_CENTER);
hBox.getChildren().add(textField);
textField.setStyle("-fx-border-color: red; -fx-border-width: 0 0 10 0;");
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class TextFieldStyleTestLauncher {public static void main(String[] args) {TextFieldStyleTest.main(args);}}
Yes, it is possible to give each side a different color. From the JavaFX CSS Reference Guide, for Region:
CSS Property: -fx-border-color
Values: <paint> | <paint> <paint> <paint> <paint> [ , [<paint> | <paint> <paint> <paint> <paint>] ]*
Default: null
Comments: A series of paint values or sets of four paint values, separated by commas. For each item in the series, if a single paint value is specified, then that paint is used as the border for all sides of the region; and if a set of four paints is specified, they are used for the top, right, bottom, and left borders of the region, in that order. If the border is not rectangular, only the first paint value in the set is used.
Note: The above is actually from one row of a table, but Stack Overflow doesn't give a way of formatting things in a table.
Meaning you can target the bottom border only by using:
.text-field {
-fx-border-color: transparent transparent red transparent;
}
The -fx-border-width CSS property (and really all the CSS properties dealing with the Region#background and Region#border properties) behaves the same way. This means you can accomplish the same thing by setting the width of every side but the bottom to zero, just like in mipa's answer.
Here's an exaple using inline CSS (i.e. setStyle):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
TextField field = new TextField("Hello, World!");
field.setStyle("-fx-border-color: transparent transparent red transparent;");
field.setMaxWidth(Region.USE_PREF_SIZE);
primaryStage.setScene(new Scene(new StackPane(field), 300, 150));
primaryStage.show();
// Remove blue outline from when TextField is focused. This
// makes it easier to see the red border.
primaryStage.getScene().getRoot().requestFocus();
}
}
Which gives the following output:
Note that most of the "borders" added by modena.css (the default user-agent style sheet in JavaFX 8+) are not actually borders. Instead, they're multiple backgrounds with different insets.
I'm trying to create a draggable selection box for a sketching program in JavaFX, one like this:
I'm only not sure how to do it. I initially wanted to do it like this: capture the mouse coordinates when the mouse is pressed and do it again at the end of a drag, then calculate the height and width and make a transparent button with a black border with these properties.
But, then I realized that when I do it like this, it is not possible to see the button while you are scaling the plane, unless you draw and delete a lot of buttons.
So, I wondered if there is a better way to do something like this or is my reasoning above right? Thanks
I would use a Rectangle instead of a Button. Just do what you describe, but update the size (and position) of the rectangle on mouse drag, instead of only adding it when the mouse is released.
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 SelectionRectangle extends Application {
private double mouseDownX ;
private double mouseDownY ;
#Override
public void start(Stage primaryStage) {
Rectangle selectionRectangle = new Rectangle();
selectionRectangle.setStroke(Color.BLACK);
selectionRectangle.setFill(Color.TRANSPARENT);
selectionRectangle.getStrokeDashArray().addAll(5.0, 5.0);
Pane pane = new Pane();
pane.setMinSize(600, 600);
pane.getChildren().add(selectionRectangle);
pane.setOnMousePressed(e -> {
mouseDownX = e.getX();
mouseDownY = e.getY();
selectionRectangle.setX(mouseDownX);
selectionRectangle.setY(mouseDownY);
selectionRectangle.setWidth(0);
selectionRectangle.setHeight(0);
});
pane.setOnMouseDragged(e -> {
selectionRectangle.setX(Math.min(e.getX(), mouseDownX));
selectionRectangle.setWidth(Math.abs(e.getX() - mouseDownX));
selectionRectangle.setY(Math.min(e.getY(), mouseDownY));
selectionRectangle.setHeight(Math.abs(e.getY() - mouseDownY));
});
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use a mouse released handler to figure out what's selected, by looking at the x, y, width, and height properties of the rectangle, as needed.
I am currently working on an assignment where i must print a circle in the center of the primary stage and 4 buttons on the bottom center area of the primary stage which move the circle, up, down, left, and right when clicked. when i run my code, my circle is filled in with the color black. I have set the stroke of the circle to be black but i have not set the circle to be filled black. I know i can just set my circle to be filled white and somewhat solve the problem, but i am wondering if anyone knows why this is happening. Also, i cannot get the Circle and the buttons to print into the same window. I can get either the circle to print by setting the primaryStage to the scene or print the buttons by setting the scene to hBox and then setting the primaryStage to the scene. How should i best change my code so that the buttons and the circle are both displayed?
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.geometry.Pos;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
public class Btest extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Create a border pane
BorderPane pane = new BorderPane();
// create Hbox, set to bottom center
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.BOTTOM_CENTER);
Button btLeft = new Button("Left");
Button btDown = new Button("Down");
Button btUp = new Button("Up");
Button btRight = new Button("Right");
hBox.getChildren().addAll(btLeft, btDown, btUp, btRight);
// Lambda's
btLeft.setOnAction((e) -> {
System.out.println("Process Left");
});
btDown.setOnAction((e) -> {
System.out.println("Process Down");
});
btUp.setOnAction(e -> {
System.out.println("Process Up");
});
btRight.setOnAction((e) -> {
System.out.println("Process Right");
});
pane.setCenter(new CenteredCircle("Center"));
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 300, 300);
//set stage and display
primaryStage.setTitle("ShowBorderPane"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
Application.launch(args);
}
}
// create custom class for circle
class CenteredCircle extends StackPane {
public CenteredCircle(String title) {
setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
Circle circle = new Circle();
circle.setStroke(Color.BLACK);
circle.setCenterX(50);
circle.setCenterY(50);
circle.setRadius(50);
getChildren().add(circle);
}
}
"Why is my circle filled Black even though i haven't set it to be filled?"
Because the default color is black. See the doc of Shape.setFill() method:
Defines parameters to fill the interior of an Shape using the settings
of the Paint context. The default value is Color.BLACK for all shapes
except Line, Polyline, and Path. The default value is null for those
shapes.
"... Also, i cannot get the Circle and the buttons to print into the same window."
Put the Hbox to the parent BorderPane, for instance into the bottom:
pane.setBottom( hBox );