LavaUI docs

class WindowScope

Swift

final class WindowScope

The per-window half of LavaUI's framework state.

ViewInvalidation, FocusManager, NodeVisibility and the caret blink were written as process-wide statics under an explicit single-window assumption. With two windows in one process each of them is a bug: window A consuming the invalidation level swallows window B's redraw, a key event delivered to B reaches the field focused in A, and A's emitTree clears the visibility set that B's animations are gated on.

The fix keeps every one of those APIs exactly where it was — forty-odd call sites across widgets say ViewInvalidation.markNeedsRedraw() with no window in scope, and threading one through all of them would be both invasive and wrong, since a widget genuinely does not know which window it is in. Instead the storage moves here, and the statics resolve to a scope:

  • Ambient. The frame loop marks a window current around everything it does for that window — input, body, layout, emit. A widget invalidating from a click handler or a body therefore lands in the right window without ever naming it.
  • By node. markBodyDirty(node) cannot use the ambient scope: an @Observable model mutated by a handler in window A can be observed by a node in window B, and B is the one that has to recompute. CompositeNode captures its scope at mount, when the ambient one is correct by construction, and routes there for the rest of its life.
  • Broadcast. Outside any window phase — MainQueue results, deferred FrameTasks — there is no answer to "which window", so a coarse invalidation goes to all of them. Wasteful in the case where only one window cared, but never wrong, which is the correct trade for the path that runs once per worker result rather than once per frame.

Single-window behaviour is unchanged: everything resolves to .main.

What deliberately stays global is anything keyed by NodeID, because those ids are unique process-wide and so cannot collide between windows — ScrollRouter, DropRouter, PointerCapture, AnimationDriver's registry. So is anything the loop owns rather than a window: FrameScheduler (one loop, one sleep, earliest wake wins), FrameTasks, MainQueue. And so are the resource caches — FontStore, ImageStore, Theme — which is the whole reason a second window is cheap.

Not thread-safe, and not meant to be: it is read and written by the frame loop only. That is exactly what MainQueue exists to preserve.

Sources/LavaUI/WindowScope.swift:45

Constructors

init(label: String, windowID: WindowID = .main)

Not documented.

Sources/LavaUI/WindowScope.swift:57

Properties

nonisolated(unsafe) static var current: WindowScope? { get }

The window whose frame is being processed, or nil between windows.

Sources/LavaUI/WindowScope.swift:69

static var currentOrMain: WindowScope { get }

Resolves the scope a static with no window in scope should act on.

Sources/LavaUI/WindowScope.swift:72

let label: String

For diagnostics only — the window title, typically.

Sources/LavaUI/WindowScope.swift:47

nonisolated(unsafe) static let main: WindowScope

The scope every static resolves to when nothing is current: tests, LavaBench, and the first window of any app.

Sources/LavaUI/WindowScope.swift:64

let windowID: WindowID

The engine window this scope drives.

Here rather than only on LavaWindow so a view can ask which window it is running in (LavaApp.currentWindow) without the framework threading an id through every body — the same ambient answer, from the same place, as the rest of this type.

Sources/LavaUI/WindowScope.swift:55

Methods

static func register(_ scope: WindowScope)

Registered by the frame loop as each window opens. main is not implicitly a member — a single-window app that never calls this still works, because broadcast falls back to main.

Sources/LavaUI/WindowScope.swift:93

static func unregister(_ scope: WindowScope)

Not documented.

Sources/LavaUI/WindowScope.swift:98

@discardableResult static func withCurrent<T>(_ scope: WindowScope, _ body: () throws -> T) rethrows -> T

Runs body with scope current, restoring whatever was current before — nested is fine, and an early return cannot leak the scope.

Sources/LavaUI/WindowScope.swift:77