Skip to content

Node

A handle to one node in a Window's tree. Returned by window.create, window.root, and the tree methods below; never constructed directly. Two handles compare equal, and hash equal, when they name the same node, so nodes can key a dict.

set, get, and focus

set(**props) sets any number of properties at once, atomically: every value is checked first, and a bad name or value raises ValueError without changing anything. Optional properties take None to clear them.

thumb.set(role="slider", label="Volume", value=0.4, value_min=0.0,
          value_max=1.0, value_step=0.1, focusable=True, cursor="pointer")
Property Value
role "button", "checkbox", "radio", "switch", "slider", "progressbar", "link", "textbox", "tab", "tablist", "tabpanel", "menu", "menuitem", "dialog", "alert", "list", "listitem", "tree", "treeitem", "heading", "img", "group", "none", "separator" (0.5.6; AccessKit has no separator role, so it is a splitter, which AT-SPI and UIA expose as a separator)
label str or None — the name assistive technology reads
value str, number, or None
value_min, value_max, value_step number or None
checked, selected, expanded bool or None
disabled bool
pressed True, False, "mixed", or None (0.5.6) — a toggle button's state; where both are set, it wins over checked
invalid, busy bool (0.5.6) — the value is not acceptable; the node is updating
description str or None (0.5.6) — a longer description than label
describedby, controls a Node, a list of Nodes, or None (0.5.6) — nodes that describe this one, and nodes it controls. They must belong to the same window. get returns the list of nodes still alive; one that is hidden or removed is left out of what assistive technology sees
current "page", "step", "location", "date", "time", True, False, or None (0.5.6) — the current item in a set
value_now, value_text number or None; str or None (0.5.6) — value_now is the number of value (setting one sets the other), value_text is how the value is said ("40 percent")
level positive int or None — a heading's level
live "off", "polite", "assertive", or None — how changes are announced
a11y_hidden bool — hidden from assistive technology
focusable bool — focusable by Tab, click, and focus(); a click on a descendant focuses the nearest focusable ancestor
tab_index int — positive values come first in Tab order, ascending; then 0 in tree order; negative leaves the node out of Tab order but focusable by click and focus()
cursor the pointer shape over the node, inherited by descendants: "default", "pointer", "text", "grab", "grabbing", "move", "not_allowed", "wait", "progress", "crosshair", "help", "col_resize", "row_resize", "ew_resize", "ns_resize", "nesw_resize", "nwse_resize", "copy", "cell", "context_menu", "zoom_in", "zoom_out", "all_scroll", or None
hit_testable bool — whether the node can be the target of pointer events
shader a Shader or None (0.5.1) — WGSL that paints the node, or transforms its content
window_region "drag", "none", or None (0.5.0) — "drag" makes the node a title bar that moves the window; "none" keeps a node inside one from dragging. See Title bar and borders

The actions assistive technology is offered follow from role and state: focusable nodes offer focus, button-like roles offer activation, a slider or a value range offers increment/decrement/set-value, and expanded offers expand/collapse. Requests arrive as the a11y_action event.

get(name) reads any property back exactly as set — an animating one at its current, mid-animation value — plus the read-only kind, focused, layer_placement, and layout_* values; see Nodes and Properties.

focus() moves keyboard focus to the node, firing unfocus and focus; the scroll views around it scroll to show it (0.4.2).

scroll_into_view() (0.4.2) scrolls every scroll view around the node, innermost first, by the least that shows it; a node longer than a view is aligned to its start. An assistive technology's scroll_into_view request does the same, after any a11y_action listener.

Cursors

A node's cursor is the pointer shape over it, inherited by its descendants: one of the named shapes in the table above, None, or (0.5.4) a tre.CursorImage, a shape drawn from pixels:

import tre

rgba = bytearray(32 * 32 * 4)             # straight-alpha RGBA8, drawn however you like
...                                       # (decode a PNG with your imaging library)
reticle = tre.CursorImage(bytes(rgba), 32, 32, hotspot=(16, 16))
canvas.set(cursor=reticle)

CursorImage(rgba, width, height, hotspot=(0, 0)) takes width * height * 4 bytes, each side 1 to 256 pixels, and a hotspot, the pixel that is the pointer's position. The engine decodes no image format. The same image made twice is one cursor, and a cursor lives as long as the process. The OS cursor is made when App.run() runs its loop (image.ready says), so the default shape shows for the first moments. The image is in device pixels, not scaled for a HiDPI display: give a larger one on a 2x screen. node.get("cursor") returns the CursorImage that was set. It works on Windows, macOS, X11 and Wayland, and was checked by making the cursor on Wayland and X11 (KDE); the pixels themselves on screen were not inspected.

animate

animate(property, to, duration_ms=0, easing=None, on_complete=None)

Eases one property from its current value to to and returns at once:

node.animate("opacity", 0.0, 300, easing=(0.3, 0.0, 0.8, 0.15),
             on_complete=lambda: node.remove())

The animatable properties are fill, stroke_color, stroke_width, opacity, corner_radius, shadows, translate_x, translate_y, scale, rotation_deg, a scroll view's scroll_offset, and a path's data, trim_start, and trim_end. Any other name raises ValueError; a to of the wrong shape raises ValueError, or TypeError for a non-number to on a numeric property. easing is "linear" or a cubic bezier (x1, y1, x2, y2). on_complete is called with no arguments exactly once, in the frame the value arrives — or in window.advance(ms) in a test — and never for an animation replaced or stopped first.

get_target(name) is where a running animation is heading — get(name) when nothing animates it — and stop_animation(name) stops it where it is. See Paint, Paths, and Animation and the Animation guide.

redraw

redraw() runs a canvas's draw callback now; see Painter. Raises ValueError on any other kind.

Events

on(event, handler) registers a listener — click, pointer_down, pointer_enter, change, focus, and the rest — and off(event) removes it; see Events and Listeners for every event, bubbling, and the Event fields. A handler takes no arguments or one, the Event. An exception raised inside a handler is caught, logged, and non-fatal.

capture_pointer() routes every later pointer event to this node until the button is released or release_pointer() is called — for drags.

Tree structure

Method Does
add_child(child) Appends child, moving it if it's attached elsewhere
insert_child(index, child) Attaches child so it ends up at index — afterwards children()[index] == child — moving it if it's attached anywhere. index counts the children once child has left its old place; past the end raises IndexError
children() This node's children, in order
parent() Its parent, or None for the root or a detached node
remove() Detaches this node from its parent; it stays alive and can be attached again
destroy() Frees this node and its whole subtree now, with their listeners

Attaching raises ValueError if child is this node or one of its ancestors, or belongs to a different Window. Moving a node keeps everything about it — the same Node, its listeners, its focus, and any running animation — which is what a keyed list reconciler needs:

for index, key in enumerate(new_order):
    list_box.insert_child(index, rows[key])

Lifetime

  • A node attached to a window lives while it's attached.
  • A node made by window.create, or detached by remove(), lives while any Node handle to it — or to anything in its subtree — exists. When the last one goes, the subtree is freed with its listeners, so a forgotten destroy() never leaks.
  • destroy() frees now. Using a handle to a freed node raises ValueError.

Switching screens is old.remove() then window.root.add_child(new):

  • Focus leaves with a removed or destroyed subtree. When focus is inside it, the focused node gets unfocus first — bubbling through the tree as it was — and nothing is focused afterwards.
  • A detached subtree keeps everything else: scroll offsets, a text input's text, caret, and selection, and running animations, which keep advancing on the window's clock. Attach it again and it's as you left it.