<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    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.<br>
    <br>
    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).<br>
    <br>
    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.<br>
    <br>
    I'll be interested in the answers to Andy's questions.<br>
    <br>
    -- Kevin<br>
    <br>
    <br>
    <div class="moz-cite-prefix">On 4/20/2023 3:51 PM, Andy Goryachev
      wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:DM5PR1001MB2172011019EC76779BFCB775E5639@DM5PR1001MB2172.namprd10.prod.outlook.com">
      
      <meta name="Generator" content="Microsoft Word 15 (filtered
        medium)">
      <style>@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}@font-face
        {font-family:"Times New Roman \(Body CS\)";
        panose-1:2 11 6 4 2 2 2 2 2 4;}p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        font-size:10.0pt;
        font-family:"Calibri",sans-serif;}a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}span.EmailStyle19
        {mso-style-type:personal-reply;
        font-family:"Courier New";
        color:windowtext;}.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;
        mso-ligatures:none;}div.WordSection1
        {page:WordSection1;}</style>
      <div class="WordSection1">
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">Hi
            Michael,<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">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().<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">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.<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">Could
            you give an example where such a logical scene graph is
            required, and it must rely on the core?<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">Thank
            you.<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New"">-andy<o:p></o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <div style="border:none;border-top:solid #B5C4DF
          1.0pt;padding:3.0pt 0in 0in 0in">
          <p class="MsoNormal" style="margin-bottom:12.0pt"><b><span style="font-size:12.0pt;color:black">From:
              </span></b><span style="font-size:12.0pt;color:black">openjfx-dev
              <a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev-retn@openjdk.org"><openjfx-dev-retn@openjdk.org></a> on behalf of Michael
              Strauß <a class="moz-txt-link-rfc2396E" href="mailto:michaelstrau2@gmail.com"><michaelstrau2@gmail.com></a><br>
              <b>Date: </b>Thursday, April 13, 2023 at 11:56<br>
              <b>To: </b>openjfx-dev <a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
              <b>Subject: </b>Content Graph<o:p></o:p></span></p>
        </div>
        <div>
          <p class="MsoNormal"><span style="font-size:11.0pt">I've
              previously proposed an enhancement to introduce a
              "document<br>
              graph" to JavaFX that would solve a problem that many
              people have<br>
              encountered over the years:<br>
              <a href="https://mail.openjdk.org/pipermail/openjfx-dev/2022-June/034417.html" moz-do-not-send="true" class="moz-txt-link-freetext">https://mail.openjdk.org/pipermail/openjfx-dev/2022-June/034417.html</a><br>
              <br>
              In a nutshell, the problem is that the JavaFX scene graph
              is a black<br>
              box in the presence of control skins, which makes it hard
              to reason<br>
              about its structure. On top of that, several nodes that
              look like they<br>
              are part of the scene graph (MenuItem or Tab) aren't scene
              graph nodes<br>
              at all.<br>
              <br>
              <br>
              1) Content Graph<br>
              <br>
              We can solve this problem by adding a content graph. The
              content graph<br>
              is a structure that comprises all content nodes of a
              scene. Content<br>
              nodes represent the significant scene content, which
              exludes all nodes<br>
              contributed by skins, but includes some of the
              aforementioned nodes<br>
              that are not part of the scene graph.<br>
              <br>
              The content graph consists of two new interfaces that live
              in the<br>
              `javafx.content` package:<br>
              <br>
                  public interface ContentNode {<br>
                      ContentParent getContentParent();<br>
              <br>
                      Styleable lookupContent(String selector);<br>
                      Set<Styleable> lookupAllContent(String
              selector);<br>
                  }<br>
              <br>
                  public interface ContentParent extends ContentNode {<br>
                      ObservableList<ContentNode>
              getContentChildren();<br>
                  }<br>
              <br>
              These interfaces are implemented by `javafx.scene.Node`
              and<br>
              `javafx.scene.Parent`, but also by other classes like
              `MenuItem` or<br>
              `Tab`. Using these interfaces, the content graph can be
              traversed in<br>
              the same way as the scene graph.<br>
              <br>
              Consider the following program, which creates a simple
              user interface:<br>
              <br>
                  var root = new VBox();<br>
                  var menuBtn = new MenuButton();<br>
                  menuBtn.getItems().add(new MenuItem("Item 1"));<br>
                  menuBtn.getItems().add(new MenuItem("Item 2", new
              Rectangle()));<br>
                  root.getChildren().add(menuBtn);<br>
                  root.getChildren().add(new Button("Button 1", new
              Rectangle()));<br>
                  root.getChildren().add(new Button("Button 2", new
              Circle()));<br>
              <br>
              Suppose a method `printSceneGraph(Node root)`, which
              traverses the<br>
              scene graph and prints out the nodes it encounters.<br>
              Here is the output before the UI is shown:<br>
              <br>
                  javafx.scene.layout.VBox<br>
                  ....javafx.scene.control.MenuButton<br>
                  ....javafx.scene.control.Button<br>
                  ....javafx.scene.control.Button<br>
              <br>
              And here's the output of the same method, but after the UI
              is shown:<br>
              <br>
                  javafx.scene.layout.VBox<br>
                  ....javafx.scene.control.MenuButton<br>
                 
              ........javafx.scene.control.skin.MenuButtonSkinBase$MenuLabeledImpl<br>
                  ............com.sun.javafx.scene.control.LabeledText<br>
                  ........javafx.scene.layout.StackPane<br>
                  ............javafx.scene.layout.StackPane<br>
                  ....javafx.scene.control.Button<br>
                  ........javafx.scene.shape.Rectangle<br>
                  ........com.sun.javafx.scene.control.LabeledText<br>
                  ....javafx.scene.control.Button<br>
                  ........javafx.scene.shape.Circle<br>
                  ........com.sun.javafx.scene.control.LabeledText<br>
              <br>
              Note that the scene graph contains many more nodes after
              skins have<br>
              been inflated, but it still doesn't contain the `MenuItem`
              instances.<br>
              <br>
              Now also suppose a method `printContentGraph(ContentNode
              root)`, which<br>
              does the same as before, but works on the content graph.<br>
              This is the output of that method:<br>
              <br>
                  javafx.scene.layout.VBox<br>
                  ....javafx.scene.control.MenuButton<br>
                  ........javafx.content.Text<br>
                  ........javafx.scene.control.MenuItem<br>
                  ............javafx.content.Text<br>
                  ........javafx.scene.control.MenuItem<br>
                  ............javafx.content.Text<br>
                  ............javafx.scene.shape.Rectangle<br>
                  ....javafx.scene.control.Button<br>
                  ........javafx.content.Text<br>
                  ........javafx.scene.shape.Rectangle<br>
                  ....javafx.scene.control.Button<br>
                  ........javafx.content.Text<br>
                  ........javafx.scene.shape.Circle<br>
              <br>
              Note that the content graph is not affected by skins, and
              the output<br>
              is the same whether the method is called before or after
              skin<br>
              inflation.<br>
              <br>
              <br>
              2) Defining the Content Model<br>
              <br>
              The content graph consists of the content model of all
              participating<br>
              controls. Implementors are responsible for defining the
              content model<br>
              of a control by overriding the protected
              `buildContentModel` method,<br>
              and adding the content that is significant for the
              control:<br>
              <br>
                  public class SplitPane {<br>
                      ...<br>
                      @Override<br>
                      protected void buildContentModel(<br>
                              Consumer<ObservableList<? extends
              ContentNode>> contentModel) {<br>
                          super.buildContentModel(contentModel);<br>
                          contentModel.accept(getItems());<br>
                      }<br>
                      ...<br>
                  }<br>
              <br>
              If a control subclass wants to extend the content model of
              an existing<br>
              control, it simply overrides the `buildContentModel`
              method again as<br>
              shown above. The content model of a subclass can only be
              extended, it<br>
              can't be constrained. This is analogous to how a subclass
              of `Labeled`<br>
              can only add new properties, but not remove the existing<br>
              `Labeled.text` and `Labeled.graphic` properties.<br>
              <br>
              Note that if a custom control is not aware of the content
              graph and<br>
              does not override the `buildContentModel` method, that's
              not an issue<br>
              in itself; it simply means that the custom control will
              not contribute<br>
              additional nodes to the content graph.<br>
              <br>
              <br>
              3) Operations on the Content Graph<br>
              <br>
              The content graph supports query operations on styleable
              nodes by<br>
              adding two new lookup methods: `lookupContent` and
              `lookupAllContent`.<br>
              This solves a problem that was discussed on the mailing
              list<br>
              previously:<br>
              <a href="https://mail.openjdk.org/pipermail/openjfx-dev/2022-December/037770.html" moz-do-not-send="true" class="moz-txt-link-freetext">https://mail.openjdk.org/pipermail/openjfx-dev/2022-December/037770.html</a><br>
              <br>
              The particular problem that was described in this email
              can be fixed<br>
              simply by using `lookupAllContent` instead of `lookupAll`:<br>
                 
              window.getScene().getRoot().lookupAllContent(".split-pane")<br>
              <br>
              <br>
              <br>
              I'm interested in hearing your thoughts on this idea. If
              there is<br>
              sufficient interest, I can contribute a PR for this
              enhancement.<o:p></o:p></span></p>
        </div>
      </div>
    </blockquote>
    <br>
  </body>
</html>