Skip to content

App

The top-level entry point — opens and drives one or more Windows together.

App

App()

Constructs an empty App with no registered windows.

app = App()

add_window

add_window(window)

Registers a Window to be opened the next time run() is called. Calling this multiple times before run() opens multiple windows together.

Since 0.5.6, it also works while run() is going: from a listener, a frame handler or LoopHandle.call_soon. The window opens on the loop's next turn. Closing it leaves the other windows running, and closing the last one ends the run (App.run returns). Adding a window that is already open raises ValueError; a window that was closed may be added again. If the new window's GPU can't be set up it closes by itself and the error is logged (the call that added it has already returned). A window still waiting to open when the last open one closes is lost with the run. max_frames applies to the new window on its own.

app.add_window(window)

run

run(max_frames=None)

The single blocking call that opens every registered window and ticks, lays out, and renders each window's own tree every frame, independently, until every window has closed.

app.run()
app.run(max_frames=60)  # each window individually stops after 60 frames
  • max_frames, when given, is a per-window budget, not a whole-app one — useful for headless/CI runs that must end on their own.
  • Raises RuntimeError if called with zero registered windows.
  • Delivers real input — pointer, wheel, keyboard, text and input-method composition, window events, and screen-reader requests — through the same pipeline window.simulate uses, invoking the listeners it reaches.
  • If no display is available, returns None without running, so headless scripts and CI can call it unconditionally.
  • Raises RuntimeError if a window's GPU can't be set up -- no GPU adapter, no device, or a surface the adapter can't drive. (Before 0.4.0 this exited the process with status 0.)
  • Installs a tracing log subscriber as early as possible — set RUST_LOG to control verbosity (e.g. RUST_LOG=warn python app.py).

thread_handle

thread_handle() -> LoopHandle

App, Window, Node, and every other Tesserae Engine object may only be used from the thread that created them — using one from another thread raises PanicException (a BaseException, not an Exception). Dropping one there is safe: if Python's garbage collector frees a Tesserae Engine object on a background thread, the actual free happens on its own thread at the next frame or Tesserae Engine call. thread_handle() returns the one object a background thread may use: a LoopHandle onto this App's event loop. Every handle from one App shares the same queue.

handle = app.thread_handle()
threading.Thread(target=watch_files, args=(handle,), daemon=True).start()
app.run()

LoopHandle

call_soon

call_soon(callback)

Queues callback (called with no arguments) to run on the App's event-loop thread and wakes the loop — including an idle one waiting for input. There it can touch a Window and its Nodes exactly like an input handler can:

# on a background thread, after detecting a file change:
text = path.read_text()                              # I/O off the UI thread
handle.call_soon(lambda: rebuild_screen(text))           # on the loop
  • Safe from any thread, before, during, or after run().
  • Callbacks run in the order they were queued, at the top of the next frame. One queued before run() runs on the first frame; one queued after run() returns waits for a later run().
  • A callback that itself calls call_soon doesn't run again in the same frame — its new entry runs on the next one.
  • An exception is logged the same way as one from an input handler and doesn't stop the loop or later callbacks.
  • A callback that changes any window's tree gets that window redrawn, even if another window's frame ran it.
  • Raises TypeError if callback isn't callable.

examples/threadsafe_reload.py is a complete, runnable file watcher built on this.