/* * jolt-tui — glimmer's terminal backend, behind the retained-tree ABI. * * The tree half of `vidya_tree.h` with a terminal under it instead of a GPU * window: nodes are integer handles, mutated by the calls below, and nothing is * drawn until `tui_frame`, which lays the whole tree out and paints it at once. * Interactions come back as a queue the caller drains and routes to its own * handlers — a callback cannot cross this boundary, so identity does instead. * * It is deliberately the same shape as libvidya's, because it is the same * glimmer backend on the other side: one reconciler, and a choice of shared * object. The differences are the ones a terminal actually forces — a focus * ring and keys instead of a pointer and hit testing, cells instead of points, * and colours that are the terminal's rather than a theme's. * * Every call stays on the thread that opened the session, which is where the * state lives; a call from another thread is inert rather than unsound. * * Tags are hiccup names without the colon: * * containers window box hbox vbox frame scroll overlay listbox * widgets label title title-2 dim-label button checkbutton entry * separator spacer progress spinner * * An unrecognized tag is kept and paints as a vertical box, so a caller ahead * of this backend still sees its children. * * Props are string-keyed, in the same names: * * every node sensitive (bool), margin, padding, width-request, * height-request, halign / valign * ("fill"|"start"|"center"|"end"), hexpand / vexpand (bool), * color, bg, bold, dim, underline, reverse, blink, italic * box orientation ("horizontal"|"vertical"), spacing * frame label (drawn into the top border) * scroll offset (rows, clamped to the content and written back) * overlay label; floats centred over everything else * text widgets label, or text * button label, kind ("default"|"primary"|"destructive") * checkbutton label, active (bool) * entry text, placeholder, rows, autofocus (bool) * listbox selected (row index; -1 for no cursor). Its children are * the rows, one node each. * spacer size * progress value (0..1), label * * A colour is a name (`red`, `bright-blue`, `default`), an index into the * xterm 256-colour palette (`"33"`), a hex triple (`#ff6432`, `#f64`), or * `"r,g,b"`. Colour and attributes are inherited by a node's whole subtree. */ #ifndef JOLTTUI_H #define JOLTTUI_H #if defined(_WIN32) # if defined(JOLTTUI_BUILD) # define JOLTTUI_API __declspec(dllexport) # else # define JOLTTUI_API __declspec(dllimport) # endif #elif defined(__GNUC__) # define JOLTTUI_API __attribute__((visibility("default"))) #else # define JOLTTUI_API #endif #ifdef __cplusplus extern "C" { #endif /* The session * * `tui_open` takes the terminal: raw mode, the alternate screen, no cursor * unless a focused entry asks for one, and mouse reporting when `mouse` is * non-zero. 1 on success, 0 if a session is already open or the terminal * refused. `tui_close` gives all of it back and is safe to call twice. * * `tui_headless` opens a session of a fixed size with no terminal at all. The * whole widget layer works there — layout, painting, focus, keys fed by hand — * and `tui_screen_line` reads the result back, which is what a test suite and * CI use. It is the same code path a real session paints through. */ JOLTTUI_API int tui_open(int mouse); JOLTTUI_API int tui_headless(int width, int height); JOLTTUI_API void tui_close(void); /* 1 once Ctrl-C, Ctrl-Q or `tui_quit` has asked the loop to stop — and before * a session is open, so a loop written around it cannot spin forever. */ JOLTTUI_API int tui_should_close(void); JOLTTUI_API void tui_quit(void); /* * One turn of the loop. * * `tui_tick` waits up to `timeout_ms` for input, handles everything that * arrived — keys, clicks, the wheel, a resize — and answers how many things it * handled, so a caller can skip a repaint when nothing did. `tui_frame` lays * the tree out, paints it, and sends only the cells that changed. * * A headless session ticks to 0 and is fed with the calls below instead. */ JOLTTUI_API int tui_tick(int timeout_ms); JOLTTUI_API void tui_frame(void); /* The screen, in cells. 0 before a session is open. */ JOLTTUI_API int tui_screen_width(void); JOLTTUI_API int tui_screen_height(void); /* * One painted row as text, trailing blanks trimmed — what a test asserts on * and what a bug report pastes. The returned pointer belongs to the library * and is valid only until the next string-returning read on this thread. */ JOLTTUI_API const char *tui_screen_line(int y); /* * Input by hand. * * `tui_feed_key` takes a key by name, as the terminal's own keys are named: * "a", "space", "enter", "tab", "shift+tab", "esc", "up", "page-down", * "ctrl+u", "alt+f", "f5". It answers 1 when the backend acted on it and 0 * when it went out as a `key` event instead. `tui_feed_click` and * `tui_feed_wheel` do the same for the mouse; the wheel's `by` is in rows and * negative is up. * * These are the same entry points a real terminal's input arrives through, so * a test drives the UI exactly as a person does. */ JOLTTUI_API int tui_feed_key(const char *name); JOLTTUI_API int tui_feed_click(int x, int y); JOLTTUI_API int tui_feed_wheel(int x, int y, int by); /* The focused node, 0 for none. Focus follows the ring — Tab and Shift-Tab * walk it in paint order, `autofocus` claims it on the first frame, and a * widget that is unmounted or turned insensitive gives it up. */ JOLTTUI_API int tui_focus(void); /* The window node, created with the session. Mount everything under it. */ JOLTTUI_API int tui_tree_root(void); JOLTTUI_API int tui_node_new(const char *tag); JOLTTUI_API void tui_node_free(int node); JOLTTUI_API int tui_node_exists(int node); JOLTTUI_API void tui_node_set_str(int node, const char *key, const char *value); JOLTTUI_API void tui_node_set_num(int node, const char *key, double value); JOLTTUI_API void tui_node_set_bool(int node, const char *key, int value); JOLTTUI_API void tui_node_clear_props(int node); /* * Reads answer the empty string / 0 for a prop that is unset or of another * type. The returned pointer belongs to the library and is valid only until * the next string-returning call of its family on this thread — props, tags * and screen lines are one family, dumps another, and an event's name and its * text are each their own, so an event can be read whole. */ JOLTTUI_API const char *tui_node_get_str(int node, const char *key); JOLTTUI_API double tui_node_get_num(int node, const char *key); JOLTTUI_API int tui_node_get_bool(int node, const char *key); /* Reading the structure back. `tui_node_tag` answers the canonical tag name * ("box" for both hbox and vbox); `tui_node_child_at` answers 0 past the end. */ JOLTTUI_API const char *tui_node_tag(int node); JOLTTUI_API int tui_node_parent(int node); JOLTTUI_API int tui_node_child_count(int node); JOLTTUI_API int tui_node_child_at(int node, int index); /* * The subtree at `node` as pretty-printed hiccup — `[:tag {props} children...]`, * one node to a line. `node` 0 means the root, so `tui_tree_dump(0)` is the * whole window. It answers what the tree *is*, read back from the arena, * rather than what a caller meant to build. Props are sorted, so two dumps of * the same tree compare as text; handlers are absent because they never * crossed this boundary. */ JOLTTUI_API const char *tui_tree_dump(int node); JOLTTUI_API int tui_node_append(int parent, int child); /* Unparents AND frees `child` with everything under it. */ JOLTTUI_API void tui_node_remove(int parent, int child); /* Moves `child` after `sibling`; `sibling` 0 means the first position. */ JOLTTUI_API int tui_node_insert_after(int parent, int child, int sibling); /* Puts `new_child` where `old_child` was, and frees `old_child`. */ JOLTTUI_API int tui_node_replace(int parent, int old_child, int new_child); /* * Drain interactions. `tui_tree_poll_event` dequeues one and answers 1 while * there was one; the accessors describe whichever was dequeued last. * * Event names are glimmer's handler props without the `on-`: * * click a button was pressed no payload * toggled a checkbutton changed num is the new state * change an entry's text changed text is the new text * activate Enter in an entry or a list text is the text or the row * select a list's cursor moved text is the row, num its index * scroll a viewport moved num is the new offset * close Esc in an overlay no payload * key a key nothing here wanted text is the key's name * * A widget does not own its value: `toggled`, `change`, `select` and `scroll` * write the new state back into the node's props as well, so a caller that * ignores the event still sees a working control, and the next prop write is * what settles it. * * A `key` event is reported on the focused node, or on the window when nothing * has focus. Bubbling it to a container's `:on-key` is the caller's to do: it * holds the handlers and knows the tree. */ JOLTTUI_API int tui_tree_poll_event(void); JOLTTUI_API int tui_tree_event_node(void); JOLTTUI_API const char *tui_tree_event_name(void); JOLTTUI_API const char *tui_tree_event_text(void); JOLTTUI_API double tui_tree_event_num(void); #ifdef __cplusplus } #endif #endif /* JOLTTUI_H */