I think you are on to something. I like the idea of extracting the layout logic into a set of reusable *Layout classes. Swing has its own Border, Grid, Flow, ... layouts that can be set on a Panel, but we don't have to do that - a reusable class would suffice. Additionally, it might contain the per-node layout constraints (I never liked the idea of the "attached" properties like HBox.setHgrow() stored in properties).
These layout classes can start as implementation detail, and once sufficiently developed, could be exposed as public APIs for use in skins and custom components. As an implementation detail, it would not require API changes in the existing controls It can even rely on the existing attached properties. What do you think? -andy PS. some formatting and possibly images got lost in the mail. From: John Hendrikx <[email protected]> Date: Sunday, July 12, 2026 at 13:43 To: [email protected] <[email protected]> Subject: [External] : Proposal to split off layout and layout metric methods from Node to interfaces it implements 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
