On 14/07/2026 22:52, Marius Hanl wrote:
> I like this idea.
> Also +1 to have reusable Layout algorithms. Copy paste Code is almost
> never a good idea.
>
> I also like the 'Bonus' section and some potential usecases, like with
> Canvas.
>
> I was wondering two things:
> - Do we really need the layout bounds? I need to admit,
> getLayoutBounds() is bothering me.
> You can not influence (override) the behavior and it can be different
> than the width and height of the node as computed by the prefXXX method.
> For example, LabeledSkinBase sometimes uses the layout bounds,
> sometimes the pref size. 

Yeah, I don't like having the layout bounds either, but it is used for
the baseline layouts (if I remember correctly, most layout constraints
are originated at (0,0), but with baseline layouts they are originated
at like (0,-12) or something depending on where the baseline of the
child should be) .  We could leave it out initially, but that would that
mean neither HBox or VBox could be fully implemented as a layout, and
they're probably some of the most common simple layouts used in skins.

I'm open for ideas here on how we could limit it to only constraint
information.  I took a brief look at base line layouts, and I think it
is almost unavoidable that there is a feedback loop here (ie. doing
multiple computeSize calls before settling on something all things in
your layout can agree on).  It may be possible to extract a method that
gives only baseline information, instead of the full layout constraints.

> - Could such a Layout container be stateless?
> My guess is 'no', but I was wondering anyway since it probably does
> not need to save any state. Usually it just needs Nodes and it will
> position them accordingly. 

There is actually no restrictions on how you provide the algorithm, but
if you wish your Layout to participate as a child of other layouts, then
I do think you'll need an instance.  And likely also for performance
reasons (caching snapped values and such).

If you have an instance, then you may as well track certain things on
that (ie. layout properties, like spacing, alignment, snap-to-pixel,
etc.), and when you have that you may as well also provide it with the
children it needs to operate on (for controls like HBox, this is usually
a filtered list of managed children only, so often you already have to
create this "2nd" list anyway).

Don't expect a 50 line class for a horizontal layout; the layout
algorithms for JavaFX are very complex; basically Region is one big
"helper" class for HBox and VBox, due to how hard it is to do things
like content bias and base lines.  If I had to guess, a simple "HBox"
has like 1000-2000 lines of code involved in layouts (if you include all
the helpers).

What I've done in a proof of concept is to make the Layout class a
regular Java class, without JavaFX properties, no listeners, or
subscriptions of any kind.  You configure it, query its min/pref/max
size, and call resizeRelocate on it, that's it.  If you need to change
it (ie. a child should be removed, or spacing needs to change) then the
owner of the layout needs to reconfigure it, and tell it to layout again.


--John

>
> -- Marius
>
> Am 12.07.2026 um 22:40 schrieb John Hendrikx:
>> 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