<div dir="ltr">Hi John,<div><br></div><div>This is not a solution for your specific problem (new children added without CSS being applied), but in general, I have run into similar issues many times. I find the JavaFX JavaDoc very valuable for most JavaFX application developers, but for lower-level library development, or for JavaFX core development, there are a number of things that are crucial but not in the javadoc.</div><div><br></div><div>One of the key things I often have to almost reverse engineer, is the exact flow that happens when a pulse is requested (e.g. Toolkit.java, QuantumToolkit.java and the Scene.ScenePulseListener).</div><div>I personally don't think JavaDoc is the best place to address this, as it might confuse the app developers. I think the openjfx wiki would be a good place for this but it requires time and a good understanding (which requires even more time). </div><div>The page at <a href="https://wiki.openjdk.org/display/OpenJFX/Graphics">https://wiki.openjdk.org/display/OpenJFX/Graphics</a> provides the skeleton that can be the basis for this. As usual, the problem is that time spent on this can not be spent in fixing bugs -- although in the medium/long term, having this improved semi-internal (or at least lower-level) doc would allow more developers to fix issues faster.</div><div><br></div><div>- Johan</div><div><br></div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sun, Sep 11, 2022 at 8:10 AM John Hendrikx <<a href="mailto:john.hendrikx@gmail.com">john.hendrikx@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">TLDR; I think layoutChildren should mention it is not a good practice to <br>
modify the list of children during the layout pass as CSS won't be <br>
applied to them for 1 rendered frame, and perhaps mention a solution <br>
(Scene#addPreLayoutPulseListener?)<br>
<br>
I've been recently bit by an issue that I think might need to be <br>
documented a bit better.<br>
<br>
LayoutChildren is typically overriden for custom layouts.<br>
<br>
It's documentation:<br>
<br>
* Invoked during the layout pass to layout the children in this<br>
* {@code Parent}. By default it will only set the size of managed,<br>
* resizable content to their preferred sizes and does not do any node<br>
* positioning.<br>
* <p><br>
* Subclasses should override this function to layout content as <br>
needed.<br>
<br>
A custom layout can be simple, but sometimes may involve adding or <br>
removing children dynamically based on the content of the control. What <br>
layoutChildren here does not mention is that if you add children during <br>
layoutChildren, there will be 1 rendered frame where these children <br>
don't have any CSS styling applied. The system detects however that <br>
children were modified and layoutChildren will be called again (in the <br>
same pass AFAIK), resulting hopefully in a stable set of children.<br>
<br>
The problem here is the fact that a frame is rendered without any CSS <br>
applied. For applications using a black background in general, the new <br>
children with a white background will cause a temporary "white flash" to <br>
be rendered. It took me ages to figure out where it came from, and it <br>
wasn't always easy to reproduce (if the children already existed, they <br>
were just modified, it only happened for cases where new children had to <br>
be added during the layout pass and which were clearly visible somewhere).<br>
<br>
Obviously, changes to CSS files had no effect on solving this (like <br>
setting everything to have a black or transparent) background at ".root" <br>
level.<br>
<br>
Calling #applyCSS on the newly added children during #layoutChildren <br>
does not solve the problem. Still one frame is rendered without CSS, and <br>
a white flash occurs.<br>
<br>
Adding the children during the #computeXXX methods is too late as well, <br>
the CSS pass has finished already by that time. This does however avoid <br>
having #layoutChildren called again in the same pass if children are <br>
added. There is this issue that relates to this: <br>
<a href="https://bugs.openjdk.org/browse/JDK-8091873" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8091873</a> where it is suggested to <br>
perhaps add a more general method for this.<br>
<br>
What I needed is a callback for my control before layout occurs and <br>
before CSS is applied.<br>
<br>
Scene has a #addPreLayoutPulseListener which mentions it is called <br>
before CSS is applied. Its #addPostLayoutPulseListener counterpart also <br>
mentions `AnimationTimer` as a way to get a callback before CSS is <br>
applied. Implementing this call back and changing the list of children <br>
there solved the "white flash" problem. It feels a bit overkill though <br>
to listen to every pulse on a Scene just for the layout of one control <br>
(that may not need layout during most passes).<br>
<br>
Aside from changing the documentation to ensure people understand that <br>
modifying the list of children during layout can have temporary CSS <br>
issues, I'm wondering what would be the preferred solution to this <br>
problem. Is there a different way of handling this that I haven't found, <br>
aside from the #addPreLayoutPulseListener or AnimationTimer ? If not, <br>
could the issue (<a href="https://bugs.openjdk.org/browse/JDK-8091873" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8091873</a>) perhaps be <br>
resolved by adding two methods? One that is called before CSS is <br>
applied, and one before any compute methods are called? If adding a <br>
method that is called before CSS, is there even a way to determine if a <br>
Node needs this call to prevent calling it on every Node on every layout <br>
pulse?<br>
<br>
The current solution I'm using is like 15 lines of code as part of a <br>
ListView skin:<br>
<br>
private final Runnable pulseListener = () -> content.manageCells();<br>
<br>
private void updatePreLayoutPulseListener(Scene old, Scene current) {<br>
if(old != null) {<br>
old.removePreLayoutPulseListener(pulseListener);<br>
}<br>
if(current != null) {<br>
current.addPreLayoutPulseListener(pulseListener);<br>
}<br>
}<br>
<br>
And in the constructor:<br>
<br>
updatePreLayoutPulseListener(null, skinnable.getScene());<br>
<br>
skinnable.sceneProperty().addListener((obs, old, current) -> <br>
updatePreLayoutPulseListener(old, current));<br>
<br>
And in dispose:<br>
<br>
updatePreLayoutPulseListener(skinnable.getScene(), null);<br>
<br>
--John<br>
<br>
<br>
<br>
<br>
<br>
</blockquote></div>