edamame 0.1.5

A fast TUI Markdown editor and viewer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
# Unified UI controls

> Part of the edamame contributor deep-dives. Index and project-wide conventions: [`AGENTS.md`](../../AGENTS.md). Sibling docs live in [`docs/dev/`](.).


Interactive elements inside modals/overlays are one family, defined in `ui::controls`. The governing rule: **a control resolves its own styling from `controls`; the parent container only reports whether it is `focused` / `disabled`.** Never hand-roll a focus style in a modal. The visual rules (what each state looks like, and why) are in [theming.md](theming.md#controls).

- **Focus is one language; the label column is the single source of truth.** `REVERSED` means "filled affordance". `controls::focused_style` (= `theme.modal_button_focused`) is the shared focus fill, and `controls::control_label_style(focused, disabled, theme)` resolves a labeled row's label column — focused → `modal_item_selected`, disabled → `modal_close_hint`, resting → `modal_item` — called by **both** the settings overlay and the welcome modal. A focused row is one unit: pad the label across the whole column so the fill spans label → widget.
- **Four flavors, declared at the definition site.** **Toggle** (`toggle_spans`) — an on/off slider whose *widget* ignores focus, so focus shows only in the label column. **Pill** (`pill_spans` over a `&[&str]`, e.g. the shared `ASK_ALWAYS_NEVER`) — a multi-value `‹ value ›` selector cycled with ←/→. A row declares Toggle vs. Pill via the `Control` enum, not by option count: a binary on/off setting is a Toggle, never a two-value pill. **Text input** — `controls::text_value_style`, with the blink-stable cursor from `ui::cursor::text_field_spans`; a field that can outgrow its width renders through `ui::cursor::scrolled_field_spans`, which windows the value so the cursor cell stays on screen, carries the scroll offset between frames, and pads the field to its full width so the focus fill does not track the value's length (every text field except the append-only insert-table numbers and the settings overlay's inline editor, including the vim command line). A labeled modal row draws it with `controls::render_text_field_row`, sized at open by `controls::text_field_width` so typing never resizes the modal (Search & Replace, Save As, the export theme name).  **Button** — `ui::button_row`, styled by `controls::button_style`.
- **The label column is the single source of truth for focus.** `controls::focused_style` (= `theme.modal_button_focused`) is the shared focus fill, and `controls::control_label_style(focused, disabled, theme)` resolves a labeled row's label column — focused → `modal_item_selected`, disabled → `modal_close_hint`, resting → `modal_item` — called by **both** the settings overlay and the welcome modal. A focused row is one unit: pad the label across the whole column so the fill spans label → widget.
- **Buttons go through `ui::button_row`, never a hand-built literal.** Construct a `Button::bracketed(label)` and let `render_button_row` (centered footer) or `render_button_at` (left-aligned inline) add the `[ … ]`, size it, place it and return the hit-rect.
- **Cycle + cascade logic is shared too.** Pill / toggle inputs route through `controls::Control::apply`, whose pill arm and every index-valued caller delegate wrap-around to `controls::cycle_index`; `controls::apply_images_cascade` (images-`Never` forces remote-`Never`, stashing/restoring the prior choice) is shared by the settings overlay and the welcome modal.