diff --git a/modules/graphics/src/main/java/javafx/application/Platform.java b/modules/graphics/src/main/java/javafx/application/Platform.java --- a/modules/graphics/src/main/java/javafx/application/Platform.java +++ b/modules/graphics/src/main/java/javafx/application/Platform.java @@ -25,6 +25,7 @@ package javafx.application; +import com.sun.javafx.tk.Toolkit; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import com.sun.javafx.application.PlatformImpl; @@ -89,6 +90,14 @@ // } /** + * Notify the pulse timer that we need the next pulse to happen. + * This flag is cleared each cycle so subsequent pulses may be requested. + */ + public static void requestNextPulse() { + Toolkit.getToolkit().requestNextPulse(); + } + + /** * Returns true if the calling thread is the JavaFX Application Thread. * Use this call the ensure that a given task is being executed * (or not being executed) on the JavaFX Application Thread. diff --git a/modules/graphics/src/main/java/javafx/scene/Scene.java b/modules/graphics/src/main/java/javafx/scene/Scene.java --- a/modules/graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/graphics/src/main/java/javafx/scene/Scene.java @@ -89,6 +89,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; import com.sun.javafx.logging.PulseLogger; @@ -590,6 +591,83 @@ return null; } + private List prePulseListeners; + private List postPulseListeners; + + /** + * Adds a new scene pre pulse listener to this scene. Every time a pulse occurs, + * this listener will be called on the JavaFX Application Thread directly + * before the CSS and layout passes, but before any rendering is done for + * this frame. This scene pulse listener is suitable for knowing when a + * scenegraph pulse is happening and also for modifying the scenegraph + * (as it is called before CSS and layout, so any changes made will be properly styled and positioned). + * + * @param r The Runnable to be called when the pulse occurs. + * @since 9 + */ + public final void addPrePulseListener(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("Scene pulse listener should not be null"); + } + if (prePulseListeners == null) { + prePulseListeners = new CopyOnWriteArrayList<>(); + } + prePulseListeners.add(r); + } + + /** + * Removes a previously registered scene pre pulse listener from listening to + * pulses in this scene. + * + * @param r The Runnable that should no longer be called when this scene pulses. + * @since 9 + */ + public final void removePrePulseListener(Runnable r) { + if (prePulseListeners == null) { + return; + } + prePulseListeners.remove(r); + } + + /** + * Adds a new scene post pulse listener to this scene. Every time a pulse occurs, + * this listener will be called on the JavaFX Application Thread directly + * after the CSS and layout passes, but before any rendering is done for + * this frame. This scene pulse listener is suitable for knowing when a + * scenegraph pulse is happening, but it is not suited to use cases related + * to modifying the scenegraph (as it is called after CSS and layout, so + * any changes will possibly be incorrect until the next pulse is run). + * An alternative (and better) solution for situations where a scenegraph + * modification is required to happen is to use either the + * {@link #addPrePulseListener(Runnable)} API or the the + * {@link javafx.animation.AnimationTimer} API. + * + * @param r The Runnable to be called when the pulse occurs. + * @since 9 + */ + public final void addPostPulseListener(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("Scene pulse listener should not be null"); + } + if (postPulseListeners == null) { + postPulseListeners = new CopyOnWriteArrayList<>(); + } + postPulseListeners.add(r); + } + + /** + * Removes a previously registered scene post pulse listener from listening to + * pulses in this scene. + * + * @param r The Runnable that should no longer be called when this scene pulses. + */ + public final void removePostPulseListener(Runnable r) { + if (postPulseListeners == null) { + return; + } + postPulseListeners.remove(r); + } + /** * Return the defined {@code SceneAntialiasing} for this {@code Scene}. *

@@ -2386,6 +2464,14 @@ disposeAccessibles(); + // run any scene pre pulse listeners immediately _before_ css / layout, + // and before scene synchronization + if (prePulseListeners != null) { + for (Runnable r : prePulseListeners) { + r.run(); + } + } + if (PULSE_LOGGING_ENABLED) { PulseLogger.newPhase("CSS Pass"); } @@ -2396,6 +2482,14 @@ } Scene.this.doLayoutPass(); + // run any scene post pulse listeners immediately _after_ css / layout, + // and before scene synchronization + if (postPulseListeners != null) { + for (Runnable r : postPulseListeners) { + r.run(); + } + } + boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty(); if (dirty) { if (PULSE_LOGGING_ENABLED) {