Skip to content

xnano.placement

xnano.placement

xnano.placement

A buffer placed on a canvas, with a position and size that are formulas.

Classes:

  • Placement –

    A buffer placed on a canvas, with a position and size that are formulas.

Placement

Placement(canvas: Canvas[DepsT, StateT], buffer: Buffer, parent_buffer: Buffer | None = None, parent_owner: Canvas[object, object] | Placement[object, object] | None = None)

Bases: _Hooked[DepsT, StateT], _TextModifiers

A buffer placed on a canvas, with a position and size that are formulas.

Assign any of x, y, width and height an Axis: a number, a percentage of the parent, or an expression. Numbers apply at once; everything else is evaluated again before every frame, so a placement can follow the clock, the canvas's size or another buffer without a callback.

Assigning a range instead ((0, 20)) limits the axis rather than setting it. For x and y the range is how far the placement may travel, so a slider's thumb is a placement whose x is limited to its track; whatever is assigned to it later, by a drag hook, is held inside the track. For width and height it is the smallest and largest size.

Percentages and ranges only change when something about the canvas does, so they cost nothing while it is still. An expression is assumed to change with time, so a canvas with one keeps drawing every frame; use a percentage for a layout that only follows the size of the canvas.

Get one from Canvas.write or nest a child placement inside another with Placement.write.

Attributes:

  • core_buffer (Buffer) –

    The core buffer that is placed.

  • canvas (Canvas[DepsT, StateT]) –

    The canvas the buffer is placed on.

  • parent (ParentExpressionScope) –

    Symbolic expressions for the parent container's width, height and size.

  • root (RootExpressionScope) –

    Symbolic expressions for the root canvas's width, height, size and time.

  • time (NumericValue) –

    Symbolic expression for elapsed seconds on the canvas clock.

  • x (int) –

    The column of the left edge, relative to the parent.

  • y (int) –

    The row of the top edge, relative to the parent.

  • width (int) –

    The width in cells.

  • height (int) –

    The height in cells.

  • layer (int) –

    Placements with a higher layer are drawn over those with a lower one.

  • is_visible (bool) –

    Whether the buffer is drawn.

  • text (str) –

    The text shown, line by line.

Examples:

import xnano

with xnano.new(width=20, height=3) as canvas:
    label = canvas.write('hi')
    label.x = (label.parent.width - label.width) // 2
    assert label.x == 9

    thumb = canvas.write('#', x=(2, 10))
    thumb.x = 40
    assert thumb.x == 10
    thumb.x = 0
    assert thumb.x == 2

Place a buffer on a canvas. Get one from canvas.write instead of calling this.

Parameters:

  • canvas (Canvas[DepsT, StateT]) –

    The canvas the buffer is placed on.

  • buffer (Buffer) –

    The buffer that is placed.

  • parent_buffer (Buffer | None, default: None ) –

    The parent buffer buffer is attached to (defaults to canvas.core_buffer).

  • parent_owner (Canvas[object, object] | Placement[object, object] | None, default: None ) –

    The placement or canvas that owns parent_buffer (defaults to canvas).

Methods:

  • local –

    Convert a cell of the screen into a cell of this placement.

  • move –

    Move the placement by a number of cells along each axis.

  • up –

    Move the placement up by count rows; see move.

  • down –

    Move the placement down by count rows; see move.

  • left –

    Move the placement left by count columns; see move.

  • right –

    Move the placement right by count columns; see move.

  • render –

    Draw the placement, with its border, padding and nested placements, to a string.

  • refresh –

    Draw a placement made from a component again, at the next frame.

  • write –

    Add a child placement inside this placement.

  • update –

    Change this placement's content with operations, styles or effects.

  • __or__ –

    Apply operation to this placement and return self.

  • __ior__ –

    Apply operation to this placement in place and return self.

  • remove –

    Take the buffer off the canvas. Removing twice is harmless.

  • __setattr__ –

    Set an attribute, or raise AttributeError for an unknown public name.

  • bold –

    Make the text bold, or undo it with False; the same as setting is_bold.

  • dim –

    Dim the text, or undo it with False; the same as setting is_dim.

  • italic –

    Make the text italic, or undo it with False; the same as setting is_italic.

  • underline –

    Underline the text, or undo it with False; the same as setting is_underlined.

  • blink –

    Make the text blink, or undo it with False; the same as setting is_blinking.

  • reverse –

    Swap the foreground and background, or undo it with False; the same as setting is_reversed.

  • hidden –

    Hide the text, or undo it with False; the same as setting is_hidden.

  • crossed_out –

    Strike the text through, or undo it with False; the same as setting is_crossed_out.

  • hook –

    Call a function when something happens on this canvas or placement.

Source code in xnano/placement.py
def __init__(
    self,
    canvas: Canvas[DepsT, StateT],
    buffer: _core.Buffer,
    parent_buffer: _core.Buffer | None = None,
    parent_owner: Canvas[object, object] | Placement[object, object] | None = None,
) -> None:
    """
    Place a buffer on a canvas. Get one from ``canvas.write`` instead of calling this.

    Args:
        canvas: The canvas the buffer is placed on.
        buffer: The buffer that is placed.
        parent_buffer: The parent buffer ``buffer`` is attached to (defaults to
            ``canvas.core_buffer``).
        parent_owner: The placement or canvas that owns ``parent_buffer`` (defaults to
            ``canvas``).
    """
    super().__init__()
    self._canvas = canvas
    self._buffer = buffer
    self._parent_buffer: _core.Buffer = (
        canvas._root if parent_buffer is None else parent_buffer
    )
    self._parent_owner: Canvas[object, object] | Placement[object, object] = cast(
        'Canvas[object, object] | Placement[object, object]',
        canvas if parent_owner is None else parent_owner,
    )
    self._bindings: dict[AxisName, tuple[NumericExpression, bool]] = {}
    self._limits: dict[AxisName, tuple[Bound | None, Bound | None, bool]] = {}
    self._removed = False
    self._is_text = False
    self._edges: EdgeBehavior | None = None
    # An area that terminal.write uses inside a canvas's block follows that canvas.
    self._edges_owner: Canvas[object, object] | None = None
    self._wrapped_text: str | None = None
    self._wrapped_width: int | None = None
    self._appearance: Appearance | None = None
    self._decorated: Decorated | None = None
    self._has_nested = False
    self._state_namespace: StateNamespace | None = None
    self._driver: _ComponentDriver | None = None
    self._renderable_effects = RenderableEffects(
        cast('Placement[object, object]', self)
    )
    self._seal()

core_buffer property

core_buffer: Buffer

The core buffer of the content, which core operations and widgets draw into.

A placement with a border, padding or margin around content that is not plain text draws that decoration in a buffer of its own; this is the buffer inside it.

canvas property

canvas: Canvas[DepsT, StateT]

The canvas the buffer is placed on.

state property

State that belongs to this placement, read with get and written with push.

It is the state of one placement, separate from the canvas's own (Canvas.state) and from that of other placements, and it is deleted when the placement is removed. Asking a removed placement for its state raises a TerminalClosedError, so finish with the state before calling remove. See StateNamespace for the path rules.

Examples:

import xnano

with xnano.new(width=8, height=1) as canvas:
    first = canvas.write('a')
    second = canvas.write('b')
    first.state.push('clicks', 2)
    assert first.state.get('clicks') == 2
    assert second.state.get('clicks', default=0) == 0

parent property

Symbolic expressions for the parent container's width, height and size.

root property

Symbolic expressions for the root canvas's width, height, size and time.

time property

Symbolic expression for elapsed seconds on the canvas clock.

x property writable

x: int

The column of the left edge, relative to the parent.

y property writable

y: int

The row of the top edge, relative to the parent.

width property writable

width: int

The width in cells.

height property writable

height: int

The height in cells.

layer property writable

layer: int

Placements with a higher layer are drawn over those with a lower one.

is_visible property writable

is_visible: bool

Whether the buffer is drawn.

edges property writable

edges: EdgeBehavior

What this content does at the edges of its area: 'clip', 'stop' or 'wrap'.

Its area is what it is written on: the canvas, the placement around it, or its slot on a canvas with a 'column' or 'row' layout. Content written on the outer canvas inside the with block of a canvas created in it lives in that canvas's area, and follows that canvas's edges. 'clip' lets it move past an edge, where it is not drawn. 'stop' holds it inside the area whenever it moves or is assigned a position, so a moved piece stops at the edge; a range given for x or y takes the place of the area's edges on that axis. 'wrap' continues text on the next rows of the area, wrapped again whenever the area's width changes; on content that is not text it is the same as 'clip'. Assign None to follow what it is written on again: the setting of the placement around it, or the canvas's edges.

Raises:

  • ValueError –

    The value is not 'clip', 'stop', 'wrap' or None.

Examples:

import xnano

with xnano.new(width=10, height=3) as canvas:
    ball = canvas.write('o', edges='stop')
    ball.right(50).down(50)
    assert (ball.x, ball.y) == (9, 2)
    note = canvas.write('one two three', edges='wrap')
    assert note.text == 'one two three'
    assert canvas.render().splitlines()[:2] == ['one two', 'three']

fx property

The visual effects of this placement, one method for each effect.

placement.fx.fade_to(foreground='red') starts a fade on the placement at once, from a hook or from anywhere else, and placement.fx.clear() stops what was started here. See RenderableEffects.

Examples:

import xnano
from xnano.events import Event

with xnano.new(width=20, height=1) as canvas:
    content = canvas.write('Press e')

    @canvas.hook(on='key')
    def flash(context: xnano.Context) -> None:
        if context.keyboard.key == 'e':
            content.fx.fade_to(foreground='red', duration_milliseconds=200)

    canvas.push(Event.key_press('e'))
    canvas.step()
    assert len(content.fx.active) == 1

region property

region: Region

The cells of the content as a Region: read and write placement.region[x, y].

Around text with a border, padding or margin, the region is the area inside them.

Examples:

import xnano

with xnano.new(width=10, height=3) as canvas:
    label = canvas.write('abc')
    label.region[0, 0] = xnano.Pixel('star')
    assert label.region[0, 0].character == '★'

text property writable

text: str

The text shown, line by line.

Assign new text to change what a text placement says; the placement grows or shrinks to fit it, unless its size was set.

style property writable

style: Style | None

A ready-made style that the colors and modifiers are applied over.

color property

color: Colors

The colors: color.fg for the text and color.bg for what is behind it.

Each is a ColorSlot. Assign a color name, an (red, green, blue) tuple, a Color or another slot; 'reset' clears the color.

Examples:

import xnano

with xnano.new(width=10, height=3) as canvas:
    label = canvas.write('hi')
    label.color.fg = 'red'
    label.color.bg = (0, 0, 80)
    label.color.bg.lighten(0.1)
    assert label.color.fg.value == xnano.Color('red')

border property writable

The border: its kind, a setting for each side, its color and its title.

Assign 'plain', 'rounded', 'double' or 'thick' to draw a border on every side, True for 'plain' and False to remove it. It works on any content, not only on text.

The border, padding and margin are drawn around the content, so the placement grows by the cells they take. A widget with no size of its own is shrunk to fit its parent, border included.

Examples:

import xnano

with xnano.new(width=12, height=5) as canvas:
    card = canvas.write('hi', border='rounded')
    card.border.top = 'double'
    card.border.left = False
    card.border.color = 'cyan'
    card.border = False
    assert not card.border

padding property writable

The space between the border and the content, edge by edge.

Assign a number of cells, a percentage such as '10%' or a dictionary such as {'vertical': 1, 'horizontal': 2}, or set one edge with padding.left = 2.

margin property writable

The transparent space outside the border, edge by edge, set like padding.

is_bold property writable

is_bold: bool

Whether the text is bold.

is_dim property writable

is_dim: bool

Whether the text is dimmed.

is_italic property writable

is_italic: bool

Whether the text is italic.

is_underlined property writable

is_underlined: bool

Whether the text is underlined.

is_blinking property writable

is_blinking: bool

Whether the text blinks.

is_reversed property writable

is_reversed: bool

Whether foreground and background are swapped.

is_hidden property writable

is_hidden: bool

Whether the text is hidden.

is_crossed_out property writable

is_crossed_out: bool

Whether the text is struck through.

local

local(point: Point) -> Point

Convert a cell of the screen into a cell of this placement.

Parameters:

  • point (Point) –

    A cell in screen coordinates, such as context.mouse.position.

Examples:

import xnano
from xnano_core import Point

with xnano.new(width=20, height=4) as canvas:
    placement = canvas.write('hi', x=10, y=1)
    assert placement.local(Point(11, 1)) == Point(1, 0)
Source code in xnano/placement.py
def local(self, point: _core.Point) -> _core.Point:
    """
    Convert a cell of the screen into a cell of this placement.

    Args:
        point: A cell in screen coordinates, such as ``context.mouse.position``.

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

        with xnano.new(width=20, height=4) as canvas:
            placement = canvas.write('hi', x=10, y=1)
            assert placement.local(Point(11, 1)) == Point(1, 0)
        ```
    """
    point = check_point(point)
    bounds = self._buffer._absolute_bounds
    return _core.Point(point.x - bounds.x, point.y - bounds.y)

move

move(x: int = 0, y: int = 0) -> Placement[DepsT, StateT]

Move the placement by a number of cells along each axis.

The move is held inside a range on the axis (x=(0, 39)), so a slider's thumb stops at the ends of its track. A position that is a formula keeps its formula and shifts it. Piping xnano.operations.Move onto the placement is also held inside its ranges.

Parameters:

  • x (int, default: 0 ) –

    Columns to move right; a negative number moves left.

  • y (int, default: 0 ) –

    Rows to move down; a negative number moves up.

Returns:

  • Placement[DepsT, StateT] –

    The placement, so calls can chain.

Raises:

  • TypeError –

    An amount is not a whole number.

Examples:

import xnano

with xnano.new(width=20, height=3) as canvas:
    thumb = canvas.write('#', x=(0, 10), y=1)
    thumb.move(x=4, y=-1)
    assert (thumb.x, thumb.y) == (4, 0)
    thumb.right(99)
    assert thumb.x == 10
Source code in xnano/placement.py
def move(self, x: int = 0, y: int = 0) -> Placement[DepsT, StateT]:
    """
    Move the placement by a number of cells along each axis.

    The move is held inside a range on the axis
    (``x=(0, 39)``), so a slider's thumb stops at the ends of its track. A position that
    is a formula keeps its formula and shifts it. Piping ``xnano.operations.Move`` onto
    the placement is also held inside its ranges.

    Args:
        x: Columns to move right; a negative number moves left.
        y: Rows to move down; a negative number moves up.

    Returns:
        The placement, so calls can chain.

    Raises:
        TypeError: An amount is not a whole number.

    Examples:
        ```python
        import xnano

        with xnano.new(width=20, height=3) as canvas:
            thumb = canvas.write('#', x=(0, 10), y=1)
            thumb.move(x=4, y=-1)
            assert (thumb.x, thumb.y) == (4, 0)
            thumb.right(99)
            assert thumb.x == 10
        ```
    """
    horizontal = check_cells(x, 'x')
    vertical = check_cells(y, 'y')
    self._require_attached()
    steps: tuple[tuple[AxisName, int], ...] = (('x', horizontal), ('y', vertical))
    for axis, step in steps:
        if step == 0:
            continue
        with self._canvas._lock:
            binding = self._bindings.get(axis)
        if binding is None:
            self._set_axis(axis, getattr(self._buffer, axis) + step)
            continue
        # A formula moves only as far as its range or edges let it, so moves past an
        # edge do not pile up and have to be undone before the opposite move shows.
        current = getattr(self._buffer, axis)
        try:
            target = self._clamp(axis, current + step, self._resolve_offline)
        except _core.RuntimeRequiredError:
            target = current + step
        self._set_axis(axis, binding[0] + _round_half_away(target - current))
    return self

up

up(count: int = 1) -> Placement[DepsT, StateT]

Move the placement up by count rows; see move.

Parameters:

  • count (int, default: 1 ) –

    How many rows to move.

Raises:

Source code in xnano/placement.py
def up(self, count: int = 1) -> Placement[DepsT, StateT]:
    """
    Move the placement up by ``count`` rows; see [`move`][xnano.placement.Placement.move].

    Args:
        count: How many rows to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(y=-check_count(count, verb='up', opposite='down'))

down

down(count: int = 1) -> Placement[DepsT, StateT]

Move the placement down by count rows; see move.

Parameters:

  • count (int, default: 1 ) –

    How many rows to move.

Raises:

Source code in xnano/placement.py
def down(self, count: int = 1) -> Placement[DepsT, StateT]:
    """
    Move the placement down by ``count`` rows; see [`move`][xnano.placement.Placement.move].

    Args:
        count: How many rows to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(y=check_count(count, verb='down', opposite='up'))

left

left(count: int = 1) -> Placement[DepsT, StateT]

Move the placement left by count columns; see move.

Parameters:

  • count (int, default: 1 ) –

    How many columns to move.

Raises:

Source code in xnano/placement.py
def left(self, count: int = 1) -> Placement[DepsT, StateT]:
    """
    Move the placement left by ``count`` columns; see [`move`][xnano.placement.Placement.move].

    Args:
        count: How many columns to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(x=-check_count(count, verb='left', opposite='right'))

right

right(count: int = 1) -> Placement[DepsT, StateT]

Move the placement right by count columns; see move.

Parameters:

  • count (int, default: 1 ) –

    How many columns to move.

Raises:

Source code in xnano/placement.py
def right(self, count: int = 1) -> Placement[DepsT, StateT]:
    """
    Move the placement right by ``count`` columns; see [`move`][xnano.placement.Placement.move].

    Args:
        count: How many columns to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(x=check_count(count, verb='right', opposite='left'))

render

render(*, format: Literal['text', 'ansi'] = 'text') -> str

Draw the placement, with its border, padding and nested placements, to a string.

Parameters:

  • format (Literal['text', 'ansi'], default: 'text' ) –

    'text' for the characters only, 'ansi' with colors and styles.

Examples:

import xnano

with xnano.new(width=10, height=3) as canvas:
    card = canvas.write('hi', border='rounded')
    assert card.render().splitlines() == ['╭──╮', '│hi│', '╰──╯']
Source code in xnano/placement.py
def render(self, *, format: Literal['text', 'ansi'] = 'text') -> str:
    """
    Draw the placement, with its border, padding and nested placements, to a string.

    Args:
        format: ``'text'`` for the characters only, ``'ansi'`` with colors and styles.

    Examples:
        ```python
        import xnano

        with xnano.new(width=10, height=3) as canvas:
            card = canvas.write('hi', border='rounded')
            assert card.render().splitlines() == ['╭──╮', '│hi│', '╰──╯']
        ```
    """
    self._flush_look()
    return self._buffer.render(format=format)

refresh

refresh() -> None

Draw a placement made from a component again, at the next frame.

A component is evaluated once, and again only when it read something that changes. When something it does not track changes, such as a field of a class-based component or a value in context.deps, call this to evaluate it again. A placement that was not made from a component has nothing to evaluate, so this does nothing.

Examples:

import xnano

@xnano.renderable
def counter(context: xnano.Context) -> str:
    return str(context.deps['count'])

dependencies = {'count': 0}
with xnano.new(width=4, height=1, deps=dependencies) as canvas:
    placement = canvas.write(counter)
    dependencies['count'] = 7
    placement.refresh()
    canvas.step()
    assert canvas.render().strip() == '7'
Source code in xnano/placement.py
def refresh(self) -> None:
    """
    Draw a placement made from a component again, at the next frame.

    A component is evaluated once, and again only when it read something that changes. When
    something it does not track changes, such as a field of a class-based component or a
    value in ``context.deps``, call this to evaluate it again. A placement that was not made
    from a component has nothing to evaluate, so this does nothing.

    Examples:
        ```python
        import xnano

        @xnano.renderable
        def counter(context: xnano.Context) -> str:
            return str(context.deps['count'])

        dependencies = {'count': 0}
        with xnano.new(width=4, height=1, deps=dependencies) as canvas:
            placement = canvas.write(counter)
            dependencies['count'] = 7
            placement.refresh()
            canvas.step()
            assert canvas.render().strip() == '7'
        ```
    """
    driver = self._driver
    if driver is not None:
        driver.refresh()

write

write(content: Content | Canvas[WrittenDepsT, WrittenStateT] | None = None, /, *, x: Axis | None = None, y: Axis | None = None, width: Axis | None = None, height: Axis | None = None, layer: int = 0, lock: bool = False, edges: EdgeBehavior | None = None, effects: Sequence[RenderableEffectSpecification] = (), style: Style | None = None, foreground: ColorValue | None = None, background: ColorValue | None = None, bold: bool = False, dim: bool = False, italic: bool = False, underline: bool = False, blink: bool = False, reverse: bool = False, hidden: bool = False, crossed_out: bool = False, title: str | None = None, border: BorderKind | bool | None = None, border_style: Style | None = None, padding: Spacing | None = None, margin: Spacing | None = None) -> Placement[DepsT, StateT]

Add a child placement inside this placement.

A child placement's x, y, width and height are measured relative to this placement's buffer, so track.write('█', x=(0, track.width - 1), y=0) moves inside track. To change this placement with an operation, a style or an effect, use update.

Parameters:

  • content (Content | Canvas[WrittenDepsT, WrittenStateT] | None, default: None ) –

    Child content to place inside this buffer.

  • x (Axis | None, default: None ) –

    Column of the child's left edge, relative to this placement.

  • y (Axis | None, default: None ) –

    Row of the child's top edge, relative to this placement.

  • width (Axis | None, default: None ) –

    Width of the child in cells.

  • height (Axis | None, default: None ) –

    Height of the child in cells.

  • layer (int, default: 0 ) –

    Layer ordering among siblings.

  • lock (bool, default: False ) –

    Lock dynamic axes to their initial values.

  • edges (EdgeBehavior | None, default: None ) –

    What the content does at the edges of its area: 'clip', 'stop' or 'wrap'; see Placement.edges. Left out, it follows what it is written on.

  • effects (Sequence[RenderableEffectSpecification], default: () ) –

    Effects to start on the child, as dictionaries with a kind key.

  • style (Style | None, default: None ) –

    Ready-made Style for text.

  • foreground (ColorValue | None, default: None ) –

    Text foreground color.

  • background (ColorValue | None, default: None ) –

    Text background color.

  • bold (bool, default: False ) –

    Whether text is bold.

  • dim (bool, default: False ) –

    Whether text is dimmed.

  • italic (bool, default: False ) –

    Whether text is italic.

  • underline (bool, default: False ) –

    Whether text is underlined.

  • blink (bool, default: False ) –

    Whether text blinks.

  • reverse (bool, default: False ) –

    Whether foreground and background are swapped.

  • hidden (bool, default: False ) –

    Whether text is hidden.

  • crossed_out (bool, default: False ) –

    Whether text is struck through.

  • title (str | None, default: None ) –

    Title for a box around text.

  • border (BorderKind | bool | None, default: None ) –

    The kind of border for a box around the content, a BorderKind such as 'plain', 'rounded', 'double' or 'thick'; True for 'plain' and False for none.

  • border_style (Style | None, default: None ) –

    Style for the box's border.

  • padding (Spacing | None, default: None ) –

    Space between the border and the text; see Canvas.write.

  • margin (Spacing | None, default: None ) –

    Space outside the box, in the same forms.

Source code in xnano/placement.py
@refuse_unknown_keywords
def write(
    self,
    content: Content | Canvas[WrittenDepsT, WrittenStateT] | None = None,
    /,
    *,
    x: Axis | None = None,
    y: Axis | None = None,
    width: Axis | None = None,
    height: Axis | None = None,
    layer: int = 0,
    lock: bool = False,
    edges: EdgeBehavior | None = None,
    effects: Sequence[RenderableEffectSpecification] = (),
    style: _core.Style | None = None,
    foreground: ColorValue | None = None,
    background: ColorValue | None = None,
    bold: bool = False,
    dim: bool = False,
    italic: bool = False,
    underline: bool = False,
    blink: bool = False,
    reverse: bool = False,
    hidden: bool = False,
    crossed_out: bool = False,
    title: str | None = None,
    border: _core.BorderKind | bool | None = None,
    border_style: _core.Style | None = None,
    padding: Spacing | None = None,
    margin: Spacing | None = None,
) -> Placement[DepsT, StateT]:
    """
    Add a child placement inside this placement.

    A child placement's ``x``, ``y``, ``width`` and ``height`` are measured relative to
    this placement's buffer, so ``track.write('█', x=(0, track.width - 1), y=0)`` moves
    inside ``track``. To change this placement with an operation, a style or an effect, use
    [`update`][xnano.placement.Placement.update].

    Args:
        content: Child content to place inside this buffer.
        x: Column of the child's left edge, relative to this placement.
        y: Row of the child's top edge, relative to this placement.
        width: Width of the child in cells.
        height: Height of the child in cells.
        layer: Layer ordering among siblings.
        lock: Lock dynamic axes to their initial values.
        edges: What the content does at the edges of its area: ``'clip'``, ``'stop'`` or
            ``'wrap'``; see [`Placement.edges`][xnano.placement.Placement.edges]. Left
            out, it follows what it is written on.
        effects: Effects to start on the child, as dictionaries with a ``kind`` key.
        style: Ready-made [`Style`][xnano.styles.Style] for text.
        foreground: Text foreground color.
        background: Text background color.
        bold: Whether text is bold.
        dim: Whether text is dimmed.
        italic: Whether text is italic.
        underline: Whether text is underlined.
        blink: Whether text blinks.
        reverse: Whether foreground and background are swapped.
        hidden: Whether text is hidden.
        crossed_out: Whether text is struck through.
        title: Title for a box around text.
        border: The kind of border for a box around the content, a
            [`BorderKind`][xnano.types.BorderKind] such as ``'plain'``, ``'rounded'``,
            ``'double'`` or ``'thick'``; ``True`` for ``'plain'`` and ``False`` for none.
        border_style: Style for the box's border.
        padding: Space between the border and the text; see
            [`Canvas.write`][xnano.canvas.Canvas.write].
        margin: Space outside the box, in the same forms.
    """
    self._require_attached()
    if edges is not None:
        edges = check_edges(edges)
    look = self._appearance
    if look is not None and self._decorated is None and self._has_frame(look):
        self._has_nested = True
        self._look_changed()
        self._flush_look()
    placement = self._canvas._place_on(
        self._content,
        content,
        parent_owner=cast('Placement[object, object]', self),
        x=x,
        y=y,
        width=width,
        height=height,
        layer=layer,
        lock=lock,
        style=style,
        foreground=foreground,
        background=background,
        bold=bold,
        dim=dim,
        italic=italic,
        underline=underline,
        blink=blink,
        reverse=reverse,
        hidden=hidden,
        crossed_out=crossed_out,
        title=title,
        border=border,
        border_style=border_style,
        padding=padding,
        margin=margin,
    )
    if effects:
        apply_effects(cast('Placement[object, object]', placement), effects)
    placement._take_edges(edges, content)
    return placement

update

update(*operations: Operation | Style) -> Placement[DepsT, StateT]

Change this placement's content with operations, styles or effects.

Parameters:

  • operations (Operation | Style, default: () ) –

    What to apply, in order: an operation, a contextual operation, a style, or an effect such as Effect.dissolve(duration_milliseconds=500).

Examples:

import xnano
from xnano.effects import Effect

with xnano.new(width=20, height=3) as canvas:
    title = canvas.write('Welcome')
    title.update(Effect.dissolve(duration_milliseconds=500))
Source code in xnano/placement.py
def update(
    self, *operations: _core.Operation | _core.Style
) -> Placement[DepsT, StateT]:
    """
    Change this placement's content with operations, styles or effects.

    Args:
        operations: What to apply, in order: an operation, a contextual operation, a style,
            or an effect such as ``Effect.dissolve(duration_milliseconds=500)``.

    Examples:
        ```python
        import xnano
        from xnano.effects import Effect

        with xnano.new(width=20, height=3) as canvas:
            title = canvas.write('Welcome')
            title.update(Effect.dissolve(duration_milliseconds=500))
        ```
    """
    self._require_attached()
    for item in operations:
        if isinstance(item, ContextualOperation):
            cast('ContextualOperation[DepsT, StateT]', item).apply_to(self)
        elif isinstance(item, Customization):
            item.apply_to(cast('Placement[object, object]', self))
        elif isinstance(item, _core.Style):
            self._buffer.update(_core.ApplyStyle(item))
        else:
            self._buffer.update(item)
            if isinstance(item, (_core.Move, _core.Place)):
                for axis in ('x', 'y'):
                    if axis in self._limits:
                        self._enforce(axis)
    return self

__or__

__or__(operation: Operation | Style) -> Placement[DepsT, StateT]

Apply operation to this placement and return self.

Source code in xnano/placement.py
def __or__(
    self, operation: _core.Operation | _core.Style
) -> Placement[DepsT, StateT]:
    """Apply ``operation`` to this placement and return ``self``."""
    return self.update(operation)

__ior__

__ior__(operation: Operation | Style) -> Placement[DepsT, StateT]

Apply operation to this placement in place and return self.

Source code in xnano/placement.py
def __ior__(
    self, operation: _core.Operation | _core.Style
) -> Placement[DepsT, StateT]:
    """Apply ``operation`` to this placement in place and return ``self``."""
    return self.update(operation)

remove

remove() -> None

Take the buffer off the canvas. Removing twice is harmless.

Source code in xnano/placement.py
def remove(self) -> None:
    """Take the buffer off the canvas. Removing twice is harmless."""
    if self._removed:
        return
    self._removed = True
    # A look that was waiting to be drawn is dropped with the placement.
    self._look_is_dirty = False
    canvas = self._canvas
    if not canvas._is_closed:
        canvas._look_was_drawn(self)
    with canvas._lock:
        nested = [
            other
            for other in canvas._placements
            if other._parent_owner is self
            or getattr(other._parent_owner, '_parent_placement', None) is self
        ]
    for other in nested:
        other.remove()
    follow = canvas._cursor_follow
    if (
        follow is not None
        and not canvas._is_closed
        and any(follow[0] is buffer for buffer in (self._buffer, self._content))
    ):
        # A cursor that followed this placement has nowhere to be once it is gone.
        canvas._cursor_view.position = None
    driver = self._driver
    if driver is not None:
        self._driver = None
        driver.close()
    self._cancel_hooks()
    self._renderable_effects.clear()
    namespace = self._state_namespace
    if namespace is not None:
        self._state_namespace = None
        namespace._remove()
    with self._canvas._lock:
        self._bindings.clear()
        self._limits.clear()
    try:
        self._parent_buffer.detach(self._buffer)
    except ValueError:
        pass
    self._canvas._release(self)
    with canvas._lock:
        children = [
            child
            for child in canvas._children
            if child._parent_placement is self and not child._is_closed
        ]
    for child in children:
        # A canvas written into this placement is closed with it.
        child.close()

__setattr__

__setattr__(name: str, value: object) -> None

Set an attribute, or raise AttributeError for an unknown public name.

Source code in xnano/_attributes.py
def __setattr__(self, name: str, value: object) -> None:
    """Set an attribute, or raise ``AttributeError`` for an unknown public name."""
    if (
        name[:1] != '_'
        and self.__dict__.get('_is_sealed')
        and not hasattr(type(self), name)
        and name not in self.__dict__
    ):
        kind = type(self).__name__
        settable = sorted(
            key
            for key in dir(type(self))
            if key[:1] != '_' and _is_settable(type(self), key)
        )
        close = difflib.get_close_matches(name, settable, n=1)
        hint = f' Did you mean {close[0]!r}?' if close else ''
        raise AttributeError(
            f'{kind} has no attribute called {name!r}, so assigning to it would change '
            f'nothing.{hint} The attributes you can assign are: {", ".join(settable)}.'
        )
    object.__setattr__(self, name, value)

bold

bold(value: bool = True) -> Self

Make the text bold, or undo it with False; the same as setting is_bold.

Parameters:

  • value (bool, default: True ) –

    Whether the text is bold.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Examples:

import xnano

with xnano.new(width=12, height=1) as canvas:
    title = canvas.write('Ready').bold().underline()
    assert title.is_bold and title.is_underlined
    title.bold(False)
    assert not title.is_bold
Source code in xnano/appearance.py
def bold(self, value: bool = True) -> Self:
    """
    Make the text bold, or undo it with ``False``; the same as setting ``is_bold``.

    Args:
        value: Whether the text is bold.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.

    Examples:
        ```python
        import xnano

        with xnano.new(width=12, height=1) as canvas:
            title = canvas.write('Ready').bold().underline()
            assert title.is_bold and title.is_underlined
            title.bold(False)
            assert not title.is_bold
        ```
    """
    self.is_bold = value
    return self

dim

dim(value: bool = True) -> Self

Dim the text, or undo it with False; the same as setting is_dim.

Parameters:

  • value (bool, default: True ) –

    Whether the text is dimmed.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def dim(self, value: bool = True) -> Self:
    """
    Dim the text, or undo it with ``False``; the same as setting ``is_dim``.

    Args:
        value: Whether the text is dimmed.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_dim = value
    return self

italic

italic(value: bool = True) -> Self

Make the text italic, or undo it with False; the same as setting is_italic.

Parameters:

  • value (bool, default: True ) –

    Whether the text is italic.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def italic(self, value: bool = True) -> Self:
    """
    Make the text italic, or undo it with ``False``; the same as setting ``is_italic``.

    Args:
        value: Whether the text is italic.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_italic = value
    return self

underline

underline(value: bool = True) -> Self

Underline the text, or undo it with False; the same as setting is_underlined.

Parameters:

  • value (bool, default: True ) –

    Whether the text is underlined.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def underline(self, value: bool = True) -> Self:
    """
    Underline the text, or undo it with ``False``; the same as setting ``is_underlined``.

    Args:
        value: Whether the text is underlined.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_underlined = value
    return self
blink(value: bool = True) -> Self

Make the text blink, or undo it with False; the same as setting is_blinking.

Parameters:

  • value (bool, default: True ) –

    Whether the text blinks.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def blink(self, value: bool = True) -> Self:
    """
    Make the text blink, or undo it with ``False``; the same as setting ``is_blinking``.

    Args:
        value: Whether the text blinks.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_blinking = value
    return self

reverse

reverse(value: bool = True) -> Self

Swap the foreground and background, or undo it with False; the same as setting is_reversed.

Parameters:

  • value (bool, default: True ) –

    Whether foreground and background are swapped.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def reverse(self, value: bool = True) -> Self:
    """
    Swap the foreground and background, or undo it with ``False``; the same as setting ``is_reversed``.

    Args:
        value: Whether foreground and background are swapped.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_reversed = value
    return self

hidden

hidden(value: bool = True) -> Self

Hide the text, or undo it with False; the same as setting is_hidden.

Parameters:

  • value (bool, default: True ) –

    Whether the text is hidden.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def hidden(self, value: bool = True) -> Self:
    """
    Hide the text, or undo it with ``False``; the same as setting ``is_hidden``.

    Args:
        value: Whether the text is hidden.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_hidden = value
    return self

crossed_out

crossed_out(value: bool = True) -> Self

Strike the text through, or undo it with False; the same as setting is_crossed_out.

Parameters:

  • value (bool, default: True ) –

    Whether the text is struck through.

Returns:

  • Self –

    This canvas or placement, so calls can chain.

Raises:

Source code in xnano/appearance.py
def crossed_out(self, value: bool = True) -> Self:
    """
    Strike the text through, or undo it with ``False``; the same as setting ``is_crossed_out``.

    Args:
        value: Whether the text is struck through.

    Returns:
        This canvas or placement, so calls can chain.

    Raises:
        TypeError: ``value`` is not ``True`` or ``False``.
    """
    self.is_crossed_out = value
    return self

hook

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

Call a function when something happens on this canvas or placement.

on says what to listen to. A hook without on hears every input event. A mouse, scroll or drag hook on a placement hears only the pointer inside the placement, and a press inside holds the pointer for the hook until the button is released. A pointer hook that matches an event stops it from reaching the parent; a hook that does not match lets it through. Keys are not consumed: while nothing has focus, every key hook hears a key.

hook(function, ...) attaches one function, and @target.hook(...) decorates the function that follows. A hook function may be a generator that receives a new context at each bare yield: write context = yield. A drag is a loop that runs while context.mouse.is_down.

every and after are seconds for on='tick' and beats for on='beat': every=2 means 2 seconds for a tick hook and 2 beats for a beat hook. On a canvas from new() a hook that is due several times in one step runs once for each interval, up to 100,000 times in one step; the intervals that remain run in the steps that follow. The function's return value is ignored, unless it is an operation, a style or an effect, which is applied to this target.

Parameters:

  • function (Hook[DepsT, StateT] | None, default: None ) –

    The function to call with a Context, or a function with no parameters, which is called without it.

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

    What to listen to: 'frame', 'tick', 'beat', 'key', 'mouse', 'scroll', 'drag', 'paste', 'focus', 'resize', 'state' or 'custom'. See HookKind.

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

    A condition that must hold: an expression, a function of the context, or a string. See When. A string tells xnano which input the hook needs, so the mouse, scroll and key-release gates it reads are turned on when the canvas opens. An expression or a function does not, so a condition that reads Mouse, Scroll or key releases needs the gate set on new() or open() yourself: mouse=True, scroll=True or keyboard='release_events'. A comparison with an input the event does not carry, such as keyboard.key on a paste, does not hold; != is one of them, so keyboard.key != 'q' hears only key events. not and not in negate the whole comparison, so not keyboard.key == 'q' and keyboard.key not in ['q'] also hold for events that have no key.

  • 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 ('tick') or beats ('beat').

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

    The key or keys an on='key' hook reacts to, such as 'q' or 'ctrl+s'. A key hook with keys hears presses and repeats; omit them and test keyboard.kind to hear releases.

  • requires_deps (bool, default: False ) –

    Whether the hook needs the dependencies given to new(deps=...).

Returns:

  • HookHandle[DepsT, StateT] | Callable[[Hook[DepsT, StateT]], HookHandle[DepsT, StateT]] –

    The hook, whose remove stops it; or, without a function, the decorator.

Raises:

  • ValueError –

    The combination of arguments does not make sense, a key name is unknown, or a when string is not a condition. The message says what to write instead.

  • TypeError –

    on is not text, when is a plain bool, or the function is not callable with a context, or is an async function.

  • TerminalError –

    requires_deps is true and the canvas was created without deps, or the canvas is on a screen with an input gate this hook needs turned off.

Examples:

import xnano
from xnano_core import Event

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

    @canvas.hook(on='key', keys=['q', 'escape'])
    def leave(context: xnano.Context) -> None:
        heard.append(context.keyboard.key)

    @canvas.hook(on='tick', every=0.5)
    def blink(context: xnano.Context) -> None:
        heard.append('tick')

    canvas.push(Event.key_press('q'))
    canvas.step(delta_seconds=1.0)
    assert heard == ['q', 'tick', 'tick']
Source code in xnano/_hooks.py
def hook(
    self,
    function: Hook[DepsT, StateT] | None = None,
    /,
    *,
    on: HookKind | None = None,
    when: When[DepsT, StateT] | None = None,
    every: float | None = None,
    after: float | None = None,
    keys: KeyName | Sequence[KeyName] | None = None,
    requires_deps: bool = False,
) -> (
    HookHandle[DepsT, StateT]
    | Callable[[Hook[DepsT, StateT]], HookHandle[DepsT, StateT]]
):
    """
    Call a function when something happens on this canvas or placement.

    ``on`` says what to listen to. A hook without ``on`` hears every input event. A mouse,
    scroll or drag hook on a placement hears only the pointer inside the placement, and a
    press inside holds the pointer for the hook until the button is released. A pointer hook
    that matches an event stops it from reaching the parent; a hook that does not match lets
    it through. Keys are not consumed: while nothing has focus, every key hook hears a key.

    ``hook(function, ...)`` attaches one function, and ``@target.hook(...)`` decorates the
    function that follows. A hook function may be a generator that receives a new context
    at each bare ``yield``: write ``context = yield``. A drag is a loop that runs while
    ``context.mouse.is_down``.

    ``every`` and ``after`` are seconds for ``on='tick'`` and beats for ``on='beat'``:
    ``every=2`` means 2 seconds for a tick hook and 2 beats for a beat hook. On a canvas
    from ``new()`` a hook that is due several times in one ``step`` runs once for each
    interval, up to 100,000 times in one step; the intervals that remain run in the steps
    that follow. The function's return value is ignored, unless it is an operation, a
    style or an effect, which is applied to this target.

    Args:
        function: The function to call with a [`Context`][xnano.context.Context], or a
            function with no parameters, which is called without it.
        on: What to listen to: ``'frame'``, ``'tick'``, ``'beat'``, ``'key'``, ``'mouse'``,
            ``'scroll'``, ``'drag'``, ``'paste'``, ``'focus'``, ``'resize'``, ``'state'``
            or ``'custom'``. See [`HookKind`][xnano.types.HookKind].
        when: A condition that must hold: an expression, a function of the context, or a
            string. See [`When`][xnano.types.When]. A string tells xnano which input
            the hook needs, so the mouse, scroll and key-release gates it reads are
            turned on when the canvas opens. An expression or a function does not, so a
            condition that reads ``Mouse``, ``Scroll`` or key releases needs the gate set
            on ``new()`` or ``open()`` yourself: ``mouse=True``, ``scroll=True`` or
            ``keyboard='release_events'``. A comparison with an input the event does not
            carry, such as ``keyboard.key`` on a paste, does not hold; ``!=`` is one of
            them, so ``keyboard.key != 'q'`` hears only key events. ``not`` and ``not in``
            negate the whole comparison, so ``not keyboard.key == 'q'`` and
            ``keyboard.key not in ['q']`` also hold for events that have no key.
        every: Repeat every this many seconds (``'tick'``) or beats (``'beat'``).
        after: Run once after this many seconds (``'tick'``) or beats (``'beat'``).
        keys: The key or keys an ``on='key'`` hook reacts to, such as ``'q'`` or
            ``'ctrl+s'``. A key hook with keys hears presses and repeats; omit them and
            test ``keyboard.kind`` to hear releases.
        requires_deps: Whether the hook needs the dependencies given to
            ``new(deps=...)``.

    Returns:
        The hook, whose ``remove`` stops it; or, without a function, the decorator.

    Raises:
        ValueError: The combination of arguments does not make sense, a key name is
            unknown, or a ``when`` string is not a condition. The message says what to
            write instead.
        TypeError: ``on`` is not text, ``when`` is a plain ``bool``, or the function is not callable with a
            context, or is an ``async`` function.
        TerminalError: ``requires_deps`` is true and the canvas was created without
            ``deps``, or the canvas is on a screen with an input gate this hook needs
            turned off.

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

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

            @canvas.hook(on='key', keys=['q', 'escape'])
            def leave(context: xnano.Context) -> None:
                heard.append(context.keyboard.key)

            @canvas.hook(on='tick', every=0.5)
            def blink(context: xnano.Context) -> None:
                heard.append('tick')

            canvas.push(Event.key_press('q'))
            canvas.step(delta_seconds=1.0)
            assert heard == ['q', 'tick', 'tick']
        ```
    """
    canvas = self._canvas
    self._require_attached()
    owner = cast('_Hooked[object, object]', self)
    normalized_keys = _check_hook_arguments(owner, on, every, after, keys)
    compiled = compile_when(when)
    if on is None and not compiled.is_opaque and compiled.reads:
        if not compiled.reads & {'keyboard', 'mouse', 'scroll'}:
            raise ValueError(
                'when reads time, the size of the parent or deps, but a hook with no on= is '
                'only checked when an input event arrives. Add on="tick" or '
                'on="frame" so it is checked as time passes, or read an input in the '
                'condition.'
            )
    if requires_deps and not canvas._deps_given:
        raise TerminalError(
            'This hook needs dependencies (requires_deps=True), but the canvas has none '
            'yet. Give them when you create the canvas: xnano.new(deps=your_dependencies) '
            'or xnano.open(deps=your_dependencies), or open the canvas with '
            'canvas.open(deps=your_dependencies) before you register this hook.'
        )

    def attach(
        callback: Hook[DepsT, StateT],
    ) -> HookHandle[DepsT, StateT]:
        return self._attach_hook(
            cast(
                'Callable[[Context[DepsT, StateT]], object]',
                _check_hook_function(callback),
            ),
            on=on,
            when=compiled,
            every=every,
            after=after,
            keys=normalized_keys,
        )

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