I haven't waded into this discussion yet, but now that it's settling down a bit, I will offer a couple very brief thoughts.

I am unconvinced that a content graph should be part of the JavaFX core. I can see how it might be useful to some apps, but it would be a large change with fairly significant impact. There are a number of concepts that might seem easy on the surface, but have implications that would need to be worked out (e.g., let's make *everything* a ContentNode that has a getParent() breaks down for non-node objects that are shareable).

Even if this does become something that we might eventually want to do, it isn't something we would rush in, and is very unlikely to happen any time soon.

I'll be interested in the answers to Andy's questions.

-- Kevin


On 4/20/2023 3:51 PM, Andy Goryachev wrote:

Hi Michael,

My current understanding is that yes, FX differs from let's say Swing in that certain things are not even Nodes (MenuItem, Window, etc.), but once the initial shock passes, one could certainly write code that constructs the logical structure using Node.getChildrenUnmodifiable().

Adding this functionality to the platform core, in my opinion, is probably unnecessary, but perhaps there are cases where it would offer a great benefit.

Could you give an example where such a logical scene graph is required, and it must rely on the core?

Thank you.

-andy

*From: *openjfx-dev <openjfx-dev-r...@openjdk.org> on behalf of Michael Strauß <michaelstr...@gmail.com>
*Date: *Thursday, April 13, 2023 at 11:56
*To: *openjfx-dev <openjfx-dev@openjdk.org>
*Subject: *Content Graph

I've previously proposed an enhancement to introduce a "document
graph" to JavaFX that would solve a problem that many people have
encountered over the years:
https://mail.openjdk.org/pipermail/openjfx-dev/2022-June/034417.html

In a nutshell, the problem is that the JavaFX scene graph is a black
box in the presence of control skins, which makes it hard to reason
about its structure. On top of that, several nodes that look like they
are part of the scene graph (MenuItem or Tab) aren't scene graph nodes
at all.


1) Content Graph

We can solve this problem by adding a content graph. The content graph
is a structure that comprises all content nodes of a scene. Content
nodes represent the significant scene content, which exludes all nodes
contributed by skins, but includes some of the aforementioned nodes
that are not part of the scene graph.

The content graph consists of two new interfaces that live in the
`javafx.content` package:

    public interface ContentNode {
        ContentParent getContentParent();

        Styleable lookupContent(String selector);
        Set<Styleable> lookupAllContent(String selector);
    }

    public interface ContentParent extends ContentNode {
        ObservableList<ContentNode> getContentChildren();
    }

These interfaces are implemented by `javafx.scene.Node` and
`javafx.scene.Parent`, but also by other classes like `MenuItem` or
`Tab`. Using these interfaces, the content graph can be traversed in
the same way as the scene graph.

Consider the following program, which creates a simple user interface:

    var root = new VBox();
    var menuBtn = new MenuButton();
    menuBtn.getItems().add(new MenuItem("Item 1"));
    menuBtn.getItems().add(new MenuItem("Item 2", new Rectangle()));
    root.getChildren().add(menuBtn);
    root.getChildren().add(new Button("Button 1", new Rectangle()));
    root.getChildren().add(new Button("Button 2", new Circle()));

Suppose a method `printSceneGraph(Node root)`, which traverses the
scene graph and prints out the nodes it encounters.
Here is the output before the UI is shown:

    javafx.scene.layout.VBox
    ....javafx.scene.control.MenuButton
    ....javafx.scene.control.Button
    ....javafx.scene.control.Button

And here's the output of the same method, but after the UI is shown:

    javafx.scene.layout.VBox
    ....javafx.scene.control.MenuButton
........javafx.scene.control.skin.MenuButtonSkinBase$MenuLabeledImpl
    ............com.sun.javafx.scene.control.LabeledText
    ........javafx.scene.layout.StackPane
    ............javafx.scene.layout.StackPane
    ....javafx.scene.control.Button
    ........javafx.scene.shape.Rectangle
    ........com.sun.javafx.scene.control.LabeledText
    ....javafx.scene.control.Button
    ........javafx.scene.shape.Circle
    ........com.sun.javafx.scene.control.LabeledText

Note that the scene graph contains many more nodes after skins have
been inflated, but it still doesn't contain the `MenuItem` instances.

Now also suppose a method `printContentGraph(ContentNode root)`, which
does the same as before, but works on the content graph.
This is the output of that method:

    javafx.scene.layout.VBox
    ....javafx.scene.control.MenuButton
    ........javafx.content.Text
    ........javafx.scene.control.MenuItem
    ............javafx.content.Text
    ........javafx.scene.control.MenuItem
    ............javafx.content.Text
    ............javafx.scene.shape.Rectangle
    ....javafx.scene.control.Button
    ........javafx.content.Text
    ........javafx.scene.shape.Rectangle
    ....javafx.scene.control.Button
    ........javafx.content.Text
    ........javafx.scene.shape.Circle

Note that the content graph is not affected by skins, and the output
is the same whether the method is called before or after skin
inflation.


2) Defining the Content Model

The content graph consists of the content model of all participating
controls. Implementors are responsible for defining the content model
of a control by overriding the protected `buildContentModel` method,
and adding the content that is significant for the control:

    public class SplitPane {
        ...
        @Override
        protected void buildContentModel(
                Consumer<ObservableList<? extends ContentNode>> contentModel) {
            super.buildContentModel(contentModel);
            contentModel.accept(getItems());
        }
        ...
    }

If a control subclass wants to extend the content model of an existing
control, it simply overrides the `buildContentModel` method again as
shown above. The content model of a subclass can only be extended, it
can't be constrained. This is analogous to how a subclass of `Labeled`
can only add new properties, but not remove the existing
`Labeled.text` and `Labeled.graphic` properties.

Note that if a custom control is not aware of the content graph and
does not override the `buildContentModel` method, that's not an issue
in itself; it simply means that the custom control will not contribute
additional nodes to the content graph.


3) Operations on the Content Graph

The content graph supports query operations on styleable nodes by
adding two new lookup methods: `lookupContent` and `lookupAllContent`.
This solves a problem that was discussed on the mailing list
previously:
https://mail.openjdk.org/pipermail/openjfx-dev/2022-December/037770.html

The particular problem that was described in this email can be fixed
simply by using `lookupAllContent` instead of `lookupAll`:
window.getScene().getRoot().lookupAllContent(".split-pane")



I'm interested in hearing your thoughts on this idea. If there is
sufficient interest, I can contribute a PR for this enhancement.

Reply via email to