Reputation: 256
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:
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.
Upvotes: 3
Views: 215
Reputation: 82461
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.
Upvotes: 1