This PR shows how virtual layouts could be implemented, as discussed on the 
mailinglist: 
https://mail.openjdk.org/archives/list/[email protected]/thread/PLNSQ3ZI63AVKEFKNT5GT6WHAD2GYMPC/

It would work by making new non-Node containers called Layouts which can 
contain a mix of either Nodes or other Layouts. Allowing Layouts to nest makes 
it possible to create a substructure similar to how nesting StackPane/HBox/VBox 
etc works today. An example is a simple control that has a graphic with a title 
and subtitle stacked vertically next to it:


+-------------+-----------------------------------------+
|             |                                         |
|             |                 Title                   |
|             |                                         |
|   Graphic   +-----------------------------------------+
|             |                                         |
|             |                Subtitle                 |
|             |                                         |
+-------------+-----------------------------------------+

For a control to model this, it would need a VBox containing the Title and 
Subtitle, and an HBox containing the Graphic and the VBox.  The two containers 
are heavy-weight Nodes and this incurs sufficiently large memory and 
performance penalties that most standard JavaFX controls will opt to instead 
roll their own layout code to avoid paying the cost for these.

With virtual layouts, the control could make use of non-Node containers. The 
control would add its three children (Graphic, Title and Subtitle) as direct 
children for display in the scene graph, but would offload their positioning to 
a virtual layout.  This roughly looks like this:


  public class TitledGraphic extends Region {
      private final HBoxLayout root;

      public TitledGraphic(Node graphic, String titleText, String subtitleText) 
{
          // Flat scene graph:
          getChildren().addAll(graphic, title, subtitle);

          // Create virtual layout:
          root = HBoxLayout.of(graphic, VBoxLayout.of(title, subtitle));
      }

      @Override
      protected void layoutChildren() {
          root.resizeRelocate(0, 0, getWidth(), getHeight());
      }

      @Override protected double computeMinWidth(double height)   { return 
root.minWidth(height); }
      @Override protected double computeMinHeight(double width)   { return 
root.minHeight(width); }
      @Override protected double computePrefWidth(double height)  { return 
root.prefWidth(height); }
      @Override protected double computePrefHeight(double width)  { return 
root.prefHeight(width); }
  }

Note: this PR does not offer the `VBoxLayout` or `HBoxLayout`; to keep this PR 
minimal, `StackPane` was chosen as an example layout.

## Changes made:
- Introduction of `Layoutable` interface.
  - This interface extends a `Measurable` interface to cleanly segregate the 
concerns of the measuring pass and the actual layout pass
- `Node` has been retrofitted to implement `Layoutable` (no new methods or API 
changes)
- Introduction of `StackPaneLayout`, containing the `StackPane` layout 
algorithm; this uses the `LayoutSupport` helper containing most of the (package 
private) helper logic of `Region` with small adjustment to accept `Layoutable`s 
instead of `Node`s
- `StackPane` has been adjusted to make use of the `StackPaneLayout`
- Bug fix included for https://bugs.openjdk.org/browse/JDK-8389585 (see the 
`Scene` changes)

No test changes, all test pass as before, including the `StackPaneTest`.

### The `Snapper` system

In order to allow the layout helpers to do correct render scale aware 
calculations, the `Snapper` type has been created. It holds the typical 
`snapSizeX/Y` functions but without needing to a do a look-up of the render 
scale on each call. As such it is likely a performance preserving change. There 
is usually only one `Snapper` for an entire scene graph. The snapper with the 
current render scale that is in use for a `Window` is accessible via the 
`Region` `snapper()` method.

Because `Snapper`s are reusable, there are some optimizations here as well. For 
render scale 1.0/1.0 there is a snapper that does no calculations, only 
rounding or ceiling. For all other render scales, the division is avoided by 
pre-calculating the reciprocal.

---------
- [x] I confirm that I make this contribution in accordance with the [OpenJDK 
Interim AI Policy](https://openjdk.org/legal/ai).

-------------

Commit messages:
 - Remove white space at end of line
 - Add StackPaneLayout, move Region helpers to LayoutSupport
 - Create new interfaces Layoutable and Measurable and use by Node

Changes: https://git.openjdk.org/jfx/pull/2241/files
  Webrev: https://webrevs.openjdk.org/?repo=jfx&pr=2241&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8389653
  Stats: 2190 lines in 16 files changed: 1682 ins; 408 del; 100 mod
  Patch: https://git.openjdk.org/jfx/pull/2241.diff
  Fetch: git fetch https://git.openjdk.org/jfx.git pull/2241/head:pull/2241

PR: https://git.openjdk.org/jfx/pull/2241

Reply via email to