javajavafx

Getting The Visual Bounds of a Node


I'm trying to make a custom border for a text field. I used the boundsInParentProperty to make the border. However, these bounds behave like a bounding box which aren't suitable for my case. The following is a snapshot of the text field with the border:

Text field with custom border

The problems arise when the node is rotated, the following is another snapshot when the text field is rotated:

enter image description here

This is a sample code:

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;

public class CustomBorder extends Application {
    @Override
    public void start(Stage primaryStage) {
        TextField textField = new TextField("Hello, World!");
        textField.setStyle("-fx-padding: 8; -fx-max-width: 20; -fx-font-size: 18;" +
                "-fx-border-insets: 0; -fx-background-insets: 0; -fx-background-radius: 0; " +
                "-fx-border-style: none; -fx-background-color: rgba(255,0,0,0.5); -fx-min-width: 140");

        textField.setRotate(-25);


        Path border = new Path();
        border.getElements().addAll(createBorder(textField, BorderLine.LEFT));
        border.getElements().addAll(createBorder(textField, BorderLine.TOP));
        border.getElements().addAll(createBorder(textField, BorderLine.RIGHT));
        border.getElements().addAll(createBorder(textField, BorderLine.BOTTOM));
        border.setManaged(false);
        border.setViewOrder(0);

        HBox root = new HBox(textField, border);
        root.setBackground(Background.fill(Color.TRANSPARENT));
        root.getTransforms().add(new Scale(2, 2));
        root.relocate(100, 100);

        primaryStage.setTitle("Custom Border");
        primaryStage.setScene(new Scene(root, 500, 400));
        primaryStage.show();

        root.requestFocus();
    }

    @NotNull
    private PathElement[] createBorder(Region region, @NotNull BorderLine borderLine) {
        MoveTo moveTo = new MoveTo();
        LineTo lineTo = new LineTo();
        switch (borderLine) {
            case LEFT -> region.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
                moveTo.setX(newValue.getMinX());
                moveTo.setY(newValue.getMinY());
                lineTo.setX(newValue.getMinX());
                lineTo.setY(newValue.getMaxY());
            });
            case TOP -> region.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
                moveTo.setX(newValue.getMinX());
                moveTo.setY(newValue.getMinY());
                lineTo.setX(newValue.getMaxX());
                lineTo.setY(newValue.getMinY());
            });
            case RIGHT -> region.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
                moveTo.setX(newValue.getMaxX());
                moveTo.setY(newValue.getMinY());
                lineTo.setX(newValue.getMaxX());
                lineTo.setY(newValue.getMaxY());
            });
            case BOTTOM -> region.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
                moveTo.setX(newValue.getMinX());
                moveTo.setY(newValue.getMaxY());
                lineTo.setX(newValue.getMaxX());
                lineTo.setY(newValue.getMaxY());
            });
        }

        return new PathElement[]{moveTo, lineTo};
    }

    public enum BorderLine {
        LEFT, RIGHT, TOP, BOTTOM;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

How can I make the border have the correct x/y values?


Solution

  • Since you're creating the text field and the border as two separate components, you need to rotate both of them. One way is to put them in a Group and rotate the Group (instead of the text field):

        public void start(Stage primaryStage) {
            TextField textField = new TextField("Hello, World!");
            textField.setStyle("-fx-padding: 8; -fx-max-width: 20; -fx-font-size: 18;" +
                    "-fx-border-insets: 0; -fx-background-insets: 0; -fx-background-radius: 0; " +
                    "-fx-border-style: none; -fx-background-color: rgba(255,0,0,0.5); -fx-min-width: 140");
    
    //        textField.setRotate(-25);
    
    
            Path border = new Path();
            border.getElements().addAll(createBorder(textField, BorderLine.LEFT));
            border.getElements().addAll(createBorder(textField, BorderLine.TOP));
            border.getElements().addAll(createBorder(textField, BorderLine.RIGHT));
            border.getElements().addAll(createBorder(textField, BorderLine.BOTTOM));
            border.setManaged(false);
            border.setViewOrder(0);
    
            Group textBox = new Group(textField, border);
            textBox.setRotate(-25);
    
    //        HBox root = new HBox(textField, border);
            HBox root = new HBox(textBox);
            root.setRotate(-25);
            root.setBackground(Background.fill(Color.TRANSPARENT));
            root.getTransforms().add(new Scale(2, 2));
            root.relocate(100, 100);
    
            primaryStage.setTitle("Custom Border");
            primaryStage.setScene(new Scene(root, 500, 400));
            primaryStage.show();
    
            root.requestFocus();
        }