Package limn.scene

Class Scene

java.lang.Object
limn.scene.Scene
All Implemented Interfaces:
WindowInput

public final class Scene extends Object implements WindowInput
Owns a widget tree bound to one window: queues native input (with coalescing), dispatches events with hit-testing and bubbling, tracks hover, click synthesis and keyboard focus (Tab traversal in layout order), ticks animations and runs the two-phase layout before painting.

Per-frame pipeline (deterministic, spec order): user input (dispatched from the backend's input-batch hook, before the Ui.post drain) → posted tasks → animation tick → layout if dirty → paint.

Coalescing: consecutive mouse-moves collapse to the newest, consecutive scrolls accumulate deltas, consecutive resizes collapse; clicks and keys are never dropped and never reordered.

Slow-handler instrumentation: every widget handler invocation is timed; exceeding the budget (default 8 ms) logs a warning naming the widget, the "StrictMode" that keeps the render thread honest.

V1 limitation: mouse capture tracks a single pressed widget/button, so chorded presses (two buttons held at once) synthesize a CLICK only for the most recent one.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static interface 
    Animation hook, called once per frame while registered.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final double
    Longest step any ticker is handed in one frame, whatever the wall clock says.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Scene(Widget root)
    A scene over root.
    Scene(Widget root, LongSupplier clock)
    Injectable clock (slow-handler instrumentation and animation ticks), public so component tests can drive animations deterministically.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Registers a ticker on wall time: setTimeScale(double) and setPaused(boolean) do not reach it.
    Registers a scene-wide keyboard handler for chords the focused widget did not want: menu accelerators, and anything else that must work without being focused.
    void
    Registers an animation ticker on scene time; frames keep coming while any is active.
    The colour cleared behind the tree each frame.
    void
    Wires this scene into a window: input, frame rendering, invalidation, clipboard.
    void
    charTyped(int codepoint)
    Committed text input, one code point (IME/layout-aware, after key events).
     
     
    void
    damage(Rect region)
    Marks a scene-space region as damaged and schedules a frame, for painting that extends beyond the invalidating widget's bounds (shadows, carets in overlays).
    void
    disposeLater(Runnable gpuCleanup)
    Like disposeLater(GpuSurface) for any GPU cleanup that must run with this scene's GL context current, e.g.
    void
    Schedules a GpuSurface for disposal at the next frame, when this scene's window/GL context is current.
    void
    fadeWindow(float target, double seconds, Runnable onArrive)
    Animates this scene's native-window opacity toward target ([0..1]) over seconds, running onArrive once it settles.
    void
    fadeWindowIn(double seconds)
    Snaps the window opacity to 0 immediately, then eases it to 1 over seconds, the flash-free "appear" sequence.
    void
    fadeWindowOut(double seconds, Runnable onGone)
    Eases the window opacity to 0, then runs onGone (typically a close).
    void
    Files dragged from the OS and dropped on the window.
    The widget holding keyboard focus, or null when nothing does.
    void
    focusTraverse(boolean backward)
    Moves focus to the next/previous focusable widget in layout (DFS) order.
     
    void
    Copies the rendering flags (partial rendering, damage debug) from the scene that spawned this one: popup/dialog scenes live in their own native windows, so without this the flags would only ever affect the window they were toggled in.
    void
    End of one native poll: dispatch the accumulated batch now.
    static void
    Installs the themed tooltip appearance (the components layer calls this once).
    boolean
    Whether each frame's damage region is highlighted for inspection.
    boolean
    Whether only damaged regions are repainted rather than the whole window.
    boolean
     
    boolean
    Whether the bound window holds OS input focus, as reported by the last focus event (false until the first one arrives; windows shown without stealing focus, like popups, start unfocused).
    void
    keyEvent(int key, boolean pressed, boolean repeat, int modifiers)
     
     
    void
    layoutPass(float newWidth, float newHeight)
    Runs measure/layout when dirty or resized (public for headless tests/embedding).
     
     
    int
     
    void
    mouseButton(int button, boolean pressed, int modifiers, float x, float y)
    Button press/release at the given cursor position.
    void
    mouseDelta(float dx, float dy)
    Relative pointer motion while the window is in PointerMode.RELATIVE: unbounded deltas in logical points (raw/unaccelerated where the platform supports it), replacing WindowInput.mouseMoved(float, float) for the duration of the capture.
    void
    mouseMoved(float x, float y)
     
    Observes every mouse press with its hit-tested target (after normal dispatch), the hook for "click outside to dismiss" overlays and popups that must react to presses landing on non-focusable widgets.
    Observes the bound window losing OS focus, the cue that dismisses transient popups (dropdowns, menus) anchored to this window: a press in another window or application never reaches observePresses(java.util.function.Consumer<limn.scene.Widget>).
    Runs observer when the bound window is destroyed, the hook for completion work driven by tickers/frames, which stop forever at that point (an abandoned fade would otherwise leak an uncompleted future).
    void
    pointerEntered(boolean entered)
    Cursor entered (true) or left the window's content area.
    void
    preeditChanged(String text, int[] blockSizes, int focusedBlock, int caret)
    In-progress IME composition ("preedit") for the focused text input: the still-composing text plus its styled blocks and caret, shown inline but not yet committed (the commit arrives later via WindowInput.charTyped(int)).
    void
    Pushes overlay as a full-scene modal layer painted on top of everything: it captures all input and confines focus (hit-testing and Tab traversal ignore the content and lower overlays).
    void
    Re-measures and repaints this whole scene, root and overlays.
    void
    Removes a modal overlay and restores focus to whatever it took it from.
    void
    Full frame: animation tick → layout (if dirty) → paint (root then overlays) → modal scrim.
    void
    renderFrame(Canvas canvas, boolean rePresent)
     
    void
    renderFrame(Canvas canvas, boolean rePresent, float gpuFrameMs)
     
    void
    Moves keyboard focus to widget, or clears it when null.
    void
    Schedules a frame and marks the whole scene damaged.
    The root widget of this scene's tree.
    void
    scrolled(float deltaX, float deltaY, float x, float y)
    Wheel/trackpad scroll; deltas in native notches (positive = up/left).
    void
    The colour cleared behind the tree each frame.
    void
    setClipboard(Clipboard newClipboard)
    Overrides the clipboard (tests inject mocks).
    void
    Sets this window's default step, the per-window root of the inheritance chain (a compact tool window beside a regular main window).
    void
    setDamageDebug(boolean enabled)
    Highlights each frame's fresh damage region (magenta wash + border), the tool for spotting under-invalidation bugs and seeing what partial rendering repaints.
    void
    Draws over the finished frame: after the root, after every overlay, after the modal scrim and after the tooltip.
    void
    Sets this window's default layout direction, the per-window root of the inheritance chain: the one line an application whose interface reads right to left writes.
    void
    setLocale(Locale locale)
    Sets this window's default locale, the per-window root of the inheritance chain: what makes two windows in two languages expressible, which one process-wide locale could not say (ADR 006 §4, delivered by ADR 035).
    void
    setPartialRendering(boolean enabled)
    Enables partial rendering: frames repaint only the damaged region (plus the previous frame's, for double buffering) instead of the whole window.
    void
    setPaused(boolean newPaused)
    Freezes or resumes scene time, keeping the timeScale() the app chose (so a paused slow-motion scene resumes in slow motion).
    void
    Budget above which an event handler is reported (default 8 ms).
    void
    Overrides the text measurer (tests use deterministic fake metrics).
    void
    setTimeScale(double scale)
    Speed of scene time, the clock every addTicker(limn.scene.Scene.Ticker) ticker integrates.
    int
     
     
    double
     
     
    void
    The bound window was destroyed.
    void
    windowFocusChanged(boolean focused)
    The window gained or lost OS input focus.
    void
    windowResized(float logicalWidth, float logicalHeight)
    Window content resized (logical points).

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • MAX_TICK_SECONDS

      public static final double MAX_TICK_SECONDS
      Longest step any ticker is handed in one frame, whatever the wall clock says.

      The frame interval is not bounded by anything: a GC pause, a window drag, a breakpoint, a laptop lid or a monitor switch can leave seconds between two frames. Unclamped, that interval reaches every ticker at once: a 0.14 s focus fade jumps straight to its end (merely ugly), and anything integrating it (a game step, a physics tick, a sweep that wraps modulo 1) skips through states it was supposed to pass through, which is how tunnelling and NaN-poisoned simulations start. 0.25 s is the conventional guard: long enough that no healthy frame is ever clipped (it is 15 frames at 60 Hz), short enough that the worst a stall can do is one slow-motion step.

      Clamping loses time on purpose: the toolkit does not bank the missing seconds and replay them. A stalled second is a second the app did not run, not a debt.

      See Also:
  • Constructor Details

    • Scene

      public Scene(Widget root)
      A scene over root. Subscribes to font, size-step and language changes.
    • Scene

      public Scene(Widget root, LongSupplier clock)
      Injectable clock (slow-handler instrumentation and animation ticks), public so component tests can drive animations deterministically.
  • Method Details

    • metrics

      public FrameMetrics metrics()
      Returns:
      live per-frame performance samples (FPS, frame time, event time)
    • setTextRuler

      public void setTextRuler(TextRuler ruler)
      Overrides the text measurer (tests use deterministic fake metrics).
    • textRuler

      public TextRuler textRuler()
      Returns:
      the scene's text measurer (defaults to the backend-installed one)
    • bind

      public void bind(NativeWindow window)
      Wires this scene into a window: input, frame rendering, invalidation, clipboard.
    • relayout

      public void relayout()
      Re-measures and repaints this whole scene, root and overlays. Call it after changing an input to measurement that is not a widget property: the active Theme, the UI font family, this scene's default control size.

      root().markNeedsLayout() is not enough: it dirties only the path to the root, so children return cached sizes under unchanged constraints, and it misses overlays entirely (open dialogs, open menus). The global path must therefore go through Scene, never through app code.

    • window

      public NativeWindow window()
      Returns:
      the bound window, or null when headless
    • setClipboard

      public void setClipboard(Clipboard newClipboard)
      Overrides the clipboard (tests inject mocks).
    • clipboard

      public Clipboard clipboard()
      Returns:
      the clipboard (window's, injected, or a local no-op; never null)
    • root

      public Widget root()
      The root widget of this scene's tree.
    • controlSize

      public ControlSize controlSize()
      Returns:
      this scene's default step, or null to fall through to a hosted root's host link and then to ControlSize.processDefault(). Nullable by design: a popup's own scene declares nothing, which is what lets it inherit from the widget that opened it.
    • setControlSize

      public void setControlSize(ControlSize size)
      Sets this window's default step, the per-window root of the inheritance chain (a compact tool window beside a regular main window). Widgets that declare their own step, and their subtrees, are unaffected. null restores fall-through. UI thread only.
    • layoutDirection

      public LayoutDirection layoutDirection()
      Returns:
      this scene's default layout direction, or null to fall through to a hosted root's host link and then to LayoutDirection.processDefault(). Nullable by design, for the reason controlSize() is: a popup's own scene declares nothing, which is what lets it inherit from the widget that opened it.
    • setLayoutDirection

      public void setLayoutDirection(LayoutDirection direction)
      Sets this window's default layout direction, the per-window root of the inheritance chain: the one line an application whose interface reads right to left writes. Widgets that declare their own direction, and their subtrees, are unaffected — which is what lets a left-to-right code editor, log pane or URL bar sit inside it. null restores fall-through. UI thread only.
    • locale

      public Locale locale()
      Returns:
      this scene's default locale, or null to fall through to a hosted root's host link and then to I18n.processLocale(). Nullable by design, for the reason controlSize() is: a popup's own scene declares nothing, which is what lets it inherit from the widget that opened it.
    • setLocale

      public void setLocale(Locale locale)
      Sets this window's default locale, the per-window root of the inheritance chain: what makes two windows in two languages expressible, which one process-wide locale could not say (ADR 006 §4, delivered by ADR 035). Widgets that declare their own locale, and their subtrees, are unaffected. null restores fall-through. Retains the declared locale's bundle tables exactly as Widget.setLocale(java.util.Locale) does. UI thread only.
    • pushOverlay

      public void pushOverlay(Widget overlay)
      Pushes overlay as a full-scene modal layer painted on top of everything: it captures all input and confines focus (hit-testing and Tab traversal ignore the content and lower overlays). This is how modal dialogs work: an in-scene overlay, no nested event loop. Focus moves to the overlay's first focusable widget.
    • removeOverlay

      public void removeOverlay(Widget overlay)
      Removes a modal overlay and restores focus to whatever it took it from.
    • setBackground

      public void setBackground(Color color)
      The colour cleared behind the tree each frame. A translucent one only shows through where the window itself is translucent.
    • background

      public Color background()
      The colour cleared behind the tree each frame. Read it before replacing it to tell a deliberately translucent scene from one that simply took a theme's canvas: a caller swapping the palette under a built scene must leave the first alone.
    • setSlowHandlerBudgetMillis

      public void setSlowHandlerBudgetMillis(long millis)
      Budget above which an event handler is reported (default 8 ms).
    • slowHandlerCount

      public int slowHandlerCount()
      Returns:
      handler-budget violations seen so far (debug/tests)
    • setPartialRendering

      public void setPartialRendering(boolean enabled)
      Enables partial rendering: frames repaint only the damaged region (plus the previous frame's, for double buffering) instead of the whole window. A subtree that misses the pass region is skipped from the paint walk too (culledFromPaint), as one that misses the canvas clip is in every mode. Default off.
    • isPartialRendering

      public boolean isPartialRendering()
      Whether only damaged regions are repainted rather than the whole window.
    • setDamageDebug

      public void setDamageDebug(boolean enabled)
      Highlights each frame's fresh damage region (magenta wash + border), the tool for spotting under-invalidation bugs and seeing what partial rendering repaints. Works with partial rendering on or off (off shows what would be repainted). Default off.
    • isDamageDebug

      public boolean isDamageDebug()
      Whether each frame's damage region is highlighted for inspection.
    • inheritRenderingFlags

      public void inheritRenderingFlags(Scene owner)
      Copies the rendering flags (partial rendering, damage debug) from the scene that spawned this one: popup/dialog scenes live in their own native windows, so without this the flags would only ever affect the window they were toggled in. (A transparent-background popup still falls back to full frames; the debug overlay works regardless.)
    • requestRender

      public void requestRender()
      Schedules a frame and marks the whole scene damaged.
    • setFrontPainter

      public void setFrontPainter(Consumer<Canvas> painter)
      Draws over the finished frame: after the root, after every overlay, after the modal scrim and after the tooltip. null removes it.

      It is not in the tree and not in hit-testing. That is the whole difference from a widget, and it is what this exists for. A widget added last to the root paints under any overlay, so it disappears the moment a modal dialog opens; an overlay pushed on top paints correctly but becomes the topmost layer, and hitAt(float, float) then routes every press to it alone, so a dialog underneath can never be driven. A front painter has neither problem because it takes no input at all.

      Meant for a recording or capture harness that has to draw something over the whole window: a mouse pointer into a filmed frame, a watermark, a debug readout. It is not a place to put user interface. Nothing here can be clicked, focused, or reached by a keyboard, and a control drawn from here would look live and be inert.

      It draws on every repaint, including partial ones, and it is given the same canvas the frame used, so what it draws outside the damaged region will be clipped away. A caller that moves what it draws must therefore mark the scene damaged itself, which requestRender() does; the capture harness renders whole frames and so does not have to.

      Painted before the damage-debug flashes, so those stay legible on top of it.

    • frontPainter

      public Consumer<Canvas> frontPainter()
      Returns:
      the front painter, or null.
    • damage

      public void damage(Rect region)
      Marks a scene-space region as damaged and schedules a frame, for painting that extends beyond the invalidating widget's bounds (shadows, carets in overlays). Widget.invalidate() is the common path.
    • observePresses

      public Runnable observePresses(Consumer<Widget> observer)
      Observes every mouse press with its hit-tested target (after normal dispatch), the hook for "click outside to dismiss" overlays and popups that must react to presses landing on non-focusable widgets. Returns a handle that unregisters the observer.
    • isWindowFocused

      public boolean isWindowFocused()
      Whether the bound window holds OS input focus, as reported by the last focus event (false until the first one arrives; windows shown without stealing focus, like popups, start unfocused).
    • observeWindowBlur

      public Runnable observeWindowBlur(Runnable observer)
      Observes the bound window losing OS focus, the cue that dismisses transient popups (dropdowns, menus) anchored to this window: a press in another window or application never reaches observePresses(java.util.function.Consumer<limn.scene.Widget>). Returns a handle that unregisters the observer.
    • modifiers

      public int modifiers()
      Returns:
      the modifier keys held right now (see Keys bits)
    • focusedWidget

      public Widget focusedWidget()
      The widget holding keyboard focus, or null when nothing does.
    • requestFocus

      public void requestFocus(Widget widget)
      Moves keyboard focus to widget, or clears it when null. Ignored for a widget that is not focusable, visible and enabled, and for one outside the topmost modal overlay. UI thread only.
    • focusTraverse

      public void focusTraverse(boolean backward)
      Moves focus to the next/previous focusable widget in layout (DFS) order.
    • addTicker

      public void addTicker(Scene.Ticker ticker)
      Registers an animation ticker on scene time; frames keep coming while any is active. Safe to call from inside another ticker's tick (e.g. one animation kicking off another); such a ticker joins on the next frame.

      Its dt is clamped to MAX_TICK_SECONDS and multiplied by timeScale(), so setPaused(boolean) freezes it. That is the right default for anything the app animates. Use addRealTimeTicker(limn.scene.Scene.Ticker) for an animation whose completion frees a resource or settles state; freezing one of those hangs it.

    • addRealTimeTicker

      public void addRealTimeTicker(Scene.Ticker ticker)
      Registers a ticker on wall time: setTimeScale(double) and setPaused(boolean) do not reach it. Its dt is still clamped to MAX_TICK_SECONDS: that guard is about a stalled clock, not about intent.

      For animations that are not content: the toolkit's own window / dialog / menu fades, widget transitions, scrollbar fades, progress sweeps. Two reasons they belong here. The hard one: several of them own a lifecycle; the fade-out is what destroys the popup window, removes the overlay that is capturing input, or completes the dialog's future, so a pause that froze them would leave a half-closed dialog on screen and a future nobody ever completes. The soft one: shell feedback that stops responding is read as a hang, not as a pause; a paused app should still highlight the button under the pointer.

    • disposeLater

      public void disposeLater(GpuSurface surface)
      Schedules a GpuSurface for disposal at the next frame, when this scene's window/GL context is current. Widgets that own GPU resources (e.g. a 3D viewport) call this from Widget.onDetached() instead of disposing inline, because dispose() would run without a current context.
    • disposeLater

      public void disposeLater(Runnable gpuCleanup)
      Like disposeLater(GpuSurface) for any GPU cleanup that must run with this scene's GL context current, e.g. a retained 3D scene's Scene3D.dispose(). Runs once, at the top of the next frame.
    • setTimeScale

      public void setTimeScale(double scale)
      Speed of scene time, the clock every addTicker(limn.scene.Scene.Ticker) ticker integrates. 1 is real time, 0.5 slow motion, 2 double speed, 0 frozen. Never negative: nothing in the toolkit's animation model runs backwards, and a negative dt would drive Transition past its start with no way back.

      What it does not touch. Wall time keeps running underneath: input, layout, Ui.postDelayed(java.lang.Runnable, long) (so the caret keeps blinking), the frame metrics, the tooltip and modal-scrim fades, and every addRealTimeTicker(limn.scene.Scene.Ticker) ticker, which is where the toolkit puts its own shell animation, so a paused app still opens dialogs, closes menus and fades windows normally. Scene time is for what the app animates, not for the chrome around it.

      Parameters:
      scale - a finite factor ≥ 0
    • timeScale

      public double timeScale()
      Returns:
      the current scene-time scale (see setTimeScale(double))
    • setPaused

      public void setPaused(boolean newPaused)
      Freezes or resumes scene time, keeping the timeScale() the app chose (so a paused slow-motion scene resumes in slow motion). Equivalent in effect to a scale of 0.

      No time is banked while paused: the paused seconds are never replayed. A scene-time ticker resumes measuring from the last frame that actually ran, which is the resume itself when the pause stopped the frame pump (so dt == 0), or the last frame a real-time ticker kept alive. Either way what it receives is the time since that frame, not the length of the pause. A ticker registered while paused still gets its own dt == 0 first frame, whenever it finally runs.

    • isPaused

      public boolean isPaused()
      Returns:
      whether scene time is frozen (see setPaused(boolean))
    • mouseMoved

      public void mouseMoved(float x, float y)
      Specified by:
      mouseMoved in interface WindowInput
    • mouseDelta

      public void mouseDelta(float dx, float dy)
      Description copied from interface: WindowInput
      Relative pointer motion while the window is in PointerMode.RELATIVE: unbounded deltas in logical points (raw/unaccelerated where the platform supports it), replacing WindowInput.mouseMoved(float, float) for the duration of the capture. The receiver may coalesce by summing. Default: ignored.
      Specified by:
      mouseDelta in interface WindowInput
    • mouseButton

      public void mouseButton(int button, boolean pressed, int modifiers, float x, float y)
      Description copied from interface: WindowInput
      Button press/release at the given cursor position.
      Specified by:
      mouseButton in interface WindowInput
    • scrolled

      public void scrolled(float deltaX, float deltaY, float x, float y)
      Description copied from interface: WindowInput
      Wheel/trackpad scroll; deltas in native notches (positive = up/left).
      Specified by:
      scrolled in interface WindowInput
    • keyEvent

      public void keyEvent(int key, boolean pressed, boolean repeat, int modifiers)
      Specified by:
      keyEvent in interface WindowInput
    • charTyped

      public void charTyped(int codepoint)
      Description copied from interface: WindowInput
      Committed text input, one code point (IME/layout-aware, after key events).
      Specified by:
      charTyped in interface WindowInput
    • preeditChanged

      public void preeditChanged(String text, int[] blockSizes, int focusedBlock, int caret)
      Description copied from interface: WindowInput
      In-progress IME composition ("preedit") for the focused text input: the still-composing text plus its styled blocks and caret, shown inline but not yet committed (the commit arrives later via WindowInput.charTyped(int)). An empty text clears the composition. Default: ignored.
      Specified by:
      preeditChanged in interface WindowInput
      Parameters:
      text - the composing text ("" clears it)
      blockSizes - code-point length of each styled block (tiles text)
      focusedBlock - index of the block being converted, or -1
      caret - caret position within text, in code points
    • pointerEntered

      public void pointerEntered(boolean entered)
      Description copied from interface: WindowInput
      Cursor entered (true) or left the window's content area.
      Specified by:
      pointerEntered in interface WindowInput
    • filesDropped

      public void filesDropped(List<Path> paths)
      Description copied from interface: WindowInput
      Files dragged from the OS and dropped on the window. The drop lands at the current pointer position (the platform moves the cursor onto the window before dropping). Default: ignored.
      Specified by:
      filesDropped in interface WindowInput
    • windowFocusChanged

      public void windowFocusChanged(boolean focused)
      Description copied from interface: WindowInput
      The window gained or lost OS input focus. On loss the scene cancels any in-flight press/drag/hover; the matching RELEASE will never arrive (it happens in another app), so it is synthesized. Default: ignored.
      Specified by:
      windowFocusChanged in interface WindowInput
    • windowResized

      public void windowResized(float logicalWidth, float logicalHeight)
      Description copied from interface: WindowInput
      Window content resized (logical points). May be coalesced by the receiver.
      Specified by:
      windowResized in interface WindowInput
    • inputBatchEnded

      public void inputBatchEnded()
      Description copied from interface: WindowInput
      End of one native poll: dispatch the accumulated batch now.
      Specified by:
      inputBatchEnded in interface WindowInput
    • addShortcutHandler

      public Runnable addShortcutHandler(Predicate<KeyEvent> handler)
      Registers a scene-wide keyboard handler for chords the focused widget did not want: menu accelerators, and anything else that must work without being focused.

      The ordering is the contract. A key event is offered, in this order, to:

      1. the focused widget, bubbling to the root, so Ctrl+C inside a text field with a selection is the field's copy and never a menu's;
      2. every handler registered here, oldest first, until one returns true;
      3. Tab traversal, if the key is Tab and still nobody took it.

      Returning true consumes the event and stops both the remaining handlers and the Tab fallback. Handlers see presses, auto-repeats and releases alike: a shortcut whose trigger is a modifier let go of (a bare Alt reaching for the menu bar) has nothing else to key on.

      Handlers are not consulted while a modal overlay owns the scene: an open dialog or an in-scene menu has the keyboard, and a shortcut belonging to what it covers must not fire behind it.

      Registering or unregistering from inside a handler is legal and takes effect on the next event, never on the one being dispatched. Call the returned Runnable to unregister; it is idempotent. UI thread only.

    • renderFrame

      public void renderFrame(Canvas canvas)
      Full frame: animation tick → layout (if dirty) → paint (root then overlays) → modal scrim.
    • renderFrame

      public void renderFrame(Canvas canvas, boolean rePresent)
      Parameters:
      rePresent - true when re-drawing the same already-settled frame only to converge the double buffers (the backend's double-present anti-flicker): identical pixels, but excluded from metrics() so FPS/frame-time count only frames rendered for content.
    • renderFrame

      public void renderFrame(Canvas canvas, boolean rePresent, float gpuFrameMs)
      Parameters:
      gpuFrameMs - backend-measured GPU time of a recently completed frame in ms (Float.NaN = no new sample); recorded into metrics() alongside this frame's CPU numbers
    • installTooltipStyle

      public static void installTooltipStyle(Function<ControlSize,TooltipStyle> supplier)
      Installs the themed tooltip appearance (the components layer calls this once).
    • fadeWindowIn

      public void fadeWindowIn(double seconds)
      Snaps the window opacity to 0 immediately, then eases it to 1 over seconds, the flash-free "appear" sequence. Call it right before NativeWindow.show() so the window is already transparent when it first maps.
    • fadeWindowOut

      public void fadeWindowOut(double seconds, Runnable onGone)
      Eases the window opacity to 0, then runs onGone (typically a close).
    • fadeWindow

      public void fadeWindow(float target, double seconds, Runnable onArrive)
      Animates this scene's native-window opacity toward target ([0..1]) over seconds, running onArrive once it settles. A newer call supersedes an in-flight fade without stacking tickers. Zero duration, an already-current value, or a headless scene (no window) jumps straight to target and runs onArrive now. UI thread only.
    • windowClosed

      public void windowClosed()
      The bound window was destroyed. A window-fade that had not yet reached its target will never tick again (a closed window renders no frames), so run its pending arrival callback now; otherwise a dialog fading out when its owner closes would leak an uncompleted result future. Runs at most once: bumping the generation kills the abandoned ticker, and clearing the field makes a later normal completion a no-op (and the callbacks it drives, like CompletableFuture.complete, are themselves idempotent).

      Focus is cleared first: widgets gate self-rescheduling work (e.g. the text-caret blink chain) on isFocused(), and a widget that stays "focused" in a dead scene would re-arm on the global UI queue forever, waking the event loop, forcing frames on every live window, and pinning this scene against GC. Clearing through setFocus(limn.scene.Widget) also runs the normal focus-lost path (composition dropped, IME state torn down).

      Specified by:
      windowClosed in interface WindowInput
    • observeWindowClosed

      public Runnable observeWindowClosed(Runnable observer)
      Runs observer when the bound window is destroyed, the hook for completion work driven by tickers/frames, which stop forever at that point (an abandoned fade would otherwise leak an uncompleted future). Observers run once and are dropped; the returned handle unregisters earlier (call it when the normal path completed first).
    • layoutPass

      public void layoutPass(float newWidth, float newHeight)
      Runs measure/layout when dirty or resized (public for headless tests/embedding).