>
> This creates two trees: the standard scene graph (unchanged)
> where Text and Graphic are direct children of Label; and a virtual
> layout tree, where an HBoxLayout is referenced by Label which in turn
> references the Text and Graphic nodes:


> The solid lines indicate the SceneGraph relation, and the dashed lines
> are the virtual layout.


> A more complicated label that has both a title and subtitle would look
> like this:


I think that some graphics got lost in the email.

On Sun, Jul 12, 2026 at 11:43 PM John Hendrikx <[email protected]>
wrote:

> Hi everyone,
>
> TL;DR -- let Node implement a Layoutable interface so it can participate
> in virtual layouts that do not require heavy-weight scene graph layout
> container nodes.
>
> In the past, I've fixed quite a few layout bugs related to duplicated
> layout calculation code in Skins.  Skins often duplicate layout code
> because they want to stay as light-weight as possible, and using layout
> containers within their structure is counter to that goal.  A Label
> could have used an HBox(graphic, text) internal structure, but to avoid
> the overhead of using a complete scene graph Node, it instead does its
> own graphic and text positioning.  This is however far more complicated
> than it seems. The graphic can be full fledged Node, with its own
> min/pref/max sizes, a content bias, etc.  When Label duplicates the
> calculations that basically HBox does that creates two problems:
>
> - Any bugs fixed in HBox won't fix Skins that use or are supposed to use
> an equivalent horizontal layout (many skins have or can have horizontal
> layouts: Labels, ComboBox, Slider, Spinner, TitledPane)
> - The duplicated calculations often make assumptions and take short-cuts
> (ie. they assume a fixed size, calling only prefWidth(-1) and often
> ignore things like content bias).
>
> *Virtual layout containers*
>
> At some point on one of these fix PR's, we discussed perhaps allowing
> the layout calculations of the major layout containers to be made
> re-usable.  In other words, a Label skin and other skins or controls
> could use the layout calculation of HBox but without creating an HBox in
> the scene graph.
>
> This could work by having the Label skin create an *HBoxLayout* -- a
> virtual layout container which is given the Text and Graphic nodes.  The
> children are also added as direct children of Label (just like they are
> now).  This creates two trees: the standard scene graph (unchanged)
> where Text and Graphic are direct children of Label; and a virtual
> layout tree, where an HBoxLayout is referenced by Label which in turn
> references the Text and Graphic nodes:
>
> The solid lines indicate the SceneGraph relation, and the dashed lines
> are the virtual layout.
>
> A more complicated label that has both a title and subtitle would look
> like this:
>
> *Introducing the Layoutable interface*
>
> From the above you may have noticed that the HBoxLayout can contain both
> Nodes and other virtual layout containers.  As the layout containers are
> supposed to be light-weight, these won't be Nodes. So in order to be
> able to compose virtual layouts, both Nodes and virtual layout
> containers need to have some kind of common ancestor.  This would be
> the *Layoutable* interface.  It contains basically a subset of methods
> that Node already has, all related to layout calculations:
>
> The measuring methods:
>
> doubleminWidth(doubleheight);
>
> doubleprefWidth(doubleheight);
>
> doublemaxWidth(doubleheight);
>
> doubleminHeight(doublewidth);
>
> doubleprefHeight(doublewidth);
>
> doublemaxHeight(doublewidth);
>
> booleanisResizable();
>
> doublegetBaselineOffset();
>
> Orientation getContentBias();
>
> The positioning query methods (used primarily for base line calculations
> and testing):
>
> doublegetLayoutX();
>
> doublegetLayoutY();
>
> Bounds getLayoutBounds();
>
> The positioning setting methods (used by layout containers to place
> nodes or other virtual containers):
>
> voidresizeRelocate(doublex, doubley, doublew, doubleh);
>
> voidrelocate(doublex, doubley);
>
> These are the basic methods that the interface would need (or split over
> several interfaces, the measurement methods can for example be in a
> Measurable interface).  To support constraints, the interface would also
> need to expose `getProperties`, however, I suggest abstracting this by
> introducing two new methods:
>
> <T> T getConstraint(ConstraintKey<T> key, T defaultValue);
>
> <T> voidsetConstraint(ConstraintKey<T> key, T value);
>
> Where ConstraintKey is a simple immutable key helper class that layout
> containers can create for easy type safe access:
>
> staticfinalConstraintKey<Priority> CHILD_GROW=
> ConstraintKey.of(Priority.class)
>
> These can still be stored in the properties map as usual, so their
> implementation is trivial.  I think however this is nicer than exposing
> the properties map in this interface.
>
> The above interface would be implemented by Node and by any virtual
> layout container.  Layouts like HBoxLayout would then accept a list
> of *Layoutable* children, which can be either Nodes or nested virtual
> layouts.
>
> There is still one missing piece to being able to fully virtualize
> layouts like this: we need to know how screen scaling and snapping is
> set up in order to position nodes that take part of a virtual layout
> according to the wishes of the owner of the virtual layout (for example
> Label).
>
> *The LayoutContext interface*
>
> This interface provides general information related to rendering pixels
> to the screen; this information can often be shared amongst all virtual
> containers in a virtual layout tree. It contains the following methods:
>
> doublegetSnapScaleX();
>
> doublegetSnapScaleY();
>
> booleanisSnapToPixel();
>
> These methods are used by a virtual layout container to do correct
> positioning according to the wishes of the owner of the virtual layout.
> So in our Label example, the Label would provide this scope matching its
> own snap settings.  All virtual containers it creates use this
> information when calculating sizes and positions.
>
> The above methods are not new either; they are currently provided by
> Region as protected methods (and so they are public API already), aside
> from `isSnapToPixel` which is already public.  My proposal would then be
> to simply have Region implement this interface, promoting the two snap
> scale methods to public.  Alternatively, we could name them
> `getRenderScaleX` and `getRenderScaleY` as that's basically what they
> are (Region gets this from Window directly).
>
> *Result*
>
> If Node implements Layoutable, and Region implements LayoutContext, then
> we have all the things we need to create a virtual layout container.  A
> LabelSkin can provide the Label itself as the LayoutContext, and the
> Node children that should participate as its Layoutables:
>
> - LabelSkin creates a HBoxLayout container passing itself (as
> LayoutContext) and the two children the layout should position (Graphic
> and Text)
> - It also still adds the two Node children as its children in the scene
> graph (unchanged)
> - In its layoutChildren callback it simply does:
> hboxLayout.resizeRelocate(x, y, w, h)
>
> When resizeRelocate is called, the virtual layout container will:
>
> - Call computeMin/Pref/Max methods which cascade as usual down the
> (virtual) tree (which will end up at real Nodes, like Text and Graphic)
> - Use appropriate snapping by delegating this to the LayoutContext
> provided (using Label's settings effectively)
> - Positioning and resizing each Layoutable in its virtual tree which
> ends up positioning also the real Nodes
>
> The algorithm here can then be shared by HBox. HBox would be stripped of
> all its layout code, leaving roughly only a set of property
> definitions.  It would then internally create the light-weight
> HBoxLayout as well (much lighter than a full Node) and delegate the
> relevant properties like alignment/spacing to the virtual layout, as
> well as its measurement methods (like minSize, prefSize, etc).  Its
> layoutChildren would similarly simply call `resizeRelocate` to apply the
> layout.
>
> *Bonus*
>
> Generally, the leafs of a virtual layout tree are Nodes, but they don't
> strictly have to be.
>
> This opens up a few interesting use cases.  A spacer needed in a virtual
> layout doesn't have to be a Node; it can simply be a Layoutable with the
> desired metrics, with a no-op resizeRelocate.  The space would be
> reserved, but no actual Node needs to occupy it.
>
> It can also open other use cases, like having a Canvas display boxes
> that are positioned via standard layout algorithms that come with
> JavaFX.  A leaf Layoutable would then simple draw some primitive on a
> Canvas in its resizeRelocate method.
>
> Let me know what you think,
>
> I've got a proof of concept working for all this where I've implemented
> HBoxLayout.  The missing to make virtual layouts a lot more integrated
> is having Node being able to participate without having to wrap it.
>
> --John
>

Reply via email to