I've learned a few new things while working on the proposed new layout
algorithm, and added a few new APIs:


1. A central concept of the new algorithm was the notion of a
text-baseline node, indicated by Node::isTextBaseline(). I've come to
realize that this property should percolate upwards in the scene
graph: if a node has a text-baseline, the node's parent should also be
considered to have a text-baseline. Adding this new behavior works
surprisingly well and produces very intuitive layout results.


2. The default behavior of all layout containers is to pick the first
text-baseline child to derive their own baseline offset. I've added
Node::prefBaselineProperty(), which makes it possible to override this
default selection: layout containers now pick the first child that
reports Node::isPrefBaseline(), and only if there is no such child,
they fall back to Node::isTextBaseline(). Developers can use this
property to fine-tune their baseline layouts.


3. Optimization: Controls that contain text will often consist of a
container of some sort and a javafx.scene.text.Text node within it.
Computing the baseline offset of such a control is very easy with the
new layout algorithm:

public double getBaselineOffset() {
    return text.getLayoutBounds().getMinY() + text.getLayoutY() +
text.getBaselineOffset();
}

This works because changing text.layoutY will automatically schedule
another layout pass for all of its parents. Re-layouting all parents
is necessary because changing layoutY can change the effective
baseline offset, and changing the baseline offset of any node can have
layout implications on any of its parents.

However, when we consider the Label control (which is probably among
the most commonly used controls), this can be a bit excessive. Label
controls are often used to display pure text, and as such, we can
often "know" the baseline offset without actually needing to schedule
a second layout pass. This assumption is only correct if the text
within the Label is top-aligned (because if it isn't, the Label
baseline offset can not be known in advance of an actual layout pass).

To leverage this assumption, I've changed the default alignment for
Label to TOP_LEFT (the default alignment of the base class Labeled is
CENTER_LEFT). In most cases, there will be no visual difference
anyway, because I imagine Label controls will seldomly be set to a
minHeight or prefHeight.

This specific scenario will enable an optimization where the first
layout pass of Label will not schedule a second layout pass. It might
be possible to find more such scenarios that can benefit from
fast-path optimizations.


4. In order to get a better understanding of the layout process, I
added additional logging to track layout passes. Then I compared the
current algorithm with the new algorithm by tracking the initial
layout after starting a sample program (i.e. all layout activity until
the first frame is rendered). In the following log, "cumulative layout
passes" means how often layoutChildren() has been invoked on any of
the scene graph nodes. The actual log output includes a tree
visualization of the entire scene graph that is being layouted, which
I've removed for the sake or brevity.

Current algorithm log output:
INFO: Layouting VBox (triggered by scene out-of-pulse), cumulative
layout passes: 49
INFO: Layouting VBox (triggered by scene pulse), cumulative layout passes: 43
INFO: Layouting VBox (triggered by scene pulse), cumulative layout passes: 0

New algorithm log output:
INFO: Layouting VBox (triggered by scene out-of-pulse), cumulative
layout passes: 86
INFO: Layouting VBox (triggered by scene pulse), cumulative layout passes: 19

A major difference is that the current algorithm will often leave the
scene graph in a 'dirty' layout state after running a complete layout
cycle, which necessitates another layout cycle as part of the next
pulse. The new algorithm, however, leaves the scene graph in a clean
layout state after a complete cycle, which takes more work at first,
but saves work that would otherwise be done in the next pulse.


5. Since the new layout algorithm will leave the scene graph in a
clean state, it is not necessary to repeatedly layout the same thing
(like in Node::doLayoutPass()). Cases like these should be identified
and may be changed to single layout invocations.


Overall, I think there's good reason to assume that the proposed
algorithm works and that it produces consistent results for
application developers. At this point it would be useful to know
whether or not to continue with the effort.

Reply via email to