A curses-style TUI library for the TI-84 Plus CE. Panels, widgets, scenes, dialogs, colors and text styles for CE C programs.
Status: complete, merged to main and released as v0.2.0; see Progress.
titrmlib is at v0.3.0, so the API may change freely until v1.0.
Phase 2 reworks how titrmlib renders and routes input, then adds scenes, overlays, a revised widget set and color. Function and type names below are proposals and may change during implementation.
perf hardware test. No eZ80 assembly.term_panel_print, _printf,
_putc, …) is written into them and stays until it is changed or cleared.term_panel_set_draw) are removed.term_scene_switch(ctx, scene).TERM_EV_SCENE_LEAVE to the outgoing scene and
TERM_EV_SCENE_ENTER to the incoming one.term_focus(ctx, panel) and term_focused(ctx).term_focus_next is removed.[vars] is an ordinary key, renamed TERM_KEY_VARS (was TERM_KEY_TAB).TERM_EV_FOCUS_LOST.Events pass along a chain until one step consumes them:
Ticks go to the active scene’s handler and the global handler.
| Event | Meaning |
|---|---|
TERM_EV_START |
Once, before the first frame |
TERM_EV_KEY |
A key nothing earlier in the chain consumed |
TERM_EV_TICK |
The interval set with term_set_tick() passed |
TERM_EV_SUBMIT |
The user confirmed with [enter]: a button, an input, or a list item (value = item index) |
TERM_EV_CHANGE |
A widget’s value changed: input text edited, list selection moved, checkbox toggled |
TERM_EV_FOCUS_LOST |
The focused panel was hidden or destroyed |
TERM_EV_SCENE_ENTER / TERM_EV_SCENE_LEAVE |
The scene became active or inactive |
TERM_EV_SELECT is removed; choosing a list item sends TERM_EV_SUBMIT.
Custom widgets send the same events.
Widgets are panels with general properties, not one-off code:
[enter] sends TERM_EV_SUBMITBuilt-in widgets:
| Widget | Behavior |
|---|---|
| Container | A plain panel that holds children. Border and title are optional fields |
| Text | Static or word-wrapped text, with a scroll offset, appending lines and optional auto-scroll to the bottom (replaces the log widget) |
| Input | Single-line editable text |
| List | Selectable items. Up/down move and send TERM_EV_CHANGE; [enter] sends TERM_EV_SUBMIT |
| Button | A focusable text panel, centered and reversed, that sends TERM_EV_SUBMIT on [enter]. term_make_button is a shortcut for those settings |
| Checkbox | A focusable on/off item with a label; [enter] toggles it and sends TERM_EV_CHANGE |
| Progress | A horizontal bar, value out of a maximum |
row_pixels table in draw_cell is replaced by drawing that
handles any color pair.Each step keeps the unit tests and hardware tests passing, with screens
re-recorded where output changes on purpose. The perf budgets tighten to
the 50 ms goal once step 1 lands.
| Step | State |
|---|---|
| 1. Retained panels, change-based rendering, key queue | Done |
| 2. Focus and event changes | Done |
| 3. Scenes | Done |
| 4. Overlays | Done |
| 5. Widget set and demo | Done |
| 6. Color | Done |
Steps 1 and 2 were built together: with retained output, the app has to be told about everything that changes what it shows, which is what step 2 adds.
os_GetCSC(). Measured in CEmu,
os_GetCSC() takes about 19 ms per call and kb_Scan() about 1.3 ms.
titrmlib finds presses by comparing scans, queues them, and repeats held
arrows and [del] itself (400 ms delay, then about 12 per second), since the
OS no longer does. Keys already down at startup are ignored.[alpha] reaches the app as TERM_KEY_ALPHA, after the alpha state
changes, so status displays can update.TERM_EV_CHANGE is only for changes the user makes (moving a list
selection, editing an input). Changes the app makes itself, like
term_list_select() or term_input_set(), send nothing.term_focus() ignores panels that aren’t focusable. Input, list and log
widgets are focusable; other panels opt in with
term_panel_set_focusable().TERM_EV_FOCUS_LOST carries the hidden panel, or NULL if it was
destroyed.perf hardware test now fails above 50 ms.bool. term_update_fn returns true when it handled
the event, which stops it going further along the chain. This is how a
scene’s handler consumes an event before the global handler sees it.term_scene_new() returns the scene’s root
panel; term_root() is the first scene, created by term_init().TERM_EV_SCENE_ENTER when term_run() starts,
after TERM_EV_START. Switching before term_run() sends nothing.TERM_EV_FOCUS_LOST, the same rule as hiding the focused panel. A panel can
only hold focus while it is in the active scene.term_panel_destroy() removes a scene that isn’t
active; the active scene and term_root() can’t be destroyed.TERM_MAX_PANELS, 32) is shared by all scenes.term_overlay_open(ctx, col, row, w, h),
term_overlay_open_centered(ctx, w, h) and term_overlay_close(overlay).
An overlay is a root panel; term_panel_destroy() on it closes it.TERM_MAX_OVERLAYS (8) can be open.TERM_EV_FOCUS_LOST if focus was inside the overlay.Asm( launch on the OS 5.3 ROM.term_panel_set_focus_attr (how focus shows: title, list selection, input
cursor, checkbox; reverse video by default), term_panel_set_align (left
or centered text), term_panel_set_submit ([enter] sends
TERM_EV_SUBMIT), term_panel_set_keys (a key handler that runs before the
built-in widget) and term_panel_send (a widget’s own events).
term_panel_set_attr is also the attribute widgets draw in.term_text_append/term_text_appendf and keeps at most
term_text_limit bytes (1024 by default), dropping the oldest lines.
term_text_autoscroll follows the end until the user scrolls up; scrolling
back to the end resumes it. A focusable text widget scrolls with up/down,
and arrows at the right edge show more text above or below. This replaces
the log widget (term_make_log and friends are removed).[x] label with a check mark glyph; [enter] toggles it and
sends TERM_EV_CHANGE with value 1 or 0; term_checkbox_set sends nothing.demo.gif shows the stage 1 demo and needs
re-recording.term_panel_set_colors(panel, fg, bg), palette indices, with
TERM_COLOR_* for common ones. It behaves like term_panel_set_attr:
it applies to output that follows, to clearing and to widgets, which
redraw in it. There are no per-cell colors beyond that and no separate
focus colors: TERM_ATTR_REVERSE swaps the panel’s colors, so focus shows
in color already.gfx_orange (0xE3) shows as yellow in the default
palette (index i is the 1555 color i | i << 8), so TERM_COLOR_ORANGE
is 0xE1. tests/hw/run.py now renders 8bpp dumps with that palette.