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 @@ -590,6 +590,39 @@ return null; } + private List scenePulseListeners; + + /** + * Adds a new scene pulse listener to this scene. Every time a pulse occurs, + * this listener will be called on the JavaFX Application Thread directly + * following the CSS and layout passes, but before the scene state is + * synchronized. + * + * @param r The Runnable to be called when the pulse occurs. + */ + public final void addScenePulseListener(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("Scene pulse listener should not be null"); + } + if (scenePulseListeners == null) { + scenePulseListeners = new ArrayList<>(); + } + scenePulseListeners.add(r); + } + + /** + * Removes a previously registered scene 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 removeScenePulseListener(Runnable r) { + if (scenePulseListeners == null) { + return; + } + scenePulseListeners.remove(r); + } + /** * Return the defined {@code SceneAntialiasing} for this {@code Scene}. *

@@ -2396,6 +2429,14 @@ } Scene.this.doLayoutPass(); + // run any scene pulse listeners immediately after css / layout, + // and before scene synchronization + if (scenePulseListeners != null) { + for (Runnable r : scenePulseListeners) { + r.run(); + } + } + boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty(); if (dirty) { if (PULSE_LOGGING_ENABLED) {