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 <swpal...@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
>
>

Reply via email to