Skip to content

Core API

The nuiitivet package exposes the core primitives, layout widgets, and state management utilities.

nuiitivet

nuiitivet package.

Core functionality and configuration primitives are exposed here.

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, ReadOnlyObservableProtocol] = 0, main_alignment: str = 'start', cross_alignment: str = 'start')

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, ReadOnlyObservableProtocol]

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'
Source code in src/nuiitivet/layout/column.py
def __init__(
    self,
    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, ReadOnlyObservableProtocol] = 0,
    main_alignment: str = "start",
    cross_alignment: str = "start",
):
    """Initialize Column and configure layout.

    Args:
        children: List of child widgets to arrange vertically.
        width: Column width.
        height: Column height.
        padding: Padding around the content.
        gap: Space between children in pixels.
        main_alignment: Vertical alignment of children.
            'start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'.
        cross_alignment: Horizontal alignment of children.
            'start', 'center', 'end', 'stretch'.
    """
    super().__init__(width=width, height=height, padding=padding)
    if children:
        for c in children:
            self.add_child(c)

    self._gap = 0
    self.gap = gap  # type: ignore

    self.main_alignment = main_alignment
    self.cross_alignment = cross_alignment

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') -> 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'
Source code in src/nuiitivet/layout/column.py
@classmethod
def builder(
    cls,
    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",
) -> "Column":
    """Create a Column that materializes children from items via ForEach.

    Args:
        items: Source data collection.
        builder: Function to create a widget for each item.
        width: Column width.
        height: Column height.
        padding: Padding around the content.
        gap: Space between children.
        main_alignment: Vertical alignment of children.
        cross_alignment: Horizontal alignment of children.
    """
    provider = ForEach(items, builder)
    return cls(
        children=[provider],
        width=width,
        height=height,
        padding=padding,
        gap=gap,
        main_alignment=main_alignment,
        cross_alignment=cross_alignment,
    )

Row

Row(children: Optional[List[Widget]] = None, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0, gap: Union[int, ReadOnlyObservableProtocol] = 0, main_alignment: str = 'start', cross_alignment: str = 'start')

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 Union[int, Tuple[int, int], Tuple[int, int, int, int]]

Padding around the content.

0
gap Union[int, ReadOnlyObservableProtocol]

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'
Source code in src/nuiitivet/layout/row.py
def __init__(
    self,
    children: Optional[List[Widget]] = None,
    *,
    width: SizingLike = None,
    height: SizingLike = None,
    padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0,
    gap: Union[int, ReadOnlyObservableProtocol] = 0,
    main_alignment: str = "start",
    cross_alignment: str = "start",
):
    """Initialize Row and configure layout.

    Args:
        children: List of child widgets to arrange horizontally.
        width: Row width. Defaults to None (shrinkwrap).
        height: Row height. Defaults to None (shrinkwrap).
        padding: Padding around the content.
        gap: Space between children in pixels.
        main_alignment: Horizontal alignment of children.
            'start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'.
        cross_alignment: Vertical alignment of children.
            'start', 'center', 'end', 'stretch'.
    """
    super().__init__(width=width, height=height, padding=padding)
    if children:
        for c in children:
            self.add_child(c)
    self._gap = 0
    self.gap = gap  # type: ignore
    self.main_alignment = main_alignment
    self.cross_alignment = cross_alignment

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') -> 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 Union[int, Tuple[int, int], Tuple[int, int, int, int]]

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'
Source code in src/nuiitivet/layout/row.py
@classmethod
def builder(
    cls,
    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",
) -> "Row":
    """Create a Row that materializes children from items via ForEach.

    Args:
        items: Source data collection.
        builder: Function to create a widget for each item.
        width: Row width.
        height: Row height.
        padding: Padding around the content.
        gap: Space between children.
        main_alignment: Horizontal alignment of children.
        cross_alignment: Vertical alignment of children.
    """
    provider = ForEach(items, builder)
    return cls(
        children=[provider],
        width=width,
        height=height,
        padding=padding,
        gap=gap,
        main_alignment=main_alignment,
        cross_alignment=cross_alignment,
    )

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')

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'
Source code in src/nuiitivet/layout/stack.py
def __init__(
    self,
    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",
) -> None:
    """Initialize the Stack layout.

    Args:
        children: List of widgets to stack on top of each other.
        width: Stack width.
        height: Stack height.
        padding: Padding around the content.
        alignment: Default alignment for children.
            (horizontal, vertical) tuple or string like "top-left", "center".
    """
    super().__init__(width=width, height=height, padding=padding)
    for c in children:
        self.add_child(c)
    self.alignment = normalize_alignment(alignment, default=("start", "start"))

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') -> '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'
Source code in src/nuiitivet/layout/stack.py
@classmethod
def builder(
    cls,
    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",
) -> "Stack":
    """Create a Stack that materializes children from items via ForEach.

    Args:
        items: Source data collection.
        builder: Function to create a widget for each item.
        width: Stack width.
        height: Stack height.
        padding: Padding around the content.
        alignment: Default alignment for children.
    """
    provider = ForEach(items, builder)
    return cls(
        children=[provider],
        width=width,
        height=height,
        padding=padding,
        alignment=alignment,
    )

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')

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'
Source code in src/nuiitivet/layout/container.py
def __init__(
    self,
    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",
):
    """Initialize the Container.

    Args:
        child: The child widget to be placed inside the container.
        width: The preferred width of the container. Defaults to None (shrinkwrap).
        height: The preferred height of the container. Defaults to None (shrinkwrap).
        padding: Padding to apply around the child. Can be a single integer,
            a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).
        alignment: How to align the child within the container. Defaults to "start".
            Can be a string (e.g., "center") or a tuple (horizontal, vertical).
    """
    super().__init__(
        width=width,
        height=height,
        padding=padding,
        max_children=1,
        overflow_policy="replace_last",
    )

    self._align = normalize_alignment(alignment, default=("start", "start"))

    # child management + layout engine
    if child is not None:
        self.add_child(child)
    self._layout = LayoutEngine(self)

add_child

add_child(w: Widget)

Keep at most one child; call ChildContainerMixin directly to bypass overrides.

Source code in src/nuiitivet/layout/container.py
def add_child(self, w: "Widget"):
    """Keep at most one child; call ChildContainerMixin directly to bypass overrides."""
    ChildContainerMixin.add_child(self, w)

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)

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
def __init__(
    self,
    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,
) -> None:
    super().__init__(width=width, height=height, padding=padding)
    if children:
        for child in children:
            self.add_child(child)

    self.main_gap = normalize_gap(main_gap)
    self.cross_gap = normalize_gap(cross_gap)
    self.main_alignment = main_alignment or "start"
    self.run_alignment = run_alignment or "start"
    self.cross_alignment = cross_alignment or "start"

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) -> 'Flow'

Create a Flow that materializes children from items via ForEach.

Source code in src/nuiitivet/layout/flow.py
@classmethod
def builder(
    cls,
    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,
) -> "Flow":
    """Create a Flow that materializes children from items via ForEach."""

    provider = ForEach(items, builder)
    return cls(
        [provider],
        main_gap=main_gap,
        cross_gap=cross_gap,
        padding=padding,
        main_alignment=main_alignment,
        run_alignment=run_alignment,
        cross_alignment=cross_alignment,
        width=width,
        height=height,
    )

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)

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
def __init__(
    self,
    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,
) -> None:
    super().__init__(width=width, height=height, padding=padding)
    if children:
        for child in children:
            self.add_child(child)

    self.columns = self._normalize_positive(columns)
    self.max_column_width = self._normalize_positive(max_column_width)
    self.aspect_ratio = float(aspect_ratio) if aspect_ratio else None
    self.main_gap = normalize_gap(main_gap)
    self.cross_gap = normalize_gap(cross_gap)
    self.main_alignment = main_alignment or "start"
    self.run_alignment = run_alignment or "start"
    self.item_alignment = self._normalize_align_pair(item_alignment)

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) -> 'UniformFlow'

Create a UniformFlow that materializes children from items via ForEach.

Source code in src/nuiitivet/layout/uniform_flow.py
@classmethod
def builder(
    cls,
    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,
) -> "UniformFlow":
    """Create a UniformFlow that materializes children from items via ForEach."""
    provider = ForEach(items, builder)
    return cls(
        [provider],
        columns=columns,
        max_column_width=max_column_width,
        aspect_ratio=aspect_ratio,
        main_gap=main_gap,
        cross_gap=cross_gap,
        padding=padding,
        main_alignment=main_alignment,
        run_alignment=run_alignment,
        item_alignment=item_alignment,
        width=width,
        height=height,
    )

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)

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]]

List of row sizes (e.g. [100, "1fr", "auto"]).

required
columns Optional[Sequence[SizingLike]]

List of column sizes.

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
Source code in src/nuiitivet/layout/grid.py
def __init__(
    self,
    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,
):
    """Initialize the Grid layout.

    Args:
        children: List of GridItems to display.
        rows: List of row sizes (e.g. [100, "1fr", "auto"]).
        columns: List of column sizes.
        width: Grid container width.
        height: Grid container height.
        padding: Padding around the grid content.
        row_gap: Vertical gap between rows.
        column_gap: Horizontal gap between columns.
    """
    super().__init__(width=width, height=height, padding=padding)

    self._rows: List[Sizing] = [parse_sizing(d) for d in rows] if rows else []
    self._columns: List[Sizing] = [parse_sizing(d) for d in columns] if columns else []
    self.areas: Optional[List[List[str]]] = None
    self.row_gap = normalize_gap(row_gap)
    self.column_gap = normalize_gap(column_gap)

    if children:
        for child in children:
            self.add_child(child)

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
@classmethod
def named_areas(
    cls,
    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.

    Args:
        children: List of child widgets (usually GridItem.named_area).
        areas: 2D list of area names (e.g. [["header", "header"], ["sidebar", "content"]]).
        width: Grid width.
        height: Grid height.
        padding: Grid padding.
        row_gap: Gap between rows.
        column_gap: Gap between columns.
        rows: Explicit row sizing overrides.
        columns: Explicit column sizing overrides.

    Returns:
        Grid: A configured Grid instance.
    """
    grid = cls(
        children=children,
        width=width,
        height=height,
        padding=padding,
        row_gap=row_gap,
        column_gap=column_gap,
        rows=rows,
        columns=columns,
    )
    grid.areas = grid._normalize_areas(areas)
    return grid

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')

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'
Source code in src/nuiitivet/layout/grid.py
def __init__(
    self,
    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",
):
    """Initialize the GridItem wrapper.

    Args:
        child: The widget to place in the grid.
        row: Row index or (start, end) tuple. 0-based.
        column: Column index or (start, end) tuple. 0-based.
        width: Override width.
        height: Override height.
        padding: Padding around the child within the grid cell.
        alignment: Alignment within the grid cell. Defaults to "start".
    """
    super().__init__(
        child=child,
        width=width,
        height=height,
        padding=padding,
        alignment=alignment,
    )
    self._row_spec = row
    self._column_spec = column
    self.area: Optional[str] = None

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
@classmethod
def named_area(
    cls,
    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.

    Args:
        child: The child widget to place.
        name: The area name matching a name in Grid.named_areas().
        width: Override width.
        height: Override height.
        padding: Padding around the child within the grid cell.
        alignment: Alignment within the grid cell. Defaults to "start".

    Returns:
        GridItem: The wrapped widget with area information.
    """
    item = cls(
        child=child,
        width=width,
        height=height,
        padding=padding,
        alignment=alignment,
    )
    item.area = name
    return item

Spacer

Spacer(*, width: SizingLike = 0, height: SizingLike = 0)

Bases: Widget

Invisible widget that reserves space.

This single Spacer supports both fixed-size and flexible behavior.

Parameters:

Name Type Description Default
width SizingLike

preferred width (int, "auto", "{f}%", 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.flex() or 0 for flexible space.

0
height SizingLike

Preferred height. Use Sizing.flex() or 0 for flexible space.

0
Source code in src/nuiitivet/layout/spacer.py
def __init__(self, *, width: SizingLike = 0, height: SizingLike = 0):
    """Initialize a Spacer.

    Args:
        width: Preferred width. Use Sizing.flex() or 0 for flexible space.
        height: Preferred height. Use Sizing.flex() or 0 for flexible space.
    """
    super().__init__(width=width, height=height)

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/flex: return 0 (minimum size, parent will allocate)
Source code in src/nuiitivet/layout/spacer.py
def preferred_size(self, 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/flex: return 0 (minimum size, parent will allocate)
    """
    w_dim = self.width_sizing
    h_dim = self.height_sizing

    pref_w = int(w_dim.value) if w_dim.kind == "fixed" else 0
    pref_h = int(h_dim.value) if h_dim.kind == "fixed" else 0

    _ = (max_width, max_height)

    return (pref_w, pref_h)

CrossAligned

CrossAligned(child: Optional[Widget], alignment: str)

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
Source code in src/nuiitivet/layout/cross_aligned.py
def __init__(
    self,
    child: Optional[Widget],
    alignment: str,
) -> None:
    """Initialize the CrossAligned wrapper.

    Args:
        child: The child widget to wrap.
        alignment: The cross-axis alignment to apply to this child.
            Common values: "start", "center", "end", "stretch".
    """
    super().__init__(
        width=child.width_sizing if child is not None else None,
        height=child.height_sizing if child is not None else None,
        padding=0,
        max_children=1,
        overflow_policy="replace_last",
    )
    self.cross_align = str(alignment)
    if child is not None:
        self.add_child(child)

Deck

Deck(children: Optional[Sequence[Widget]] = None, index: Union[int, _ObservableValue[int]] = 0, *, width: SizingLike = None, height: SizingLike = None, padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0)

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)

For animated tabs with gesture support, see DeckController.

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, _ObservableValue[int]]

The index of the child to display. Can be an integer or an Observable[int]. Defaults to 0.

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
Source code in src/nuiitivet/layout/deck.py
def __init__(
    self,
    children: Optional[Sequence[Widget]] = None,
    index: Union[int, _ObservableValue[int]] = 0,
    *,
    width: SizingLike = None,
    height: SizingLike = None,
    padding: Union[int, Tuple[int, int], Tuple[int, int, int, int]] = 0,
) -> None:
    """Initialize the Deck layout.

    Args:
        children: A list of child widgets. All are mounted, but only one is visible.
        index: The index of the child to display. Can be an integer or an Observable[int].
            Defaults to 0.
        width: The preferred width of the container. Defaults to None.
        height: The preferred height of the container. Defaults to None.
        padding: Padding to apply around the visible child. Defaults to 0.
    """
    super().__init__(width=width, height=height, padding=padding)

    # Add all children
    if children:
        for child in children:
            self.add_child(child)

    # Handle index (Observable or plain int)
    self._index_observable: Optional[_ObservableValue[int]] = None
    self._index_subscription = None
    if isinstance(index, _ObservableValue):
        self._index_observable = index
        self._current_index = index.value
        # Subscribe to changes
        self._index_subscription = index.subscribe(self._on_index_changed)
    else:
        self._current_index = int(index)

    # Validate initial index
    self._validate_index()

current_index property

current_index: int

Get the currently selected child index.

set_index

set_index(index: int) -> None

Set the selected child index (for non-Observable usage).

Source code in src/nuiitivet/layout/deck.py
def set_index(self, index: int) -> None:
    """Set the selected child index (for non-Observable usage)."""
    if self._index_observable is not None:
        raise ValueError("Cannot set_index when using Observable index")
    old_index = self._current_index
    self._current_index = index
    self._validate_index()
    if old_index != self._current_index:
        self.mark_needs_layout()

layout

layout(width: int, height: int) -> None

Layout all children (to preserve state), position selected child.

Source code in src/nuiitivet/layout/deck.py
def layout(self, width: int, height: int) -> None:
    """Layout all children (to preserve state), position selected child."""
    super().layout(width, height)
    children = expand_layout_children(self.children_snapshot())
    if not children:
        return

    pad_left, pad_top, pad_right, pad_bottom = self.padding
    available_w = max(0, width - pad_left - pad_right)
    available_h = max(0, height - pad_top - pad_bottom)

    # Layout ALL children so they maintain state
    for child in children:
        child.layout(available_w, available_h)

    # Position selected child (others will not be painted)
    if 0 <= self._current_index < len(children):
        selected_child = children[self._current_index]
        selected_child.set_layout_rect(pad_left, pad_top, available_w, available_h)

paint

paint(canvas, x: int, y: int, width: int, height: int) -> None

Paint only the selected child.

Source code in src/nuiitivet/layout/deck.py
def paint(self, canvas, x: int, y: int, width: int, height: int) -> None:
    """Paint only the selected child."""
    children = expand_layout_children(self.children_snapshot())
    if not children or self._current_index >= len(children):
        return

    # Auto-layout fallback for Tests or direct paint calls
    if any(c.layout_rect is None for c in children):
        self.layout(width, height)

    # Only paint the selected child
    selected_child = children[self._current_index]
    rect = selected_child.layout_rect
    if rect is None:
        return

    rel_x, rel_y, w, h = rect
    abs_x = x + rel_x
    abs_y = y + rel_y

    selected_child.set_last_rect(abs_x, abs_y, w, h)
    selected_child.paint(canvas, abs_x, abs_y, w, h)

hit_test

hit_test(x: int, y: int) -> bool

Only allow hit testing on the selected child.

Source code in src/nuiitivet/layout/deck.py
def hit_test(self, x: int, y: int) -> bool:
    """Only allow hit testing on the selected child."""
    children = expand_layout_children(self.children_snapshot())
    if not children or self._current_index >= len(children):
        return False

    # Only the selected child should participate in hit testing
    selected_child = children[self._current_index]
    return selected_child.hit_test(x, y)

dispose

dispose() -> None

Clean up subscriptions.

Source code in src/nuiitivet/layout/deck.py
def dispose(self) -> None:
    """Clean up subscriptions."""
    if self._index_subscription is not None:
        self._index_subscription.dispose()
        self._index_subscription = None

Collapsible

Collapsible(child: Optional[Widget] = None, *, opened: Union[bool, ReadOnlyObservableProtocol[bool]] = True, motion: Motion = _DEFAULT_MOTION, motion_out: Optional[Motion] = None, axis: Axis = 'both', alignment: Union[str, Tuple[str, str]] = 'top_left')

Bases: 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.

Initialize a Collapsible.

Parameters:

Name Type Description Default
child Optional[Widget]

The Widget whose layout size is animated.

None
opened Union[bool, ReadOnlyObservableProtocol[bool]]

bool / Observable[bool]. When False the child collapses to zero size along the animated axes; when True it expands to the child's natural size.

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, motion is used for both.

None
axis Axis

Which axis/axes to animate ("both", "horizontal", "vertical"). Axes that are not animated pass the child's natural size through unchanged.

'both'
alignment Union[str, Tuple[str, str]]

Alignment of the child within the animated rectangle.

'top_left'
Source code in src/nuiitivet/layout/collapsible.py
def __init__(
    self,
    child: Optional[Widget] = None,
    *,
    opened: Union[bool, ReadOnlyObservableProtocol[bool]] = True,
    motion: Motion = _DEFAULT_MOTION,
    motion_out: Optional[Motion] = None,
    axis: Axis = "both",
    alignment: Union[str, Tuple[str, str]] = "top_left",
) -> None:
    """Initialize a Collapsible.

    Args:
        child: The ``Widget`` whose layout size is animated.
        opened: ``bool`` / ``Observable[bool]``. When ``False`` the child
            collapses to zero size along the animated axes; when ``True``
            it expands to the child's natural size.
        motion: Base motion used for both open (enter) and close (exit).
        motion_out: Optional motion that overrides only the close (exit)
            direction. When omitted, ``motion`` is used for both.
        axis: Which axis/axes to animate (``"both"``, ``"horizontal"``,
            ``"vertical"``). Axes that are not animated pass the child's
            natural size through unchanged.
        alignment: Alignment of the child within the animated rectangle.
    """
    super().__init__(max_children=1, overflow_policy="replace_last")
    self._opened: Union[bool, ReadOnlyObservableProtocol[bool]] = opened
    self._motion_in = motion
    self._motion_out = motion_out if motion_out is not None else motion
    self._axis: Axis = axis
    self._align = normalize_alignment(alignment, default=("start", "start"))

    self._width_anim: Animatable[float] = Animatable(0.0, motion=self._motion_in)
    self._height_anim: Animatable[float] = Animatable(0.0, motion=self._motion_in)
    self._width_sub: Optional["Disposable"] = None
    self._height_sub: Optional["Disposable"] = None
    self._initialized = False

    if child is not None:
        self.add_child(child)

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)

Bases: _ScrollableBase

Scrolls its child along the vertical axis.

Source code in src/nuiitivet/layout/scrollable.py
def __init__(
    self,
    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,
) -> None:
    """Initialize the scrollable.

    The three configuration concerns are kept separate: ``scrollbar_style``
    owns the bar's appearance, ``style`` owns placement (viewport padding,
    bar offset, overlay vs. inline), and ``scrollbar_behavior`` owns
    temporal behavior (auto-hide, track clicks…).

    Args:
        child: The widget to make scrollable.
        controller: External :class:`ScrollController` (auto-created when
            omitted). ``physics`` and ``scroll_multiplier`` are read from it.
        scrollbar_visible: Whether the scrollbar is shown. Accepts a ``bool``
            or an ``Observable[bool]`` for reactive visibility.
        width: Width sizing override.
        height: Height sizing override.
        scrollbar_behavior: Scrollbar interaction behavior (auto-hide,
            track clicks…).
        scrollbar_style: Scrollbar appearance (thickness, min thumb length).
        style: Placement (viewport padding, scrollbar padding, overlay).
    """
    super().__init__(width=width, height=height)

    if child is None:
        raise ValueError("Scrollable requires a child widget")

    self._child = child
    self.direction = self._direction
    self._apply_axis_sizing_defaults()

    if controller is None:
        self._controller = ScrollController(axes=(self.direction,), primary_axis=self.direction)
        self._owns_controller = True
    else:
        if not controller.has_axis(self.direction):
            raise ValueError(f"ScrollController does not support required axis {self.direction}")
        self._controller = controller
        self._owns_controller = False

    # Scroll-engine configuration is owned by the controller.
    self.physics = self._controller.physics
    self.scroll_multiplier = self._controller.scroll_multiplier

    self._scrollbar_behavior = scrollbar_behavior or ScrollbarBehavior()
    self._scrollbar_style = scrollbar_style or ScrollbarStyle()
    self._scrollable_style = style or ScrollableStyle()
    #: Bar offset from the viewport edge, parsed to (left, top, right, bottom).
    self._scrollbar_padding = parse_padding(self._scrollable_style.scrollbar_padding)
    self._scrollbar_visible: ScrollbarVisibleLike = scrollbar_visible

    self._viewport = ScrollViewport(
        child=child,
        controller=self._controller,
        direction=self.direction,
        padding=parse_padding(self._scrollable_style.viewport_padding),
    )
    self.add_child(self._viewport)

    self._scrollbar = self._scrollbar_class(
        self._controller,
        behavior=self._scrollbar_behavior,
        style=self._scrollbar_style,
    )
    self.add_child(self._scrollbar)

    # Allow the scrollbar to coordinate with this container (e.g. cancel an
    # active content drag when the user begins dragging the thumb).
    try:
        setattr(self._scrollbar, "_scroll_container", self)
    except Exception:
        exception_once(logger, "scrollable_set_scroll_container_exc", "Failed to set scrollbar._scroll_container")

    # Drag-scroll state
    self._is_dragging = False
    self._drag_start_pos = 0.0
    self._drag_start_offset = 0.0
    self._content_pointer_id: Optional[int] = None

    # Scrollbar regions (for hit-testing)
    self._scrollbar_rect: Optional[Tuple[int, int, int, int]] = None
    self._scrollbar_thumb_rect: Optional[Tuple[int, int, int, int]] = None

    # Listener disposal handle (may be a Disposable or a callable)
    self._scroll_unsubscribe: Optional[object] = None

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)

Bases: _ScrollableBase

Scrolls its child along the horizontal axis.

Source code in src/nuiitivet/layout/scrollable.py
def __init__(
    self,
    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,
) -> None:
    """Initialize the scrollable.

    The three configuration concerns are kept separate: ``scrollbar_style``
    owns the bar's appearance, ``style`` owns placement (viewport padding,
    bar offset, overlay vs. inline), and ``scrollbar_behavior`` owns
    temporal behavior (auto-hide, track clicks…).

    Args:
        child: The widget to make scrollable.
        controller: External :class:`ScrollController` (auto-created when
            omitted). ``physics`` and ``scroll_multiplier`` are read from it.
        scrollbar_visible: Whether the scrollbar is shown. Accepts a ``bool``
            or an ``Observable[bool]`` for reactive visibility.
        width: Width sizing override.
        height: Height sizing override.
        scrollbar_behavior: Scrollbar interaction behavior (auto-hide,
            track clicks…).
        scrollbar_style: Scrollbar appearance (thickness, min thumb length).
        style: Placement (viewport padding, scrollbar padding, overlay).
    """
    super().__init__(width=width, height=height)

    if child is None:
        raise ValueError("Scrollable requires a child widget")

    self._child = child
    self.direction = self._direction
    self._apply_axis_sizing_defaults()

    if controller is None:
        self._controller = ScrollController(axes=(self.direction,), primary_axis=self.direction)
        self._owns_controller = True
    else:
        if not controller.has_axis(self.direction):
            raise ValueError(f"ScrollController does not support required axis {self.direction}")
        self._controller = controller
        self._owns_controller = False

    # Scroll-engine configuration is owned by the controller.
    self.physics = self._controller.physics
    self.scroll_multiplier = self._controller.scroll_multiplier

    self._scrollbar_behavior = scrollbar_behavior or ScrollbarBehavior()
    self._scrollbar_style = scrollbar_style or ScrollbarStyle()
    self._scrollable_style = style or ScrollableStyle()
    #: Bar offset from the viewport edge, parsed to (left, top, right, bottom).
    self._scrollbar_padding = parse_padding(self._scrollable_style.scrollbar_padding)
    self._scrollbar_visible: ScrollbarVisibleLike = scrollbar_visible

    self._viewport = ScrollViewport(
        child=child,
        controller=self._controller,
        direction=self.direction,
        padding=parse_padding(self._scrollable_style.viewport_padding),
    )
    self.add_child(self._viewport)

    self._scrollbar = self._scrollbar_class(
        self._controller,
        behavior=self._scrollbar_behavior,
        style=self._scrollbar_style,
    )
    self.add_child(self._scrollbar)

    # Allow the scrollbar to coordinate with this container (e.g. cancel an
    # active content drag when the user begins dragging the thumb).
    try:
        setattr(self._scrollbar, "_scroll_container", self)
    except Exception:
        exception_once(logger, "scrollable_set_scroll_container_exc", "Failed to set scrollbar._scroll_container")

    # Drag-scroll state
    self._is_dragging = False
    self._drag_start_pos = 0.0
    self._drag_start_offset = 0.0
    self._content_pointer_id: Optional[int] = None

    # Scrollbar regions (for hit-testing)
    self._scrollbar_rect: Optional[Tuple[int, int, int, int]] = None
    self._scrollbar_thumb_rect: Optional[Tuple[int, int, int, int]] = None

    # Listener disposal handle (may be a Disposable or a callable)
    self._scroll_unsubscribe: Optional[object] = None

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')

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
def __init__(
    self,
    *,
    width: Union[SizingLike, ReadOnlyObservableProtocol] = None,
    height: Union[SizingLike, ReadOnlyObservableProtocol] = None,
    padding: Union[PaddingLike, ReadOnlyObservableProtocol] = None,
    max_children: Optional[int] = None,
    overflow_policy: str = "none",
) -> None:
    self._layout_cache_token = 0
    self._needs_layout = True
    super().__init__(
        width=width,
        height=height,
        padding=padding,
        max_children=max_children,
        overflow_policy=overflow_policy,
    )

mark_needs_layout

mark_needs_layout() -> None

Mark this widget as needing layout recalculation.

Source code in src/nuiitivet/widgeting/widget.py
def mark_needs_layout(self) -> None:
    """Mark this widget as needing layout recalculation."""
    already_dirty = self._needs_layout
    self._needs_layout = True
    parent = getattr(self, "_parent", None)
    if isinstance(parent, Widget):
        # Always propagate to root.  An early-return guard ("if already
        # dirty, skip") would be valid only if the invariant "every dirty
        # node's ancestors are also dirty" were globally maintained.
        # However, clear_needs_layout() is called only on AppScope (the
        # root), leaving intermediate nodes dirty.  When the next animation
        # tick fires, a selective guard would stop propagation at the first
        # already-dirty intermediate, never reaching the cleared root.
        # Walking all the way to the root on every call is O(tree-depth)
        # – the same cost as the original code when no ancestor was dirty.
        try:
            parent.mark_needs_layout()
        except Exception:
            exception_once(
                logger,
                f"widget_mark_needs_layout_parent_exc:{type(parent).__name__}",
                "Widget.mark_needs_layout() failed for parent=%s",
                type(parent).__name__,
            )
    if not already_dirty:
        self.invalidate()

find_ancestor

find_ancestor(widget_type: Type[T]) -> Optional[T]

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
def find_ancestor(self, widget_type: Type[T]) -> Optional[T]:
    """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).

    Args:
        widget_type: The type of widget to find.

    Returns:
        The nearest ancestor of the specified type, or None if not found.

    Example:
        navigator = self.find_ancestor(Navigator)
        if navigator:
            navigator.push(...)
    """
    current = self._parent
    while current is not None:
        if isinstance(current, widget_type):
            return current  # type: ignore
        current = getattr(current, "_parent", None)
    return None

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
def modifier(self, modifier: Union[Modifier, ModifierElement]) -> Widget:
    """Apply a modifier to this widget, returning the wrapped result.

    Args:
        modifier: The modifier (or modifier element) to apply.

    Returns:
        The wrapped widget (e.g. ModifierBox) or the widget itself if modified in-place.
    """
    return modifier.apply(self)

ComposableWidget

ComposableWidget(*args, **kwargs)

Bases: BuilderHostMixin, Widget

Widget base that participates in build/recomposition.

Composition widgets can override build() and use scope() / render_scope() for fine-grained recomposition.

Source code in src/nuiitivet/widgeting/widget_builder.py
def __init__(self, *args, **kwargs) -> None:  # type: ignore[override]
    super().__init__(*args, **kwargs)
    self._built = None
    self._scope_root: Optional[RecomposeScope] = None
    self._build_ctx: Optional[BuildScopeContext] = None
    self._scope_registry: Dict[str, RecomposeScope] = {}
    self._scope_nodes: Dict[str, Any] = {}
    self._active_scope_ids: Set[str] = set()
    self._scope_metadata: Dict[str, ScopeMetadata] = {}
    self._dependency_scope_index: Dict[str, Set[str]] = {}
    # Scopes whose inputs were invalidated and must be rebuilt on the next
    # render. A scope rebuilds only when it is new, unbuilt, or dirty — not
    # whenever the host's build() re-runs (idempotent recomposition).
    self._dirty_scopes: Set[str] = set()

Navigator

Navigator(screen: Route | Widget | None = None, *, layer_composer: NavigationLayerComposer | None = None)

Bases: ComposableWidget

A minimal navigation stack.

Initialization forms
  • Navigator(screen): start with a single screen (Route or Widget).
  • Navigator.routes([...]): pre-populated stack (e.g. deep linking).
  • Navigator.intents(initial_route=..., routes={...}): Intent-based routing.
Features
  • push/pop
  • root()/set_root()
  • of(context)
  • 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 Route or Widget. If None, the navigator starts with an empty stack (use :meth:routes or :meth:intents factories for alternative initialization).

None
layer_composer NavigationLayerComposer | None

Optional custom layer composer.

None
Source code in src/nuiitivet/navigation/navigator.py
def __init__(
    self,
    screen: Route | Widget | None = None,
    *,
    layer_composer: NavigationLayerComposer | None = None,
) -> None:
    """Initialize a Navigator with a single initial screen.

    Args:
        screen: The initial screen as a ``Route`` or ``Widget``. If ``None``,
            the navigator starts with an empty stack (use :meth:`routes` or
            :meth:`intents` factories for alternative initialization).
        layer_composer: Optional custom layer composer.
    """
    super().__init__()
    self._intent_routes: Mapping[type[Any], Callable[[Any], Route | Widget]] = {}
    self._transition: _NavTransition | None = None
    self._transition_handle: TransitionHandle | None = None
    self._transition_engine = TransitionEngine()
    self._pending_pop_requests: int = 0
    self._exiting_route: Route | None = None
    self._layer_composer: NavigationLayerComposer = layer_composer or _DefaultNavigationLayerComposer()

    initial_routes: list[Route] = []
    if screen is not None:
        initial_routes.append(self._to_initial_route(screen))
    self._stack = RouteStackRuntime(initial_routes=initial_routes)

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 Route or Widget instances. The last item becomes the top of the stack.

required
layer_composer NavigationLayerComposer | None

Optional custom layer composer.

None
Source code in src/nuiitivet/navigation/navigator.py
@classmethod
def routes(
    cls,
    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).

    Args:
        screens: Sequence of ``Route`` or ``Widget`` instances. The last item
            becomes the top of the stack.
        layer_composer: Optional custom layer composer.
    """
    if not screens:
        raise ValueError("Navigator.routes(...) requires at least one screen")
    instance = cls(layer_composer=layer_composer)
    initial_routes = [instance._to_initial_route(s) for s in screens]
    instance._stack = RouteStackRuntime(initial_routes=initial_routes)
    return instance

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 Route or Widget.

required
layer_composer NavigationLayerComposer | None

Optional custom layer composer.

None
Source code in src/nuiitivet/navigation/navigator.py
@classmethod
def intents(
    cls,
    *,
    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.

    Args:
        initial_route: The initial Intent instance used to resolve the first route.
        routes: Mapping of Intent types to route builder functions. Each
            builder returns a ``Route`` or ``Widget``.
        layer_composer: Optional custom layer composer.
    """
    instance = cls(layer_composer=layer_composer)
    instance._intent_routes = dict(routes)
    initial = instance._resolve_intent_to_route(initial_route)
    instance._stack = RouteStackRuntime(initial_routes=[initial])
    return instance

request_back async

request_back() -> bool

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
async def request_back(self) -> bool:
    """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.
    """

    if not self.can_pop():
        return False

    transition = self._transition
    handle = self._transition_handle

    if transition is not None and handle is not None and transition.kind == "pop":
        self._pending_pop_requests += 1
        self._force_finish_pop_transition()
        return True

    if transition is not None and handle is not None and transition.kind == "push":
        # Finish push quickly, then pop once.
        self._force_finish_push_transition()

    did_pop = await self._pop_once(skip_animation=False)
    if not did_pop:
        # will_pop canceled; treat as handled.
        return True
    return True

Observable

Observable(default: T, *, compare: Optional[CompareFunc[T]] = None)

Bases: _ObservableValue[T]

Descriptor for a per-instance observable that can also be used standalone.

Source code in src/nuiitivet/observable/value.py
def __init__(self, default: T, *, compare: Optional[CompareFunc[T]] = None):
    super().__init__(initial=default, owner=None, name=None, compare=compare)
    self.default = default
    self.name: Optional[str] = None
    self.compare = compare

OSChrome dataclass

OSChrome(variant: OSChromeVariant = 'default')

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(color: 'ColorSpec', width: float = 1.0)

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.

batch

batch() -> BatchContext

Context manager for batching Observable updates.

Source code in src/nuiitivet/observable/batching.py
def batch() -> BatchContext:
    """Context manager for batching Observable updates."""
    current: Optional[Any] = _batch_context.get()
    if current is not None:
        return current
    return BatchContext()

set_default_font_family

set_default_font_family(family_name: Optional[str]) -> None

Set the system-wide default font family.

This font will be prioritized over locale-based defaults. Pass None to reset to automatic locale detection.

Source code in src/nuiitivet/rendering/skia/font.py
def set_default_font_family(family_name: Optional[str]) -> None:
    """Set the system-wide default font family.

    This font will be prioritized over locale-based defaults.
    Pass None to reset to automatic locale detection.
    """
    global _USER_DEFAULT_FONT_FAMILY
    _USER_DEFAULT_FONT_FAMILY = family_name

register_font

register_font(path: str, family_name: str) -> None

Register a font file under a custom family name.

Call this at application startup before any rendering. Once registered, the family name can be used wherever a font_family is accepted (e.g. TextStyle(font_family=...), Icon(..., font_family=...)).

Fonts are loaded lazily on first use and cached for subsequent calls.

Parameters:

Name Type Description Default
path str

Absolute or relative path to a .ttf or .otf font file.

required
family_name str

The name to associate with this font.

required
Source code in src/nuiitivet/rendering/skia/font.py
def register_font(path: str, family_name: str) -> None:
    """Register a font file under a custom family name.

    Call this at application startup before any rendering. Once registered,
    the family name can be used wherever a ``font_family`` is accepted (e.g.
    ``TextStyle(font_family=...)``, ``Icon(..., font_family=...)``).

    Fonts are loaded lazily on first use and cached for subsequent calls.

    Args:
        path: Absolute or relative path to a ``.ttf`` or ``.otf`` font file.
        family_name: The name to associate with this font.
    """
    _FONT_REGISTRY[family_name] = path