Hi Scott,

On 18.12.2014 22:49, Scott Palmer wrote:
Short Version:

Is there good documentation on how JavaFX performs layout? (Not specific
layouts, but layout in general.)
Not an official documentation I'm afraid (at least I don't know of any), just the javadoc and a few blog posts: Amy Fowler made some short introduction about layouts here: https://amyfowlersblog.wordpress.com/2011/06/02/javafx2-0-layout-a-class-tour/ I tried to explain some parts of the layout process that were confusing people from time to time (based on the JIRA issues that were reported to me): https://blogs.oracle.com/jfxprg/entry/the_peculiarities_of_javafx_layout (I'm sorry I never got to make part 2 :-( ).



Long Version:

I was looking into a layout issue in ControlsFX (BreadCrumbBar width is
reported incorrectly and it doesn't behave when right-aligned).

What I found is that Parent and SkinBase assume that layout has already
occurred on child nodes when computing the preferred width/height.  The
JavaDocs indicate this is a known assumption:

      * Calculates the preferred width of this {@code SkinBase}. The default
      * implementation calculates this width as the width of the area
occupied
      * by its managed children when they are positioned at their
      * current positions at their preferred widths.

Parent.computePrefWidth(double height) javadocs contain the exact same
comment.

Note however that the Region class documents Region.computePrefWidth(double
height) differently.  It simply states:

      * Computes the preferred width of this region for the given height.
      * Region subclasses should override this method to return an
appropriate
      * value based on their content and layout strategy.  If the subclass
      * doesn't have a VERTICAL content bias, then the height parameter can
be
      * ignored.

Node.prefWidth(double height), which is what
Control.computePrefWidth(double height) delegates to if there is no
SkinBase, clearly states:
  "Returns the node's preferred width for use in layout calculations."

It's the "for use in layout calculations" part, combined with the fact that
prefWidth relies on the layout having already happened, that confuses me.

Clearly layout is working in most cases, so this must all work out in the
general case.  But as I understand it layout happens top-down, so
constraints imposed by the parent container must be accounted for when
positioning and sizing the children.  That implies that the child nodes are
not yet laid out when their preferred size is queried.

I fixed the issues I was having with BreadCrumbBar by overriding the
implementation of computePrefWidth(double height) with one that does not
care about the current X position of the child Nodes.  It still relies on
the layout bounds via Node.prefWidth(double height), and duplicates some of
the calculations that an actual layout will do, but it worked to solve the
problems of right-aligned layout and a widthProperty that was clearly lying.

I feel like I am missing something fundamental about how Layout works
though.
Is there documentation that explains the general layout algorithm and how
to make sense of the apparent circular dependency?
The way it should work is that pref* min* max* methods
should be able to compute the layout without the layout actually happening. This is basically a kind-of 2-pass top-down layout: take a root (or any other parent in a tree) you want to layout. First, the computation of size hints happens (and might be traversing down the tree). After this stage, you know the size hints of the root's children, but no layout happened yet. Now the layout happens and the children are given their sizes (which might not be equal to pref size), the layout is done recursively on the children (now they know their final size they have all the information). Theoretically, you could even cache the size hints in your skins (until some of their prerequisites change) and use them at any stage of the layout pass, even as the parent's sizes change during the layout. This approach also means that size hints cannot depend on their ancestor's size (which is something many people tried to do).
But it seems you already understand all this well.

Now the confusing parts:
* Some default implementation look like they rely on the layout, but this is actually because they have no layout by default. Or more commonly, the layout does just the resize, not movement. This means we can use layout bound's x, y already at compute* methods as this is something not managed by Parent and may be set through relocate() explicitly by the developer. I see SkinBase does actually moves the children by default (to contentX, contentY) and yet still uses layout bounds in compute* methods. This might lead to a bug in certain cases IMO, but usually would work as the position does not change. Maybe some child's margin change could break this, I would have to play with it to find out.

Probably one JIRA task here you could file would be to improve the javadoc so that it's clear what is managed by class and what not, so that the developer knows what is expected to be changed explicitly and is never changed from within the class.

*Group (with autosize) is an exception and it's preferred size depends on children's layout bounds which depend on the actual layout (!). This might complicate the layout and brings a certain complexity to the whole layout mechanism, so I'm not particularly proud of this, but there was no other way to fix the problems we had with Group without braking backward-compatibility and actually the most important functionality of the Group - that is that it's layout bounds include the full bounds of it's children (inc. effects et al.), which means we can't compute it's preferred size based on the children's size hints, we really need to do the actual layout first.

Hope it helps!

-Martin

Reply via email to