JavaFX Border don't fit for node with custom shape

Maran23 :

I'm trying to figure out, if it is possible to draw a border for a node with a custom shape. Currently the border doesn't fit the shape of the node.

This is what it currently looks like:
enter image description here

The shape is achieved by the following CSS:

.arrow-tail {
    -fx-shape: "M 0 0 L 10 0 L 10 10 L 0 10 L 10 5 Z";
}

.arrow-head {
    -fx-shape: "M 0 0 L 10 5 L 0 10 Z";
}

This is the important code of the arrow class where the CSS is used:

public class Arrow extends HBox {

    public void Arrow(Node graphic, String title) {
        getChildren().addAll(getArrowTail(), getArrowMiddlePart(graphic, title), getArrowHead());
    }

    private final Region getArrowTail() {
        final Region arrowTail = new Region();
        arrowTail.setMinWidth(10);
        arrowTail.getStyleClass().add("arrow-tail");
        return arrowTail;
     }

     private final Node getArrowMiddlePart(Node graphic, String text) {
        labelTitle = new Label(text);
        labelTitle.setGraphic(graphic);
        labelTitle.idProperty().bind(idProperty());

        final Tooltip tooltip = new Tooltip();
        tooltip.textProperty().bind(labelTitle.textProperty());
        Tooltip.install(labelTitle, tooltip);

        final HBox arrowMiddlePart = new HBox(labelTitle);
        arrowMiddlePart.minWidthProperty().bind(minWidthProperty());
        arrowMiddlePart.setAlignment(Pos.CENTER);
        return arrowMiddlePart;
   }

    private final Region getArrowHead() {
        final Region arrowHead = new Region();
        arrowHead.setMinWidth(10);
        arrowHead.getStyleClass().add("arrow-head");
        return arrowHead;
    }  

}

The Arrow class is a HBox, where I create a custom shaped region as arrow tail and arrow head and another HBox with an label in it as arrow middle part.

fabian :

Unfortunately there doesn't seem to be a way to set the borders for the different sides of a Region with a shape applied to it independently.

I recommend extending Region directly adding a Path as first child and overriding layoutChildren resize this path.

public class Arrow extends Region {

    private static final double ARROW_LENGTH = 10;
    private static final Insets MARGIN = new Insets(1, ARROW_LENGTH, 1, ARROW_LENGTH);

    private final HBox container;
    private final HLineTo hLineTop;
    private final LineTo tipTop;
    private final LineTo tipBottom;
    private final LineTo tailBottom;

    public Arrow(Node graphic, String title) {
        Path path = new Path(
                new MoveTo(),
                hLineTop = new HLineTo(),
                tipTop = new LineTo(ARROW_LENGTH, 0),
                tipBottom = new LineTo(-ARROW_LENGTH, 0),
                new HLineTo(),
                tailBottom = new LineTo(ARROW_LENGTH, 0),
                new ClosePath());

        tipTop.setAbsolute(false);
        tipBottom.setAbsolute(false);

        path.setManaged(false);
        path.setStrokeType(StrokeType.INSIDE);
        path.getStyleClass().add("arrow-shape");

        Label labelTitle = new Label(title, graphic);
        container = new HBox(labelTitle);

        getChildren().addAll(path, container);
        HBox.setHgrow(labelTitle, Priority.ALWAYS);
        labelTitle.setAlignment(Pos.CENTER);
        labelTitle.setMaxWidth(Double.POSITIVE_INFINITY);
    }

    @Override
    protected void layoutChildren() {
        // hbox layout
        Insets insets = getInsets();
        double left = insets.getLeft();
        double top = insets.getTop();
        double width = getWidth();
        double height = getHeight();
        layoutInArea(container,
                left, top,
                width - left - insets.getRight(), height - top - insets.getBottom(),
                0, MARGIN, true, true, HPos.LEFT, VPos.TOP);

        // adjust arrow shape
        double length = width - ARROW_LENGTH;
        double h2 = height / 2;

        hLineTop.setX(length);

        tipTop.setY(h2);
        tipBottom.setY(h2);
        tailBottom.setY(h2);
    }

    @Override
    protected double computeMinWidth(double height) {
        Insets insets = getInsets();
        return 2 * ARROW_LENGTH + insets.getLeft() + insets.getRight() + container.minWidth(height);
    }

    @Override
    protected double computeMinHeight(double width) {
        Insets insets = getInsets();
        return 2 + insets.getTop() + insets.getBottom() + container.minHeight(width);
    }

    @Override
    protected double computePrefWidth(double height) {
        Insets insets = getInsets();
        return 2 * ARROW_LENGTH + insets.getLeft() + insets.getRight() + container.prefWidth(height);
    }

    @Override
    protected double computePrefHeight(double width) {
        Insets insets = getInsets();
        return 2 + insets.getTop() + insets.getBottom() + container.prefHeight(width);
    }

    @Override
    protected double computeMaxWidth(double height) {
        Insets insets = getInsets();
        return 2 * ARROW_LENGTH + insets.getLeft() + insets.getRight() + container.maxWidth(height);
    }

    @Override
    protected double computeMaxHeight(double width) {
        Insets insets = getInsets();
        return 2 + insets.getTop() + insets.getBottom() + container.maxHeight(width);
    }

}

CSS

.arrow-shape {
    -fx-fill: dodgerblue;
    -fx-stroke: black;
}

Note that the code would be simpler, if you extend HBox, but this would allow other classes access to the child list which could result in removal of the Path; extending Region allows us to keep the method protected preventing this kind of access but requires us to implement the compute... methods and the layouting of the children.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=107186&siteId=1