LavaUI docs

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 setClientSize is the only way it learns one, and the initial width/height here 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