class Editor
Swift
final class Editor
The engine: windows, renderer, fonts, images, clipboard and the D-Bus hosts a desktop panel needs.
Swift wrapper over canvas::Engine — direct C++ interop, no C shim.
This used to wrap a flat canvas::swiftEditor* free-function API (a
SwiftEditor* opaque handle passed to every call) because Swift's C++
interop wouldn't import Engine as a class at the time. That's no longer
true: Engine has a proper move constructor (pimpl'd, non-copyable) and
imports cleanly, so this now just holds one and calls its methods
directly — no OpaquePointer, no strdup/free, no manually building
char** arrays for tree/property lists.
C++ containers: use the named specializations in canvas (U8Vector,
StringVector, …). Bare std.vector<T> is still unavailable to Swift
(ClangImporter + libstdc++ vector<bool>); the using aliases import.
VoidResult (std::expected) .error() returns a reference that Swift
interop won't call, so open failures surface as nil/false — check
stderr if you need the message (Engine logs it).
Sources/LavaUI/Editor.swift:26
Properties
var appMenuIsAttached: Bool { get }
Whether this window's menu is currently exported to a global menu.
Sources/LavaUI/Editor.swift:768
var clipboardText: String { get set }
Registers a face for glyph lookup; returns its id, or nil on failure. Idempotent per (path, pixelSize). System clipboard. Empty when headless.
Sources/LavaUI/Editor.swift:354
var isOpen: Bool { get }
Whether the main window is still open — false once the user has closed it
or requestClose() was called.
Sources/LavaUI/Editor.swift:139
var isWindowVisible: Bool { get }
False while minimized/occluded. The frame loop gates continuous (animation-driven) redraw work on this, since Yoga/cull-rect visibility has no idea the whole window is off-screen.
Sources/LavaUI/Editor.swift:582
var notificationsCount: Int { get }
How many notifications are live. See notification(_:).
Sources/LavaUI/Editor.swift:888
var notificationsIsServing: Bool { get }
Whether this process is serving notifications.
Sources/LavaUI/Editor.swift:877
var notificationsNextExpiryMs: Int64 { get }
See Engine::notificationsNextExpiryMs.
Sources/LavaUI/Editor.swift:882
var notificationsRevision: UInt64 { get }
Changes whenever the set of notifications or any of their contents does.
Sources/LavaUI/Editor.swift:885
var resources: any GPUResourceHost { get set }
Who names GPU resources for the frames this editor produces.
Itself, unless told otherwise — which is what makes an ordinary app unaware that this question exists. A client under a shared renderer assigns the compositor here, once, at startup:
guard let editor = LavaClient.open(title: "My App") else { exit(1) }
editor.resources = compositor // ids now come from the renderer
Set before loading anything. Ids already stamped into a UIFont or a
UIImage are not revisited, so a switch after the fact leaves the
resources loaded before it naming things in the wrong process.
Sources/LavaUI/ResourceHost.swift:295
var statusNotifierIsServing: Bool { get }
Whether the tray is live, either owning org.kde.StatusNotifierWatcher or
following another desktop's.
Sources/LavaUI/Editor.swift:815
var statusNotifierItemCount: Int { get }
How many tray items there are. See statusNotifierItem(_:).
Sources/LavaUI/Editor.swift:824
var statusNotifierMenuItemCount: Int { get }
How many rows the open tray menu has.
Sources/LavaUI/Editor.swift:964
var statusNotifierMenuRevision: UInt64 { get }
Changes when the open tray menu's layout does, including when it first arrives: a menu is empty until the application answers.
Sources/LavaUI/Editor.swift:959
var statusNotifierRevision: UInt64 { get }
Changes whenever the item list or any item's display data does.
Sources/LavaUI/Editor.swift:821
var windowCount: Int { get }
Number of open windows. Zero is how an app knows to exit.
Sources/LavaUI/Editor.swift:181
var windowIDs: [WindowID] { get }
Ids of the open windows, in creation order.
Sources/LavaUI/Editor.swift:184
Methods
func appMenuAddItem(id: String, title: String, enabled: Bool, checked: Int32)
Adds an item to the open menu.
- id: Returned by
appMenuPopActivation()when the panel activates the item. - title: The item's label.
- enabled: Whether it can be activated.
- checked: -1 not checkable, 0 unchecked, 1 checked.
Sources/LavaUI/Editor.swift:791
func appMenuAddSeparator()
Adds a separator to the open menu.
Sources/LavaUI/Editor.swift:796
@discardableResult func appMenuAttach() -> Bool
Register this window's menu with the session AppMenu registrar.
Sources/LavaUI/Editor.swift:689
@discardableResult func appMenuAttach(windowId: UInt32) -> Bool
The same, under an id this process supplies rather than an X11 window id — which is what a compositor client has instead: its surface id, the number the panel hears about when the window takes focus.
Sources/LavaUI/Editor.swift:695
func appMenuBeginMenu(id: String, title: String)
Opens a top-level menu in the exported menu being rebuilt. Close it with appMenuEndMenu().
Sources/LavaUI/Editor.swift:778
func appMenuBeginUpdate()
Starts rebuilding the exported menu. Follow with appMenuBeginMenu,
appMenuAddItem and friends, then appMenuCommitUpdate().
Sources/LavaUI/Editor.swift:775
func appMenuCommitUpdate()
Publishes the menu built since appMenuBeginUpdate().
Sources/LavaUI/Editor.swift:799
func appMenuDetach()
Withdraws this window's exported menu from the global menu registrar.
Sources/LavaUI/Editor.swift:765
func appMenuEndMenu()
Closes the menu appMenuBeginMenu(id:title:) opened.
Sources/LavaUI/Editor.swift:783
func appMenuPoll()
Runs the exported menu's GLib loop once. Call once per frame.
Sources/LavaUI/Editor.swift:771
func appMenuPopActivation() -> String
Panel-activated MenuID raw string, or empty if the queue is empty.
Sources/LavaUI/Editor.swift:802
static func appMenuRegistrarAvailable() -> Bool
True if a menu registrar — org.lavaui.AppMenu.Registrar or the
canonical com.canonical.AppMenu.Registrar — is on the session bus and
canvas was built with libdbusmenu-glib.
Sources/LavaUI/Editor.swift:683
@discardableResult func attachDrawArena(id: String, window: WindowID = .main) -> Bool
Drives this window from a draw arena another process writes, instead
of from a DrawList submitted in this one.
The producer creates the arena and this attaches to it, so the order is producer-then-renderer. Returns false if it is missing or its header does not check out.
Sources/LavaUI/Editor.swift:247
func capturePng(x: Int32 = 0, y: Int32 = 0, w: Int32 = 0, h: Int32 = 0, maxSide: Int32 = 0, window: WindowID = .main) -> (png: [UInt8], w: Int32, h: Int32)?
The same capture as PNG bytes.
For a caller that is not about to put it in JSON — the compositor
answering CaptureSurface for a client, where base64 would be a third
more bytes on the wire and a decode at the other end, to satisfy a
protocol the renderer is not speaking.
Sources/LavaUI/Editor.swift:661
func capturePngBase64(x: Int32 = 0, y: Int32 = 0, w: Int32 = 0, h: Int32 = 0, maxSide: Int32 = 0, window: WindowID = .main) -> (b64: String, w: Int32, h: Int32)?
Capture the resolve target as PNG (base64). Region in framebuffer
pixels; omit or pass w/h ≤ 0 for the full frame.
maxSide > 0 downsamples so the longer encoded side ≤ maxSide.
Returns (base64, encodedWidth, encodedHeight) or nil on failure.
Sources/LavaUI/Editor.swift:639
func closeWindow(_ window: WindowID)
Closes one window. Every other window, and the device, survive it.
Sources/LavaUI/Editor.swift:176
nonisolated static func decodeImage(path: String, maxPixelSize: UInt32 = 0, turn: ImageTurn = .none) -> (pixels: [UInt8], width: UInt32, height: UInt32)?
Decodes an image file to RGBA8 without touching the GPU.
Safe to call from a worker thread — that is the whole point. Decoding a JPEG is tens of milliseconds and does not need the device; only the upload does. Returns nil if the file will not decode.
maxPixelSize (0 = native) caps the longer edge. Returned width and
height are the size after any downscale, so the caller sizes its
texture from these rather than from what the file claimed.
turn is applied after the orientation the file itself declares — the
decoder always applies that one — so it composes with what is on screen
rather than with how the sensor read out. It is done here and not by
the caller so that there is one implementation of turning pixels in the
whole system: a viewer that turned a photograph one way on screen and
another way into the file it wrote would be wrong in the way nobody
notices until the file is saved.
Sources/LavaUI/Editor.swift:407
nonisolated static func decodeImageData(bytes: [UInt8], maxPixelSize: UInt32 = 0) -> (pixels: [UInt8], width: UInt32, height: UInt32)?
The same decode from encoded bytes already in memory, for an image that never had a path — downloaded, generated, unpacked from an archive.
bytes are the encoded file (PNG, JPEG, …), not raw pixels: the
format is sniffed the way it is for a file.
Sources/LavaUI/Editor.swift:424
func detachDrawArena(window: WindowID = .main)
Stops driving window from a draw arena; the inverse of attachDrawArena(id:window:).
Sources/LavaUI/Editor.swift:252
func droppedFiles(window: WindowID = .main) -> [String]
Paths from the most recent .fileDrop event. Valid only while
handling that event — the next drop overwrites them.
Sources/LavaUI/Editor.swift:569
nonisolated static func encodeJpeg(pixels: [UInt8], width: UInt32, height: UInt32, quality: UInt32 = 92) -> [UInt8]?
RGBA8 → baseline JPEG at quality (1-100), always at native size.
For writing a transformed photograph back where it came from. PNG is the better encoder for everything this framework normally does, and the wrong one here: re-encoding a 24-megapixel JPEG losslessly turns six megabytes into a hundred and thirty, which is not what anyone means by "save".
Alpha is discarded — JPEG has none. Do not route an image that uses it through here.
Sources/LavaUI/Editor.swift:510
nonisolated static func encodePng(pixels: [UInt8], width: UInt32, height: UInt32, maxSide: UInt32 = 0) -> [UInt8]?
RGBA8 → PNG. maxSide 0 is native.
Sources/LavaUI/Editor.swift:486
func framebufferSize(window: WindowID = .main) -> (w: Float, h: Float)
Current swapchain / framebuffer size in pixels.
Sources/LavaUI/Editor.swift:590
func framesInFlight(window: WindowID = .main) -> Int
Frames published for window that its consumer has not taken yet.
Zero unless this window publishes to another process. See
FrameSink.framesInFlight — advisory back-pressure, not a lock.
Sources/LavaUI/Editor.swift:341
func hasImage(key: String) -> Bool
Whether the engine already has this key resident.
Sources/LavaUI/Editor.swift:525
func injectKey(key: Int32, action: Int32 = 1, mods: Int32 = 0)
Inject a key event. action: 0 release, 1 press, 2 repeat (GLFW).
Sources/LavaUI/Editor.swift:626
func injectPointerButton(button: Int32, pressed: Bool, x: Float, y: Float, mods: Int32 = 0)
Inject mouse button. button is GLFW-style (0 = left); mods too
(KeyMods), for a scripted Ctrl+click or Shift+click.
Sources/LavaUI/Editor.swift:614
func injectPointerMove(x: Float, y: Float)
Inject pointer motion into the same queue as GLFW (layout pixels).
Sources/LavaUI/Editor.swift:608
func injectScroll(dx: Float, dy: Float)
Inject wheel/trackpad delta (notches), same coalescing queue as real scroll.
Sources/LavaUI/Editor.swift:621
func injectText(_ utf8: String)
Inject UTF-8 text as character events (focused text field path).
Sources/LavaUI/Editor.swift:631
func isVisible(window: WindowID) -> Bool
False while this window is minimized/occluded.
Sources/LavaUI/Editor.swift:585
@discardableResult func loadFont(path: String, pixelSize: Float) -> Bool
Install face for draw-list text (must match UIFont used for measure).
Sources/LavaUI/Editor.swift:347
func loadImage(path: String) -> UIImage?
Load a PNG/JPEG for Image views. Returns a handle or nil on failure.
Idempotent per absolute path.
Sources/LavaUI/Editor.swift:380
func notification(_ index: Int) -> NotificationInfo
One live notification, by index in 0..<notificationsCount.
Sources/LavaUI/Editor.swift:891
func notificationActionKey(_ index: Int, action: Int) -> String
The key of a notification's action — what notificationInvokeAction(_:key:)
takes. index is the notification's position, action the action's.
Sources/LavaUI/Editor.swift:909
func notificationActionLabel(_ index: Int, action: Int) -> String
The label shown for a notification's action.
Sources/LavaUI/Editor.swift:914
func notificationDismiss(_ id: UInt32)
Closes notification id, as dismissed by the user.
Sources/LavaUI/Editor.swift:924
func notificationDismissAll()
Closes every notification.
Sources/LavaUI/Editor.swift:929
func notificationInvokeAction(_ id: UInt32, key: String)
Invokes the action named key on notification id and closes the notification.
Sources/LavaUI/Editor.swift:919
func notificationsPoll()
Pumps the notification bus and retires expired notifications. Call every frame.
Sources/LavaUI/Editor.swift:880
func notificationsSetPaused(_ paused: Bool)
Holds every notification's countdown while paused — the pointer is over the stack.
Sources/LavaUI/Editor.swift:932
func notificationsStart() -> Bool
Claims org.freedesktop.Notifications and starts serving. False when the
session already has a notification daemon, or has no bus.
Sources/LavaUI/Editor.swift:874
static func open(assetsRoot: String, width: Int32 = 1280, height: Int32 = 800, title: String = "FBD Editor") -> Editor?
Opens a window with its own Vulkan renderer and shows it.
Returns nil when the window or the renderer cannot be created; the
engine logs why on standard error.
- assetsRoot: Directory holding the engine's shaders and bundled fonts.
- width: Initial width of the window, in pixels.
- height: Initial height of the window, in pixels.
- title: The window title.
Sources/LavaUI/Editor.swift:48
static func openClient(width: Float = 1280, height: Float = 800) -> Editor?
Opens an engine that lays out and emits frames for another process to draw: no Vulkan, no window, no GPU.
Everything above this — LavaWindow, the view tree, layout, emit —
is unchanged and unaware. That is the point: a client is not a second
frame loop, it is the same one with the parts that need a screen
answering honestly that they have none. renderFrame succeeds and
draws nowhere, capturePngBase64 returns nil, loadImage returns nil
(see below), and the rest behaves exactly as it does in a window.
Two things a client cannot answer for itself and must be told:
- Size. There is no surface to measure, so
setClientSizeis the only way it learns one, and the initialwidth/heighthere are a guess until something says otherwise. - Input. The
inject*entry points are the whole input path, not a test affordance layered over a real one — which is what lets the agent server drive a client with no compositor on the other end.
Known gaps, both by design and both scoped as later work: images fail
to load (a texture id is per-process and there is no RegisterFont
equivalent for images yet), and registerFont hands out ids from a
local table that means nothing to a renderer elsewhere.
Sources/LavaUI/Editor.swift:86
func openWindow(width: Float = 800, height: Float = 600, title: String) -> WindowID?
Opens an additional window. Returns its id, or nil on failure.
It starts hidden. Draw a frame into it, then setVisible(true, window:)
— showing a window before its first frame presents an undefined
swapchain image, which looks like a flash of garbage.
A client engine accepts this too: the window is an arena and an input
queue, and nothing on screen. Asking the compositor for a surface is
LavaApp.openWindow, which is what an app should call.
Sources/LavaUI/Editor.swift:168
func pollInputEvent(window: WindowID = .main) -> InputEvent?
Raw input: mouse, resize, key (see InputEventKind).
Sources/LavaUI/Editor.swift:560
func postInputEvent(_ event: InputEvent, window: WindowID = .main)
Queues an event that arrived already formed — from a renderer in another process — rather than one derived from a device here.
The only way some events can reach a client at all: .resize,
.nodeHover, .nodeScroll and .nodeAnimationDone are answers a
renderer produces by looking at its own retained scene, and a client
has no scene and no device to synthesize them from. A .resize is
also state, not just an event, and updates what framebufferSize
reports from here on.
Thread-safe, like every input entry point — the queue is the one thing
in a window that has always been touched from more than one thread.
Callers still have to wakeEventLoop() if the loop may be parked.
Sources/LavaUI/Editor.swift:130
func publishFrames(to sink: any FrameSink, window: WindowID = .main)
Sends this window's frames somewhere other than the engine — a shared arena another process renders from.
Takes effect for DrawLists built after it, so a client installs the
sink before LavaApp.run rather than during. A list already holding
the old sink keeps writing to it, which is the safe way round: the
alternative is swapping the storage out from under a frame that is
halfway emitted.
Sources/LavaUI/Editor.swift:323
func pumpEvents(timeout: Double)
Drive the window from this thread. timeout < 0 blocks until an event
arrives, 0 polls, > 0 waits at most that long. Blocking is what keeps
an idle UI at zero CPU while still waking immediately on input.
Sources/LavaUI/Editor.swift:147
func registerFont(path: String, pixelSize26_6: UInt32, faceIndex: UInt32, rasterFlags: UInt32) -> UInt32?
Loads one face of a font file at a size, returning its font id, or nil
when the file cannot be loaded.
Idempotent per file contents (canvas::FontKey), not per path.
- path: The font file.
- pixelSize26_6: The size in 26.6 fixed point: pixels × 64.
- faceIndex: Which face of a collection (
.ttc); 0 for a single-face file. - rasterFlags: A
canvas::RasterFlagshinting selection.
Sources/LavaUI/Editor.swift:368
func registerImage(path: String, maxPixelSize: UInt32, turn: ImageTurn) -> UIImage?
Not documented.
Sources/LavaUI/ResourceHost.swift:163
func registerImage(data: [UInt8], maxPixelSize: UInt32) -> UIImage?
Not documented.
Sources/LavaUI/ResourceHost.swift:211
func registerImageAsync(path: String, maxPixelSize: UInt32, turn: ImageTurn, completion: @escaping @Sendable (UIImage?) -> Void)
Decode on a worker, upload on the main thread.
The split is not an optimization, it is a requirement in both directions: decoding a JPEG is tens of milliseconds and would stall the frame, and uploading touches the Vulkan device, which is the main thread's alone.
Sources/LavaUI/ResourceHost.swift:186
func releaseImage(key: String)
Not documented.
Sources/LavaUI/ResourceHost.swift:271
@discardableResult func renderFrame(window: WindowID = .main) -> Bool
Render and present one frame.
Sources/LavaUI/Editor.swift:234
func requestClose()
Ask the frame loop to exit (GLFW should-close). Safe from menu actions.
Sources/LavaUI/Editor.swift:142
@discardableResult func scrollSceneUnclaimed(dx: Float, dy: Float, window: WindowID = .main) -> Bool
Offers a wheel notch to the retained scroll containers under the pointer, after nothing in this process wanted it.
The renderer normally keeps the wheel — that is what lets a list scroll while this process is busy — and only forwards the event where a node declared a handler of its own. Whether that handler takes a given notch is a question only this process can answer, so when the answer is no, the event goes back. Returns whether anything moved.
Sources/LavaUI/Editor.swift:221
func setClientSize(width: Float, height: Float, window: WindowID = .main)
Tells a client window how big it is, queueing the .resize its tree
needs to re-lay-out. No-op on a window that has a renderer, which
measures its own surface instead.
Sources/LavaUI/Editor.swift:96
func setCursor(_ shape: CursorShape, window: WindowID = .main)
The pointer image over this window.
Windowed only: a client has no pointer to set, and asks the compositor
instead (CursorBridge). No-op there rather than an error, so the same
call site works in both modes.
Sources/LavaUI/Editor.swift:113
func setMinimumSize(width: Float, height: Float, window: WindowID = .main)
Constrains interactive resizing of a local window. Zero means no minimum on that axis. Client surfaces use their compositor host.
Sources/LavaUI/Editor.swift:102
func setViewTransform(zoom: Float, panX: Float = 0, panY: Float = 0, window: WindowID = .main)
Whole-window camera. Layout and Yoga stay at zoom=1; the quad shader applies center-zoom then pan. Hit-tests must unproject first.
Sources/LavaUI/Editor.swift:599
func setVisible(_ visible: Bool, window: WindowID = .main)
Shows or hides a window without closing it.
Sources/LavaUI/Editor.swift:228
func statusNotifierActivate(_ key: String, x: Int32 = 0, y: Int32 = 0)
Sends the item a left click (Activate). The coordinates are screen
pixels, which many items ignore.
Sources/LavaUI/Editor.swift:847
func statusNotifierCloseMenu()
Drops the tray menu statusNotifierOpenMenu(_:) opened. The item is not told.
Sources/LavaUI/Editor.swift:955
func statusNotifierContextMenu(_ key: String, x: Int32 = 0, y: Int32 = 0)
Asks the item to show its own context menu (ContextMenu), for a right click.
Sources/LavaUI/Editor.swift:852
func statusNotifierItem(_ index: Int) -> StatusNotifierItemInfo
One tray item, by index in 0..<statusNotifierItemCount.
Sources/LavaUI/Editor.swift:827
func statusNotifierMenuAboutToShow(_ itemId: Int32)
Asks the owning application to fill a tray submenu before it is opened.
Sources/LavaUI/Editor.swift:987
func statusNotifierMenuActivate(_ itemId: Int32)
Runs a row of the open tray menu, in the application that owns it.
Sources/LavaUI/Editor.swift:982
func statusNotifierMenuItem(_ index: Int) -> ImportedMenuItem
One row of the open tray menu, by index in 0..<statusNotifierMenuItemCount.
Sources/LavaUI/Editor.swift:969
func statusNotifierOpenMenu(_ key: String) -> Bool
Opens key's DBusMenu. False when the item exports none — activate it
instead.
Sources/LavaUI/Editor.swift:950
func statusNotifierPoll()
Runs the tray's GLib loop once. Call every frame while the panel is up.
Sources/LavaUI/Editor.swift:818
func statusNotifierScroll(_ key: String, delta: Int32, orientation: String = "vertical")
Sends the item a scroll of delta steps, "vertical" or "horizontal".
Sources/LavaUI/Editor.swift:864
func statusNotifierSecondaryActivate(_ key: String, x: Int32 = 0, y: Int32 = 0)
Sends the item a middle click (SecondaryActivate).
Sources/LavaUI/Editor.swift:857
@discardableResult func statusNotifierStart() -> Bool
Own org.kde.StatusNotifierWatcher. False if the name is taken or
there is no session bus.
Sources/LavaUI/Editor.swift:811
func stopPublishingFrames(window: WindowID)
Drops the sink publishFrames installed, and the arena it owns.
A closed client window that left its sink behind would keep a shared-memory mapping for a surface nobody is drawing. The engine window and the sink are separate objects; closing one does not drop the other.
Sources/LavaUI/Editor.swift:333
func submitDrawList(_ list: DrawList)
Publishes a finished frame to the window list was created for.
The list must belong to this editor.
Sources/LavaUI/Editor.swift:288
func takeInternalRepaint(window: WindowID = .main) -> Bool
Consumes "this window needs redrawing for a reason of its own".
The third repaint signal, alongside "a producer published" and "input arrived": the renderer moves scene nodes itself — a scroll today — and when it does, nothing is published and nothing is queued, because the point of a retained tree is that the producer is not involved.
Sources/LavaUI/Editor.swift:208
func unloadImage(path: String)
Drops one reference to a loaded image.
The GPU memory is not normally freed here. At zero references the image
goes dormant, keeping its pixels and its id so reloading the same key
costs nothing. Eviction from there is LRU, and never touches an image a
window's current frame still names. Standalone images are evicted
against a byte budget (LAVA_IMAGE_CACHE_MB, 256 by default); atlased
ones against atlas occupancy, since a cell is far too small to move a
byte budget but the cells themselves run out. Allocated atlas pages
stay resident either way.
Sources/LavaUI/Editor.swift:539
func uploadImage(key: String, path: String? = nil, pixels: [UInt8], width: UInt32, height: UInt32) -> UIImage?
Uploads pre-decoded pixels. Main thread only — it touches the device.
key is the texture identity (see UIImage.cacheKey); path is the
file it came from, and defaults to key for callers that decode at
native size and need no distinction.
Sources/LavaUI/Editor.swift:459
func uploadPixels(_ pixels: UnsafeRawBufferPointer, width: UInt32, height: UInt32, stride: UInt32, format: PixelFormat) -> UIImage?
Not documented.
Sources/LavaUI/ResourceHost.swift:226
func wakeEventLoop()
Unblock a waiting pumpEvents from any thread (agent socket watcher).
Sources/LavaUI/Editor.swift:150
func windowShouldClose(_ window: WindowID = .main) -> Bool
Whether this window has been asked to close (its titlebar X, a WM
request). The caller decides what that means — closing the last window
usually ends the app, closing any other is just closeWindow.
False for a window that is already closed: "asked to close" and "does
not exist" are different questions, and windowCount answers the
second.
Sources/LavaUI/Editor.swift:198