From e604a8f4eac9331f47d2f4644dd297ed10a94296 Mon Sep 17 00:00:00 2001 From: Marius Hanl Date: Thu, 23 Jul 2026 01:20:35 +0200 Subject: [PATCH] Let SvgNode extend from Region --- README.md | 33 ++++++++++-- .../java/tools/maran/svgnode/SvgNode.java | 52 ++++++++----------- .../maran/svgnode/manual/SvgNodeSampler.java | 13 ++++- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 282419e..1fe31f5 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,7 @@ A lightweight, optimized JavaFX node for rendering SVG paths at any size. Fully - 🎨 Render any SVG path as a JavaFX node - 🔗 No dependencies - will use your provided JavaFX runtime -- ⚡ Optimized to be efficient and have a tiny footprint, so you can render 100 `SvgNode` without any problem. - - This is achieved by `SvgNode` extending from `Parent`, skipping size calculations and only initializing properties when needed +- ⚡ Optimized to be efficient and have a tiny footprint, so you can render 1000 `SvgNode` instances without any problem. - 📐 Uniform rasterization with a single `size` property - 📄 FXML-compatible with attribute and constant-based usage - 🎭 CSS-stylable via `.svg-node` and `.svg` style classes. By default, the SVG automatically adjusts its color based on the background - just like text @@ -217,11 +216,37 @@ The API of `SvgNode` matches what is the de-facto standard for SVG libraries, in #### Changelog -> Version 2.0.0 +##### Version 2.0.0 + +This is release contains breaking changes. See below. - Made all properties CSS styleable. This is especially interesting together with CSS transitions. - Renamed `svgColor` to `color`. This is a breaking change. See the naming above why this was done. +- Extend from `Region`. Initially, `SvgNode` extended from `Parent` but unfortunately the JavaFX API will be against you. +You can influence the layout bounds calculation, it simply is not public. JavaFX will then try to derive the layout bounds from the children, which is not what we want. +This will lead to a wrong positioning in some cases where the SVG is more rectangular. It also results in a weird double layout, which is not the case with `Region` + - Advantage: We can set a background and border. Performance is the same. + +Example: + +```text ++-------------------------------+ +| SvgNode | +| 24x24 | ++-------------------------------+ +| SVG | +| 24x12 | ++-------------------------------+ +| SvgNode | +| 24x24 | ++-------------------------------+ +``` + +Here, the layout bounds should be 24x24, but will be calculated as 24x12 because of the inner SVG. + +The idea is that the `SvgNode` has always the same width and height and will adjust the inner SVG to be centered. +This works fine, but as soon as other nodes use the layout bounds for the calculation (which is done sometimes), it will be wrong, not honoring the pref size. -> Version 1.0.0 +##### Version 1.0.0 - Initial release diff --git a/src/main/java/tools/maran/svgnode/SvgNode.java b/src/main/java/tools/maran/svgnode/SvgNode.java index 0d0ce88..a559552 100644 --- a/src/main/java/tools/maran/svgnode/SvgNode.java +++ b/src/main/java/tools/maran/svgnode/SvgNode.java @@ -13,13 +13,12 @@ import javafx.css.converter.PaintConverter; import javafx.css.converter.SizeConverter; import javafx.css.converter.StringConverter; -import javafx.scene.Parent; import javafx.scene.layout.Background; import javafx.scene.layout.Region; import javafx.scene.paint.Paint; import javafx.scene.shape.SVGPath; -/// Node to show an SVG path with the specified size. +/// Node to show an SVG path with the specified size and color. /// /// # Java usage /// @@ -55,7 +54,7 @@ /// ``` /// /// @author Marius Hanl -public class SvgNode extends Parent { +public class SvgNode extends Region { private static final double DEFAULT_SIZE = 24.0; private static final String DEFAULT_PATH = ""; @@ -220,54 +219,47 @@ public final void setColor(Paint value) { } @Override - public double minHeight(double width) { - return getSize(); - } - - @Override - public double minWidth(double height) { - return getSize(); - } - - @Override - public double prefHeight(double width) { - return getSize(); + protected double computeMinHeight(double width) { + return snappedTopInset() + getSize() + snappedBottomInset(); } @Override - public double prefWidth(double height) { - return getSize(); + protected double computeMinWidth(double height) { + return snappedLeftInset() + getSize() + snappedRightInset(); } @Override - protected double computeMinHeight(double width) { - return getSize(); + protected double computePrefHeight(double width) { + return snappedTopInset() + getSize() + snappedBottomInset(); } @Override - protected double computeMinWidth(double height) { - return getSize(); + protected double computePrefWidth(double height) { + return snappedLeftInset() + getSize() + snappedRightInset(); } @Override - protected double computePrefHeight(double width) { - return getSize(); + protected double computeMaxHeight(double width) { + return snappedTopInset() + getSize() + snappedBottomInset(); } @Override - protected double computePrefWidth(double height) { - return getSize(); + protected double computeMaxWidth(double height) { + return snappedLeftInset() + getSize() + snappedRightInset(); } @Override protected void layoutChildren() { - double nodeSize = getSize(); + double left = getInsets().getLeft(); + double top = getInsets().getTop(); + double contentWidth = getWidth() - left - getInsets().getRight(); + double contentHeight = getHeight() - top - getInsets().getBottom(); double width = svgContent.svgWidth; double height = svgContent.svgHeight; - double x = (nodeSize - width) / 2; - double y = (nodeSize - height) / 2; - svgContent.resizeRelocate(x, y, width, height); + double x = left + (contentWidth - width) / 2; + double y = top + (contentHeight - height) / 2; + svgContent.resizeRelocate(snapPositionX(x), snapPositionY(y), width, height); } /// Gets the {@code CssMetaData} associated with this class. @@ -327,7 +319,7 @@ public StyleableProperty getStyleableProperty(SvgNode node) { private static final List> STYLEABLES; static { - List> metadata = Parent.getClassCssMetaData(); + List> metadata = Region.getClassCssMetaData(); final List> styleables = new ArrayList<>(metadata.size() + 3); styleables.addAll(metadata); styleables.add(PATH); diff --git a/src/test/java/tools/maran/svgnode/manual/SvgNodeSampler.java b/src/test/java/tools/maran/svgnode/manual/SvgNodeSampler.java index 62843f7..577c2ad 100644 --- a/src/test/java/tools/maran/svgnode/manual/SvgNodeSampler.java +++ b/src/test/java/tools/maran/svgnode/manual/SvgNodeSampler.java @@ -15,6 +15,10 @@ import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; +import javafx.scene.layout.Background; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; @@ -138,7 +142,7 @@ private void addButtonGraphics() { } private void addLabels() { - view.getChildren().add(sectionLabel("Label and SvgNode styled with CSS (+hover effect)")); + view.getChildren().add(sectionLabel("Usage with Label, CSS (Hover), Background, Border")); Label label = new Label("SVG with text", new SvgNode(HEART)); @@ -157,7 +161,12 @@ private void addLabels() { } """)); - view.getChildren().add(new HBox(4, label, svgNodeHover)); + SvgNode svgNodeBg = new SvgNode(HEART); + svgNodeBg.setColor(Color.RED); + svgNodeBg.setBackground(Background.fill(Color.MISTYROSE)); + svgNodeBg.setBorder( new Border(new BorderStroke(Color.LIGHTGREEN, BorderStrokeStyle.SOLID, null, BorderStroke.THICK))); + + view.getChildren().add(new HBox(4, label, svgNodeHover, svgNodeBg)); } private void addStaticSvgs() {