Widgets

⚠ verified v0.0.496 (current: v0.0.505)

Widgets

Import widgets from plexi_sdk.widgets:

from plexi_sdk.widgets import (
    Button, ButtonStyle,
    KeyMap,
    ListView,
    ScrollState,
    TextArea, TextAreaTheme,
    TextBuffer, Cursor, Selection,
    emit_text_input,
)

ButtonStyle

Visual style for Button widgets — fill, hover, active colors and font size.

Button

Stateful button widget. Tracks its own hover/click state across frames.

Usage:

btn = Button("ok", x=20, y=40, w=80, h=32, label="OK")

def on_render(self, ctx):
    if btn.render(ctx):
        handle_ok()

render

def render(ctx: 'RenderContext') -> bool

Draw the button and return True if clicked this frame.


KeyMap

Declarative keyboard shortcut registry.

Usage:

km = KeyMap()
km.bind("s", mod="cmd", action="save")
km.bind("q", action="quit")

def on_key(self, ctx, key, mods):
    action = km.handle(key, mods)
    if action == "save":
        save()
    elif action == "quit":
        quit()

bind

def bind(key: str, action: str, mod: str = '') -> None

Bind a key + optional modifier to an action name.

mod is a pipe-separated string of modifier names: “cmd”, “shift”, “ctrl”, “alt”. Order does not matter. Example: km.bind("s", "save", mod="cmd")


handle

def handle(key: str, mods: dict) -> 'str | None'

Return the action name for this key+mods, or None if unbound.

mods is the dict from on_key: {“shift”: bool, “ctrl”: bool, “alt”: bool, “meta”: bool}. “meta” is treated as “cmd”.


ListView

Stateful scrollable list widget with fixed item height.

Usage:

class MyApp(App):
    def on_init(self, ctx):
        self._list = ListView(item_height=48.0)

    def on_render(self, ctx):
        ctx.render(Column([
            AppBar("Title"),
            self._list.render([
                ListItem(title=items[i], selected=i == self._list.selected_index)
                for i in range(len(items))
            ]),
        ]))

    def on_key(self, ctx, key, mods):
        if self._list.handle_key(key):
            self.emit.schedule_render(after_ms=16)

    def on_click(self, ctx, x, y, button):
        idx = self._list.hit_test(y)
        if idx is not None:
            self._list.set_selected(idx)

set_selected

def set_selected(idx: int) -> None

handle_key

def handle_key(key: str) -> bool

Move selection for j/k/arrows. Returns True if the key was consumed.


hit_test

def hit_test(y: float) -> int | None

Return item index at screen-coord y (from last render), or None.


render

def render(items: list) -> '_ListViewRenderer'

Return a renderable Component containing the given items.

Call inside Column([…]) on every frame — the returned object is lightweight and safe to recreate each render.


ScrollState

Pure data model for vertical scroll state.

Offset is always clamped to [0, max_offset] after any mutation. Negative inputs for content_height and viewport_height are clamped to 0 with a warning rather than raising, so callers with stale layout data degrade gracefully.

scroll_by

def scroll_by(dy: float) -> None

Positive dy scrolls down (reveals content below).


scroll_to

def scroll_to(y: float) -> None

Set offset directly. Clamped to [0, max_offset].


ensure_visible

def ensure_visible(y: float, margin: float = 0.0) -> None

Adjust offset so content-coord y is inside the viewport.

If y is above the viewport (y < offset + margin), scroll up so y lands at offset + margin. If y is below the viewport (y > offset + viewport_height - margin), scroll down so y lands at offset + viewport_height - margin. Otherwise no-op. Result is always clamped.


TextAreaTheme

Color and spacing theme for TextArea widgets.

TextArea

TextBuffer + ScrollState + theme → DrawCommands.

The widget owns geometry (position + size) at render time; the buffer and scroll are passed in so callers control state.

on_key

def on_key(key: str, shift: bool = False, ctrl: bool = False) -> None

Handle a key event. Mutates buffer + keeps cursor visible via scroll.


ensure_cursor_visible

def ensure_cursor_visible() -> None

render

def render(x: float, y: float, w: float, h: float) -> list[dict]

Return DrawCommands for a TextArea at the given viewport rect.

Updates scroll.viewport_height and scroll.content_height as a side effect (so it clamps correctly when the viewport changes size). All returned coordinates are absolute (include x, y offsets).


Cursor

Row/column position in a TextBuffer.

Selection

Anchor is fixed; head follows the cursor.

The selection is inclusive of anchor and exclusive of head, like most editors. Use normalized() to always get (start, end) in document order.

is_empty

def is_empty() -> bool

normalized

def normalized() -> tuple[Cursor, Cursor]

Return (start, end) where start <= end in document order.


TextBuffer

Pure text model: lines, cursor, and optional selection.

Internal representation: list of strings, no trailing newlines per line. An empty buffer contains exactly one empty string.

text

def text() -> str

selected_text

def selected_text() -> str

insert

def insert(ch: str) -> None

Insert text at cursor, replacing any existing selection first.


insert_newline

def insert_newline() -> None

Convenience wrapper; equivalent to insert(‘\n’).


delete_back

def delete_back() -> None

Backspace. Deletes selection if one exists, else deletes char before cursor.


delete_forward

def delete_forward() -> None

Forward delete. Symmetric to delete_back.


move_cursor

def move_cursor(direction: str, select: bool = False) -> None

Move cursor in the given direction.

Valid directions: ‘left’, ‘right’, ‘up’, ‘down’, ‘home’, ‘end’, ‘doc_start’, ‘doc_end’.

If select=True the selection head is extended; otherwise any existing selection is cleared first (standard editor behaviour: pressing an arrow key without Shift collapses to the nearer edge).


set_cursor

def set_cursor(row: int, col: int, select: bool = False) -> None

Jump cursor to (row, col), clamped to valid positions.


select_to

def select_to(row: int, col: int) -> None

Extend selection head to (row, col); creates selection if none.


clear_selection

def clear_selection() -> None

select_all

def select_all() -> None