> > 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 >
