Skip to content

xnano.components

xnano.components

xnano.components

Components: functions and objects that draw or play themselves, and run again only when something they read changes.

Classes:

Functions:

  • renderable –

    Turn a function into a component that draws itself.

  • playable –

    Turn a function into a component that plays itself.

ComponentContext dataclass

ComponentContext(component: RenderableComponent[DepsT, StateT] | PlayableComponent[DepsT, StateT], placement: Placement[DepsT, StateT] | None, arguments: Mapping[str, object])

Bases: Generic[DepsT, StateT]

What a component's hook receives besides the hook's own context.

Attributes:

Examples:

import xnano

@xnano.renderable
def gauge(context: xnano.Context, ratio: float = 0.0) -> str:
    return '#' * round(ratio * 10)

with xnano.new(width=12, height=1) as canvas:
    seen = []
    gauge.hook(
        lambda context, component_context: seen.append(
            component_context.arguments['ratio']
        ),
        on='tick',
    )
    canvas.write(gauge(ratio=0.5))
    canvas.step(delta_seconds=0.1)
    assert seen == [0.5]

component instance-attribute

component: RenderableComponent[DepsT, StateT] | PlayableComponent[DepsT, StateT]

The component the hook belongs to.

placement instance-attribute

placement: Placement[DepsT, StateT] | None

The placement the component is drawn in, or None for a playable component.

arguments instance-attribute

arguments: Mapping[str, object]

The component's own parameters, by name.

ComponentHook

ComponentHook(owner: _HookSource, function: Callable[..., object], settings: _HookSettings)

What component.hook(...) returns: a hook on every placement of a component.

The hook is installed on the placements that exist now and on those created later; remove takes it off all of them. It never starts a placement of its own.

Attributes:

Create the hook. Use component.hook(...) instead of calling this.

Parameters:

  • owner (_HookSource) –

    The component or component function the hook was added to.

  • function (Callable[..., object]) –

    The function to call with a context and a component context.

  • settings (_HookSettings) –

    The keywords of hook.

Methods:

  • remove –

    Take the hook off every placement. Removing twice does nothing.

Source code in xnano/components.py
def __init__(
    self,
    owner: _HookSource,
    function: Callable[..., object],
    settings: _HookSettings,
) -> None:
    """
    Create the hook. Use ``component.hook(...)`` instead of calling this.

    Args:
        owner: The component or component function the hook was added to.
        function: The function to call with a context and a component context.
        settings: The keywords of ``hook``.
    """
    self._owner = owner
    self._function = function
    self._settings = settings
    self._handles: list[tuple[object, HookHandle[object, object]]] = []
    self._is_removed = False

handles property

handles: tuple[HookHandle[object, object], ...]

The hook on each placement it is installed on right now.

is_removed property

is_removed: bool

Whether remove was called.

remove

remove() -> None

Take the hook off every placement. Removing twice does nothing.

Source code in xnano/components.py
def remove(self) -> None:
    """Take the hook off every placement. Removing twice does nothing."""
    if self._is_removed:
        return
    self._is_removed = True
    if self in self._owner._hooks:
        self._owner._hooks.remove(self)
    handles, self._handles = self._handles, []
    for _, handle in handles:
        handle.remove()

RenderableComponent

RenderableComponent(function: _ComponentFunction[..., object, object], bound: BoundArguments)

Bases: _BoundComponent, Generic[DepsT, StateT]

A renderable function with its own parameters given: gauge(ratio=0.7).

Write it with canvas.write(component). It is evaluated once, and again only when it read something that changes. See renderable.

Attributes:

Keep the function and its arguments. Call the decorated function instead.

Methods:

  • __xnano_render__ –

    Draw the component with the parameters it was given.

  • hook –

    Call a function when something happens to a placement of this component.

Source code in xnano/components.py
def __init__(
    self,
    function: _ComponentFunction[..., object, object],
    bound: inspect.BoundArguments,
) -> None:
    """Keep the function and its arguments. Call the decorated function instead."""
    super().__init__()
    self.function = function
    self._bound = bound

arguments property

arguments: Mapping[str, object]

The component's own parameters, by name.

__xnano_render__

__xnano_render__(context: Context[DepsT, StateT]) -> Renderable

Draw the component with the parameters it was given.

Source code in xnano/components.py
def __xnano_render__(self, context: Context[DepsT, StateT]) -> Renderable:
    """Draw the component with the parameters it was given."""
    return cast('Renderable', self._call(context))

hook

hook(function: Callable[..., object], /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook
hook(*, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> Callable[[Callable[..., object]], ComponentHook]
hook(function: Callable[..., object] | None = None, /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]

Call a function when something happens to a placement of this component.

The function receives the hook's Context and a ComponentContext. The arguments are those of hook on a placement, and a mouse, scroll or drag hook hears only the pointer inside the placement. Keys are not consumed: while nothing has focus, every key hook hears a key. On a component function the hook is installed on every placement of that function, those that exist now and those created later; on a component made by calling it, only on its placements. A playable component has no placement: its hook is installed on the canvas when the component is played.

Parameters:

  • function (Callable[..., object] | None, default: None ) –

    The function to call, or None to use this as a decorator.

  • on (HookKind | None, default: None ) –

    What to listen to; see HookKind.

  • when (When[object, object] | None, default: None ) –

    A condition that must hold; see When.

  • every (float | None, default: None ) –

    Repeat every this many seconds ('tick') or beats ('beat').

  • after (float | None, default: None ) –

    Run once after this many seconds or beats.

  • keys (KeyName | Sequence[KeyName] | None, default: None ) –

    The key or keys an on='key' hook reacts to.

  • requires_deps (bool, default: False ) –

    Whether the hook needs the dependencies given to new(deps=...). Checked each time the hook is installed.

Returns:

Raises:

  • ValueError –

    The combination of arguments does not make sense; see hook.

  • TerminalError –

    requires_deps is true and the canvas was created without deps.

Examples:

import xnano
from xnano_core import Event

@xnano.renderable
def label(context: xnano.Context, text: str = 'x') -> str:
    return text

with xnano.new(width=10, height=1) as canvas:
    pressed = []

    @label.hook(on='key', keys='a')
    def heard(
        context: xnano.Context,
        component_context: xnano.components.ComponentContext,
    ) -> None:
        pressed.append(component_context.arguments['text'])

    canvas.write(label(text='hi'))
    canvas.push(Event.key_press('a'))
    canvas.deliver_events()
    assert pressed == ['hi']
Source code in xnano/components.py
def hook(
    self,
    function: Callable[..., object] | None = None,
    /,
    *,
    on: HookKind | None = None,
    when: When[object, object] | None = None,
    every: float | None = None,
    after: float | None = None,
    keys: KeyName | Sequence[KeyName] | None = None,
    requires_deps: bool = False,
) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]:
    """
    Call a function when something happens to a placement of this component.

    The function receives the hook's [`Context`][xnano.context.Context] and a
    [`ComponentContext`][xnano.components.ComponentContext]. The arguments are those of
    ``hook`` on a placement, and a mouse, scroll or drag hook hears only the pointer
    inside the placement. Keys are not consumed: while nothing has focus, every key hook
    hears a key. On a component function the hook is installed on every
    placement of that function, those that exist now and those created later; on a
    component made by calling it, only on its placements. A playable component has no
    placement: its hook is installed on the canvas when the component is played.

    Args:
        function: The function to call, or ``None`` to use this as a decorator.
        on: What to listen to; see [`HookKind`][xnano.types.HookKind].
        when: A condition that must hold; see [`When`][xnano.types.When].
        every: Repeat every this many seconds (``'tick'``) or beats (``'beat'``).
        after: Run once after this many seconds or beats.
        keys: The key or keys an ``on='key'`` hook reacts to.
        requires_deps: Whether the hook needs the dependencies given to
            ``new(deps=...)``. Checked each time the hook is installed.

    Returns:
        A [`ComponentHook`][xnano.components.ComponentHook], whose ``remove`` takes the
        hook off every placement; or, without a function, the decorator.

    Raises:
        ValueError: The combination of arguments does not make sense; see
            [`hook`][xnano.canvas.Canvas.hook].
        TerminalError: ``requires_deps`` is true and the canvas was created without
            ``deps``.

    Examples:
        ```python
        import xnano
        from xnano_core import Event

        @xnano.renderable
        def label(context: xnano.Context, text: str = 'x') -> str:
            return text

        with xnano.new(width=10, height=1) as canvas:
            pressed = []

            @label.hook(on='key', keys='a')
            def heard(
                context: xnano.Context,
                component_context: xnano.components.ComponentContext,
            ) -> None:
                pressed.append(component_context.arguments['text'])

            canvas.write(label(text='hi'))
            canvas.push(Event.key_press('a'))
            canvas.deliver_events()
            assert pressed == ['hi']
        ```
    """
    from xnano._hooks import _check_hook_arguments, _check_hook_function
    from xnano._when import compile_when

    _check_hook_arguments(
        cast('_Hooked[object, object]', _PlacementLike()), on, every, after, keys
    )
    compile_when(when)
    settings = _HookSettings(on, when, every, after, keys, requires_deps)

    def attach(callback: Callable[..., object]) -> ComponentHook:
        component_hook = ComponentHook(
            self, _check_hook_function(callback, arguments=2), settings
        )
        self._hooks.append(component_hook)
        try:
            self._install_everywhere(component_hook)
        except BaseException:
            component_hook.remove()
            raise
        return component_hook

    if function is None:
        return attach
    return attach(function)

PlayableComponent

PlayableComponent(function: _ComponentFunction[..., object, object], bound: BoundArguments)

Bases: _BoundComponent, Generic[DepsT, StateT]

A playable function with its own parameters given: arpeggio(root='C4').

Play it with canvas.play(component). See playable.

Attributes:

Keep the function and its arguments. Call the decorated function instead.

Methods:

  • __xnano_play__ –

    Return what to play, with the parameters the component was given.

  • hook –

    Call a function when something happens to a placement of this component.

Source code in xnano/components.py
def __init__(
    self,
    function: _ComponentFunction[..., object, object],
    bound: inspect.BoundArguments,
) -> None:
    """Keep the function and its arguments. Call the decorated function instead."""
    super().__init__()
    self.function = function
    self._bound = bound

arguments property

arguments: Mapping[str, object]

The component's own parameters, by name.

__xnano_play__

__xnano_play__(context: Context[DepsT, StateT]) -> Playable

Return what to play, with the parameters the component was given.

Source code in xnano/components.py
def __xnano_play__(self, context: Context[DepsT, StateT]) -> Playable:
    """Return what to play, with the parameters the component was given."""
    return cast('Playable', self._call(context))

hook

hook(function: Callable[..., object], /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook
hook(*, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> Callable[[Callable[..., object]], ComponentHook]
hook(function: Callable[..., object] | None = None, /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]

Call a function when something happens to a placement of this component.

The function receives the hook's Context and a ComponentContext. The arguments are those of hook on a placement, and a mouse, scroll or drag hook hears only the pointer inside the placement. Keys are not consumed: while nothing has focus, every key hook hears a key. On a component function the hook is installed on every placement of that function, those that exist now and those created later; on a component made by calling it, only on its placements. A playable component has no placement: its hook is installed on the canvas when the component is played.

Parameters:

  • function (Callable[..., object] | None, default: None ) –

    The function to call, or None to use this as a decorator.

  • on (HookKind | None, default: None ) –

    What to listen to; see HookKind.

  • when (When[object, object] | None, default: None ) –

    A condition that must hold; see When.

  • every (float | None, default: None ) –

    Repeat every this many seconds ('tick') or beats ('beat').

  • after (float | None, default: None ) –

    Run once after this many seconds or beats.

  • keys (KeyName | Sequence[KeyName] | None, default: None ) –

    The key or keys an on='key' hook reacts to.

  • requires_deps (bool, default: False ) –

    Whether the hook needs the dependencies given to new(deps=...). Checked each time the hook is installed.

Returns:

Raises:

  • ValueError –

    The combination of arguments does not make sense; see hook.

  • TerminalError –

    requires_deps is true and the canvas was created without deps.

Examples:

import xnano
from xnano_core import Event

@xnano.renderable
def label(context: xnano.Context, text: str = 'x') -> str:
    return text

with xnano.new(width=10, height=1) as canvas:
    pressed = []

    @label.hook(on='key', keys='a')
    def heard(
        context: xnano.Context,
        component_context: xnano.components.ComponentContext,
    ) -> None:
        pressed.append(component_context.arguments['text'])

    canvas.write(label(text='hi'))
    canvas.push(Event.key_press('a'))
    canvas.deliver_events()
    assert pressed == ['hi']
Source code in xnano/components.py
def hook(
    self,
    function: Callable[..., object] | None = None,
    /,
    *,
    on: HookKind | None = None,
    when: When[object, object] | None = None,
    every: float | None = None,
    after: float | None = None,
    keys: KeyName | Sequence[KeyName] | None = None,
    requires_deps: bool = False,
) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]:
    """
    Call a function when something happens to a placement of this component.

    The function receives the hook's [`Context`][xnano.context.Context] and a
    [`ComponentContext`][xnano.components.ComponentContext]. The arguments are those of
    ``hook`` on a placement, and a mouse, scroll or drag hook hears only the pointer
    inside the placement. Keys are not consumed: while nothing has focus, every key hook
    hears a key. On a component function the hook is installed on every
    placement of that function, those that exist now and those created later; on a
    component made by calling it, only on its placements. A playable component has no
    placement: its hook is installed on the canvas when the component is played.

    Args:
        function: The function to call, or ``None`` to use this as a decorator.
        on: What to listen to; see [`HookKind`][xnano.types.HookKind].
        when: A condition that must hold; see [`When`][xnano.types.When].
        every: Repeat every this many seconds (``'tick'``) or beats (``'beat'``).
        after: Run once after this many seconds or beats.
        keys: The key or keys an ``on='key'`` hook reacts to.
        requires_deps: Whether the hook needs the dependencies given to
            ``new(deps=...)``. Checked each time the hook is installed.

    Returns:
        A [`ComponentHook`][xnano.components.ComponentHook], whose ``remove`` takes the
        hook off every placement; or, without a function, the decorator.

    Raises:
        ValueError: The combination of arguments does not make sense; see
            [`hook`][xnano.canvas.Canvas.hook].
        TerminalError: ``requires_deps`` is true and the canvas was created without
            ``deps``.

    Examples:
        ```python
        import xnano
        from xnano_core import Event

        @xnano.renderable
        def label(context: xnano.Context, text: str = 'x') -> str:
            return text

        with xnano.new(width=10, height=1) as canvas:
            pressed = []

            @label.hook(on='key', keys='a')
            def heard(
                context: xnano.Context,
                component_context: xnano.components.ComponentContext,
            ) -> None:
                pressed.append(component_context.arguments['text'])

            canvas.write(label(text='hi'))
            canvas.push(Event.key_press('a'))
            canvas.deliver_events()
            assert pressed == ['hi']
        ```
    """
    from xnano._hooks import _check_hook_arguments, _check_hook_function
    from xnano._when import compile_when

    _check_hook_arguments(
        cast('_Hooked[object, object]', _PlacementLike()), on, every, after, keys
    )
    compile_when(when)
    settings = _HookSettings(on, when, every, after, keys, requires_deps)

    def attach(callback: Callable[..., object]) -> ComponentHook:
        component_hook = ComponentHook(
            self, _check_hook_function(callback, arguments=2), settings
        )
        self._hooks.append(component_hook)
        try:
            self._install_everywhere(component_hook)
        except BaseException:
            component_hook.remove()
            raise
        return component_hook

    if function is None:
        return attach
    return attach(function)

RenderableFunction

RenderableFunction(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], object])

Bases: _ComponentFunction[ParametersT, DepsT, StateT]

A function that draws itself: what renderable returns.

The function is the component. canvas.write(function) draws it with its default parameters, and canvas.write(function(ratio=0.7)) gives it its own parameters first. Calling it does not run it; it makes a RenderableComponent.

Attributes:

Keep the function. Use @xnano.renderable or @xnano.playable instead.

Methods:

  • __call__ –

    Give the component its own parameters; the context is passed for you.

  • __xnano_render__ –

    Draw the component with its default parameters.

  • hook –

    Call a function when something happens to a placement of this component.

Source code in xnano/components.py
def __init__(
    self,
    function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], object],
) -> None:
    """Keep the function. Use ``@xnano.renderable`` or ``@xnano.playable`` instead."""
    super().__init__()
    self._function = function
    self._signature, self._takes_context = _own_signature(function, self._decorator)
    functools.update_wrapper(self, function)  # pyright: ignore[reportArgumentType]

arguments property

arguments: Mapping[str, object]

The parameters the component is written with: none, so the defaults apply.

__call__

__call__(*arguments: args, **keywords: kwargs) -> RenderableComponent[DepsT, StateT]

Give the component its own parameters; the context is passed for you.

Source code in xnano/components.py
def __call__(
    self, *arguments: ParametersT.args, **keywords: ParametersT.kwargs
) -> RenderableComponent[DepsT, StateT]:
    """Give the component its own parameters; the context is passed for you."""
    return cast(
        'RenderableComponent[DepsT, StateT]', self._bind(*arguments, **keywords)
    )

__xnano_render__

__xnano_render__(context: Context[DepsT, StateT]) -> Renderable

Draw the component with its default parameters.

Source code in xnano/components.py
def __xnano_render__(self, context: Context[DepsT, StateT]) -> Renderable:
    """Draw the component with its default parameters."""
    return cast('Renderable', self._call_with_defaults(context))

hook

hook(function: Callable[..., object], /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook
hook(*, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> Callable[[Callable[..., object]], ComponentHook]
hook(function: Callable[..., object] | None = None, /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]

Call a function when something happens to a placement of this component.

The function receives the hook's Context and a ComponentContext. The arguments are those of hook on a placement, and a mouse, scroll or drag hook hears only the pointer inside the placement. Keys are not consumed: while nothing has focus, every key hook hears a key. On a component function the hook is installed on every placement of that function, those that exist now and those created later; on a component made by calling it, only on its placements. A playable component has no placement: its hook is installed on the canvas when the component is played.

Parameters:

  • function (Callable[..., object] | None, default: None ) –

    The function to call, or None to use this as a decorator.

  • on (HookKind | None, default: None ) –

    What to listen to; see HookKind.

  • when (When[object, object] | None, default: None ) –

    A condition that must hold; see When.

  • every (float | None, default: None ) –

    Repeat every this many seconds ('tick') or beats ('beat').

  • after (float | None, default: None ) –

    Run once after this many seconds or beats.

  • keys (KeyName | Sequence[KeyName] | None, default: None ) –

    The key or keys an on='key' hook reacts to.

  • requires_deps (bool, default: False ) –

    Whether the hook needs the dependencies given to new(deps=...). Checked each time the hook is installed.

Returns:

Raises:

  • ValueError –

    The combination of arguments does not make sense; see hook.

  • TerminalError –

    requires_deps is true and the canvas was created without deps.

Examples:

import xnano
from xnano_core import Event

@xnano.renderable
def label(context: xnano.Context, text: str = 'x') -> str:
    return text

with xnano.new(width=10, height=1) as canvas:
    pressed = []

    @label.hook(on='key', keys='a')
    def heard(
        context: xnano.Context,
        component_context: xnano.components.ComponentContext,
    ) -> None:
        pressed.append(component_context.arguments['text'])

    canvas.write(label(text='hi'))
    canvas.push(Event.key_press('a'))
    canvas.deliver_events()
    assert pressed == ['hi']
Source code in xnano/components.py
def hook(
    self,
    function: Callable[..., object] | None = None,
    /,
    *,
    on: HookKind | None = None,
    when: When[object, object] | None = None,
    every: float | None = None,
    after: float | None = None,
    keys: KeyName | Sequence[KeyName] | None = None,
    requires_deps: bool = False,
) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]:
    """
    Call a function when something happens to a placement of this component.

    The function receives the hook's [`Context`][xnano.context.Context] and a
    [`ComponentContext`][xnano.components.ComponentContext]. The arguments are those of
    ``hook`` on a placement, and a mouse, scroll or drag hook hears only the pointer
    inside the placement. Keys are not consumed: while nothing has focus, every key hook
    hears a key. On a component function the hook is installed on every
    placement of that function, those that exist now and those created later; on a
    component made by calling it, only on its placements. A playable component has no
    placement: its hook is installed on the canvas when the component is played.

    Args:
        function: The function to call, or ``None`` to use this as a decorator.
        on: What to listen to; see [`HookKind`][xnano.types.HookKind].
        when: A condition that must hold; see [`When`][xnano.types.When].
        every: Repeat every this many seconds (``'tick'``) or beats (``'beat'``).
        after: Run once after this many seconds or beats.
        keys: The key or keys an ``on='key'`` hook reacts to.
        requires_deps: Whether the hook needs the dependencies given to
            ``new(deps=...)``. Checked each time the hook is installed.

    Returns:
        A [`ComponentHook`][xnano.components.ComponentHook], whose ``remove`` takes the
        hook off every placement; or, without a function, the decorator.

    Raises:
        ValueError: The combination of arguments does not make sense; see
            [`hook`][xnano.canvas.Canvas.hook].
        TerminalError: ``requires_deps`` is true and the canvas was created without
            ``deps``.

    Examples:
        ```python
        import xnano
        from xnano_core import Event

        @xnano.renderable
        def label(context: xnano.Context, text: str = 'x') -> str:
            return text

        with xnano.new(width=10, height=1) as canvas:
            pressed = []

            @label.hook(on='key', keys='a')
            def heard(
                context: xnano.Context,
                component_context: xnano.components.ComponentContext,
            ) -> None:
                pressed.append(component_context.arguments['text'])

            canvas.write(label(text='hi'))
            canvas.push(Event.key_press('a'))
            canvas.deliver_events()
            assert pressed == ['hi']
        ```
    """
    from xnano._hooks import _check_hook_arguments, _check_hook_function
    from xnano._when import compile_when

    _check_hook_arguments(
        cast('_Hooked[object, object]', _PlacementLike()), on, every, after, keys
    )
    compile_when(when)
    settings = _HookSettings(on, when, every, after, keys, requires_deps)

    def attach(callback: Callable[..., object]) -> ComponentHook:
        component_hook = ComponentHook(
            self, _check_hook_function(callback, arguments=2), settings
        )
        self._hooks.append(component_hook)
        try:
            self._install_everywhere(component_hook)
        except BaseException:
            component_hook.remove()
            raise
        return component_hook

    if function is None:
        return attach
    return attach(function)

PlayableFunction

PlayableFunction(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], object])

Bases: _ComponentFunction[ParametersT, DepsT, StateT]

A function that plays itself: what playable returns.

canvas.play(function) plays it with its default parameters, and canvas.play(function(root='C4')) gives it its own parameters first.

Attributes:

Keep the function. Use @xnano.renderable or @xnano.playable instead.

Methods:

  • __call__ –

    Give the component its own parameters; the context is passed for you.

  • __xnano_play__ –

    Return what to play with the default parameters.

  • hook –

    Call a function when something happens to a placement of this component.

Source code in xnano/components.py
def __init__(
    self,
    function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], object],
) -> None:
    """Keep the function. Use ``@xnano.renderable`` or ``@xnano.playable`` instead."""
    super().__init__()
    self._function = function
    self._signature, self._takes_context = _own_signature(function, self._decorator)
    functools.update_wrapper(self, function)  # pyright: ignore[reportArgumentType]

arguments property

arguments: Mapping[str, object]

The parameters the component is written with: none, so the defaults apply.

__call__

__call__(*arguments: args, **keywords: kwargs) -> PlayableComponent[DepsT, StateT]

Give the component its own parameters; the context is passed for you.

Source code in xnano/components.py
def __call__(
    self, *arguments: ParametersT.args, **keywords: ParametersT.kwargs
) -> PlayableComponent[DepsT, StateT]:
    """Give the component its own parameters; the context is passed for you."""
    return cast(
        'PlayableComponent[DepsT, StateT]', self._bind(*arguments, **keywords)
    )

__xnano_play__

__xnano_play__(context: Context[DepsT, StateT]) -> Playable

Return what to play with the default parameters.

Source code in xnano/components.py
def __xnano_play__(self, context: Context[DepsT, StateT]) -> Playable:
    """Return what to play with the default parameters."""
    return cast('Playable', self._call_with_defaults(context))

hook

hook(function: Callable[..., object], /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook
hook(*, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> Callable[[Callable[..., object]], ComponentHook]
hook(function: Callable[..., object] | None = None, /, *, on: HookKind | None = None, when: When[object, object] | None = None, every: float | None = None, after: float | None = None, keys: KeyName | Sequence[KeyName] | None = None, requires_deps: bool = False) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]

Call a function when something happens to a placement of this component.

The function receives the hook's Context and a ComponentContext. The arguments are those of hook on a placement, and a mouse, scroll or drag hook hears only the pointer inside the placement. Keys are not consumed: while nothing has focus, every key hook hears a key. On a component function the hook is installed on every placement of that function, those that exist now and those created later; on a component made by calling it, only on its placements. A playable component has no placement: its hook is installed on the canvas when the component is played.

Parameters:

  • function (Callable[..., object] | None, default: None ) –

    The function to call, or None to use this as a decorator.

  • on (HookKind | None, default: None ) –

    What to listen to; see HookKind.

  • when (When[object, object] | None, default: None ) –

    A condition that must hold; see When.

  • every (float | None, default: None ) –

    Repeat every this many seconds ('tick') or beats ('beat').

  • after (float | None, default: None ) –

    Run once after this many seconds or beats.

  • keys (KeyName | Sequence[KeyName] | None, default: None ) –

    The key or keys an on='key' hook reacts to.

  • requires_deps (bool, default: False ) –

    Whether the hook needs the dependencies given to new(deps=...). Checked each time the hook is installed.

Returns:

Raises:

  • ValueError –

    The combination of arguments does not make sense; see hook.

  • TerminalError –

    requires_deps is true and the canvas was created without deps.

Examples:

import xnano
from xnano_core import Event

@xnano.renderable
def label(context: xnano.Context, text: str = 'x') -> str:
    return text

with xnano.new(width=10, height=1) as canvas:
    pressed = []

    @label.hook(on='key', keys='a')
    def heard(
        context: xnano.Context,
        component_context: xnano.components.ComponentContext,
    ) -> None:
        pressed.append(component_context.arguments['text'])

    canvas.write(label(text='hi'))
    canvas.push(Event.key_press('a'))
    canvas.deliver_events()
    assert pressed == ['hi']
Source code in xnano/components.py
def hook(
    self,
    function: Callable[..., object] | None = None,
    /,
    *,
    on: HookKind | None = None,
    when: When[object, object] | None = None,
    every: float | None = None,
    after: float | None = None,
    keys: KeyName | Sequence[KeyName] | None = None,
    requires_deps: bool = False,
) -> ComponentHook | Callable[[Callable[..., object]], ComponentHook]:
    """
    Call a function when something happens to a placement of this component.

    The function receives the hook's [`Context`][xnano.context.Context] and a
    [`ComponentContext`][xnano.components.ComponentContext]. The arguments are those of
    ``hook`` on a placement, and a mouse, scroll or drag hook hears only the pointer
    inside the placement. Keys are not consumed: while nothing has focus, every key hook
    hears a key. On a component function the hook is installed on every
    placement of that function, those that exist now and those created later; on a
    component made by calling it, only on its placements. A playable component has no
    placement: its hook is installed on the canvas when the component is played.

    Args:
        function: The function to call, or ``None`` to use this as a decorator.
        on: What to listen to; see [`HookKind`][xnano.types.HookKind].
        when: A condition that must hold; see [`When`][xnano.types.When].
        every: Repeat every this many seconds (``'tick'``) or beats (``'beat'``).
        after: Run once after this many seconds or beats.
        keys: The key or keys an ``on='key'`` hook reacts to.
        requires_deps: Whether the hook needs the dependencies given to
            ``new(deps=...)``. Checked each time the hook is installed.

    Returns:
        A [`ComponentHook`][xnano.components.ComponentHook], whose ``remove`` takes the
        hook off every placement; or, without a function, the decorator.

    Raises:
        ValueError: The combination of arguments does not make sense; see
            [`hook`][xnano.canvas.Canvas.hook].
        TerminalError: ``requires_deps`` is true and the canvas was created without
            ``deps``.

    Examples:
        ```python
        import xnano
        from xnano_core import Event

        @xnano.renderable
        def label(context: xnano.Context, text: str = 'x') -> str:
            return text

        with xnano.new(width=10, height=1) as canvas:
            pressed = []

            @label.hook(on='key', keys='a')
            def heard(
                context: xnano.Context,
                component_context: xnano.components.ComponentContext,
            ) -> None:
                pressed.append(component_context.arguments['text'])

            canvas.write(label(text='hi'))
            canvas.push(Event.key_press('a'))
            canvas.deliver_events()
            assert pressed == ['hi']
        ```
    """
    from xnano._hooks import _check_hook_arguments, _check_hook_function
    from xnano._when import compile_when

    _check_hook_arguments(
        cast('_Hooked[object, object]', _PlacementLike()), on, every, after, keys
    )
    compile_when(when)
    settings = _HookSettings(on, when, every, after, keys, requires_deps)

    def attach(callback: Callable[..., object]) -> ComponentHook:
        component_hook = ComponentHook(
            self, _check_hook_function(callback, arguments=2), settings
        )
        self._hooks.append(component_hook)
        try:
            self._install_everywhere(component_hook)
        except BaseException:
            component_hook.remove()
            raise
        return component_hook

    if function is None:
        return attach
    return attach(function)

renderable

renderable(function: Callable[[], Renderable]) -> RenderableFunction[[], None, None]
renderable(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Renderable]) -> RenderableFunction[ParametersT, DepsT, StateT]
renderable(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Renderable] | Callable[[], Renderable]) -> RenderableFunction[ParametersT, DepsT, StateT] | RenderableFunction[[], None, None]

Turn a function into a component that draws itself.

The function takes the Context first, then its own parameters, and returns anything canvas.write accepts; a function with no parameters at all is called without the context. The decorated object is the component: canvas.write(gauge) draws it with its defaults, canvas.write(gauge(ratio=0.7)) gives it parameters, and gauge.hook(...) adds behavior to its placements.

A component is evaluated once, and again only when it read something that changes: the time or the event (every frame), the size of its buffer, the parent or the canvas (when the screen is resized), a state path (when it changes), or an input such as context.keyboard (when it arrives). What it reads is recorded as it runs. Reads of context.deps and of mutable fields on self are not tracked: when they change, call placement.refresh().

Parameters:

Raises:

  • TypeError –

    The decorated object is a class or is not callable, or the function's first parameter is keyword-only or variadic, so it cannot take the context.

Examples:

import xnano

@xnano.renderable
def gauge(context: xnano.Context, ratio: float = 0.5) -> str:
    return '#' * round(ratio * 10)

with xnano.new(width=12, height=1) as canvas:
    canvas.write(gauge(ratio=0.3))
    assert canvas.render().strip() == '###'
Source code in xnano/components.py
def renderable(  # pyright: ignore[reportGeneralTypeIssues]
    function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Renderable]
    | Callable[[], Renderable],
    /,
) -> RenderableFunction[ParametersT, DepsT, StateT] | RenderableFunction[[], None, None]:
    """
    Turn a function into a component that draws itself.

    The function takes the [`Context`][xnano.context.Context] first, then its own parameters,
    and returns anything ``canvas.write`` accepts; a function with no parameters at all is
    called without the context. The decorated object is the component:
    ``canvas.write(gauge)`` draws it with its defaults, ``canvas.write(gauge(ratio=0.7))`` gives
    it parameters, and ``gauge.hook(...)`` adds behavior to its placements.

    A component is evaluated once, and again only when it read something that changes: the
    time or the event (every frame), the size of its buffer, the parent or the canvas (when
    the screen is resized), a state path (when it changes), or an input such as
    ``context.keyboard`` (when it arrives). What it reads is recorded as it runs. Reads of
    ``context.deps`` and of mutable fields on ``self`` are not tracked: when they change, call
    [`placement.refresh()`][xnano.placement.Placement.refresh].

    Args:
        function: The function ``(context, **own_parameters) -> content``.

    Raises:
        TypeError: The decorated object is a class or is not callable, or the function's
            first parameter is keyword-only or variadic, so it cannot take the context.

    Examples:
        ```python
        import xnano

        @xnano.renderable
        def gauge(context: xnano.Context, ratio: float = 0.5) -> str:
            return '#' * round(ratio * 10)

        with xnano.new(width=12, height=1) as canvas:
            canvas.write(gauge(ratio=0.3))
            assert canvas.render().strip() == '###'
        ```
    """
    return RenderableFunction(
        cast(
            'Callable[Concatenate[Context[DepsT, StateT], ParametersT], Renderable]',
            function,
        )
    )

playable

playable(function: Callable[[], Playable]) -> PlayableFunction[[], None, None]
playable(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Playable]) -> PlayableFunction[ParametersT, DepsT, StateT]
playable(function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Playable] | Callable[[], Playable]) -> PlayableFunction[ParametersT, DepsT, StateT] | PlayableFunction[[], None, None]

Turn a function into a component that plays itself.

The function takes the Context first, then its own parameters, and returns anything canvas.play accepts; a function with no parameters at all is called without the context. It is evaluated once when played, and again on a beat only when it read something that changes (the time or the beat, a state path or an input), playing the new result each time. canvas.play returns a PlayHandle whose remove() stops it.

Parameters:

Raises:

  • TypeError –

    The decorated object is a class or is not callable, or the function's first parameter is keyword-only or variadic, so it cannot take the context.

Examples:

import xnano

@xnano.playable
def chord(context: xnano.Context, root: str = 'C4') -> xnano.Note:
    return xnano.note(root, beats=0.5)

with xnano.new(width=8, height=1) as canvas:
    canvas.play(chord(root='E4'))
    canvas.step(delta_seconds=0.5)
    assert canvas.audio.render_sound().peak > 0.0
Source code in xnano/components.py
def playable(  # pyright: ignore[reportGeneralTypeIssues]
    function: Callable[Concatenate[Context[DepsT, StateT], ParametersT], Playable]
    | Callable[[], Playable],
    /,
) -> PlayableFunction[ParametersT, DepsT, StateT] | PlayableFunction[[], None, None]:
    """
    Turn a function into a component that plays itself.

    The function takes the [`Context`][xnano.context.Context] first, then its own parameters,
    and returns anything ``canvas.play`` accepts; a function with no parameters at all is
    called without the context. It is evaluated once when played, and again
    on a beat only when it read something that changes (the time or the beat, a state path or
    an input), playing the new result each time. ``canvas.play`` returns a
    [`PlayHandle`][xnano.music.PlayHandle] whose ``remove()`` stops it.

    Args:
        function: The function ``(context, **own_parameters) -> playable``.

    Raises:
        TypeError: The decorated object is a class or is not callable, or the function's
            first parameter is keyword-only or variadic, so it cannot take the context.

    Examples:
        ```python
        import xnano

        @xnano.playable
        def chord(context: xnano.Context, root: str = 'C4') -> xnano.Note:
            return xnano.note(root, beats=0.5)

        with xnano.new(width=8, height=1) as canvas:
            canvas.play(chord(root='E4'))
            canvas.step(delta_seconds=0.5)
            assert canvas.audio.render_sound().peak > 0.0
        ```
    """
    return PlayableFunction(
        cast(
            'Callable[Concatenate[Context[DepsT, StateT], ParametersT], Playable]',
            function,
        )
    )