Skip to content

Conversation

@andy-goryachev-oracle
Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle commented Nov 17, 2025

Requesting VFlow re-layout when signaled by a Node embedded in TextCell.

NOTES

Screenshot 2025-11-17 at 13 43 12

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8371067: RichTextArea: requestLayout by inline node doesn't reach VFlow (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1975/head:pull/1975
$ git checkout pull/1975

Update a local copy of the PR:
$ git checkout pull/1975
$ git pull https://git.openjdk.org/jfx.git pull/1975/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1975

View PR using the GUI difftool:
$ git pr show -t 1975

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1975.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 17, 2025

👋 Welcome back angorya! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 17, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@andy-goryachev-oracle andy-goryachev-oracle marked this pull request as ready for review November 18, 2025 16:20
@openjdk openjdk bot added the rfr Ready for review label Nov 18, 2025
@mlbridge
Copy link

mlbridge bot commented Nov 18, 2025

Webrevs

// https://github.com/andy-goryachev/AppFramework/blob/1e9f2197ce510a77ec5f719a2cb7112b0b6cf7be/src/goryachev/fx/FX.java#L1081
// with the author's permission
/** returns a parent of the specified type, or null. if node is an instance of the specified class, returns node */
public static <T> T getAncestorOfClass(Class<T> c, Node node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really recommend to not do anything like that. Normally, the layouting system, especially when requesting a layout should work and bubble up correctly. I wonder why this is needed. And if we found a special case, if we can solve it better.

Background: In the past projects, I often saw code like that and it turned out that this was never needed. It is often less readable and hurts the performance a bit.
We also improve coupling between components, which I would not recommend as well. Especially since subclasses can change a lot and it would be nice if everything still work out of the box.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Maran23 for looking into this PR!

My experience is exactly opposite - I do use it often. The Node (or JComponent) hierarchy is a hierarchy, explicitly retaining the pointers to the each member's parent.

A specific member can be a child of a certain Parent, direct or otherwise, by design, and this method allows to get to that parent easily.

Let me ask you this - what is the alternative? Maintain a duplicate pointer?

Also, keep in mind this is not public API, it's a utility.

Copy link
Member

@Maran23 Maran23 Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is:
Everything is part of the RichTextArea. Requesting a layout from a node inside should request a layout for each parent, also RichTextArea. So you should normally know that on the RichTextArea level, which also manages the VFlow. So it can do the corresponding actions, just by the node requesting the layout.

If we take a look at other more complex Controls, they do the following:

  • VirtualFlow.requestCellLayout -> sets a flag
  • layout is requested, and due to the flag, we know what to do

Maybe something that could be done here as well? Could also be done by Properties perhaps.

A specific member can be a child of a certain Parent, direct or otherwise, by design, and this method allows to get to that parent easily.
...
Also, keep in mind this is not public API, it's a utility.

Yes, we can always get the parent hierarchy. But that does not mean we should.
Making assumptions about the hierarchy will make subclasses and customizations (e.g. in the Skin) worse. If we extend RichTextArea and use another Node then VFlow, then we can expect TextCells not to work anymore?

In JavaFX, as you can also see in the codebase and other controls, retrieving an ancestor somewhere in the scene graph is pretty much never done or needed.
I did not have a look on this particular issue, but what I want to suggest is to take another look at the problem and how to solve it. So we don't need to rely on finding a specific node that might be somewhere in the scene graph.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly from what I've traced, is that requestLayout from the embedded node is propagating upwards until it reaches TextCell which extends a BorderPane. At this point requestLayout in Parent invokes markDirtyLayout with the forceParentLayout parameter/flag being false and the propagation stops.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know why. Because the TextCell is not managed.

Therefore, this will be set:

boolean layoutRoot = false;
@Override final void notifyManagedChanged() {
layoutRoot = !isManaged() || sceneRoot;
}

Indeed, the TextCell will be handled as layout root, therefore not propagating any layout request further up.
So the real problem is, that the TextCell is not managed.

Otherwise the layout would be propagated to the RichTextArea, which can do the corresponding actions. So I would suggest looking into that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me repeat what I mean:

  • If you call: setManaged(false), you will effectively tell JavaFX: I'm the layout root, I can handle ALL layout changes from my children without my parents.
  • This is not the case here, so making the TextCell unmanaged while still retrieving a parent (VFlow) fights this design.
  • Which leads me to the conclusion: Maybe this should not be unmanaged, since it does not fulfill the required preconditions
  • Checking the VirtualFlow as an example, it will not set anything to unmanaged

Lets check some existing examples of unmanaged nodes:

XYChart. See how this was done by design, the consequences were known:

// mark plotContent as unmanaged as its preferred size changes do not effect our layout
plotContent.setManaged(false);
plotArea.setManaged(false);


ScrollPaneSkin has actually the same case as you. This is how they dealt with it:

// prevent requestLayout requests from within scrollNode from percolating up
viewRect.setManaged(false);

viewContent = new StackPane() {
@Override public void requestLayout() {
// if scrollNode requested layout, will want to recompute
nodeSizeInvalid = true;
super.requestLayout(); // add as layout root for next layout pass
// Need to layout the ScrollPane as well in case scrollbars
// appeared or disappeared.
ScrollPaneSkin.this.getSkinnable().requestLayout();
}

This could be a solution for your problem. Since they also set the parent viewRect as unmanaged, the layout request will never hit the ScrollPane. But with that solution the child of the viewRect, called viewContent does request the ScrollPane layout. A clever solution without anything like ancestor searching. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation on Node.managed property states

Defines whether or not this node's layout will be managed by its parent. 

The layout of TextCell is not managed by its parent (VFlow.content), by design.

The VFlow is the entity that does the layout, for a reason. It's a complicated thing, and multiple moving parts are connected (one example: it performs multiple layout cycles in the same layout pass when scrollbars appear/disappear during the layout pass, to avoid jumping and flicker - something we occasionally see with ScrollPane and ListView, see Note 1).

Notes

  1. I occasionally see the continuous flicker/layout when resizing the list of pages in the latest Monkey Tester, but so far I was unable to capture the exact conditions to create a reproducible test case (this is unrelated to this PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you read what I wrote above? I made multiple suggestions as how we can deal with the situation, even without making things managed again.

To reiterate, what is the problem?

  • We now have a dependency from TextCell to VFlow. Not explicitly, but rather hidden, by searching all parents (which is also costly when doing that often). I would prefer a clear separation of concerns
  • This makes subclassing/extending harder. What happens, if I want to write my own VFlow, but still use everything else, including TextCell? What if I want to use TextCell for a component, that has no VFlow? What happens if we find a VFlow from another component even? Because we used the TextCell somewhere else?
  • Other components made it clear how to use managed and unamanged nodes. And how to bubble up a request still. Why do we want to make an exception here? This is the first time we need to search the parent hierarchy
  • Why not e.g. binding to the needsLayoutProperty from a TextCell from within VFlow and just request the layout when set?

Also another point: The test is green for me with the changes from: #1945
So I would like to know, if maybe there was a bug even?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for looking into this and offering your suggestions! The thing is, the RTA is a bit more complex than the situation you describe, due to the presence of other requirements (such as side areas that house line numbers and possibly other paragraph decorators).

The design of the skin is such that only VFlow does the layout. The VFlow is the sole actor that deals with its complicated content. Without a very good reason, this design is unlikely to change. Furthermore, the VFlow is still an implementation detail, so one can't just subclass the skin (this is true for other controls as well). Neither VFlow nor TextCell nor RichUtils are public API.

Yes, the test is green with #1945 but it fails in master. And yes, the bug is still there even with #1945 , as can be seen with the monkey tester:

expected:
Screenshot 2025-11-24 at 13 27 15

observed:
Screenshot 2025-11-24 at 13 27 05

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While looking at the test, discovered an issue with the SimpleViewOnlyStyledModel:
https://bugs.openjdk.org/browse/JDK-8372438

thanks!

*/
public void add(Node node) {
flow().getChildren().add(node);
embedsNode = true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention that if the TextCell has no children (neither Text nor embedded nodes) that getAncestorOfClass is bypassed ?
Or is this to indicate that getAncestorOfClass should only be invoked if there are any embedded nodes (non Text ones) present ?
If the latter then note that add(Node) is called for both the adding of Text and embedded nodes and maybe you intended to overload the add method with add(Text) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this is by design. VFlow is the component that lays out the text cells.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, I find this code ambiguous - so just trying to clarify which of the two options presented you intend ?
If it's the first then maybe an alternative variable/flag name should be considered ?
If it's the second then embedsNode is always set true as soon as any content is added which means the first option is what is actually occurring ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expectation is that most text cells contain TextFlow, with the VFlow managing the layout. Once VFlow determines the target width, it queries the TextFlow for its preferred height to determine the actual size.

For paragraphs that contains Regions - either inline or as whole paragraphs - this logic needs to be augmented to propagate the requestLayout() flag up the hierarchy. We don't want to do this for pure text cells, but we must do it for embedded nodes.

addNode(Node) handles the inline node case, RichParagraph.of(Supplier<Region>) does the "full-width" case.

so to answer your question - if I understand the issue - the embedsNode flag was added to enable telling VFlow that it needs to reflow because some embedded node asked for it. Pure text cells don't need this signaling enabled.

Did I answer your question (satisfactorily :-) ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so ....

Pure text cells don't need this signaling enabled.

If this is what is intended, then from how I read the code that is not what is happening because add(Node) is called even for Text only cells. See:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, thank you so much!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again @Jugen for pointing this out. Sorry, it took a while for me to understand what's going on.

Comment on lines +471 to +474
VFlow vf = RichUtils.getAncestorOfClass(VFlow.class, this);
if (vf != null) {
vf.requestLayout();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would setNeedsLayout(true); maybe work here instead ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, we need to signal this to VFlow who does the layout.

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 2

@openjdk
Copy link

openjdk bot commented Nov 20, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfr Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants