Skip to content

xnano.components.loader

xnano.components.loader

xnano.components.loader


Show determinate progress or an indeterminate spinner.

Classes:

  • Loader

    Determinate progress indicator or indeterminate spinner.

Functions:

Attributes:

LoaderStyle module-attribute

LoaderStyle: TypeAlias = Literal['spinner', 'bar', 'line']

Visual style for a Loader component.

LoaderSymbolPreset module-attribute

LoaderSymbolPreset: TypeAlias = Literal[
    "dots", "line", "grow", "circle", "bounce", "arc"
]

Named spinner frame presets.

LoaderSymbols module-attribute

LoaderSymbols: TypeAlias = (
    Sequence[str] | LoaderSymbolPreset
)

Explicit spinner frames or a named preset.

LoaderLabel module-attribute

LoaderLabel: TypeAlias = (
    "str | Text | Literal[False] | None"
)

Label text, nested Text, auto percentage, or hidden.

Loader dataclass

Loader(
    value: float | None = None,
    total: float | None = None,
    style: LoaderStyle = "spinner",
    label: LoaderLabel = None,
    symbols: LoaderSymbols = "dots",
    interval: int = 80,
    foreground: "ColorLike" = "green",
    background: "ColorLike | None" = None,
    filled_color: "ColorLike | None" = None,
    unfilled_color: "ColorLike | None" = None,
    running: bool = True,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = False
)

Bases: Component

Determinate progress indicator or indeterminate spinner.

Leave value as None to show a spinner. Set value to a ratio, or pair it with total, to show measurable progress.

Example

Loader(value=42, total=100, label=True)

Attributes:

  • value (float | None) –

    Current amount, ratio, or None for indeterminate.

  • total (float | None) –

    When set, ratio is value / total.

  • style (LoaderStyle) –

    "spinner", "bar", or "line".

  • label (LoaderLabel) –

    Overlay text, auto percentage, nested Text, or hidden.

  • symbols (LoaderSymbols) –

    Spinner frames or a named preset.

  • interval (int) –

    Milliseconds between spinner frames.

  • foreground ('ColorLike') –

    Primary foreground / filled color (deprecated alias: color).

  • background ('ColorLike | None') –

    Widget background color.

  • filled_color ('ColorLike | None') –

    Line style filled-portion color.

  • unfilled_color ('ColorLike | None') –

    Line style unfilled-portion color.

  • running (bool) –

    Whether spinner animation advances.

Methods:

  • component_post_init

    Resolve spinner frames and start the animation epoch.

  • restart

    Reset the spinner epoch to the current monotonic time.

  • current_frame

    Return the active spinner glyph, advanced by the framework clock.

  • inline_text

    Return the spinner glyph plus its label for inline embedding.

  • compose

    Compose spinner text or gauge content for this loader.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

value class-attribute instance-attribute

value: float | None = None

Current amount, ratio, or None for indeterminate work.

total class-attribute instance-attribute

total: float | None = None

When set, the ratio is value / total.

style class-attribute instance-attribute

style: LoaderStyle = 'spinner'

"spinner", block "bar", or thin "line" gauge.

label class-attribute instance-attribute

label: LoaderLabel = None

Overlay text. None auto-derives a percentage when determinate.

symbols class-attribute instance-attribute

symbols: LoaderSymbols = 'dots'

Spinner frames or a named preset.

interval class-attribute instance-attribute

interval: int = 80

Milliseconds between spinner frames.

foreground class-attribute instance-attribute

foreground: 'ColorLike' = 'green'

Primary foreground / filled color (deprecated alias: color).

background class-attribute instance-attribute

background: 'ColorLike | None' = None

Widget background color.

filled_color class-attribute instance-attribute

filled_color: 'ColorLike | None' = None

Line style filled-portion color (defaults to foreground).

unfilled_color class-attribute instance-attribute

unfilled_color: 'ColorLike | None' = None

Line style unfilled-portion color.

running class-attribute instance-attribute

running: bool = True

Whether spinner animation advances with wall time.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=False, kw_only=True
)

Whether layout should use the loader's natural size.

resolved_symbols property

resolved_symbols: tuple[str, ...]

Resolved spinner frame ladder.

ratio property

ratio: float

Return the completion ratio, clamped to 0.01.0.

Indeterminate loaders report 0.0.

finished property

finished: bool

Whether determinate progress has reached completion.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

focused property

focused: bool

Whether this component currently holds field focus.

component_post_init

component_post_init() -> None

Resolve spinner frames and start the animation epoch.

Source code in xnano/components/loader.py
def component_post_init(self) -> None:
    """Resolve spinner frames and start the animation epoch."""
    if self.style not in ("spinner", "bar", "line"):
        raise ValueError(f"Unsupported loader style: {self.style!r}")
    if self.interval <= 0:
        raise ValueError("Loader interval must be greater than zero.")
    self._resolved_symbols = resolve_loader_symbols(self.symbols)
    self._epoch_ns = time.monotonic_ns()
    self._frozen_frame = 0

restart

restart() -> None

Reset the spinner epoch to the current monotonic time.

Source code in xnano/components/loader.py
def restart(self) -> None:
    """Reset the spinner epoch to the current monotonic time."""
    self._epoch_ns = time.monotonic_ns()
    self._frozen_frame = 0

current_frame

current_frame() -> str

Return the active spinner glyph, advanced by the framework clock.

The inline path: embed this in any Text/flow content to animate a spinner mid-line without a manual per-tick frame counter — e.g. Text(content=f"{loader.current_frame()} generating…").

Source code in xnano/components/loader.py
def current_frame(self) -> str:
    """Return the active spinner glyph, advanced by the framework clock.

    The inline path: embed this in any ``Text``/flow content to animate a
    spinner mid-line without a manual per-tick frame counter — e.g.
    ``Text(content=f"{loader.current_frame()} generating…")``.
    """
    return self._resolved_symbols[self._spinner_frame_index()]

inline_text

inline_text() -> str

Return the spinner glyph plus its label for inline embedding.

Source code in xnano/components/loader.py
def inline_text(self) -> str:
    """Return the spinner glyph plus its label for inline embedding."""
    label = self._resolve_label_text()
    frame = self.current_frame()
    return frame if not label else f"{frame} {label}"

compose

compose(ctx: 'ComponentRenderContext')

Compose spinner text or gauge content for this loader.

Returns:

  • Interface-neutral content for this loader.

Source code in xnano/components/loader.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose spinner text or gauge content for this loader.

    Returns:
        Interface-neutral content for this loader.
    """
    if self.style == "spinner":
        return self._compose_spinner()
    if self.value is None:
        # Indeterminate bar/line: pulse the active spinner frame as
        # the gauge label while progress stays at zero.
        frame = self._resolved_symbols[self._spinner_frame_index()]
        label = self._resolve_label_text()
        pulse_label = frame if label is None else f"{frame} {label}"
        if self.style == "line":
            return LineGauge(
                progress=0.0,
                label=pulse_label,
                foreground=self.foreground,
                filled_color=self.filled_color or self.foreground,
                unfilled_color=self.unfilled_color,
                background=self.background,
                z=self.z,
                visible=self.visible,
            )
        return Gauge(
            progress=0.0,
            label=pulse_label,
            foreground=self.foreground,
            background=self.background,
            z=self.z,
            visible=self.visible,
        )
    return self._compose_determinate()

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

resolve_loader_symbols

resolve_loader_symbols(
    symbols: LoaderSymbols,
) -> tuple[str, ...]

Resolve a preset name or frame sequence into spinner frames.

Parameters:

  • symbols (LoaderSymbols) –

    Named preset or ordered frame strings.

Returns:

  • tuple[str, ...]

    A non-empty tuple of frame strings.

Raises:

Source code in xnano/components/loader.py
def resolve_loader_symbols(symbols: LoaderSymbols) -> tuple[str, ...]:
    """Resolve a preset name or frame sequence into spinner frames.

    Args:
        symbols: Named preset or ordered frame strings.

    Returns:
        A non-empty tuple of frame strings.

    Raises:
        ValueError: When no frames are provided.
    """
    if isinstance(symbols, str):
        if symbols in _SYMBOL_PRESETS:
            return _SYMBOL_PRESETS[symbols]
        raise ValueError(f"Unknown loader symbol preset: {symbols!r}")
    frames = tuple(str(frame) for frame in symbols)
    if not frames:
        raise ValueError("Loader symbols require at least one frame.")
    return frames