nandi/jolt-nativepublic Fork 0
c7d6ea8b8cda7e4ab2805c20463e0d0f770580c6
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Paint the same tree into a terminal, for the machines with no window a7f6202 · on c7d6ea8b8cda7e4ab2805c20463e0d0f770580c6 · nandi · 17d ago
jolttui.h · 219 lines · 9.5 KBC Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * 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 */