class Editor
Swift
final class Editor
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:23
Properties
var appMenuIsAttached: Bool { get }
Not documented.
Sources/LavaUI/Editor.swift:720
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:342
var isOpen: Bool { get }
Not documented.
Sources/LavaUI/Editor.swift:125
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:546
var notificationsCount: Int { get }
Not documented.
Sources/LavaUI/Editor.swift:808
var notificationsIsServing: Bool { get }
Not documented.
Sources/LavaUI/Editor.swift:802
var notificationsRevision: UInt64 { get }
Not documented.
Sources/LavaUI/Editor.swift:806
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:217
var statusNotifierIsServing: Bool { get }
Not documented.
Sources/LavaUI/Editor.swift:752
var statusNotifierItemCount: Int { get }
Not documented.
Sources/LavaUI/Editor.swift:758
var statusNotifierMenuItemCount: Int { get }
Not documented.
Sources/LavaUI/Editor.swift:872
var statusNotifierMenuRevision: UInt64 { get }
Not documented.
Sources/LavaUI/Editor.swift:868
var statusNotifierRevision: UInt64 { get }
Not documented.
Sources/LavaUI/Editor.swift:756
var windowCount: Int { get }
Number of open windows. Zero is how an app knows to exit.
Sources/LavaUI/Editor.swift:163
var windowIDs: [WindowID] { get }
Ids of the open windows, in creation order.
Sources/LavaUI/Editor.swift:166
Methods
func appMenuAddItem(id: String, title: String, enabled: Bool, checked: Int32)
Not documented.
Sources/LavaUI/Editor.swift:732
func appMenuAddSeparator()
Not documented.
Sources/LavaUI/Editor.swift:736
@discardableResult func appMenuAttach() -> Bool
Register this window's menu with the session AppMenu registrar.
Sources/LavaUI/Editor.swift:650
@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:656
func appMenuBeginMenu(id: String, title: String)
Not documented.
Sources/LavaUI/Editor.swift:726
func appMenuBeginUpdate()
Not documented.
Sources/LavaUI/Editor.swift:724
func appMenuCommitUpdate()
Not documented.
Sources/LavaUI/Editor.swift:738
func appMenuDetach()
Not documented.
Sources/LavaUI/Editor.swift:718
func appMenuEndMenu()
Not documented.
Sources/LavaUI/Editor.swift:730
func appMenuPoll()
Not documented.
Sources/LavaUI/Editor.swift:722
func appMenuPopActivation() -> String
Panel-activated MenuID raw string, or empty if the queue is empty.
Sources/LavaUI/Editor.swift:741
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:644
@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:248
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:622
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:600
func closeWindow(_ window: WindowID)
Closes one window. Every other window, and the device, survive it.
Sources/LavaUI/Editor.swift:158
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:386
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:403
func detachDrawArena(window: WindowID = .main)
Not documented.
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:533
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:489
nonisolated static func encodePng(pixels: [UInt8], width: UInt32, height: UInt32, maxSide: UInt32 = 0) -> [UInt8]?
RGBA8 → PNG. maxSide 0 is native.
Sources/LavaUI/Editor.swift:465
func framebufferSize(window: WindowID = .main) -> (w: Float, h: Float)
Current swapchain / framebuffer size in pixels.
Sources/LavaUI/Editor.swift:554
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:329
func hasImage(key: String) -> Bool
Whether the engine already has this key resident.
Sources/LavaUI/Editor.swift:504
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:587
func injectPointerButton(button: Int32, pressed: Bool, x: Float, y: Float)
Inject mouse button. button is GLFW-style (0 = left).
Sources/LavaUI/Editor.swift:577
func injectPointerMove(x: Float, y: Float)
Inject pointer motion into the same queue as GLFW (layout pixels).
Sources/LavaUI/Editor.swift:572
func injectScroll(dx: Float, dy: Float)
Inject wheel/trackpad delta (notches), same coalescing queue as real scroll.
Sources/LavaUI/Editor.swift:582
func injectText(_ utf8: String)
Inject UTF-8 text as character events (focused text field path).
Sources/LavaUI/Editor.swift:592
func isVisible(window: WindowID) -> Bool
False while this window is minimized/occluded.
Sources/LavaUI/Editor.swift:549
@discardableResult func loadFont(path: String, pixelSize: Float) -> Bool
Install face for draw-list text (must match UIFont used for measure).
Sources/LavaUI/Editor.swift:335
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:359
func notification(_ index: Int) -> NotificationInfo
Not documented.
Sources/LavaUI/Editor.swift:810
func notificationActionKey(_ index: Int, action: Int) -> String
Not documented.
Sources/LavaUI/Editor.swift:826
func notificationActionLabel(_ index: Int, action: Int) -> String
Not documented.
Sources/LavaUI/Editor.swift:830
func notificationDismiss(_ id: UInt32)
Not documented.
Sources/LavaUI/Editor.swift:838
func notificationDismissAll()
Not documented.
Sources/LavaUI/Editor.swift:842
func notificationInvokeAction(_ id: UInt32, key: String)
Not documented.
Sources/LavaUI/Editor.swift:834
func notificationsPoll()
Not documented.
Sources/LavaUI/Editor.swift:804
func notificationsSetPaused(_ paused: Bool)
Not documented.
Sources/LavaUI/Editor.swift:844
func notificationsStart() -> Bool
Not documented.
Sources/LavaUI/Editor.swift:800
static func open(assetsRoot: String, width: Int32 = 1280, height: Int32 = 800, title: String = "FBD Editor") -> Editor?
Not documented.
Sources/LavaUI/Editor.swift:36
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:74
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.
Sources/LavaUI/Editor.swift:150
func pollInputEvent(window: WindowID = .main) -> InputEvent?
Raw input: mouse, resize, key (see InputEventKind).
Sources/LavaUI/Editor.swift:524
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:118
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:321
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:133
func registerFont(path: String, pixelSize26_6: UInt32, faceIndex: UInt32, rasterFlags: UInt32) -> UInt32?
Not documented.
Sources/LavaUI/Editor.swift:347
func registerImage(path: String, maxPixelSize: UInt32, turn: ImageTurn) -> UIImage?
Not documented.
Sources/LavaUI/ResourceHost.swift:138
func registerImage(data: [UInt8], maxPixelSize: UInt32) -> UIImage?
Not documented.
Sources/LavaUI/ResourceHost.swift:186
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:161
func releaseImage(key: String)
Not documented.
Sources/LavaUI/ResourceHost.swift:201
@discardableResult func renderFrame(window: WindowID = .main) -> Bool
Render and present one frame.
Sources/LavaUI/Editor.swift:215
func renderFrames(_ windows: [WindowID])
Renders several windows at once, one thread per window.
A frame is mostly the window's own — its command pool, its fences, its buffers — but not entirely: picking up a resize, growing the shared glyph atlas and freeing images no window references any more all reach across every window at once. Those run here, before and after the group, because none of them is safe while a window is recording.
Rendering one window at a time needs none of this — renderFrame
brackets itself.
Sources/LavaUI/Editor.swift:229
func requestClose()
Ask the frame loop to exit (GLFW should-close). Safe from menu actions.
Sources/LavaUI/Editor.swift:128
@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:203
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:84
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:101
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:90
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:563
func setVisible(_ visible: Bool, window: WindowID = .main)
Not documented.
Sources/LavaUI/Editor.swift:209
func statusNotifierActivate(_ key: String, x: Int32 = 0, y: Int32 = 0)
Not documented.
Sources/LavaUI/Editor.swift:778
func statusNotifierCloseMenu()
Not documented.
Sources/LavaUI/Editor.swift:866
func statusNotifierContextMenu(_ key: String, x: Int32 = 0, y: Int32 = 0)
Not documented.
Sources/LavaUI/Editor.swift:782
func statusNotifierItem(_ index: Int) -> StatusNotifierItemInfo
Not documented.
Sources/LavaUI/Editor.swift:760
func statusNotifierMenuAboutToShow(_ itemId: Int32)
Not documented.
Sources/LavaUI/Editor.swift:892
func statusNotifierMenuActivate(_ itemId: Int32)
Not documented.
Sources/LavaUI/Editor.swift:888
func statusNotifierMenuItem(_ index: Int) -> ImportedMenuItem
Not documented.
Sources/LavaUI/Editor.swift:876
func statusNotifierOpenMenu(_ key: String) -> Bool
Opens key's DBusMenu. False when the item exports none — activate it
instead.
Sources/LavaUI/Editor.swift:862
func statusNotifierPoll()
Not documented.
Sources/LavaUI/Editor.swift:754
func statusNotifierScroll(_ key: String, delta: Int32, orientation: String = "vertical")
Not documented.
Sources/LavaUI/Editor.swift:792
func statusNotifierSecondaryActivate(_ key: String, x: Int32 = 0, y: Int32 = 0)
Not documented.
Sources/LavaUI/Editor.swift:786
@discardableResult func statusNotifierStart() -> Bool
Own org.kde.StatusNotifierWatcher. False if the name is taken or
there is no session bus.
Sources/LavaUI/Editor.swift:750
func submitDrawList(_ list: DrawList)
Not documented.
Sources/LavaUI/Editor.swift:286
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:190
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:518
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:438
func wakeEventLoop()
Unblock a waiting pumpEvents from any thread (agent socket watcher).
Sources/LavaUI/Editor.swift:136
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:180