[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    Re: BaselineOffset of StackPane is inconsistent
From:       Scott Palmer <swpalmer () gmail ! com>
Date:       2023-01-27 2:33:33
Message-ID: CAL3e5iG-YJ7fU5iz+c4UOA8MyN+xnYdiEgOAxgaW4qzOzD+3LA () mail ! gmail ! com
[Download RAW message or body]

I determined this is caused by the StackPane's child node's layoutY value
sometimes being 0.0 and sometimes being 7.5.  Which is strange, since
layout should always be complete on the TreeCells by the time they are
painted.

Which got me thinking.. sure maybe on one pass it wasn't initialized or
something, but why would it keep changing?

Then I learned that updateItem is being called much more often than I had
thought.  I had previously thought it was only called when the TreeCell was
meant to render a new or different TreeItem.  Apparently it is called when
the TreeItem selection changes.  I'm not sure why, as no parameters to
updateItem include the selection state, so a TreeCell doesn't know that is
why it is being called, and the documentation and sample code doesn't
mention it.
In fact the documentation for Cell isItemChanged specifically states:

     * This method is called by Cell subclasses so that certain
CPU-intensive
     * actions (specifically, calling {@link #updateItem(Object, boolean)})
are
     * only performed when necessary

*(that is, they are only performed     * when the currently set {@link
#itemProperty() item} is considered to be     * different than the proposed
new item that could be set).*

Which implies to me (along with the method name "updateItem") that
assigning a different item to the Cell is the only reason for updateItem to
be called.
This is clearly not the case.

Another observation is that, while changing the selected item such that I
observe the misalignment, the last time getBaselineOffset is called for any
particular cell while processing that event, it always returns the same
value of 7.5, but it seems it doesn't always use this value for when it
paints.
E.g. if I toggle the selection of a cell, updateItem is called once,
creating new Nodes for the Cell's graphic, and getBaselineOffset for the
StackPane is called 4 times.  The first 3 times the first managed child of
the StackPane has layoutY = 0.0, the last time it is 7.5.

There's a bug here somewhere...

Scott


On Tue, Jan 24, 2023 at 1:43 PM Scott Palmer <swpalmer@gmail.com> wrote:

> I'm seeing something odd with the alignment of content in a TextFlow and
> I'm not sure if I'm doing something wrong, or if there is a bug there.
> The getBaselineOffset() method of the StackPane is returning inconsistent
> values.  If I sub-class it to force a constant offset, everything works.
> Since the StackPane content is always the same, I would expect
> getBaselineOffset to always return the same value, but in my sample program
> (below) sometimes it returns 6.5 and other times it returns 14.0. (Tested
> on macOS 13.2 w. OpenJDK 19/OpenJFX19)
> Parent.getBaselineOffset() is documented as: "Calculates the baseline
> offset based on the first managed child." - which should always be the same
> in my example. Sure enough, I checked and
> getChildren().get(0).getBaselineOffset() is always returning the same value
> (13.0) - so where is the discrepancy coming from?
>
> Possibly related to https://bugs.openjdk.org/browse/JDK-8091236
>
> The following program demonstrates the issue.  While selecting things in
> the TreeView the TextFlows are repainted with different alignment between
> the StackPane node and the Text nodes.
>
> package bugs;
>
> import javafx.application.Application;
> import javafx.scene.Scene;
> import javafx.scene.control.ContentDisplay;
> import javafx.scene.control.Label;
> import javafx.scene.control.TreeCell;
> import javafx.scene.control.TreeItem;
> import javafx.scene.control.TreeView;
> import javafx.scene.layout.StackPane;
> import javafx.scene.layout.VBox;
> import javafx.scene.paint.Color;
> import javafx.scene.shape.Circle;
> import javafx.scene.shape.Polygon;
> import javafx.scene.text.Text;
> import javafx.scene.text.TextFlow;
> import javafx.stage.Stage;
>
> public class TextFlowWithIcons extends Application {
>     public static void main(String[] args) {
>         launch(args);
>     }
>
>     @Override
>     public void start(Stage primaryStage) {
>         TreeItem<String> node1 = new TreeItem<>("item");
>         TreeItem<String> node2 = new TreeItem<>("text");
>         TreeItem<String> node3 = new TreeItem<>("tree");
>         TreeItem<String> root = new TreeItem<>("ROOT");
>         root.setExpanded(true);
>         root.getChildren().addAll(node1, node2, node3);
>         var treeView = new TreeView<String>(root);
>         treeView.setCellFactory(this::cellFactory);
>         VBox box = new VBox(8, new Label("Fiddle with the
> TreeView...\nWatch the alignment bounce around."), treeView);
>         var scene = new Scene(box);
>         primaryStage.setScene(scene);
>         primaryStage.setTitle("Graphic Alignment Issue");
>         primaryStage.show();
>     }
>
>     private TreeCell<String> cellFactory(TreeView<String> tv) {
>         return new CustomCell();
>     }
>
>     private static class CustomCell extends TreeCell<String> {
>
>         public CustomCell() {
>             setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
>         }
>
>         @Override
>         protected void updateItem(String item, boolean empty) {
>             super.updateItem(item, empty);
>             if (item != null && !empty) {
>                 var text1 = new Text("Some ");
>                 var text2 = new Text("colored ");
>                 var text3 = new Text(item);
>                 text2.setFill(Color.GREEN);
>                 var circle = new Circle(6);
>                 circle.setStroke(Color.BLACK);
>                 circle.setFill(Color.RED);
>                 var triangle = new Polygon(-6, 6, 6, 6, 0, -6);
>                 triangle.setFill(Color.YELLOW);
>                 triangle.setStroke(Color.BLACK);
>                 triangle.setScaleX(0.5);
>                 triangle.setScaleY(0.5);
>                 triangle.setTranslateX(4);
>                 triangle.setTranslateY(4);
>                 var icon = new StackPane(circle, triangle)
> // uncomment to fix
> //                {
> //                    @Override
> //                    public double getBaselineOffset() {
> //                        return 12;
> //                    }
> //                }
>                 ;
>                 var textFlow = new TextFlow(icon, text1, text2, text3);
>                 setGraphic(textFlow);
>             } else {
>                 setGraphic(null);
>             }
>             setText(null);
>         }
>
>     }
> }
>
> Regards,
>
> Scott
>
>

[Attachment #3 (text/html)]

<div dir="ltr">I determined this is caused by the StackPane&#39;s child node&#39;s \
layoutY value sometimes being 0.0 and sometimes being 7.5.   Which is strange, since \
layout should always be complete on the TreeCells by the time they are \
painted.<div><br></div><div>Which  got me thinking.. sure maybe on one pass it \
wasn&#39;t initialized or something, but why would  it keep \
changing?</div><div><br></div><div>Then I learned that  updateItem is being called \
much  more often than I had thought.   I had previously thought it was only called \
when the TreeCell was meant to render a new or different TreeItem.   Apparently it is \
called when the TreeItem selection changes.   I&#39;m not sure why, as no parameters \
to updateItem include the selection state, so a TreeCell doesn&#39;t know that is why \
it is being called, and the documentation and sample code doesn&#39;t mention \
it.</div><div>In fact the documentation for Cell isItemChanged specifically \
states:</div><div><br></div><div>        * This method is called by Cell subclasses \
so that certain CPU-intensive<br>        * actions (specifically, calling {@link \
#updateItem(Object, boolean)}) are<br>        * only performed when necessary \
<b>(that is, they are only performed<br>        * when the currently set {@link \
#itemProperty() item} is considered to be<br>        * different than the proposed \
new item that could be set).</b><br></div><div><br></div><div>Which  implies to me \
(along with the method name &quot;updateItem&quot;) that assigning a different item \
to the Cell is the only reason for updateItem to be called.</div><div>This is clearly \
not the case.</div><div><br></div><div>Another observation is that, while changing \
the selected item  such that I observe the misalignment, the last time \
getBaselineOffset is called for any particular cell while processing that event, it \
always returns the same value of 7.5, but it seems it doesn&#39;t always use this \
value for when it paints.</div><div>E.g. if I toggle the selection of a cell, \
updateItem is called once, creating new Nodes for the Cell&#39;s graphic, and \
getBaselineOffset for the StackPane is called 4 times.   The first 3 times the first \
managed child of the StackPane has layoutY = 0.0, the last time it is \
7.5.</div><div><br></div><div>There&#39;s a bug here \
somewhere...</div><div><br></div><div>Scott<br><div><br></div></div></div><br><div \
class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Jan 24, 2023 at 1:43 PM \
Scott Palmer &lt;<a href="mailto:swpalmer@gmail.com">swpalmer@gmail.com</a>&gt; \
wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px \
0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div \
dir="ltr">I&#39;m seeing something odd with the alignment of content in a TextFlow \
and I&#39;m not sure if I&#39;m doing something  wrong, or if there is a bug \
there.<div>The getBaselineOffset() method of the StackPane is returning inconsistent \
values.   If I sub-class it to force a constant offset, everything works.   Since the \
StackPane content is always the same, I would  expect getBaselineOffset to always \
return the same value, but in my sample program (below) sometimes it returns 6.5 and \
other times it returns 14.0. (Tested on macOS 13.2 w. OpenJDK \
19/OpenJFX19)</div><div>Parent.getBaselineOffset() is documented as: &quot;Calculates \
the baseline offset based on the first managed child.&quot; - which should always be \
the same in my example. Sure enough, I checked and \
getChildren().get(0).getBaselineOffset() is always returning the same value (13.0) - \
so where is the discrepancy  coming from?</div><div><br></div><div>Possibly related \
to  <a href="https://bugs.openjdk.org/browse/JDK-8091236" \
target="_blank">https://bugs.openjdk.org/browse/JDK-8091236</a></div><div><br></div><div>The \
following program demonstrates the issue.   While selecting things in the TreeView \
the TextFlows are  repainted  with different alignment between the  StackPane node  \
and the Text nodes.  <br><div><br><font face="monospace">package bugs;<br><br>import \
javafx.application.Application;<br>import javafx.scene.Scene;<br>import \
javafx.scene.control.ContentDisplay;<br>import javafx.scene.control.Label;<br>import \
javafx.scene.control.TreeCell;<br>import javafx.scene.control.TreeItem;<br>import \
javafx.scene.control.TreeView;<br>import javafx.scene.layout.StackPane;<br>import \
javafx.scene.layout.VBox;<br>import javafx.scene.paint.Color;<br>import \
javafx.scene.shape.Circle;<br>import javafx.scene.shape.Polygon;<br>import \
javafx.scene.text.Text;<br>import javafx.scene.text.TextFlow;<br>import \
javafx.stage.Stage;<br><br>public class TextFlowWithIcons extends Application {<br>   \
public static void main(String[] args) {<br>            launch(args);<br>      \
}<br><br>      @Override<br>      public void start(Stage primaryStage) {<br>         \
TreeItem&lt;String&gt; node1 = new TreeItem&lt;&gt;(&quot;item&quot;);<br>            \
TreeItem&lt;String&gt; node2 = new TreeItem&lt;&gt;(&quot;text&quot;);<br>            \
TreeItem&lt;String&gt; node3 = new TreeItem&lt;&gt;(&quot;tree&quot;);<br>            \
TreeItem&lt;String&gt; root = new TreeItem&lt;&gt;(&quot;ROOT&quot;);<br>            \
root.setExpanded(true);<br>            root.getChildren().addAll(node1, node2, \
node3);<br>            var treeView = new TreeView&lt;String&gt;(root);<br>           \
treeView.setCellFactory(this::cellFactory);<br>            VBox box = new VBox(8, new \
Label(&quot;Fiddle with the TreeView...\nWatch the alignment bounce around.&quot;), \
treeView);<br>            var scene = new Scene(box);<br>            \
primaryStage.setScene(scene);<br>            primaryStage.setTitle(&quot;Graphic \
Alignment Issue&quot;);<br>            primaryStage.show();<br>      }<br><br>      \
private TreeCell&lt;String&gt; cellFactory(TreeView&lt;String&gt; tv) {<br>           \
return new CustomCell();<br>      }<br><br>      private static class CustomCell \
extends TreeCell&lt;String&gt; {<br><br>            public CustomCell() {<br>         \
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);<br>            }<br><br>            \
@Override<br>            protected void updateItem(String item, boolean empty) {<br>  \
super.updateItem(item, empty);<br>                  if (item != null &amp;&amp; \
!empty) {<br>                        var text1 = new Text(&quot;Some &quot;);<br>     \
var text2 = new Text(&quot;colored &quot;);<br>                        var text3 = \
new Text(item);<br>                        text2.setFill(Color.GREEN);<br>            \
var circle = new Circle(6);<br>                        \
circle.setStroke(Color.BLACK);<br>                        \
circle.setFill(Color.RED);<br>                        var triangle = new Polygon(-6, \
6, 6, 6, 0, -6);<br>                        triangle.setFill(Color.YELLOW);<br>       \
triangle.setStroke(Color.BLACK);<br>                        \
triangle.setScaleX(0.5);<br>                        triangle.setScaleY(0.5);<br>      \
triangle.setTranslateX(4);<br>                        triangle.setTranslateY(4);<br>  \
var icon = new StackPane(circle, triangle)</font></div><div><font face="monospace">// \
uncomment to fix</font></div><div><font face="monospace">//                        \
{<br>//                              @Override<br>//                              \
public double getBaselineOffset() {<br>//                                    return \
12;<br>//                              }<br>//                        \
}<br></font></div><div><font face="monospace">                        ;<br>           \
var textFlow = new TextFlow(icon, text1, text2, text3);<br>                        \
setGraphic(textFlow);<br>                  } else {<br>                        \
setGraphic(null);<br>                  }<br>                  setText(null);<br>      \
}<br><br>      }<br>}</font><br></div></div><div><font \
face="monospace"><br></font></div><div><font face="arial, \
sans-serif">Regards,</font></div><div><font face="arial, \
sans-serif"><br></font></div><div><font face="arial, \
sans-serif">Scott</font></div><div><font face="arial, \
sans-serif"><br></font></div></div> </blockquote></div>



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic