Hi Nir,

None of the other toolkits are a close enough match to "copy" without
having to adopt a substantially different approach. Also I'm not sure
we'd want to, because I don't see it being better in the ones I checked.

WPF is probably the closest to what FX does, and it does have
inheritance of snapping/layout-rounding, but not quite the way I'm
suggesting below (it's more like an "inherit/override" model rather than
how visible works in FX). Since an unsnapped parent can give you
fractional coordinates/bounds that a child can't really recover from, I
think opting out an entire subtree is the cleaner model, so I wouldn't
do what WPF does.

QT I think uses device-independent coordinates at the UI level and then
maps those to device pixels using the device-pixel ratio. The snapping
however occurs after layout, not during.  This means that it doesn't
what it is snapping (gap / content)

Flutter just leaves more of this as a problem for the widgets/user. You
basically query the device-pixel ratio, and if you want really crisp
pixel-sensitive UI, you have to make sure you choose appropriate
locations/sizes yourself. FX is a lot more friendly here.

My conclusion: FX is not doing this badly at all, and none of the
toolkits I checked seem to be clearly better (just different).

--John


On 10/09/2026 09:26, Nir Lisker wrote:
> I have to wonder if there isn't already existing literature on
> snapping. Every UI toolkit (not only in Java) has had to deal with
> snapping in some way, we're not doing anything new.
> What do they do? Are there any established algorithms in papers?
>
> - Nir
>
> On Thu, Sep 10, 2026 at 4:33 AM John Hendrikx
> <[email protected]> wrote:
>
>     I did some deeper digging -- there are exactly two use cases within FX
>     where snapping is turned off:
>
>     1. The dot in a radio button is rendered without snapping to ensure it
>     is drawn exactly center of the radio button circle -- having snapping
>     inherit would not break this as the dot has no children
>
>     2. The show-hide-columns icon in a table header row has snapping
>     off for
>     the same reasons (to ensure the icon is exactly centered in its
>     box) --
>     inheritance again won't interfere here
>
>     This is also precisely what I had speculated as a possible use case:
>     tiny controls / stylings (only a few pixels in size) that would be
>     better rendered in an exact location as even a single pixel deviation
>     would be noticeable.
>
>     --John
>
>     On 10/09/2026 03:05, John Hendrikx wrote:
>     > On 09/09/2026 20:31, Marius Hanl wrote:
>     >> I'm especially wondering if we even need the 'snapToPixel'
>     property at
>     >> all.
>     >> I never needed it, never saw it being used or set somewhere else.
>     >>
>     >> With that said, I was also wondering about potential
>     consequences to
>     >> performance if we try to track the parent snapping property.
>     >> If we move forward with that, we need to find a good implementation
>     >> for that. 
>     > There are already precedents here; see disableProperty (local,
>     writable)
>     > and disabledProperty (read-only effective).  The effective snapping
>     > setting to used is cached locally, so to querying it is just as
>     fast as
>     > it is now.  The effective state needs to be computed when
>     reparenting
>     > (rare) or when someone actually changes it (even more rare).
>     >
>     > --John
>     >
>     >
>     >> -- Marius
>     >>
>     >> On 9/9/26 4:52 PM, John Hendrikx wrote:
>     >>> The docs on `snapToPixelProperty` state:
>     >>>
>     >>> /**
>     >>>
>     >>> * Defines whether this region adjusts position, spacing, and size
>     >>> values of
>     >>>
>     >>> * its children to pixel boundaries. This defaults to true,
>     which is
>     >>> generally
>     >>>
>     >>> * the expected behavior in order to have crisp user interfaces. A
>     >>> value of
>     >>>
>     >>> * false will allow for fractional alignment, which may lead to
>     "fuzzy"
>     >>>
>     >>> * looking borders.
>     >>>
>     >>> */
>     >>>
>     >>> This does leave a lot ambiguous as to what should happen when
>     a region
>     >>> is not snapped, but its children (or grand children) are
>     snapped again.
>     >>>
>     >>> The problem:
>     >>>
>     >>> 1. When an ancestor is unsnapped, no amount of snapping of
>     children will
>     >>> return the claimed "crisp user interfaces" for those
>     children.  Once an
>     >>> ancestor decides to allocate a non-integer number of pixels at a
>     >>> non-grid aligned location, even snapped children will not look
>     crisp.
>     >>>
>     >>> 2. Children that are snapped that receive a width/height that
>     is not an
>     >>> integer multiple will be faced with a dilemma:
>     >>>
>     >>>    - Do they round their width/height down to not exceed their
>     allocated
>     >>> size?
>     >>>    - Do they round it up to ensure they cover the allocated size
>     >>> potentially overlapping another child of the unsnapped parent?
>     >>>       - That could undo whatever the parent was trying to
>     achieve...
>     >>>
>     >>>    - What do they do with those fractional pixels that are
>     left over
>     >>> when
>     >>> distributing (snapped) space over their own children?
>     >>>       - Add the "left over" space to one of those, which then
>     pushes the
>     >>> problem down another layer (the grand child receives a non-integer
>     >>> number of pixels from a snapped(!!) parent)?
>     >>>       - Not pass them down to any child, but let the
>     background of the
>     >>> container fill that space?
>     >>>
>     >>> The current implementation in JavaFX for this situation, an
>     unsnapped
>     >>> parent with snapped children,  essentially ignores these
>     questions and
>     >>> allows the layout algorithm to use the width and height as
>     given. The
>     >>> result can therefore depend on the exact implementation and
>     order of
>     >>> calculations, without the situation itself being explicitly
>     accounted
>     >>> for.
>     >>>
>     >>> Furthermore, re-enabling snapping below an unsnapped ancestor
>     seems
>     >>> counterintuitive and is unlikely to be useful in practice. If an
>     >>> ancestor has explicitly opted out of pixel snapping,
>     descendants cannot
>     >>> generally undo that decision without either compromising the
>     ancestor's
>     >>> positioning or size allocations, or propagating the
>     fractional-layout
>     >>> problem further down the hierarchy
>     >>>
>     >>> Proposal:
>     >>>
>     >>> I think the existing API documentation has enough leeway to
>     change how
>     >>> we implement snapping in the case of an unsnapped ancestor.  I
>     therefore
>     >>> propose to make snapping dependent on the ancestor.  A node is
>     only
>     >>> snapped if all of its ancestors are snapped.
>     >>>
>     >>> This would eliminate a number of ambiguities with relatively
>     little
>     >>> downside:
>     >>>
>     >>> - As today, children of an unsnapped container can still end
>     up looking
>     >>> fuzzy. However, they would follow the unsnapped calculation
>     path rather
>     >>> than being presented with an inconsistent combination of
>     snapped and
>     >>> unsnapped constraints.
>     >>> - There would be no need to define how a snapped child should
>     resolve
>     >>> fractional space received from an unsnapped parent.
>     >>> - We would not need to decide where fractional space should go
>     when
>     >>> distributing space among descendants, or whether that space
>     should be
>     >>> propagated to another level.
>     >>> - We would avoid having a snapped child round its dimensions
>     up or down
>     >>> in a way that could contradict the layout decisions made by its
>     >>> unsnapped parent.
>     >>>
>     >>> So once an ancestor opts out of snapping, that should apply to the
>     >>> entire subtree.  This makes it much more predictable (and also
>     is likely
>     >>> what was intended) and avoids having to find an unsatisfactory
>     solution
>     >>> for all the edge cases when combining snapped nodes with unsnapped
>     >>> ancestors.
>     >>>
>     >>> I'm curious what others think, and this is relevant as we're
>     about to
>     >>> enshrine snapping rules in the next release I think.
>     >>>
>     >>> --John
>

Reply via email to