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
RuntimeErrorif 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.simulateuses, invoking the listeners it reaches. - If no display is available, returns
Nonewithout running, so headless scripts and CI can call it unconditionally. - Raises
RuntimeErrorif 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
tracinglog subscriber as early as possible — setRUST_LOGto 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 afterrun()returns waits for a laterrun(). - A callback that itself calls
call_soondoesn'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
TypeErrorifcallbackisn't callable.
examples/threadsafe_reload.py is a complete, runnable file watcher
built on this.