Core API¶
The nuiitivet package exposes the core primitives, layout widgets, and state management utilities.
nuiitivet
¶
nuiitivet core package.
This module is the single source of truth for all public core symbols (layout, widgets, state management, theming, animation, modifiers, ...).
Applications should not import from this package directly. Instead they pick a
UI design system root — currently only :mod:nuiitivet.material — which
re-exports everything here plus its own widgets::
import nuiitivet.material as nv
nv.Column(...) # core symbol, re-exported here
nv.Button(...) # material symbol
That single import is the whole supported surface.
Reaching into the internal modules below (nuiitivet.layout.column,
nuiitivet.widgeting.widget, ...) is unsupported and may break without notice.
InputFilterLike
module-attribute
¶
InputFilterLike = Union[InputFilter, Callable[[str], str]]
An :class:InputFilter, or a plain str -> str callable standing in for one.
RendererMode
module-attribute
¶
Renderer selection accepted by :meth:nuiitivet.runtime.app.App.run.
"auto": try the GPU first and silently fall back to software (raster) rendering when the GPU backend is unavailable. This is the default."gpu": require the GPU. Initialization or per-frame GPU failures raise a :class:RuntimeErrorinstead of degrading, so remote/GPU-less environments surface a clear error rather than running unexpectedly slowly."cpu": always render in software (raster); the GPU is never touched.
Note that App.run always needs a display/window; truly headless environments
should render offscreen via :meth:App.render_to_png instead.
Column
¶
Column(children: Optional[Sequence[Widget]] = None, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, gap: Union[int, ObservableBase] = 0, main_alignment: str = 'start', cross_alignment: str = 'start', key: Optional[str] = None)
Bases: Widget
Layout children vertically.
Parameters - gap: pixels between children - cross_alignment: 'start'|'center'|'end' for cross-axis (horizontal) alignment - main_alignment: 'start'|'center'|'end'|'space-between'|'space-around'|'space-evenly' - padding: inner padding as int (all sides), (h, v), or (left, top, right, bottom) - overflow: 'visible'|'clip'|'scroll' - how to handle children that overflow the container - 'visible' (default): children may extend beyond container (Phase 1 behavior) - 'clip': children are clipped to container bounds - 'scroll': requires VerticalScrollable / HorizontalScrollable wrapper (Phase 3)
Initialize Column and configure layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Optional[Sequence[Widget]]
|
List of child widgets to arrange vertically. |
None
|
width
|
SizingLike
|
Column width. |
None
|
height
|
SizingLike
|
Column height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the content. |
0
|
gap
|
Union[int, ObservableBase]
|
Space between children in pixels. |
0
|
main_alignment
|
str
|
Vertical alignment of children. 'start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'. |
'start'
|
cross_alignment
|
str
|
Horizontal alignment of children. 'start', 'center', 'end', 'stretch'. |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/column.py
builder
classmethod
¶
builder(items: ItemsLike, builder: BuilderFn, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, gap: int = 0, main_alignment: str = 'start', cross_alignment: str = 'start', key: Optional[str] = None) -> Column
Create a Column that materializes children from items via ForEach.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
ItemsLike
|
Source data collection. |
required |
builder
|
BuilderFn
|
Function to create a widget for each item. |
required |
width
|
SizingLike
|
Column width. |
None
|
height
|
SizingLike
|
Column height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the content. |
0
|
gap
|
int
|
Space between children. |
0
|
main_alignment
|
str
|
Vertical alignment of children. |
'start'
|
cross_alignment
|
str
|
Horizontal alignment of children. |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/column.py
Row
¶
Row(children: Optional[List[Widget]] = None, *, width: SizingLike = None, height: SizingLike = None, padding: PaddingLike = 0, gap: Union[int, ObservableBase] = 0, main_alignment: str = 'start', cross_alignment: str = 'start', key: Optional[str] = None)
Bases: Widget
Layout children horizontally.
Parameters - gap: pixels between children - cross_alignment: 'start'|'center'|'end' for cross-axis (vertical) alignment - main_alignment: 'start'|'center'|'end'|'space-between'|'space-around'|'space-evenly' - padding: inner padding as int (all sides), (h, v), or (left, top, right, bottom)
Initialize Row and configure layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Optional[List[Widget]]
|
List of child widgets to arrange horizontally. |
None
|
width
|
SizingLike
|
Row width. Defaults to None (shrinkwrap). |
None
|
height
|
SizingLike
|
Row height. Defaults to None (shrinkwrap). |
None
|
padding
|
PaddingLike
|
Padding around the content. |
0
|
gap
|
Union[int, ObservableBase]
|
Space between children in pixels. |
0
|
main_alignment
|
str
|
Horizontal alignment of children. 'start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'. |
'start'
|
cross_alignment
|
str
|
Vertical alignment of children. 'start', 'center', 'end', 'stretch'. |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/row.py
builder
classmethod
¶
builder(items: ItemsLike, builder: BuilderFn, *, width: SizingLike = None, height: SizingLike = None, padding: PaddingLike = 0, gap: int = 0, main_alignment: str = 'start', cross_alignment: str = 'start', key: Optional[str] = None) -> Row
Create a Row that materializes children from items via ForEach.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
ItemsLike
|
Source data collection. |
required |
builder
|
BuilderFn
|
Function to create a widget for each item. |
required |
width
|
SizingLike
|
Row width. |
None
|
height
|
SizingLike
|
Row height. |
None
|
padding
|
PaddingLike
|
Padding around the content. |
0
|
gap
|
int
|
Space between children. |
0
|
main_alignment
|
str
|
Horizontal alignment of children. |
'start'
|
cross_alignment
|
str
|
Vertical alignment of children. |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/row.py
Stack
¶
Stack(children: Sequence[Widget], *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, alignment: AlignmentLike = 'top-left', key: Optional[str] = None)
Bases: Widget
Layout children on top of each other.
Parameters - children: List of widgets to stack. - alignment: How to align children within the stack.
Initialize the Stack layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Sequence[Widget]
|
List of widgets to stack on top of each other. |
required |
width
|
SizingLike
|
Stack width. |
None
|
height
|
SizingLike
|
Stack height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the content. |
0
|
alignment
|
AlignmentLike
|
Default alignment for children. (horizontal, vertical) tuple or string like "top-left", "center". |
'top-left'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/stack.py
builder
classmethod
¶
builder(items: ItemsLike, builder: BuilderFn, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, alignment: AlignmentLike = 'center', key: Optional[str] = None) -> 'Stack'
Create a Stack that materializes children from items via ForEach.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
ItemsLike
|
Source data collection. |
required |
builder
|
BuilderFn
|
Function to create a widget for each item. |
required |
width
|
SizingLike
|
Stack width. |
None
|
height
|
SizingLike
|
Stack height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the content. |
0
|
alignment
|
AlignmentLike
|
Default alignment for children. |
'center'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/stack.py
Container
¶
Container(child: Optional[Widget] = None, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, alignment: Union[str, Tuple[str, str]] = 'start', key: Optional[str] = None)
Bases: Widget
Lightweight layout-only Container.
This Container is a minimal single-child layout box. It intentionally does not perform background/shadow/border drawing or clipping.
Initialize the Container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Optional[Widget]
|
The child widget to be placed inside the container. |
None
|
width
|
SizingLike
|
The preferred width of the container. Defaults to None (shrinkwrap). |
None
|
height
|
SizingLike
|
The preferred height of the container. Defaults to None (shrinkwrap). |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding to apply around the child. Can be a single integer, a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom). |
0
|
alignment
|
Union[str, Tuple[str, str]]
|
How to align the child within the container. Defaults to "start". Can be a string (e.g., "center") or a tuple (horizontal, vertical). |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/container.py
Flow
¶
Flow(children: Optional[Sequence[Widget]] = None, *, main_gap: int = 0, cross_gap: int = 0, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, main_alignment: str = 'start', run_alignment: str = 'start', cross_alignment: str = 'start', width: SizingLike = None, height: SizingLike = None, key: Optional[str] = None)
Bases: Widget
Layout children in rows that wrap.
Arranges children in a horizontal run, wrapping to a new line when the line runs out of space.
Source code in src/nuiitivet/layout/flow.py
builder
classmethod
¶
builder(items: ItemsLike, builder: BuilderFn, *, main_gap: int = 0, cross_gap: int = 0, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, main_alignment: str = 'start', run_alignment: str = 'start', cross_alignment: str = 'start', width: SizingLike = None, height: SizingLike = None, key: Optional[str] = None) -> 'Flow'
Create a Flow that materializes children from items via ForEach.
Source code in src/nuiitivet/layout/flow.py
UniformFlow
¶
UniformFlow(children: Optional[Sequence[Widget]] = None, *, columns: Optional[int] = None, max_column_width: Optional[int] = None, aspect_ratio: Optional[float] = None, main_gap: int = 0, cross_gap: int = 0, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, main_alignment: str = 'start', run_alignment: str = 'start', item_alignment: AlignValue = 'stretch', width: SizingLike = None, height: SizingLike = None, key: Optional[str] = None)
Bases: Widget
Layout children in a uniform grid.
This layout arranges children into columns with equal width.
Source code in src/nuiitivet/layout/uniform_flow.py
builder
classmethod
¶
builder(items: ItemsLike, builder: BuilderFn, *, columns: Optional[int] = None, max_column_width: Optional[int] = None, aspect_ratio: Optional[float] = None, main_gap: int = 0, cross_gap: int = 0, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, main_alignment: str = 'start', run_alignment: str = 'start', item_alignment: AlignValue = 'stretch', width: SizingLike = None, height: SizingLike = None, key: Optional[str] = None) -> 'UniformFlow'
Create a UniformFlow that materializes children from items via ForEach.
Source code in src/nuiitivet/layout/uniform_flow.py
Grid
¶
Grid(children: Optional[Sequence[Widget]], rows: Optional[Sequence[SizingLike]], columns: Optional[Sequence[SizingLike]], *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, row_gap: int = 0, column_gap: int = 0, key: Optional[str] = None)
Bases: Widget
Two-dimensional layout container with explicit tracks.
Initialize the Grid layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Optional[Sequence[Widget]]
|
List of GridItems to display. |
required |
rows
|
Optional[Sequence[SizingLike]]
|
One track size per row: an |
required |
columns
|
Optional[Sequence[SizingLike]]
|
One track size per column; same forms as |
required |
width
|
SizingLike
|
Grid container width. |
None
|
height
|
SizingLike
|
Grid container height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the grid content. |
0
|
row_gap
|
int
|
Vertical gap between rows. |
0
|
column_gap
|
int
|
Horizontal gap between columns. |
0
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/grid.py
named_areas
classmethod
¶
named_areas(children: Sequence[Widget], areas: Sequence[Sequence[str]], *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, row_gap: int = 0, column_gap: int = 0, rows: Optional[Sequence[SizingLike]] = None, columns: Optional[Sequence[SizingLike]] = None) -> Grid
Create a Grid using named template areas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Sequence[Widget]
|
List of child widgets (usually GridItem.named_area). |
required |
areas
|
Sequence[Sequence[str]]
|
2D list of area names (e.g. [["header", "header"], ["sidebar", "content"]]). |
required |
width
|
SizingLike
|
Grid width. |
None
|
height
|
SizingLike
|
Grid height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Grid padding. |
0
|
row_gap
|
int
|
Gap between rows. |
0
|
column_gap
|
int
|
Gap between columns. |
0
|
rows
|
Optional[Sequence[SizingLike]]
|
Explicit row sizing overrides. |
None
|
columns
|
Optional[Sequence[SizingLike]]
|
Explicit column sizing overrides. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Grid |
Grid
|
A configured Grid instance. |
Source code in src/nuiitivet/layout/grid.py
GridItem
¶
GridItem(child: Widget, row: Optional[GridIndex] = None, column: Optional[GridIndex] = None, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, alignment: Union[str, Tuple[str, str]] = 'start', key: Optional[str] = None)
Bases: Container
Annotate a child with explicit grid placement data.
Initialize the GridItem wrapper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Widget
|
The widget to place in the grid. |
required |
row
|
Optional[GridIndex]
|
Row index or (start, end) tuple. 0-based. |
None
|
column
|
Optional[GridIndex]
|
Column index or (start, end) tuple. 0-based. |
None
|
width
|
SizingLike
|
Override width. |
None
|
height
|
SizingLike
|
Override height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the child within the grid cell. |
0
|
alignment
|
Union[str, Tuple[str, str]]
|
Alignment within the grid cell. Defaults to "start". |
'start'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/grid.py
named_area
classmethod
¶
named_area(child: Widget, name: str, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, alignment: Union[str, Tuple[str, str]] = 'start') -> GridItem
Create a GridItem placed in a named template area.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Widget
|
The child widget to place. |
required |
name
|
str
|
The area name matching a name in Grid.named_areas(). |
required |
width
|
SizingLike
|
Override width. |
None
|
height
|
SizingLike
|
Override height. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding around the child within the grid cell. |
0
|
alignment
|
Union[str, Tuple[str, str]]
|
Alignment within the grid cell. Defaults to "start". |
'start'
|
Returns:
| Name | Type | Description |
|---|---|---|
GridItem |
GridItem
|
The wrapped widget with area information. |
Source code in src/nuiitivet/layout/grid.py
Spacer
¶
Bases: Widget
Invisible widget that reserves space.
This single Spacer supports both fixed-size and space-filling behavior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
SizingLike
|
preferred width (int, "auto", "wt", "wt{n}", or Sizing) |
0
|
height
|
SizingLike
|
preferred height (same accepted formats as width) |
0
|
Initialize a Spacer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
SizingLike
|
Preferred width. Use Sizing.weight() or 0 for filling space. |
0
|
height
|
SizingLike
|
Preferred height. Use Sizing.weight() or 0 for filling space. |
0
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/spacer.py
preferred_size
¶
preferred_size(max_width: Optional[int] = None, max_height: Optional[int] = None) -> Tuple[int, int]
Return preferred size based on Sizings.
- fixed: return the fixed value
- auto/weight: return 0 (minimum size, parent will allocate)
Source code in src/nuiitivet/layout/spacer.py
CrossAligned
¶
CrossAligned(child: Optional[Widget], alignment: str, *, key: Optional[str] = None)
Bases: Widget
Layout wrapper that overrides cross-axis alignment in Row/Column.
This sets cross_align metadata on the wrapper. Row/Column may read
this to override their cross_alignment for this child only.
Initialize the CrossAligned wrapper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Optional[Widget]
|
The child widget to wrap. |
required |
alignment
|
str
|
The cross-axis alignment to apply to this child. Common values: "start", "center", "end", "stretch". |
required |
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/cross_aligned.py
Deck
¶
Deck(children: Optional[Sequence[Widget]] = None, index: Union[int, ObservableBase[int]] = 0, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, key: Optional[str] = None)
Bases: Widget
Display only one child at a time.
All children remain mounted (state preserved), but only the selected child is visible and rendered.
Usage
Deck(children=[HomeTab(), SearchTab(), ProfileTab()], index=0)
For type-safe index, use IntEnum: class Section(IntEnum): HOME = 0 SEARCH = 1 Deck(children=[...], index=Section.HOME)
Initialize the Deck layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Optional[Sequence[Widget]]
|
A list of child widgets. All are mounted, but only one is visible. |
None
|
index
|
Union[int, ObservableBase[int]]
|
The index of the child to display. Can be an integer or any
read-observable of int — a plain |
0
|
width
|
SizingLike
|
The preferred width of the container. Defaults to None. |
None
|
height
|
SizingLike
|
The preferred height of the container. Defaults to None. |
None
|
padding
|
Union[int, Tuple[int, int], Tuple[int, int, int, int]]
|
Padding to apply around the visible child. Defaults to 0. |
0
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/deck.py
current_index
property
¶
The selected child index, clamped to the children that exist.
Resolved from the observable on every read rather than served from the
cache the subscription maintains. The subscription is mount-scoped -- it
exists to call mark_needs_layout, which only means anything in a
tree -- so without this a Deck that had not been mounted yet, or had been
taken out, would report a stale index to code that is entitled to ask.
on_mount
¶
Follow the index observable for as long as this Deck is in the tree.
The observable belongs to the app and outlives this widget, so a bare
subscribe in the constructor left it holding _on_index_changed
forever -- the leak this framework's bind() convention exists to
prevent. A dispose() method used to sit here doing the cleanup, and
never ran: Widget has no dispose hook for anything to call it
from.
Source code in src/nuiitivet/layout/deck.py
set_index
¶
Set the selected child index (for non-Observable usage).
Source code in src/nuiitivet/layout/deck.py
focus_traversal_children
¶
focus_traversal_children() -> List[Widget]
Return only the selected page, so Tab skips the pages behind it.
The index addresses the post-expansion list — the same list
paint and hit_test work on — so it is resolved on every call and
stays correct when a ForEach child changes its item count.
Source code in src/nuiitivet/layout/deck.py
layout
¶
Layout all children (to preserve state), position selected child.
Source code in src/nuiitivet/layout/deck.py
paint
¶
Paint only the selected child.
Source code in src/nuiitivet/layout/deck.py
hit_test
¶
Only allow hit testing on the selected child.
The Deck itself never becomes the hit target (S = none); it defers to the selected child only (C descends into one child), routed through the shared hit-participation helper.
Source code in src/nuiitivet/layout/deck.py
Collapsible
¶
Collapsible(child: Optional[Widget] = None, *, opened: Union[bool, ObservableBase[bool]] = True, motion: Motion = _DEFAULT_MOTION, motion_out: Optional[Motion] = None, axis: Axis = 'both', alignment: Union[str, Tuple[str, str]] = 'top-left', key: Optional[str] = None)
Bases: FocusTraversalBlocker, Widget
Single-child widget that animates its layout size open and closed.
The child is always mounted and laid out at its own natural size; the allocated rectangle reported to the parent is interpolated per axis. The animated rectangle is clipped internally, so the child never overflows its allocated bounds.
preferred_size only reports that interpolated rectangle: measuring is
speculative and repeated, so it never retargets. layout owns the
animation -- both the retarget and the first-layout snap to the initial
state.
Initialize a Collapsible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Optional[Widget]
|
The |
None
|
opened
|
Union[bool, ObservableBase[bool]]
|
|
True
|
motion
|
Motion
|
Base motion used for both open (enter) and close (exit). |
_DEFAULT_MOTION
|
motion_out
|
Optional[Motion]
|
Optional motion that overrides only the close (exit)
direction. When omitted, |
None
|
axis
|
Axis
|
Which axis/axes to animate ( |
'both'
|
alignment
|
Union[str, Tuple[str, str]]
|
Alignment of the child within the animated rectangle. |
'top-left'
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/collapsible.py
blocks_focus_traversal
property
¶
Keep the child out of the Tab sequence while the collapsible is closed.
The child stays mounted and laid out at its natural size while closed,
so without this its focusable widgets would remain Tab stops behind the
clip. Traversal follows the opened flag rather than the size
animation: the content becomes reachable as soon as it starts expanding
(it is on screen by then) and unreachable as soon as it starts
collapsing.
preferred_size
¶
preferred_size(max_width: Optional[int] = None, max_height: Optional[int] = None) -> Tuple[int, int]
Report the current (possibly interpolated) outer size.
Measuring never touches the animation: the reported size follows
whatever layout last resolved. The constraints are remembered so
the following layout measures the child the same way.
Source code in src/nuiitivet/layout/collapsible.py
VerticalScrollable
¶
VerticalScrollable(child: Widget, *, controller: Optional[ScrollController] = None, scrollbar_visible: ScrollbarVisibleLike = True, width: SizingLike = None, height: SizingLike = None, scrollbar_behavior: Optional[ScrollbarBehavior] = None, scrollbar_style: Optional[ScrollbarStyle] = None, style: Optional[ScrollableStyle] = None, key: Optional[str] = None)
Bases: _ScrollableBase
Scrolls its child along the vertical axis.
Source code in src/nuiitivet/layout/scrollable.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
HorizontalScrollable
¶
HorizontalScrollable(child: Widget, *, controller: Optional[ScrollController] = None, scrollbar_visible: ScrollbarVisibleLike = True, width: SizingLike = None, height: SizingLike = None, scrollbar_behavior: Optional[ScrollbarBehavior] = None, scrollbar_style: Optional[ScrollbarStyle] = None, style: Optional[ScrollableStyle] = None, key: Optional[str] = None)
Bases: _ScrollableBase
Scrolls its child along the horizontal axis.
Source code in src/nuiitivet/layout/scrollable.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
ForEach
¶
ForEach(items: ItemsLike, builder: BuilderFn, *, key_fn: Optional[Callable[[Any, int], Any]] = None, key: Optional[str] = None)
Bases: ComposableWidget
Initialize the ForEach data provider.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
ItemsLike
|
The source data collection. Can be an Iterable, an Observable, or an object with a .value attribute (checking value first). |
required |
builder
|
BuilderFn
|
A function that takes (item, index) and returns a Widget. |
required |
key_fn
|
Optional[Callable[[Any, int], Any]]
|
An optional function |
None
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload
(this ForEach node itself — not the per-item identity, which is
|
None
|
Source code in src/nuiitivet/layout/for_each.py
ScrollbarBehavior
dataclass
¶
ScrollbarBehavior(auto_hide: bool = True, hide_delay: float = 1.0, fade_duration: float = 0.15, hide_threshold: float = 0.25, track_click_behavior: str = 'jump', interactive: bool = True, hit_slop: Optional[int] = None)
Immutable behavior configuration for scrollbar widgets.
MenuBar
¶
MenuBar(items: Sequence[MenuEntry], *, style: Optional[MenuBarStyle] = None)
The application menu bar model: the root of the declarative menu tree.
Registered on the App (App(menu=nv.MenuBar([...]))) and replaced
wholesale via app.menu = .... Structure is not observable — entry
properties (label / enabled / checked) may be Observables, but adding
or removing entries means assigning a new model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
Sequence[MenuEntry]
|
Top-level entries. Bar entries are usually submenus
( |
required |
style
|
Optional[MenuBarStyle]
|
Optional per-instance style; |
None
|
Source code in src/nuiitivet/menubar/model.py
MenuBarArea
¶
Bases: MenuBarSlotBase
Marks where the registered menu bar renders, instead of the default spot.
Place it anywhere in the tree — typically inside a CustomChrome header
row — and the App-registered menu model renders there; the App's automatic
insertion below the chrome is suppressed. Menu definitions, callbacks and
shortcuts are unaffected by placement.
With no menu registered it renders nothing, so conditional menus are fine.
If several MenuBarArea widgets are mounted at once, only the first one
renders (a warning is logged).
On macOS the menu goes to the global menu bar (NSMenu) and the area
collapses to zero size — a chrome written around a MenuBarArea
degrades to a plain title bar with no platform branching.
Source code in src/nuiitivet/menubar/slots.py
MenuBarStyle
dataclass
¶
MenuBarStyle(bar_height: int = 34, bar_horizontal_padding: int = 4, item_horizontal_padding: int = 10, item_gap: int = 2, item_corner_radius: int = 6, label_size: int = 13, popup_corner_radius: int = 8, popup_min_width: int = 180, bar_background: Optional[ColorSpec] = None, bar_foreground: Optional[ColorSpec] = None, bar_disabled_foreground: Optional[ColorSpec] = None, bar_open_background: Optional[ColorSpec] = None, bar_state_layer: Optional[ColorSpec] = None, popup_background: Optional[ColorSpec] = None, popup_foreground: Optional[ColorSpec] = None, popup_accelerator: Optional[ColorSpec] = None, popup_disabled_foreground: Optional[ColorSpec] = None, popup_state_layer: Optional[ColorSpec] = None, popup_divider: Optional[ColorSpec] = None)
Immutable visual style for the in-app menu bar.
Holds geometry plus optional per-instance color overrides, mirroring
:class:~nuiitivet.scrolling.ScrollbarStyle: the app-wide default palette
is supplied by the design system via
:class:~nuiitivet.menubar.MenuBarThemeData, and the nullable ColorSpec
fields here override it per instance. A None color falls back to the
theme. Attach it to the model root: MenuBar(items, style=...).
On macOS none of this applies — the global menu bar is rendered by the OS
(see :mod:nuiitivet.menubar.nsmenu).
Attributes:
| Name | Type | Description |
|---|---|---|
bar_height |
int
|
Height of the horizontal bar in pixels. |
bar_horizontal_padding |
int
|
Padding at the left/right ends of the bar. |
item_horizontal_padding |
int
|
Horizontal padding inside each top-level item. |
item_gap |
int
|
Gap between top-level items. |
item_corner_radius |
int
|
Corner radius of the top-level item highlight. |
label_size |
int
|
Font size of top-level item labels. |
popup_corner_radius |
int
|
Corner radius of popup surfaces. |
popup_min_width |
int
|
Minimum popup width in pixels. |
bar_background |
Optional[ColorSpec]
|
Per-instance override ( |
copy_with
¶
merged_palette
¶
merged_palette(theme_data: Optional[MenuBarThemeData]) -> MenuBarThemeData
The effective palette: theme data with this style's overrides applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theme_data
|
Optional[MenuBarThemeData]
|
App-wide palette from the active theme, or |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
MenuBarThemeData
|
class: |
MenuBarThemeData
|
color field on this style is replaced by that override. |
Source code in src/nuiitivet/menubar/style.py
MenuBarThemeData
dataclass
¶
MenuBarThemeData(bar_background: ColorSpec = ('#000000', 0.04), bar_foreground: ColorSpec = ('#000000', 0.87), bar_disabled_foreground: ColorSpec = ('#000000', 0.38), bar_open_background: ColorSpec = ('#000000', 0.12), bar_state_layer: ColorSpec = '#000000', popup_background: ColorSpec = '#FFFFFF', popup_foreground: ColorSpec = ('#000000', 0.87), popup_accelerator: ColorSpec = ('#000000', 0.6), popup_disabled_foreground: ColorSpec = '#000000', popup_state_layer: ColorSpec = '#000000', popup_divider: ColorSpec = ('#000000', 0.12))
App-wide themeable colors for the menu bar, resolved at paint time.
The defaults are neutral, design-system-agnostic literals so that a bare
MenuBarThemeData() is usable even when no design system is registered.
Attributes:
| Name | Type | Description |
|---|---|---|
bar_background |
ColorSpec
|
Background of the horizontal bar. |
bar_foreground |
ColorSpec
|
Label color of top-level bar items. |
bar_disabled_foreground |
ColorSpec
|
Label color of disabled top-level items. |
bar_open_background |
ColorSpec
|
Background of the top-level item whose menu is open. |
bar_state_layer |
ColorSpec
|
Hover/press state-layer color for bar items. |
popup_background |
ColorSpec
|
Popup container background. |
popup_foreground |
ColorSpec
|
Popup item label color. |
popup_accelerator |
ColorSpec
|
Accelerator text color in popup items. |
popup_disabled_foreground |
ColorSpec
|
Base color for disabled popup items. |
popup_state_layer |
ColorSpec
|
Hover/press/focus state-layer color for popup items. |
popup_divider |
ColorSpec
|
Separator line color inside popups. |
copy_with
¶
MenuEntry
¶
MenuEntry(label: ObservableStr = '', *, on_select: Optional[VoidCallback] = None, shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True, checked: Optional[MutableObservableBase[bool]] = None, submenu: Optional[Sequence['MenuEntry']] = None, _role: MenuRole = NONE, _separator: bool = False)
One entry in a declarative menu model.
A single type covers all roles:
- Action:
MenuEntry("Open...", on_select=..., shortcut="Accel+O") - Submenu:
MenuEntry("File", submenu=[...])— top-level bar entries are simply entries with asubmenu; nesting is unlimited. - Separator:
MenuEntry.separator() - Standard item:
MenuEntry.quit()and friends — prebuilt entries whose activation calls a built-in App/Window method.
label and enabled may be Observables and propagate live to
whichever surface renders the model. checked (presence makes the entry
checkable) must be a writable Observable: activation toggles it before
on_select runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
ObservableStr
|
Entry label; a plain string or an Observable. |
''
|
on_select
|
Optional[VoidCallback]
|
Called with no arguments when the entry is activated. May be
sync or async. Exactly one of |
None
|
shortcut
|
Optional[ShortcutLike]
|
Accelerator gesture, as a spec string ( |
None
|
enabled
|
ObservableBool
|
Whether the entry can be activated; a bool or an Observable. |
True
|
checked
|
Optional[MutableObservableBase[bool]]
|
Writable Observable holding the check state. Presence makes
the entry checkable; activation toggles the value, then calls
|
None
|
submenu
|
Optional[Sequence['MenuEntry']]
|
Child entries. Mutually exclusive with |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the combination of arguments is invalid. |
Source code in src/nuiitivet/menus/model.py
resolved_label
¶
resolved_enabled
¶
separator
classmethod
¶
quit
classmethod
¶
quit(*, label: Optional[ObservableStr] = None, shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True) -> 'MenuEntry'
Exit the application (calls app.exit()).
Source code in src/nuiitivet/menus/model.py
close_window
classmethod
¶
close_window(*, label: ObservableStr = 'Close Window', shortcut: Optional[ShortcutLike] = 'Accel+W', enabled: ObservableBool = True) -> 'MenuEntry'
Close the window (calls window.close()).
Source code in src/nuiitivet/menus/model.py
minimize
classmethod
¶
minimize(*, label: ObservableStr = 'Minimize', shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True) -> 'MenuEntry'
Minimize the window (calls window.minimize()).
Source code in src/nuiitivet/menus/model.py
maximize
classmethod
¶
maximize(*, label: Optional[ObservableStr] = None, shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True) -> 'MenuEntry'
Maximize / zoom the window (calls window.maximize()).
Source code in src/nuiitivet/menus/model.py
restore
classmethod
¶
restore(*, label: ObservableStr = 'Restore', shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True) -> 'MenuEntry'
Restore the window (calls window.restore()).
The way back from :meth:full_screen, :meth:maximize, and
:meth:minimize: exits full screen, or restores the pre-maximize
size, or brings a minimized window back.
Source code in src/nuiitivet/menus/model.py
full_screen
classmethod
¶
full_screen(*, label: ObservableStr = 'Full Screen', shortcut: Optional[ShortcutLike] = None, enabled: ObservableBool = True) -> 'MenuEntry'
Toggle full screen (calls window.full_screen()).
Source code in src/nuiitivet/menus/model.py
MenuRole
¶
Bases: Enum
Built-in command a standard :class:MenuEntry invokes.
A role entry needs no on_select: activating it calls the mapped
built-in method (see nuiitivet.menubar.controller). Roles are also what
the macOS bridge will use to relocate items to their conventional places
(e.g. Quit into the application menu).
Widget
¶
Widget(*, width: Union[SizingLike, ReadOnlyObservableProtocol] = None, height: Union[SizingLike, ReadOnlyObservableProtocol] = None, padding: Union[PaddingLike, ReadOnlyObservableProtocol] = None, max_children: Optional[int] = None, overflow_policy: str = 'none', key: Optional[str] = None)
Bases: BindingHostMixin, LifecycleHostMixin, InputHubMixin, ChildContainerMixin, WidgetKernel
Leaf-friendly widget base composed from mixins.
This class intentionally does not participate in build/recomposition.
Widgets that need build()/rebuild()/scope() must inherit
ComposableWidget.
Source code in src/nuiitivet/widgeting/widget.py
mark_needs_layout
¶
Mark this widget as needing layout recalculation.
Source code in src/nuiitivet/widgeting/widget.py
find_ancestor
¶
Find the nearest ancestor of the specified type.
Traverses up the widget tree looking for an ancestor that matches the given type. This is used to implement context lookup patterns like Navigator.of(context).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
widget_type
|
Type[T]
|
The type of widget to find. |
required |
Returns:
| Type | Description |
|---|---|
Optional[T]
|
The nearest ancestor of the specified type, or None if not found. |
Example
navigator = self.find_ancestor(Navigator) if navigator: navigator.push(...)
Source code in src/nuiitivet/widgeting/widget.py
modifier
¶
modifier(modifier: Union[Modifier, ModifierElement]) -> Widget
Apply a modifier to this widget, returning the wrapped result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
modifier
|
Union[Modifier, ModifierElement]
|
The modifier (or modifier element) to apply. |
required |
Returns:
| Type | Description |
|---|---|
Widget
|
The wrapped widget (e.g. ModifierBox) or the widget itself if modified in-place. |
Source code in src/nuiitivet/widgeting/widget.py
ComposableWidget
¶
Bases: BuilderHostMixin, Widget
Widget base that participates in build/recomposition.
Composition widgets can override build() and use scope() /
render_scope() for fine-grained recomposition.
Layout metadata is transparent: a
value declared on this widget wins, and an undeclared one is derived from
the widget its build() returned — so extracting a subtree into a
composable does not change how the tree lays out.
Source code in src/nuiitivet/widgeting/widget_builder.py
Box
¶
Box(child: Optional[Widget] = None, width: SizingLike = None, height: SizingLike = None, padding: PaddingLike = 0, background_color: Optional[ColorSpec] = None, border_width: float = 0, border_color: Optional[ColorSpec] = None, corner_radius: Union[float, Tuple[float, float, float, float]] = 0, shadows: ShadowLike = None, alignment: Union[str, Tuple[str, str]] = 'center', *, key: Optional[str] = None)
Bases: CachedPaintMixin, Widget
(Advanced) Low-level drawing primitive.
Note
Prefer using Modifiers (e.g. .background(), .border(), .shadows())
on a Container or other widgets. This widget is used internally
to implement those modifiers.
A widget that draws a box with optional background, border, and shadow.
Source code in src/nuiitivet/widgets/box.py
shadows
property
writable
¶
The shadow layers this box draws, ordered back to front.
visual_clip_rect
¶
Return the rect content is clipped to, in this widget's local coords.
None when the box clips nothing, which is the common case.
This publishes, under the name geometric readers probe for (see
nuiitivet._interaction.perception), the same answer paint and
:meth:hit_test already act on. Without it a child laid out outside
the box -- an oversized decorative shape trimmed to a corner, the idiom
that fakes a gradient -- keeps its full layout rect as far as anything
reading the tree geometrically is concerned, and so looks reachable at
coordinates where none of it is painted.
Derived from layout state rather than last_rect: an action settles
the tree by laying it out without painting, so paint state is stale
exactly when this is needed.
Source code in src/nuiitivet/widgets/box.py
corner_radii_pixels
¶
Return the resolved corner radii in pixels for the given size.
InputFilter
¶
Bases: ABC
A rule applied to text as the user types it.
Subclasses implement :meth:apply, which receives the value before the
edit and the value the edit proposes, and returns the value to accept.
Returning old rejects the edit outright; returning new accepts it
unchanged.
apply
abstractmethod
¶
Geometry
¶
Geometry(child: Widget, *, width: SizingLike = None, height: SizingLike = None, key: Optional[str] = None)
Bases: Widget
Publishes this widget's own measured geometry to its subtree.
Transparent to layout: the single child receives this widget's own size.
Each layout pass publishes that size as an Observable[Size], read by
descendants via :meth:Geometry.of — bind it (mapped); a .value read at
build time is a one-time snapshot. Map it into a value widget, or drive a
Deck index for a structural switch::
class Panel(ComposableWidget):
def build(self) -> Widget:
size = Geometry.of(self).size
return Deck(
children=[_NarrowLayout(...), _WideLayout(...)],
index=size.map(lambda s: 1 if s.width >= 600 else 0),
)
Geometry(Panel())
The nearest ancestor provider wins, so wrapping a panel makes descendants
react to the panel, not the window; with no nearer provider, reads fall back
to the root Geometry the app installs at the window and track the window
size. The size is measured during layout and published between frames:
consumers see it one frame later, never a torn mix of old and new.
Wrap child, publishing this widget's measured size to its subtree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
child
|
Widget
|
The single child laid out at this widget's own size. |
required |
width
|
SizingLike
|
Sizing for this widget ( |
None
|
height
|
SizingLike
|
Sizing for this widget; see |
None
|
key
|
Optional[str]
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/layout/geometry.py
size
property
¶
size: Observable[Size]
This widget's resolved (width, height), published between frames.
of
classmethod
¶
of(context: Widget) -> GeometryT
Return the nearest ancestor :class:Geometry (nearest provider wins).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Widget
|
A widget in the subtree from which to search upward. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called before |
Source code in src/nuiitivet/layout/geometry.py
preferred_size
¶
preferred_size(max_width: Optional[int] = None, max_height: Optional[int] = None) -> Tuple[int, int]
Report preferred size: own fixed sizing wins, else the child's.
A weight / "wt" sizing is not fixed, so the child's intrinsic
size is reported here and the parent's weight distribution then stretches
this widget to fill — which is what lets a filling Geometry measure
the space available to a content pane.
Source code in src/nuiitivet/layout/geometry.py
layout
¶
Lay the child out at this widget's own size, then queue its publish.
Source code in src/nuiitivet/layout/geometry.py
paint
¶
Paint the child at this widget's own rect (transparent to paint).
Source code in src/nuiitivet/layout/geometry.py
Size
¶
Bases: NamedTuple
An immutable (width, height) pair in logical pixels.
A NamedTuple so it is compared by value: two equal sizes are ==,
which lets a size report de-dupe an unchanged measurement without a custom
comparator. It also unpacks like a tuple (w, h = size).
Shadow
dataclass
¶
Shadow(color: ColorSpec, blur_radius: float = 0.0, offset: Tuple[float, float] = (0.0, 0.0), spread_radius: float = 0.0)
A single shadow layer, in CSS box-shadow terms.
A widget's shadow is one or more of these layers, stacked back to front.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
ColorSpec
|
Shadow color. Supports |
required |
blur_radius
|
float
|
CSS blur-radius, in pixels. A value of 0.0 means a hard-edged shadow. |
0.0
|
offset
|
Tuple[float, float]
|
(dx, dy) translation of the shadow relative to the widget. |
(0.0, 0.0)
|
spread_radius
|
float
|
CSS spread-radius, in pixels: outward inflation of the shadow rect, applied before the blur. Corner radii grow by the same amount. A negative value shrinks the rect. |
0.0
|
Navigator
¶
Navigator(screen: Route | Widget | None = None, *, layer_composer: NavigationLayerComposer | None = None, key: str | None = None)
Bases: ComposableWidget
A minimal navigation stack.
Initialization forms
Navigator(screen): start with a single screen (RouteorWidget).Navigator.routes([...]): pre-populated stack (e.g. deep linking).Navigator.intents(initial_route=..., routes={...}): Intent-based routing.
Features
- push/pop
- of(context) / of(context, root=True)
- optional fade-in on push
Initialize a Navigator with a single initial screen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
screen
|
Route | Widget | None
|
The initial screen as a |
None
|
layer_composer
|
NavigationLayerComposer | None
|
Optional custom layer composer. |
None
|
key
|
str | None
|
Stable widget identity for dev-bridge targeting and hot reload. |
None
|
Source code in src/nuiitivet/navigation/navigator.py
stack
property
¶
stack: tuple[Route, ...]
The route stack, bottom to top.
A route that is being animated out is still here, and stays until its exit transition finalizes and the route is disposed. Filtering it out would report a pop as done while the outgoing screen is still mounted, still laid out and still painted — a caller waiting on the depth would go through on a transition that has not happened. So this reports what the stack runtime holds, and code that wants "the pop finished" waits for it::
navigator.pop()
await app.wait_for(lambda: len(navigator.stack) == 1)
Reading this never builds a widget: the routes are handed out as they
are, and Route.build_widget() constructs on demand. Asking what is on
the stack must not change what is on screen.
Not to be confused with :meth:snapshot_stack, which is the hot-reload
restore log.
in_transition
property
¶
Whether a navigation is in flight — not merely whether one animates.
True from the moment a back navigation is requested, through the spawned
task, through any push or pop transition, until the stack has settled.
The wider definition is the load-bearing part: :meth:pop runs the pop as
a task, so on the narrow "is a transition object alive" reading this would
be False for the whole window between pop() returning and the task
starting, and await wait_for(lambda: not nav.in_transition) would go
through immediately, having waited for nothing.
Prefer waiting on what actually changed — :attr:stack, or the screen on
top — and reach for this when the depth is not what moved.
routes
classmethod
¶
routes(screens: Sequence[Route | Widget], *, layer_composer: NavigationLayerComposer | None = None) -> Navigator
Create a Navigator with a pre-populated stack.
Use this when the navigator should start with multiple screens already on the stack (e.g. deep linking, state restoration).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
screens
|
Sequence[Route | Widget]
|
Sequence of |
required |
layer_composer
|
NavigationLayerComposer | None
|
Optional custom layer composer. |
None
|
Source code in src/nuiitivet/navigation/navigator.py
intents
classmethod
¶
intents(*, initial_route: Any, routes: Mapping[type[Any], Callable[[Any], Route | Widget]], layer_composer: NavigationLayerComposer | None = None) -> Navigator
Create a Navigator configured for Intent-based routing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_route
|
Any
|
The initial Intent instance used to resolve the first route. |
required |
routes
|
Mapping[type[Any], Callable[[Any], Route | Widget]]
|
Mapping of Intent types to route builder functions. Each
builder returns a |
required |
layer_composer
|
NavigationLayerComposer | None
|
Optional custom layer composer. |
None
|
Source code in src/nuiitivet/navigation/navigator.py
of
classmethod
¶
of(context: Widget, root: bool = False) -> NavigatorT
Return the Navigator that navigation from context should drive.
The nearest ancestor wins, so a nested navigator keeps its own history. With no ancestor the answer is the App's own navigator, which makes this the single entry point for both the nested and the top-level case.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Widget
|
A widget in the subtree from which to resolve. |
required |
root
|
bool
|
Skip the ancestor search and return the App's navigator, to drive a whole-window transition from inside a nested navigator. |
False
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called before |
Source code in src/nuiitivet/navigation/navigator.py
snapshot_stack
¶
Capture the restorable descriptors of routes pushed onto this navigator.
Returns an ordered list, one entry per route added via :meth:push
(bottom to top): a :class:_PushDescriptor for a declarative (intent)
push, or None for an opaque, non-restorable push. Routes from the
initial construction stack are excluded — a hot reload rebuilds those
from the factory. Pair with :meth:restore_stack across a reload.
This is not the route stack. It is the restore log: one entry per
declarative push, None for a raw widget push, and nothing at all
for the routes the navigator was constructed with. For the stack, use
:attr:stack.
Source code in src/nuiitivet/navigation/navigator.py
restore_stack
¶
Replay pushed routes from descriptors onto the freshly built navigator.
Each restorable descriptor is resolved against the current route table
(by intent qualified name) and pushed without animation, rebuilding the
stack the author had before a reload. Replay stops at the first entry
that cannot be restored — an opaque (None) push or an intent whose
route is no longer registered — leaving the remainder collapsed, the
documented degradation analogous to unmatched Observable paths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
descriptors
|
Sequence[_PushDescriptor | None]
|
The list returned by :meth: |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of routes restored (pushed) onto the stack. |
Source code in src/nuiitivet/navigation/navigator.py
pop
¶
Request a back navigation. The pop itself runs as a task.
Source code in src/nuiitivet/navigation/navigator.py
request_back
async
¶
Request a single back action.
This API is designed for user back inputs (Esc/back button). If a pop transition is already running, the request is queued and the current transition is completed immediately.
Queue consumption policy: - Intermediate queued pops are performed without animation. - The last queued pop (if any) uses the normal pop behavior.
Source code in src/nuiitivet/navigation/navigator.py
focus_traversal_children
¶
focus_traversal_children() -> list[Widget]
Return only the top route, so Tab never reaches a covered one.
Every route stays mounted — that is how a screen keeps its state while another one sits on top of it — and only the top one is painted. The Tab sequence has to stop at the same boundary.
Source code in src/nuiitivet/navigation/navigator.py
NavigatorProtocol
¶
Bases: Protocol
The navigation surface a ViewModel depends on.
Annotate an injected navigator with this protocol so the ViewModel stays independent of the widget tree::
class CartViewModel:
def __init__(self, navigator: NavigatorProtocol) -> None:
self._navigator = navigator
def checkout(self) -> None:
self._navigator.push(OrderCompleteIntent())
:class:~nuiitivet.navigation.navigator.Navigator and
:class:~nuiitivet.material.navigator.MaterialNavigator satisfy it
structurally, and a hand-written fake needs only these three methods --
no widget tree and no App.
Route
dataclass
¶
Route(builder: Callable[[], Widget], transition_spec: TransitionSpec = (lambda: empty())(), _widget: Widget | None = None)
A unit of navigation.
Notes
This is intentionally minimal for Phase 3.
OverlayAware
¶
Bases: Generic[T]
Mixin that lets a widget receive its own :class:OverlayHandle.
When a widget inheriting OverlayAware is displayed through any
:class:Overlay show API (show, dialog, side_sheet, bottom_sheet,
snackbar, loading), the framework injects the created handle
into the widget instance before mounting. The widget (or its ViewModel)
can then close itself via self.overlay_handle.close(value) without
requiring the caller to wire the handle manually.
Type parameter T represents the result type returned from
handle.close(value) / await handle. Note that the return type
of the Overlay show APIs stays OverlayHandle[Any]; only the
widget-side view is typed.
Example
class MyDialog(ComposableWidget, OverlayAware[str]): ... def on_save(self) -> None: ... self.overlay_handle.close("saved") ... result = await overlay.dialog(MyDialog())
overlay_handle
property
¶
Return the handle injected by the overlay framework.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If accessed before the widget is displayed via
an |
OverlayProtocol
¶
Bases: Protocol
The core overlay surface a ViewModel depends on.
Core :class:~nuiitivet.overlay.overlay.Overlay offers no scenario-specific
presentation APIs -- dialog, snackbar, and the sheets live on
:class:~nuiitivet.material.overlay.MaterialOverlay. A ViewModel that only
dismisses overlays can depend on this protocol; one that presents them
should use nuiitivet.material.OverlayProtocol
(:class:~nuiitivet.material.protocols.MaterialOverlayProtocol), which
extends this one.
close
¶
Close an overlay entry, optionally with a result value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Result delivered to the awaiting caller. |
None
|
target
|
Widget | Route | None
|
Entry to close, identified by its route or a widget inside it. Defaults to the topmost entry. |
None
|
Source code in src/nuiitivet/overlay/protocols.py
CancelToken
¶
Tells a run whether its result is still wanted.
One token per run, created and set by the operator; the run only reads it.
That is the whole difference from the hand-rolled threading.Event this
replaces: there is no clear() to misuse and no way to mistake another
run's flag for your own.
Checking is optional and cooperative -- a superseded run's result is discarded whether or not it ever looks. Python cannot interrupt a thread from outside, so this only helps work with a seam to check at::
def load(query: str, cancel: CancelToken) -> list[Row]:
rows: list[Row] = []
for page in range(PAGE_COUNT):
if cancel.superseded:
return [] # stop paying for an answer nobody wants
rows += fetch(query, page)
return rows
A single blocking call has no such seam, and gains nothing from the token.
Source code in src/nuiitivet/observable/switched.py
superseded
property
¶
Whether a newer run has started, making this run's result unwanted.
Clock
¶
Bases: Protocol
Clock API compatible with pyglet.clock.
Install an implementation with :func:set_clock to control when scheduled
callbacks run — a test that drives a cross-thread observable write or a
debounced observable needs this, since the default fallback clock fires on
a background thread at wall-clock time.
Implementations must identify scheduled callbacks by equality, the rule
pyglet.clock uses: unschedule(obj.method) has to cancel a timer
armed with obj.method, even though each attribute access produces a
distinct bound-method object. Comparing by id() instead silently leaks
timers.
Observable
¶
Bases: _ObservableValue[T]
Descriptor for a per-instance observable that can also be used standalone.
Writes from a thread other than the UI thread are marshalled onto it and
coalesced, so a subscriber may safely touch widgets whichever thread set
the value. Pass dispatch=False for an observable no widget will ever
bind to: notification then stays synchronous on the writing thread, and
every intermediate value is delivered rather than only the latest per tick.
Source code in src/nuiitivet/observable/value.py
Clocks
¶
The runtime's installed clock (read and replace).
get
staticmethod
¶
get() -> Clock
Return the clock currently installed.
Read the clock through this call every time instead of keeping a
reference: the backend installs its own clock during App.run(),
so a saved reference goes stale. Save and restore around a test with
this.
Source code in src/nuiitivet/observable/clocks.py
FileDropEvent
dataclass
¶
FileDropEvent(paths: tuple[Path, ...], x: float, y: float, local_x: Optional[float] = None, local_y: Optional[float] = None)
OS file paths dropped onto the window, delivered to a widget.
x / y are window coordinates of the drop point (top-left origin,
logical pixels); local_x / local_y are relative to the receiving
widget's top-left and are populated at delivery time.
PointerEvent
dataclass
¶
PointerEvent(id: int, type: PointerEventType, x: float, y: float, pointer_type: PointerType = UNKNOWN, dx: float = 0.0, dy: float = 0.0, scroll_x: float = 0.0, scroll_y: float = 0.0, button: Optional[int] = None, buttons: int = 0, timestamp: float = 0.0, is_primary: bool = True, modifier_keys: int = 0, local_x: float = 0.0, local_y: float = 0.0)
Immutable pointer event payload delivered to widgets.
Coordinate semantics
x / y are screen (window) coordinates. local_x / local_y
are relative to the top-left of the widget that receives the event and
are populated at dispatch time; on an event that has not been routed to a
widget they default to the screen coordinates' offset of 0.0.
PointerEventType
¶
Bases: str, Enum
Pointer event actions.
Shortcut
dataclass
¶
A key gesture: one key plus a mask of the modifiers held with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key name, normalized via :func: |
required |
modifiers
|
int
|
A bitmask of |
0
|
display
property
¶
Human-readable accelerator label for this gesture, per platform.
macOS uses the compact symbol form in Apple's canonical modifier order
(⌃⌥⇧⌘S); other platforms use the Ctrl+Shift+S form.
MOD_ACCEL renders as its resolved physical modifier (⌘ on macOS,
Ctrl elsewhere), so one :class:Shortcut yields the right label
everywhere — this is what menu items show next to their command.
conflicts_with_text_input
property
¶
Return True if this gesture is one a focused text field would type.
Such a gesture does not fire while a text field holds focus — the field
turns it into a character instead. Alt gestures count as conflicting
on every platform, so they are unavailable there too; see
:func:produces_text for why that cannot be narrowed down safely.
parse
classmethod
¶
Parse a "Accel+Shift+S"-style spec into a :class:Shortcut.
Modifier names are case-insensitive and accept the usual spellings:
Accel/Primary, Ctrl/Control, Alt/Option,
Meta/Cmd/Command/Super/Win, Shift. The last
token is the key. A literal + is written as the final token
("Accel++").
Raises:
| Type | Description |
|---|---|
ValueError
|
If the spec is empty, names an unknown modifier, or carries no key. |
Source code in src/nuiitivet/input/shortcut.py
matches
¶
Return True if a key press of key with modifier_keys is this gesture.
MOD_ACCEL is resolved to the platform's physical modifier here, and
the modifier mask must match exactly — Accel+S does not fire on
Accel+Shift+S.
Source code in src/nuiitivet/input/shortcut.py
ShortcutBinding
dataclass
¶
ShortcutBinding(shortcut: Shortcut, on_trigger: VoidCallback, scope: ShortcutScope = FOREGROUND)
A gesture bound to the callback it triggers, and the scope it is live in.
This is the unit the shortcut dispatch tier stores, kept as a type rather
than a bare callable so richer command semantics (can_execute, menu
binding) can be added without changing every call site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shortcut
|
Shortcut
|
The gesture that triggers the binding. |
required |
on_trigger
|
VoidCallback
|
Called with no arguments when the gesture fires. May be sync or async. |
required |
scope
|
ShortcutScope
|
When the binding is live. Defaults to
:attr: |
FOREGROUND
|
ShortcutScope
¶
Bases: Enum
When a :class:ShortcutBinding is live.
The members widen: each is a superset of the one before it.
Attributes:
| Name | Type | Description |
|---|---|---|
FOCUS |
Live only while the subtree contains the focused node. Needed only when the same command has several targets on screen at once (a dual-pane file manager, a split-view editor), so nothing but focus can decide which one acts. |
|
FOREGROUND |
Live while the subtree is on the topmost interactable layer — not hidden, not on a covered route, not behind a blocking overlay. The default, and the right answer for almost everything. |
|
MOUNT |
Live while the subtree is in the widget tree at all, displayed or not. How an app-wide command is expressed: bind it on the content root, which stays mounted across navigation. |
FocusSource
¶
Bases: str, Enum
Indicates how a :class:FocusNode acquired focus.
Attributes:
| Name | Type | Description |
|---|---|---|
KEYBOARD |
Focus acquired via keyboard navigation (Tab / Shift-Tab). |
|
POINTER |
Focus acquired via a pointer interaction (click-to-focus). |
Theme
dataclass
¶
Theme(mode: str, extensions: List[ThemeExtension] = list(), name: str = '')
Theme container holding design system extensions.
The Theme class itself is design-agnostic. It holds a list of ThemeExtension objects (like MaterialThemeData, CupertinoThemeData) that define the actual look and feel.
extension
¶
of
staticmethod
¶
Return the current :class:Theme from the nearest :class:AppScope.
Reading registers a dependency. The reader is recorded, and a theme change invalidates it: a composable is rebuilt, a leaf is re-measured and repainted. Nothing subscribes and nothing has to unsubscribe.
Read where the value is consumed. A widget with a build() reads
there; a leaf has no build(), so it reads in paint() or
preferred_size(). Never read in __init__ or on_mount: what is
resolved at mount and kept on a field is never corrected again.
A detached context — a widget deliberately measured outside an App, as
tests and offscreen sizing do — resolves no AppScope and quietly
falls back to the light default rather than raising. Paint code runs this
on every frame, so a raising lookup there would turn a cosmetic problem
into a crash. Under a pull that fallback is self-correcting: the next
read, once the widget is attached, resolves the real theme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Any
|
A widget in the subtree from which to search upward. |
required |
Returns:
| Type | Description |
|---|---|
'Theme'
|
The app's current theme, or |
'Theme'
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
Source code in src/nuiitivet/theme/theme.py
TypeScale
¶
The 15 baseline Material Design 3 type-scale roles.
Values follow the MD3 2021 baseline type scale
(https://m3.material.io/styles/typography/type-scale-tokens):
(font_size, line_height, weight, tracking).
TypeScaleToken
dataclass
¶
Immutable typographic metrics for a single type-scale role.
Attributes:
| Name | Type | Description |
|---|---|---|
font_size |
float
|
Glyph size in px. |
line_height |
float
|
Absolute line height in px (faithful to MD3 tokens, not a multiplier). Only affects multi-line layout. |
weight |
int
|
Font weight (100-900); MD3 roles use 400 (Regular) or 500 (Medium). |
tracking |
float
|
Letter spacing in px; may be negative (e.g. Display Large). |
copy_with
¶
Return a new token with the given fields replaced.
Example
TypeScale.TITLE_MEDIUM.copy_with(weight=700)
from_size
classmethod
¶
from_size(font_size: float, *, line_height: float | None = None, weight: int = 400, tracking: float = 0.0) -> 'TypeScaleToken'
Build a token from a raw size when no semantic role applies.
Intended for config-driven numeric sizes (e.g. a widget's *Style
exposing label_font_size), not for public typography. line_height
defaults to font_size * 1.25 to preserve historical spacing.
Source code in src/nuiitivet/theme/type_scale.py
ThemeManager
¶
ThemeManager(initial: Optional[Theme] = None)
Holds the current Theme and notifies its owner on changes.
Source code in src/nuiitivet/theme/manager.py
generation
property
¶
Count of theme changes so far.
Bumped before :attr:on_change runs, so anything deriving a value from
the theme can tell whether what it holds is still current.
set_theme
¶
set_theme(theme: Theme) -> None
Replace the current theme and notify the owner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theme
|
Theme
|
The theme to make current. |
required |
Source code in src/nuiitivet/theme/manager.py
ThemeExtension
¶
Animatable
¶
Animatable(initial_value: float, motion: Optional[Motion] = None)
Bases: ObservableBase[T]
Declarative, retargetable animation value.
Assign to target to retarget motion without reversing.
value is read-only and reflects the current animated value.
Subclasses :class:ObservableBase so it is recognised by the pure-C
isinstance fast path used on the widget construction hot path;
it also structurally satisfies :class:ReadOnlyObservableProtocol.
Initialize float animatable value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_value
|
float
|
Initial float value. |
required |
motion
|
Optional[Motion]
|
Optional motion model. If omitted, target assignment updates immediately. |
None
|
Source code in src/nuiitivet/animation/animatable.py
vector
staticmethod
¶
vector(initial_value: V, converter: VectorConverter[V], motion: Optional[Motion] = None) -> 'Animatable[V]'
Initialize vector-converted animatable value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_value
|
V
|
Initial value. |
required |
converter
|
VectorConverter[V]
|
Value/vector converter. |
required |
motion
|
Optional[Motion]
|
Optional motion model. If omitted, target assignment updates immediately. |
None
|
Source code in src/nuiitivet/animation/animatable.py
snap_to
¶
Immediately set both value and target without animating.
Stops any active motion and resets the resolved value and target to
value. Useful for establishing an initial state before the first
animated transition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
New current and target value. |
required |
Source code in src/nuiitivet/animation/animatable.py
set_motion
¶
set_motion(motion: Motion) -> None
Replace the active motion model.
Useful when the same animatable must switch motions depending on
direction (e.g. distinct enter/exit timing). Any in-flight motion
continues from its current value/velocity using the new motion on the
next target assignment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motion
|
Motion
|
New motion model to drive subsequent retargets. |
required |
Source code in src/nuiitivet/animation/animatable.py
stop
¶
Stop any active motion and keep the current value.
Source code in src/nuiitivet/animation/animatable.py
Motion
¶
Bases: Protocol
Protocol for declarative motion.
Motion specs are responsible for advancing MotionState over time and handling retargeting without pausing.
LinearMotion
¶
BezierMotion
¶
Bezier time-based motion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x1
|
float
|
Control point 1 x. |
required |
y1
|
float
|
Control point 1 y. |
required |
x2
|
float
|
Control point 2 x. |
required |
y2
|
float
|
Control point 2 y. |
required |
duration
|
float
|
Duration in seconds for a full transition. |
required |
Source code in src/nuiitivet/animation/motion.py
SpringMotion
¶
SpringMotion(stiffness: float, damping: float, mass: float, *, initial_velocity: float | Sequence[float] = 0.0, tolerance: float = 0.001)
Spring-based motion.
The integrator is sub-stepped so stiff springs stay stable when the frame
rate (and thus dt) drops: a single large semi-implicit Euler step can
diverge once dt approaches 2 / sqrt(stiffness / mass) (e.g. a
stiffness of 1400 diverges below ~30 fps). Each step splits dt into
fixed-size sub-steps so the result is stable and frame-rate independent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stiffness
|
float
|
Spring stiffness constant. |
required |
damping
|
float
|
Damping coefficient. |
required |
mass
|
float
|
Mass attached to the spring. |
required |
initial_velocity
|
float | Sequence[float]
|
Initial velocity in units per second. |
0.0
|
Source code in src/nuiitivet/animation/motion.py
TransitionDefinition
dataclass
¶
TransitionDefinition(motion: Motion, pattern: TransitionPattern)
Defines a complete transition with motion and pattern.
FadePattern
¶
FadePattern(start_alpha: float = 0.0, end_alpha: float = 1.0, *, start_progress: float = 0.0, end_progress: float = 1.0)
Controls opacity based on progress.
The optional start_progress / end_progress window confines the fade
to a sub-range of the overall progress, holding start_alpha before it and
end_alpha after it. This expresses the MD3 "fade through" timing, where
the outgoing element fades out over the first part of the transition and the
incoming element fades in over the later part, so the two never sit at half
opacity simultaneously. The defaults (0.0 → 1.0) keep a plain linear
fade over the full progress.
Source code in src/nuiitivet/animation/transition_pattern.py
SlidePattern
¶
Controls translation based on progress.
Source code in src/nuiitivet/animation/transition_pattern.py
ScalePattern
¶
ScalePattern(start_scale_x: float = 0.8, start_scale_y: float = 0.8, end_scale_x: float = 1.0, end_scale_y: float = 1.0)
Controls scale based on progress.
Source code in src/nuiitivet/animation/transition_pattern.py
FractionalSlidePattern
¶
FractionalSlidePattern(start_x: float = 0.0, start_y: float = 0.0, end_x: float = 0.0, end_y: float = 0.0)
Controls translation as a fraction of the widget's allocated size.
A value of +1.0 means one full width (for x) or height (for y) offset. This allows sheet transitions to slide in/out by exactly their own size, regardless of the actual pixel dimensions.
Source code in src/nuiitivet/animation/transition_pattern.py
Desktop
¶
Operating-system desktop integration (notifications, ...).
notify
staticmethod
¶
Raise a desktop notification with title and an optional body.
Fire-and-forget: returns immediately, never raises, and is safe to call from an event handler or a worker thread alike. Failures are logged once per process instead of surfacing — a notification must never take the app down. Delivery is best-effort: the OS may still suppress it (permissions, focus modes) without an error.
Source code in src/nuiitivet/platform/desktop.py
FileDialog
¶
Native open-file / save-file / open-directory dialogs.
All methods are coroutines: the dialog runs in an OS helper process and is awaited from a worker thread, so the UI keeps painting while it is open. Call them from an async event handler::
class Editor(nv.ComposableWidget):
async def _open(self) -> None:
path = await nv.FileDialog.open_file(file_types=["txt", "md"])
if path is None:
return # cancelled
self.text.value = path.read_text()
Each method returns the selected :class:~pathlib.Path, or None when
the user cancelled. :class:FileDialogError is raised when the dialog
cannot be shown at all (e.g. no zenity/kdialog on Linux).
open_file
async
staticmethod
¶
open_file(*, title: Optional[str] = None, initial_dir: Union[Path, str, None] = None, file_types: Optional[Sequence[str]] = None) -> Optional[Path]
Pick an existing file to open.
file_types restricts the picker to extensions given without the
leading dot (e.g. ["png", "jpg"]); None allows any file.
A leading ~ in initial_dir is expanded to the home directory.
Source code in src/nuiitivet/platform/file_dialog.py
open_files
async
staticmethod
¶
open_files(*, title: Optional[str] = None, initial_dir: Union[Path, str, None] = None, file_types: Optional[Sequence[str]] = None) -> list[Path]
Pick one or more existing files to open.
Like :meth:open_file with multiple selection. Cancelling returns an
empty list — the dialog cannot return zero selections otherwise.
Source code in src/nuiitivet/platform/file_dialog.py
save_file
async
staticmethod
¶
save_file(*, title: Optional[str] = None, initial_dir: Union[Path, str, None] = None, default_name: Optional[str] = None, file_types: Optional[Sequence[str]] = None) -> Optional[Path]
Pick a destination path to save to.
The returned path may not exist yet; writing the file is the caller's
job. The native dialog asks for overwrite confirmation where the
platform does so. file_types restricts the saved name's extension
where the platform's save dialog supports it; the macOS osascript
fallback has no type filter and ignores it.
Source code in src/nuiitivet/platform/file_dialog.py
open_directory
async
staticmethod
¶
open_directory(*, title: Optional[str] = None, initial_dir: Union[Path, str, None] = None) -> Optional[Path]
Pick an existing directory.
Source code in src/nuiitivet/platform/file_dialog.py
FileDialogError
¶
Bases: RuntimeError
A dialog could not be shown (helper missing or helper failed).
Distinct from cancellation: the user cancelling a dialog returns None,
never raises.
TrayIcon
¶
TrayIcon(*, icon: Optional[Union[str, Path]] = None, tooltip: ObservableStr = '', menu: Optional[Sequence[MenuEntry]] = None, on_activate: Optional[VoidCallback] = None, dock_visibility: str = 'always')
A system tray icon: image, tooltip, menu, and an activate callback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
icon
|
Optional[Union[str, Path]]
|
Path to the icon image file (PNG recommended). On macOS a
filename stem ending in |
None
|
tooltip
|
ObservableStr
|
Hover text; a plain string or an Observable one. |
''
|
menu
|
Optional[Sequence[MenuEntry]]
|
The tray menu as :class: |
None
|
on_activate
|
Optional[VoidCallback]
|
Called when the icon itself is activated the platform's
conventional way. Support varies: on macOS only without a
|
None
|
dock_visibility
|
str
|
macOS Dock presence: |
'always'
|
Source code in src/nuiitivet/platform/tray.py
dock_visibility
property
¶
The macOS Dock policy: "always", "auto", or "never".
installed
property
¶
Whether the icon is actually showing in the system tray.
False until the backend installs it, and again after removal or
when the platform cannot host one (no pystray, no tray area). Apps
adapt through this — e.g. bind a window's close_action to it, or
exit when a tray they depend on is unavailable.
AppScope
¶
AppScope(app: 'App', child: Widget, *, key: Optional[str] = None)
Bases: Widget
Inherited widget that provides access to the App instance.
Also the theme provider: Theme.of resolves against the nearest one of
these. Theme changes are fanned out by the App itself (which owns the
:class:ThemeManager) to every open window; this scope only serves reads.
Source code in src/nuiitivet/runtime/app.py
app
property
¶
The App this scope belongs to, or None once it has been collected.
The App-scoped half of X.of(context) resolves through here — see
:func:nuiitivet.widgeting.context_lookup.find_app.
ExitPolicy
¶
Bases: Enum
When App.run() returns.
Attributes:
| Name | Type | Description |
|---|---|---|
LAST_WINDOW_CLOSED |
The default — the app exits once no window remains open. |
|
MAIN_WINDOW_CLOSED |
Closing the main window closes every other window and exits, regardless of what else is open. |
|
EXPLICIT |
Only |
OSChrome
dataclass
¶
OS-managed window decoration.
Maps variant directly to pyglet.window.Window.WINDOW_STYLE_*.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variant
|
OSChromeVariant
|
Window style. One of: "default", "dialog", "tool", "borderless", "transparent". |
'default'
|
CustomChrome
dataclass
¶
CustomChrome(header: 'Widget', corner_radius: float = 0.0, border: Optional[Border] = None)
Custom (app-drawn) window decoration.
Always uses a borderless OS window (no OS title bar).
Wraps header in :class:~nuiitivet.runtime.title_bar.WindowDragArea
so the user can drag the window by the header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
header
|
'Widget'
|
Widget rendered as the window's title-bar area. |
required |
corner_radius
|
float
|
Corner radius in logical pixels applied by the render layer. Content is clipped to a rounded rectangle; pixels outside the rounded corners are cleared to transparent. Note that true desktop-transparency in the corners requires platform-level window compositing support. |
0.0
|
border
|
Optional[Border]
|
Optional border drawn around the window edge inside the rounded-corner boundary. |
None
|
Border
dataclass
¶
Border specification for CustomChrome.
Attributes:
| Name | Type | Description |
|---|---|---|
color |
'ColorSpec'
|
Border color (any ColorSpec accepted by the theme system). |
width |
float
|
Border width in logical pixels. |
AppProtocol
¶
Bases: Protocol
The application surface a ViewModel depends on.
Annotate the app a ViewModel receives with this protocol so the ViewModel
stays independent of the runtime. Pass it per method call -- the View's
event handler resolves App.of(context) and hands it over::
class SettingsViewModel:
def apply_dark_mode(self, app: AppProtocol) -> None:
app.set_theme("dark")
App.of(context) returns the running app typed as this protocol, and a
hand-written fake needs only these three methods -- no widget tree and no
event loop.
WindowProtocol
¶
Bases: Protocol
The window surface a ViewModel depends on.
Annotate the window a ViewModel receives with this protocol so it can
command its window -- close it, hide it, resize it -- without holding the
full :class:~nuiitivet.runtime.window.Window. Pass it per method call --
the View's event handler resolves Window.of(context) and hands it
over::
class LauncherViewModel:
def send_to_background(self, window: WindowProtocol) -> None:
window.hide()
Window.of(context) returns an object satisfying it, and a fake needs
only these members. Every method is a no-op when the OS window does not
exist (not realized yet, or already closed).
Window
¶
Window(content: Widget | RootFactory, width: WindowSizingLike = 'auto', height: WindowSizingLike = 'auto', *, title: str | None | ObservableBase[str | None] = None, chrome: OSChrome | CustomChrome | None = _UNSET, background: ColorSpec = SURFACE, overlay_factory: Callable[[], Overlay] | None = None, window_position: WindowPositionLike | None = None, resizable: bool = True, accepts_first_mouse: bool = True, menu: MenuBar | None = None, parent: Window | None = None, modal: bool = False, close_action: str | ObservableBase[str] = 'close')
One OS window: its widget tree, services, and lifecycle.
Construction builds a model only — no OS window, no mounted tree.
:meth:open realizes it (and registers it with the running
:class:~nuiitivet.runtime.app.App); :meth:close destroys it. One
object is one window lifetime: a closed Window is finished, and showing
the same content again means constructing a new one. State that must
survive a window lives in app-layer Observables passed into the content.
Initialize the Window model. :meth:open realizes it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
Widget | RootFactory
|
The root content. Accepts either a ready |
required |
width
|
WindowSizingLike
|
Window width specification. |
'auto'
|
height
|
WindowSizingLike
|
Window height specification. |
'auto'
|
title
|
str | None | ObservableBase[str | None]
|
OS window title. Accepts a plain string or an
:class: |
None
|
chrome
|
OSChrome | CustomChrome | None
|
Window decoration. Pass an :class: |
_UNSET
|
background
|
ColorSpec
|
Window background color. |
SURFACE
|
overlay_factory
|
Callable[[], Overlay] | None
|
Optional overlay factory. |
None
|
window_position
|
WindowPositionLike | None
|
Initial window position. Accepts a 9-point
alignment string (e.g. |
None
|
resizable
|
bool
|
Whether the window can be resized. |
True
|
accepts_first_mouse
|
bool
|
macOS only. When |
True
|
menu
|
MenuBar | None
|
The menu bar model (:class: |
None
|
parent
|
Window | None
|
The parent window, or |
None
|
modal
|
bool
|
Whether this window blocks input to its parent chain
while open (framework modal). Requires |
False
|
close_action
|
str | ObservableBase[str]
|
What the OS close button does: |
'close'
|
Source code in src/nuiitivet/runtime/window.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 | |
navigator
property
¶
navigator: Navigator
This App's root :class:~nuiitivet.navigation.Navigator.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the App has no content root yet. |
overlay
property
¶
This App's root :class:~nuiitivet.overlay.Overlay.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the App has no content root yet. |
menu
property
writable
¶
menu: MenuBar | None
The registered application menu bar model, or None.
Assigning replaces the model wholesale and rebuilds the rendered bar; item properties (label / enabled / checked) may be Observables and propagate live without replacement.
ime
property
¶
This window's IME geometry (cursor rect and window location).
One instance per window, so two windows never race each other's candidate-window positioning.
app
property
¶
The owning :class:~nuiitivet.runtime.app.App.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the window is not attached to an App (it attaches
at :meth: |
is_open
property
¶
Observable open state: True between :meth:open and :meth:close.
is_visible
property
¶
Whether the window is visible (or will be, once realized).
Hidden is not closed: the object, its widget tree, and its geometry stay alive, and the window still counts for the App's exit policy. On Windows/Linux the taskbar entry follows this by itself.
title
property
¶
The window title's current value, or None if unset.
Resolves the title given at construction: a plain string is returned as
is; an :class:~nuiitivet.observable.protocols.ObservableBase is
unwrapped to its current value. Exposed for dev tooling -- the dev
bridge's status reports it so an assistant can confirm which app is
running -- and never raises: an observable whose read fails reports
None rather than propagating.
modifier_keys
property
¶
The keyboard-modifier keys currently held down.
A bitmask of MOD_SHIFT/MOD_CTRL/MOD_ALT/MOD_META. This is
the single authoritative source of "which modifier keys are down",
maintained by the backend on every key press and release and cleared on
window deactivation. It is exposed for framework internals only and must
not be treated as mutable application state.
of
staticmethod
¶
Return the Window whose tree contains context.
The returned object is the same Window the opener holds — there is
no proxy type. A ViewModel should receive it typed as
:class:~nuiitivet.runtime.protocols.WindowProtocol. Valid from
on_mount, not from __init__, like every .of() lookup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Widget
|
The widget context. |
required |
Returns:
| Type | Description |
|---|---|
Window
|
The owning Window. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called before |
Source code in src/nuiitivet/runtime/window.py
can_handle_back_event
¶
Return True if a back action would be handled.
This is a non-mutating check used by backends to decide whether to consume the OS/back key or let default handlers run (e.g. ESC-to-exit).
Source code in src/nuiitivet/runtime/window.py
handle_back_event
async
¶
Handle a user back action (e.g. Esc).
Priority: - Overlay: close topmost entry if any - Navigator: pop one route if possible
A back action never reaches past a blocking layer. If the overlay had
nothing left to close but is still painting one -- a dialog already
dismissed and animating out is the case that reaches here -- the event
stops, rather than popping the screen the user can still see behind it.
This is the keyboard half of what Overlay.hit_test does for the
pointer; a pass-through layer (toast, banner) blocks neither.
Source code in src/nuiitivet/runtime/window.py
open
¶
open() -> Window
Realize the window: build and mount the tree, register with the App.
The OS window itself is created by the running backend — immediately
when the loop is already running, or when app.run() starts for
windows opened before it.
Returns:
| Type | Description |
|---|---|
Window
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the window is already open, is already closed (one object is one window lifetime), no App exists yet, or the parent window is not open. |
Source code in src/nuiitivet/runtime/window.py
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 | |
close
¶
Destroy the window: unmount the tree and close the OS window.
Closing is one-way — the object is finished afterwards. Children close first, transitively. Closing a window that is not open is a no-op.
Source code in src/nuiitivet/runtime/window.py
hide
¶
Hide the window, keeping the object and its widget tree alive.
The counterpart of :meth:show — the pair a tray-resident app parks
and summons its window with. Before the backend realizes the OS
window this just records the desired state (so a window can start
hidden); hiding a window that is not open is a no-op.
Source code in src/nuiitivet/runtime/window.py
show
¶
Make the window visible and bring it to the front, focused.
Also the "summon" action for an already-visible window: it raises and refocuses. Showing a window that is not open is a no-op (a closed Window is finished — construct a new one).
Source code in src/nuiitivet/runtime/window.py
render_to_png
¶
Render the current UI to a PNG file.
Settles first: an interactive app reaches its final layout over the next
frame or two (see :meth:_settle_pending_size_changes), which a single
render would otherwise never draw.
Source code in src/nuiitivet/runtime/window.py
invalidate
¶
Request that the next frame be redrawn.
This sets an internal dirty flag which the render loop checks to decide whether to re-render the UI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
immediate
|
bool
|
If True and running in pyglet, bypass FPS throttle for next draw |
False
|
content
|
bool
|
If True (default), mark the widget tree as changed so the next frame is fully re-painted. Pass False for surface-loss redraws (window show/activate) where the tree is unchanged and the GPU path may re-blit its cached full frame instead of re-walking the tree. |
True
|
Source code in src/nuiitivet/runtime/window.py
request_focus
¶
request_focus(node: Optional[FocusNode], source: FocusSource = KEYBOARD) -> None
Set focus to the given FocusNode. Pass None to clear focus.
Source code in src/nuiitivet/runtime/window.py
center
¶
Center the window on its screen.
Source code in src/nuiitivet/runtime/window.py
maximize
¶
Maximize the window.
Source code in src/nuiitivet/runtime/window.py
minimize
¶
Minimize the window.
Source code in src/nuiitivet/runtime/window.py
restore
¶
Restore the window from maximized/minimized/full-screen state.
Source code in src/nuiitivet/runtime/window.py
full_screen
¶
Enter full screen mode (no toggle; :meth:restore is the way back).
Source code in src/nuiitivet/runtime/window.py
move_to
¶
Move the window to a specific screen position.
Source code in src/nuiitivet/runtime/window.py
resize
¶
Resize the window.
Source code in src/nuiitivet/runtime/window.py
WindowScope
¶
Bases: Widget
Inherited widget that provides access to the owning :class:Window.
Every window's root is wrapped in one of these (inside the app-wide
AppScope), so Window.of(context) — and the window-scoped fallback
of Overlay.of / Navigator.of — resolves to the window the context
belongs to, never to a process-wide default.
Source code in src/nuiitivet/runtime/window.py
WindowPosition
dataclass
¶
Represents how the OS window is positioned within a screen.
Coordinates:
- alignment uses the 9-point vocabulary from the layout system.
- offset is applied after alignment in logical pixels.
The offset uses UI coordinates: $+x$ is right, $+y$ is down.
WindowSizing
dataclass
¶
Represents how a window requests its initial size along an axis.
Fonts
¶
Application-wide font configuration (default family, bundled fonts).
set_default_family
staticmethod
¶
Set the application-wide default font family.
The family is prioritized over locale-based defaults wherever no
explicit font_family is given. Pass None to reset to automatic
locale detection.
Source code in src/nuiitivet/rendering/fonts.py
register
staticmethod
¶
Register a font file under a custom family name.
Call at application startup, before any widget is rendered. Once
registered, the family name can be used wherever a font_family is
accepted (e.g. TextStyle(font_family=...),
Icon(..., font_family=...)). The file is loaded lazily on first
use and cached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Absolute or relative path to a |
required |
family_name
|
str
|
The name to associate with this font. |
required |
Source code in src/nuiitivet/rendering/fonts.py
allow
¶
allow(pattern: Union[str, Pattern[str]]) -> InputFilter
Keep only the characters that match pattern, dropping the rest.
The pattern is matched against one character at a time, so
allow(r"[A-Za-z ]") yields a field that letters and spaces can be typed
into and nothing else. Use :func:matching for a rule about the text as a
whole.
Source code in src/nuiitivet/widgets/input_filter.py
deny
¶
deny(pattern: Union[str, Pattern[str]]) -> InputFilter
Drop the characters that match pattern, keeping the rest.
The inverse of :func:allow, and the better fit when only a few characters
are unwanted: deny(r"[\s]") for a field that must not contain spaces.
Source code in src/nuiitivet/widgets/input_filter.py
digits_only
¶
digits_only() -> InputFilter
matching
¶
matching(pattern: Union[str, Pattern[str]]) -> InputFilter
Reject an edit unless the resulting text matches pattern in full.
Unlike :func:allow, which filters character by character and always
accepts something, this rejects the keystroke outright when the result
would not match -- the rule it enforces is about the text as a whole, so
there is no partial result to keep. It is what expresses "at most one
decimal point"::
input_filter=matching(r"[0-9]*\.?[0-9]*")
The pattern must accept every string the user has to be able to pass
through on the way to a finished value, including "" if the field is to
be clearable.
Source code in src/nuiitivet/widgets/input_filter.py
max_length
¶
max_length(max_chars: int) -> InputFilter
batch
¶
Context manager for batching Observable updates.
focusable
¶
focusable(enabled: bool = True, on_focus_change: Optional[FocusChangeCallback] = None, on_key: Optional[Callable[[str, int], bool]] = None, on_key_up: Optional[Callable[[str, int], bool]] = None) -> FocusableModifier
Mark the widget as focusable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether the widget is focusable. |
True
|
on_focus_change
|
Optional[FocusChangeCallback]
|
Callback invoked when focus state changes. |
None
|
on_key
|
Optional[Callable[[str, int], bool]]
|
Callback invoked as |
None
|
on_key_up
|
Optional[Callable[[str, int], bool]]
|
Callback invoked as |
None
|
Source code in src/nuiitivet/modifiers/focus.py
on_mount
¶
Return a modifier that runs callback when the widget is mounted.
The callback runs right after the widget's :meth:Widget.on_mount hook and
before its children are mounted. Exceptions are logged and contained.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
VoidCallback
|
A no-argument callable. If it is a coroutine function, it is started as a task on mount and cancelled on unmount. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
An |
OnMountModifier
|
class: |
Note
Mount is not "once per logical component". A ComposableWidget
rebuild discards the built subtree and mounts freshly-created widget
instances, so the callback runs again for the new instance. Use it for
work tied to the widget instance's presence in the tree, not for
one-time initialization of a component.
Source code in src/nuiitivet/modifiers/lifecycle.py
on_size_changed
¶
Return a modifier that calls callback with the widget's measured size.
The callback receives a :class:Size - the widget's own (width, height)
after layout, excluding its position. Use it to feed a size into imperative
state: a ViewModel, or a plain Observable a child widget binds to::
class ResponsiveScaffold(ComposableWidget):
def __init__(self) -> None:
super().__init__()
self.expanded = Observable(False)
def _on_size(self, size: Size) -> None:
self.expanded.value = size.width >= 700
def build(self) -> Widget:
rail = NavigationRail(..., expanded=self.expanded)
return Row([rail, card], ...).modifier(on_size_changed(self._on_size))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
SizeCallback
|
A callable taking a :class: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
An |
OnSizeChangedModifier
|
class: |
Note
Fires once with the first measurement, so the callback alone is enough to seed the state it drives. After that it fires only when the measured size actually changed; a widget that is re-laid-out at the same size, or merely moved, is silent.
Note
Dispatched between frames, never during layout, so the callback may safely do anything - push a route, write an Observable, replace children - and its effect lands on the frame after the measurement.
That includes the first call, which therefore arrives after the first paint. Give an Observable the value you expect at the initial size and the first report writes the same value, which de-dupes: no visible transition. Seed it differently and the widget animates once on startup.
Warning
Avoid making the callback change the measured widget's own size: that
feeds back into the next measurement and can oscillate. The structurally
safe pattern is to measure a widget whose size the parent imposes
(Sizing.weight(...) / "wt") and let the callback change only what
is inside it.
Source code in src/nuiitivet/modifiers/size_changed.py
on_unmount
¶
Return a modifier that runs callback when the widget is unmounted.
The callback runs right after the widget's :meth:Widget.on_unmount hook
and before its children are unmounted. Exceptions are logged and contained.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
VoidCallback
|
A no-argument callable. A coroutine function is scheduled as a task, which may outlive the widget — prefer a synchronous callback for cleanup that must complete. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
An |
OnUnmountModifier
|
class: |
Source code in src/nuiitivet/modifiers/lifecycle.py
opacity
¶
Return a modifier that applies opacity to a widget during paint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
OpacityLike
|
Opacity value between 0.0 (transparent) and 1.0 (opaque), or an observable providing opacity. |
required |
Note
Opacity is paint-only. Layout and hit-testing remain unchanged.
Source code in src/nuiitivet/modifiers/transform.py
rotate
¶
Return a modifier that rotates a widget during paint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
AngleLike
|
Rotation angle in degrees, or an observable providing degrees. |
required |
origin
|
OriginLike
|
Rotation origin. Accepts any of the nine-point alignment tokens in canonical hyphen form ("center", "top-left", "top-center", "top-right", "center-left", "center-right", "bottom-left", "bottom-center", "bottom-right"); the underscore form ("top_left") is accepted as an alias. Alternatively an (x, y) tuple in local coords. Defaults to "center". |
'center'
|
Note
Rotation is paint-only. Layout and hit-testing remain untransformed.
Source code in src/nuiitivet/modifiers/transform.py
scale
¶
Return a modifier that scales a widget during paint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
ScaleLike
|
Scale factor (uniform) or (sx, sy) tuple, or an observable. |
required |
origin
|
OriginLike
|
Scale origin. Accepts any of the nine-point alignment tokens in canonical hyphen form ("center", "top-left", "top-center", "top-right", "center-left", "center-right", "bottom-left", "bottom-center", "bottom-right"); the underscore form ("top_left") is accepted as an alias. Alternatively an (x, y) tuple in local coords. Defaults to "center". |
'center'
|
Note
Scale is paint-only. Layout and hit-testing remain untransformed.
Source code in src/nuiitivet/modifiers/transform.py
shadows
¶
Draw a stack of shadow layers behind the widget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers
|
ShadowLike
|
A |
required |
Returns:
| Type | Description |
|---|---|
ShadowModifier
|
The modifier to apply. |
Source code in src/nuiitivet/modifiers/shadow.py
translate
¶
Return a modifier that translates a widget during paint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
offset
|
TranslateLike
|
Translation offset as (dx, dy) tuple, or an observable. |
required |
Note
Translation is paint-only. Layout and hit-testing remain untransformed.