Package limn.scene

Class Scene

java.lang.Object
limn.scene.Scene

public final class Scene extends Object
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.

One scene can serve a window for its whole life: to show another page in it, give the scene another root with setRoot(limn.scene.Widget<?>).

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.
    void
    Says something out loud with a fixed string.
    void
    Says something out loud to whoever is using an assistive technology: a status that changed where nothing on screen says so, a background task that finished, an error that has no control to attach itself to.
     
    void
    Wires this scene into a window: input, frame rendering, invalidation, clipboard.
    boolean
    Whether an editing command can be done now by the widget that has the keyboard here, or by one of its ancestors.
    boolean
    canEdit(EditCommand command, @Nullable Widget<?> from)
    Whether an editing command can be done now by from or by one of its ancestors: the question canEdit(EditCommand) asks of the focused widget, asked of a widget the caller remembered, as a menu does of the one that had the keyboard before it opened.
     
    The clock this scene's animations tick on, for a scene built to stand beside it.
    @Nullable ControlSize
     
    void
    damage(Rect region)
    Marks a scene-space region as damaged and schedules a frame, for painting somewhere a widget's Widget.invalidate() does not reach: its bounds grown by its paintOutset(), which is where a shadow or a focus ring is declared.
    void
    disposeLater(@Nullable Runnable gpuCleanup)
    Like disposeLater(GpuSurface) for any GPU cleanup that must run with this scene's GL context current, e.g.
    void
    disposeLater(@Nullable GpuSurface surface)
    Schedules a GpuSurface for disposal at the next frame, when this scene's window/GL context is current.
    boolean
    edit(EditCommand command)
    Does an editing command in the widget that has the keyboard here, or in the first of its ancestors that can, as the user's own act: what the command's chord does there.
    boolean
    edit(EditCommand command, @Nullable Widget<?> from)
    Does an editing command in from, or in the first of its ancestors that can.
    void
    fadeWindow(float target, double seconds, @Nullable 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, @Nullable Runnable onGone)
    Eases the window opacity to 0, then runs onGone (typically a close), by the rules of fadeWindow(float, double, java.lang.Runnable): never inside this call, and after seconds (or on the next turn of the UI queue for none) when the fade goes straight there.
    @Nullable Widget<?>
    The first widget whose id is id, searching the root's tree in order, parents before children, then each overlay in the order it was pushed.
    <T> @Nullable T
    find(String id, Class<T> type)
    find(String), typed.
    @Nullable Widget<?>
    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.
    @Nullable Consumer<Canvas>
     
    void
    Publishes a popup that draws in a window of its own as part of this scene's accessible tree, under the widget that opened it, the way a native drop-down list is a child of its field: a combo's list, a date picker's calendar.
    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.
    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
     
    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).
    @Nullable LayoutDirection
     
    void
    layoutPass(float newWidth, float newHeight)
    Runs measure/layout when dirty or resized (public for headless tests/embedding).
    @Nullable Locale
     
     
    int
     
    Watches every change to every widget in this scene's tree, overlays included, from every origin.
    observePresses(Consumer<@Nullable 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.
    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).
    Observes the bound window entering or leaving fullscreen, the cue that closes a surface open in a window of its own over it (a menu, a drop-down list, a calendar), as the platforms dismiss their own menus on a change of their window's state: the change resizes and moves the window under a surface placed against its old geometry, and a window fullscreen at a named mode is minimized as soon as another window takes the focus.
    void
    Sizes the bound window to what this scene's content asks for: the root measured with no bound on either axis, rounded up to whole points, kept within the window's size limits and no larger than its display's work area.
    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).
    void
    pushPopup(Widget<?> popup)
    Pushes popup as pushOverlay(limn.scene.Widget<?>) does — it captures all input and confines focus — for a surface that opens from a control and belongs to it: a combo's list, a menu, a date picker's calendar.
    void
    Re-measures and repaints this whole scene, root and overlays.
    void
    removeOverlay(Widget<?> overlay)
    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, int bufferAge)
     
    void
    renderFrame(Canvas canvas, FrameInfo frame)
    Renders the frame a window's backend asked for, with everything the backend said about it: the re-present flag, the GPU-time sample and the back buffer's age.
    void
    requestFocus(@Nullable Widget<?> widget)
    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
    setBackground(@Nullable Color color)
    The colour cleared behind the tree each frame, chosen by the application and kept whatever the palette does: a scene that clears to transparent to let the desktop through, a video that wants black around it.
    void
    setClipboard(Clipboard newClipboard)
    Overrides the clipboard (tests inject mocks).
    void
    setControlSize(@Nullable 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).
    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
    setDragKey(int key, int modifiers)
    Sets the key that begins a drag from the keyboard on the focused widget.
    void
    setFrontPainter(@Nullable Consumer<Canvas> painter)
    Draws over the finished frame: after the root, after every overlay, after the modal scrim and after the tooltip.
    void
    setLayoutDirection(@Nullable 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.
    void
    setLocale(@Nullable 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.
    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
    setRoot(Widget<?> root)
    Replaces this scene's root with root, which is how a window shows another page: one scene, bound once, given each page's tree in turn.
    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
     
    @Nullable NativeWindow
     

    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.

      A widget belongs to one scene as its root or one of its overlays: one that is already another scene's root, or an overlay on another scene's stack, is refused, as pushOverlay(Widget) and every container's add refuse it. That scene lets go of it when it is taken off the stack (removeOverlay(limn.scene.Widget<?>)), when it is given another root (setRoot(limn.scene.Widget<?>)), or when its window closes. A widget that has a parent is refused too: a root has none.

      Parameters:
      root - the widget tree to lay out, paint and dispatch to; never null
      Throws:
      IllegalArgumentException - if root has a parent, or is another scene's root or an overlay on another scene's stack
    • Scene

      public Scene(Widget<?> root, LongSupplier clock)
      Injectable clock (slow-handler instrumentation and animation ticks), public so component tests can drive animations deterministically.
      Parameters:
      root - as Scene(Widget) takes it, and refused as it refuses it
      clock - what this scene reads as now, in nanoseconds
      Throws:
      IllegalArgumentException - if root has a parent, or is another scene's root or an overlay on another scene's stack
  • Method Details

    • metrics

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

      public LongSupplier clock()
      The clock this scene's animations tick on, for a scene built to stand beside it.

      A surface this scene opens in a window of its own — a combo's list, a menu's cascade, a date picker's calendar, a dialog's card — is the root of a second scene, and that scene owns the fade whose last frame destroys the window (fadeWindowOut(double, java.lang.Runnable)). Built on the wall clock while its opener runs on an injected one, that fade advances by real microseconds however much time the opener's clock is told has passed, so the window never goes: a headless harness could open a popup and never close it, and did (the accessibility gallery's, 2026-09-15). So the four surfaces pass this along, and a popup's time is its opener's time. With no clock injected both are System.nanoTime(), which is every application.

      Returns:
      what this scene reads as now
    • 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.

      A scene whose window closed may be bound again, to the same window's successor or to another: the tree the close let go of comes back to it. Not what another scene, or a container, took meanwhile: an overlay taken stays its taker's and is no longer on this scene's stack, and a root taken refuses the bind, since a scene has no tree without its root; take the root out of where it went first, or give this scene another one with setRoot(limn.scene.Widget<?>).

      Parameters:
      window - the window this scene draws in and takes its input from
      Throws:
      IllegalStateException - if this scene's window closed and its root has been taken since: it has a parent, or it is in another scene
    • 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.

    • pack

      public void pack()
      Sizes the bound window to what this scene's content asks for: the root measured with no bound on either axis, rounded up to whole points, kept within the window's size limits and no larger than its display's work area. The call a dialog or a tool window makes once its content is in place, instead of guessing a size in its configuration; a window created hidden can be packed before it is shown, and appears at its size.

      What the content asks for is each widget's natural size: a label its text, a button its label and padding, a column the sum of its children. The width is settled first, and the height is then measured at that width, so a paragraph that wraps gets the lines it wraps into. A dialog or a form packs well. Content that fills whatever it is given does not ask for much: a flexible child asks for its floor (Expanded.atLeast), a list or a table for little more than its header, and a widget with no natural size on an axis leaves the window's size there as it was. Text that wraps asks for its whole length on one line, which the work area then caps. Bounds on the content are what a packed window follows: a paragraph with Widget.setMaxWidth(float) packs to that width, and a list given a floor packs to that height. The layout is redone at the new size on the next frame. UI thread only.

      Throws:
      IllegalStateException - when the scene is bound to no window
    • window

      public @Nullable 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.
    • setRoot

      public void setRoot(Widget<?> root)
      Replaces this scene's root with root, which is how a window shows another page: one scene, bound once, given each page's tree in turn. It works whether or not the scene is bound to a window, and on a scene whose window has closed, whose next bind(limn.backend.NativeWindow) brings the new root in.

      The old root leaves the scene as a widget taken out of its container does: every widget in it is told Widget.onDetached(), a popup, menu or popover opened from one of its widgets closes (a menu shown at a point of the scene, at no widget, stays open), the keyboard, the pointer's hover and a press held in it are taken out, a drag it started in the window is cancelled (its source told the drag ended with nothing), and an assistive technology is told the window's content changed. A drag already handed to the system, to leave the window, is the system's: it goes on, a drop on the window goes to the new root, and the source hears the system's answer when the system ends it. It then belongs to no scene: it may be added to a container, pushed as an overlay, made another scene's root, or given back to this one later. The new root joins as a new scene's root does, and the next frame lays out and paints the whole window. The overlays on the stack stay where they are.

      The keyboard goes to the new root's first focusable widget, arriving as focusTraverse(boolean) puts it there, only when it was in the old root or in a popup that closes as the old root leaves (a popover, a combo box's list, a menu bar's menu drawn in the scene, where it had taken the keyboard); a widget the new root's content asks for with requestFocus(limn.scene.Widget<?>) as it joins (from an Widget.onAttached()) is focused instead. An overlay opened while the keyboard was in the old root gives it to the new root's first focusable widget when it closes, in place of the widget that left.

      Asked again from code this call runs (an onDetached of the old root, an onAttached of the new one, the handler of a popup closing, the onArrive of a fade or a motion that ends as its container leaves), the change waits for this one to be complete and is then made; when several are asked, the last one wins. A root that asks for another as it joins hands the window on, and so on: when, after the root it was given and 64 more asked for in turn, yet another is asked for, a chain that never settles (two pages whose guards disagree, each sending the window to the other as it joins), the call sets no more and throws an IllegalStateException, the scene showing the last root it set. What such code throws is thrown once the change is complete; with a chain that never settles, it is attached to that IllegalStateException as suppressed.

      Refused as a new scene refuses its root, before anything moves: a widget with a parent, another scene's root, an overlay on another scene's stack or on this one's, and the widget another call is making a scene's root. Setting the root this scene already has does nothing.

      Parameters:
      root - the new root; never null
      Throws:
      IllegalArgumentException - if root has a parent, is another scene's root, or is an overlay on this or another scene's stack
      IllegalStateException - if the roots asked for while it sets one never settle, with what the code it ran threw, if anything, as suppressed
    • find

      public @Nullable Widget<?> find(String id)
      The first widget whose id is id, searching the root's tree in order, parents before children, then each overlay in the order it was pushed. A hidden widget is found like a shown one. UI thread only.
      Parameters:
      id - the id to look for
      Returns:
      the widget, or null when none has that id
    • find

      public <T> @Nullable T find(String id, Class<T> type)
      find(String), typed.
      Type Parameters:
      T - that class
      Parameters:
      id - the id to look for
      type - the class the widget is expected to be
      Returns:
      the widget, or null when none has that id
      Throws:
      ClassCastException - when the widget with that id is not a type
    • controlSize

      public @Nullable 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(@Nullable 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 @Nullable 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(@Nullable 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 @Nullable 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(@Nullable 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. 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, or stays on the one the overlay's content asked for with requestFocus(limn.scene.Widget<?>) as it joined (from an Widget.onAttached()).

      A widget that is another scene's root, or an overlay on another scene's stack, is refused before anything moves, as a container's add and a new scene refuse it: the scene that holds it would go on naming it as its own and laying it out at its own window's size. That scene lets go of it when it is taken off the stack (removeOverlay(limn.scene.Widget<?>)), when it is given another root (setRoot(limn.scene.Widget<?>)), or when its window closes. A widget that has a parent is refused the same way: an overlay is a root of its own, and as a container's child too it would be laid out, drawn and dispatched to twice. So are this scene's own root and an overlay already on its stack, which one push holds once.

      Parameters:
      overlay - the overlay's root
      Throws:
      IllegalArgumentException - if overlay has a parent, is this or another scene's root, or is an overlay on this or another scene's stack
    • pushPopup

      public void pushPopup(Widget<?> popup)
      Pushes popup as pushOverlay(limn.scene.Widget<?>) does — it captures all input and confines focus — for a surface that opens from a control and belongs to it: a combo's list, a menu, a date picker's calendar. What differs is what an assistive technology is told about the page beneath: under a modal overlay it is published not enabled, because a dialog blocks its owner as a native modal disables the window behind it; under a popup it stays enabled and merely offers no verb, because a native drop-down list does not disable the field it opened from. Closed with removeOverlay(limn.scene.Widget<?>).
      Parameters:
      popup - the popup's root widget
      Throws:
      IllegalArgumentException - if popup has a parent, is this or another scene's root, or is an overlay on this or another scene's stack, as pushOverlay(Widget) refuses it
    • graftPopup

      public void graftPopup(Scene popup)
      Publishes a popup that draws in a window of its own as part of this scene's accessible tree, under the widget that opened it, the way a native drop-down list is a child of its field: a combo's list, a date picker's calendar. An assistive technology then never leaves this window's tree while the popup is open; the list's cursor is this tree's own, and the popup's window publishes nothing of its own.

      Call it before popup is bound to its window, and only for a popup whose keyboard is its opener's: the field keeps it and drives the popup, so the focused node stays in this tree. A pointer click in the popup's window makes it the desktop's key window; such a popup hands every key that reaches that window on to its opener, and gives this window the keyboard back when it closes while holding it, as the combo box's list and the date picker's calendar do. The popup's root names its opener with Widget.setInheritanceHost(limn.scene.Widget<?>); its nodes are published under the opener's, in this window's coordinates, while its window is visible, and leave the tree when that window closes. What a reader performs on them is performed in the popup's scene.

      This scene may itself be a grafted popup, a panel of controls in a window of its own: a popup grafted into it is then published by the tree this one is published in, under its opener there, to any depth, and leaves that tree when its own window closes or this one's does.

      Parameters:
      popup - the popup's scene, not yet bound
      Throws:
      IllegalArgumentException - if the popup's root names no opener
      IllegalStateException - if the popup is already bound, or grafted elsewhere
    • removeOverlay

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

      public void setBackground(@Nullable Color color)
      The colour cleared behind the tree each frame, chosen by the application and kept whatever the palette does: a scene that clears to transparent to let the desktop through, a video that wants black around it. A translucent one only shows through where the window itself is translucent.

      A scene whose application chose none, which is every scene until this is called, clears to the background of the palette in effect and follows it when the palette switches, in every window, those opened after the switch included.

      Parameters:
      color - the colour to clear to, or null to follow the palette again
    • background

      public Color background()
      Returns:
      the colour the next frame clears to: the one setBackground(limn.graphics.Color) chose, or the background of the palette in effect when none was chosen
    • 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 on: the mode the toolkit is correct in is the one it runs, and every widget is held to it by DamageContractTest. false is the escape hatch for a scene that composites something the toolkit cannot see, and for a capture harness that wants whole frames on purpose; nothing else should need it.

    • 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.
    • announce

      public void announce(I18nString text, Accessible.Politeness politeness)
      Says something out loud to whoever is using an assistive technology: a status that changed where nothing on screen says so, a background task that finished, an error that has no control to attach itself to.

      It is on the scene and not on a widget because it is a message to the user rather than a property of a box. Nothing is announced automatically: a status label that updates silently stays silent until an application adds this call.

      It buys the frame that delivers it, and only while something is listening. The steady state while a screen reader is reading a quiet interface is a loop parked with no frame pending, and an announcement enqueued into that state would never be spoken — the application's one way of saying something out loud going silent exactly when the interface is quiet, which is when it is most likely to be used. With nothing listening the entry is still queued and still bounded, so an application's diagnostics do not depend on a reader being present, and no frame is spent on speech nobody will hear.

      UI thread only.

      Parameters:
      text - what to say; resolved under this scene's own language when it is drained
      politeness - whether it waits for the assistive technology to finish, or interrupts it. Not a boolean, because all three platforms carry the distinction and it decides whether a user is cut off mid-sentence.
      Throws:
      NullPointerException - if either argument is null
    • announce

      public void announce(String text, Accessible.Politeness politeness)
      Says something out loud with a fixed string. UI thread only.
      Parameters:
      text - what to say
      politeness - whether it waits, or interrupts
      Throws:
      NullPointerException - if either argument is null
    • setFrontPainter

      public void setFrontPainter(@Nullable 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 @Nullable 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 somewhere a widget's Widget.invalidate() does not reach: its bounds grown by its paintOutset(), which is where a shadow or a focus ring is declared. A caret drawn in an overlay is one. Widget.invalidate() is the common path.
    • observePresses

      public Subscription observePresses(Consumer<@Nullable 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.
      Parameters:
      observer - told about every press; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • 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 Subscription 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<?>>).
      Parameters:
      observer - told when the window loses focus; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • observeWindowFullscreen

      public Subscription observeWindowFullscreen(Consumer<Boolean> observer)
      Observes the bound window entering or leaving fullscreen, the cue that closes a surface open in a window of its own over it (a menu, a drop-down list, a calendar), as the platforms dismiss their own menus on a change of their window's state: the change resizes and moves the window under a surface placed against its old geometry, and a window fullscreen at a named mode is minimized as soon as another window takes the focus. The person can make the change on macOS with the green button or Control-Command-F while a list or a calendar is open, because neither takes the focus from the window. A modal dialog's window hears it too, and moves into the scene.

      Told once per change, at the end of the input batch that brought it, even while a modal blocks the window; see WindowInput.windowFullscreenChanged(boolean).

      Parameters:
      observer - told whether the window is fullscreen now; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • modifiers

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

      public Subscription observeChanges(ChangeObserver observer)
      Watches every change to every widget in this scene's tree, overlays included, from every origin. One registration serves a whole window, adds nothing per widget, and works on an unbound scene -- which is the trap Scene's own metrics listener was moved into the constructor for, and which every component test, a combo box's popup and a dialog's modal scene before it binds all depend on.

      This is a channel over a tree, not over a set of widgets. It hears what the scene holds now, and goes quiet about a widget the scene no longer contains -- so a recycled list cell written to while unmounted reaches its own watchers and no scene's. A consumer that must follow one particular widget wherever it goes registers on the widget.

      Parameters:
      observer - told about every change in this tree; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • focusedWidget

      public @Nullable Widget<?> focusedWidget()
      The widget holding keyboard focus, or null when nothing does.
    • requestFocus

      public void requestFocus(@Nullable Widget<?> widget)
      Moves keyboard focus to widget, or clears it when null. Ignored for a widget that is not focusable, for one that is hidden or disabled itself or through any ancestor (a control inside a hidden tab or a disabled form), for one inside a subtree sliding shut, for one outside the topmost modal overlay, and for one in another scene or in none. One merely scrolled out of its viewport is not refused: arriving reveals it. UI thread only.

      A widget still joining this scene is the exception: a container's Widget.onAttached() runs before its children are attached, so a form that starts the keyboard on its first field from there asks for a widget that is not in the scene yet. The request is kept until the join is complete, and the field is focused then if it has joined and nothing above refuses it; a later request, null included, replaces it. A join that an onAttached cut short before reaching the field focuses nothing. Pushed as an overlay or a popup, the field asked for is the overlay's first focus: the push does not move on to its first stop.

      An application calling this is code moving the focus, so the two FOCUS changes it announces carry CODE. A gesture that moves focus does not come through here: a click and a Tab enter the funnel from the scene's own input paths, and the two components that move focus out of a gesture -- a tab strip's arrow key and a radio group's -- enter it through Widget.requestFocus(Change.Origin). Reading the method rather than the entry point would report every keyboard-driven focus move in the toolkit as made by code.

    • focusTraverse

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

      CODE, because this is the method an application calls; the Tab key reaches the same traversal through the scene's own key path and announces USER.

    • 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(@Nullable 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(@Nullable 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))
    • addShortcutHandler

      public Subscription 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.

      One system reverses the first two for a menu bar: where the bar is shown in the system's own, on macOS, the system runs a bar's accelerator before the key is sent to the window at all. See MenuBar.

      Handlers are not consulted while a modal overlay owns the scene's keyboard: an open dialog or an in-scene menu has it, and a shortcut belonging to what it covers must not fire behind it. An overlay that gave the keyboard back and is fading out, or a list that leaves the keyboard in the field that opened it, does not own it.

      A handler that throws is contained and read as did not handle, so the chord goes on to the next handler and to Tab traversal -- the policy Work.deliverIf already states for the same shape.

      Registering or unregistering from inside a handler is legal and takes effect on the next event, never on the one being dispatched.

      Parameters:
      handler - consulted for chords nobody focused wanted; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • canEdit

      public boolean canEdit(EditCommand command)
      Whether an editing command can be done now by the widget that has the keyboard here, or by one of its ancestors. What a menu's item of an editing role is enabled by, and what a tool bar's button for the command may be.
      Returns:
      false with nothing focused
    • canEdit

      public boolean canEdit(EditCommand command, @Nullable Widget<?> from)
      Whether an editing command can be done now by from or by one of its ancestors: the question canEdit(EditCommand) asks of the focused widget, asked of a widget the caller remembered, as a menu does of the one that had the keyboard before it opened.
      Parameters:
      from - a widget of this scene, or null, which can do nothing
    • edit

      public boolean edit(EditCommand command)
      Does an editing command in the widget that has the keyboard here, or in the first of its ancestors that can, as the user's own act: what the command's chord does there.
      Returns:
      whether a widget did it
    • edit

      public boolean edit(EditCommand command, @Nullable Widget<?> from)
      Does an editing command in from, or in the first of its ancestors that can.
      Parameters:
      from - a widget of this scene, or null, which does nothing
      Returns:
      whether a widget did it
    • setDragKey

      public void setDragKey(int key, int modifiers)
      Sets the key that begins a drag from the keyboard on the focused widget. The default is Ctrl+Shift+D, and Cmd+Shift+D on macOS.
      Parameters:
      key - a Keys constant
      modifiers - the Keys.MOD_* bits held with it
    • isDragging

      public boolean isDragging()
      Returns:
      whether a drag is in flight in this scene
    • 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, FrameInfo frame)
      Renders the frame a window's backend asked for, with everything the backend said about it: the re-present flag, the GPU-time sample and the back buffer's age. A frame callback of an application's own, one that does something before or after the scene paints, forwards the FrameInfo whole through here.

      It replaced an overload that took the flag and the sample and assumed the age, which every such callback in the demo called: when the backend began reading the age from the driver, those callbacks went on dropping it, and on Wayland, where the age is three or four, the demo's window drew black but for the status bar that changes every frame (2026-09-25).

      Parameters:
      canvas - the backend's canvas for this frame
      frame - what the backend passed to the frame callback
    • renderFrame

      public void renderFrame(Canvas canvas, boolean rePresent, float gpuFrameMs, int bufferAge)
      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
      bufferAge - how many presents old the canvas's contents are (FrameInfo.bufferAge): 0 repaints the whole frame, 1 what changed since the last frame, 2 that plus the frame before, and so on. The overloads without it assume 2, ordinary double buffering, which is what rendering into a canvas of one's own has always been treated as
    • 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, @Nullable Runnable onGone)
      Eases the window opacity to 0, then runs onGone (typically a close), by the rules of fadeWindow(float, double, java.lang.Runnable): never inside this call, and after seconds (or on the next turn of the UI queue for none) when the fade goes straight there.
    • fadeWindow

      public void fadeWindow(float target, double seconds, @Nullable 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, and its onArrive never runs. Zero duration, an already-current value, or a headless scene (no window) jumps straight to target, and onArrive runs once, after seconds when it is more than zero and on the next turn of the UI queue when it is not, unless a newer call supersedes it first. Never inside this call: a fade asked for again in its own onArrive does not recurse, and one that asked for seconds keeps its pace while the loop sleeps between steps. A window closed before then runs it as it closes, as it runs a fade's in flight. Under Wayland, which gives a program no window opacity, nothing shows, and the fade keeps the same time and runs onArrive the same way. UI thread only.
    • observeWindowClosed

      public Subscription 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).
      Parameters:
      observer - told once, when the window is destroyed; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • layoutPass

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

      What the new sizes take out of reach waits for the next frame: a transform that a pass turns edge-on, so that it draws its child as nothing, has the popups hung inside closed and the focus, the hover and a press inside taken out when renderFrame(Canvas) runs, which lays out again before it paints, so that no handler edits a tree while it is being laid out. A caller that runs this alone and reads the scene before rendering a frame still finds such a popup open and the focus inside.

      Parameters:
      newWidth - the window's width
      newHeight - the window's height