Skip to main content

kui_ffi/
types.rs

1//! Opaque handles and the repr(C) mirrors of the core's spec structs —
2//! what `include/kui.h` declares, field for field. See the ABI notes in
3//! `abi` for which side may write which struct.
4
5use super::*;
6
7/// A C callback that builds into a context: `kui_run`'s view, and the body
8/// of a `kui_titlebar_with` / `kui_tooltip_with`. Here rather than in
9/// `run`, because the widgets take one whether or not this build has a
10/// windowed runner.
11pub(crate) type ViewFn = extern "C" fn(user: *mut c_void, ctx: *mut KuiCtx);
12/// A C callback that receives one event: `kui_run`'s, and only its — so
13/// unlike `ViewFn` it is not here in a build without the runner.
14#[cfg(feature = "runner")]
15pub(crate) type EventFn = extern "C" fn(user: *mut c_void, ev: *const KuiEvent);
16/// A C callback that hears the window go: `kui_on_teardown`'s, with
17/// `kui_run`'s `user`. Runner-only, as `EventFn` is.
18#[cfg(feature = "runner")]
19pub(crate) type TeardownFn = extern "C" fn(user: *mut c_void);
20
21// ---------------------------------------------------------------------------
22// Opaque + repr(C) types
23
24/// The opaque context every `kui_*` call takes: a core plus its pending
25/// event queue.
26///
27/// A standalone context from [`kui_ctx_new`] owns its core and lives until
28/// [`kui_ctx_free`]. The context handed to a `kui_run` view callback or a
29/// plugin's `kui_ext_view` borrows the runner's frame instead and is valid
30/// for that call only; the builder entry points work the same on both,
31/// while input, polling and draw data belong to the owning side.
32pub struct KuiCtx {
33    pub(crate) core: *mut Core,
34    /// Keep-alive for standalone contexts; never read directly.
35    pub(crate) _owned: Option<Box<Core>>,
36    pub(crate) events: Vec<UiEvent>,
37    /// Payload most recently handed out by kui_poll_event; freed on the next
38    /// poll (or context free) so C never manages event payload lifetime.
39    pub(crate) last_payload: Option<Box<KuiValue>>,
40    /// Text most recently handed out by kui_edit_text; freed on the next call.
41    pub(crate) last_edit_text: Option<String>,
42    /// The WGSL module most recently handed out by `kui_fragment_source`;
43    /// valid until the next call, like every other borrowed string here.
44    pub(crate) fragment_source: String,
45    /// This frame's fragment draws, in `KuiFragmentDraw` form, so
46    /// `kui_draw_data` can hand out a pointer that outlives the call.
47    pub(crate) fragment_draws: Vec<KuiFragmentDraw>,
48    /// `kui_draw_data`'s transcription of the frame's texture draws.
49    pub(crate) texture_draws: Vec<KuiTextureDraw>,
50    /// Whether the two above are this frame's: `kui_draw_data` transcribes
51    /// once a frame, so a second call in the same frame hands out the same
52    /// arrays rather than freeing the ones the first call handed out.
53    /// Cleared by `kui_frame_begin`, which is where kui.h ends them, and
54    /// by `kui_frame_finish`, so a call while the frame built does not
55    /// stand for the frame it finished.
56    pub(crate) draws_current: bool,
57    /// What `kui_image_pixels` last handed out, so the pointer outlives
58    /// the call.
59    pub(crate) image_pixels: Option<std::sync::Arc<Vec<u8>>>,
60    /// Warnings most recently handed out by kui_take_warnings; their strings
61    /// stay valid until the next call.
62    pub(crate) last_warnings: Vec<kui_core::Warning>,
63    /// Warnings drained from the core and not handed out yet: a `cap`
64    /// shorter than what was raised leaves the rest here.
65    pub(crate) pending_warnings: Vec<kui_core::Warning>,
66    /// Access tree most recently handed out by kui_access_tree; its strings
67    /// stay valid until the next call.
68    pub(crate) last_access: kui_core::AccessTree,
69    /// The runs most recently handed out by kui_access_runs, held apart
70    /// from `last_access` so reading an editor's runs leaves the strings
71    /// kui_access_tree handed out alone.
72    pub(crate) last_runs: Vec<kui_core::AccessRun>,
73    /// Announcements most recently handed out by kui_take_announcements;
74    /// their strings stay valid until the next call.
75    pub(crate) last_announcements: Vec<kui_core::Announcement>,
76    /// Window commands taken from the core and not yet handed out one at a
77    /// time by `kui_take_window_command`.
78    pub(crate) window_commands: VecDeque<WindowCommand>,
79    /// The same for the menu actions `kui_take_menu_action` hands out, and
80    /// the text of the one most recently handed out — borrowed by the
81    /// caller until the next call, like every other string here.
82    pub(crate) menu_actions: VecDeque<kui_core::MenuAction>,
83    pub(crate) menu_text: String,
84    pub(crate) menu_html: String,
85    /// The label most recently read back by `kui_menu_bar_menu`,
86    /// `kui_menu_bar_item` or `kui_menu_item`, and the accelerator beside
87    /// it: their own, so reading a menu leaves a taken action's `text`
88    /// alone, as kui.h says it is (borrowed until the next
89    /// `kui_take_menu_action`).
90    pub(crate) row_text: String,
91    pub(crate) menu_accel: String,
92    /// What `kui_request_copy` most recently handed out, on the same terms.
93    pub(crate) copy_text: String,
94    /// The chord most recently handed out by `kui_devtools_key`, on the
95    /// same terms.
96    pub(crate) devtools_key: String,
97    /// The file dialog `kui_take_file_request` most recently handed out,
98    /// whose strings — and the filter `kui_file_request_filter` read,
99    /// its extensions joined — are borrowed until the next call.
100    pub(crate) file_request: Option<kui_core::FileDialog>,
101    pub(crate) file_filter_text: String,
102    /// The tab name most recently handed out by `kui_devtools_current_tab`,
103    /// on the same terms.
104    pub(crate) devtools_tab: String,
105    /// The selection most recently handed out by `kui_selection_text` /
106    /// `kui_selection_html`; valid until the next such call, like every
107    /// other borrowed string here.
108    pub(crate) selection_text: String,
109    pub(crate) selection_html: String,
110    /// The family names most recently handed out by `kui_font_families`,
111    /// held so the `KuiStr`s written into the host's array stay valid
112    /// until the next call.
113    pub(crate) font_families: Vec<String>,
114    /// The families most recently handed out by `kui_system_fonts`, held
115    /// for the same reason: their names and weights are what the
116    /// `KuiSystemFont`s written into the host's array point at.
117    pub(crate) system_fonts: Vec<kui_core::SystemFont>,
118    /// The node list most recently handed out by `kui_nodes`; borrowed
119    /// until the next call, like every other reading here.
120    pub(crate) nodes: Option<Box<KuiValue>>,
121    /// The name most recently handed out by kui_ctx_window_name; valid
122    /// until the next call.
123    pub(crate) last_window_name: Option<Rc<str>>,
124    /// Which slot this context is a C extension's fill of, and the params
125    /// the host passed it: what `kui_slot_name` / `kui_slot_params`
126    /// answer. `None` on every other context - a standalone one, a C host's
127    /// view callback - where they answer false and NULL.
128    pub(crate) slot_name: Option<String>,
129    pub(crate) slot_namespace: Option<String>,
130    pub(crate) slot_params: Option<KuiValue>,
131    /// The extensions this context hosts, in origin order.
132    /// `kui_ctx_add_extension` fills it, `kui_slot` fills *them* in place,
133    /// `kui_frame_finish` lets them take `ns/root` and warn about slots
134    /// nobody declared, and an event whose origin names one is delivered to
135    /// it rather than queued for the host. `kui_run_with` moves it into
136    /// the window's runner. Empty on a borrowing context: an extension
137    /// does not host extensions, and a host's under the runner is the
138    /// runner's.
139    pub(crate) extensions: kui_core::Extensions,
140    /// Why the last `kui_ctx_add_extension` said false. Valid until the
141    /// next call, like every other borrowed string here.
142    pub(crate) last_ext_error: String,
143    /// Set only for the length of a `kui_run_with` view callback: the
144    /// runner's own `Ui`, erased to a pointer because `KuiCtx` is what C
145    /// holds and has no lifetime to carry one.
146    ///
147    /// It exists because under the windowed runner the extension list is
148    /// the *runner's*, not this context's — `kui_native::Launcher` owns it, and
149    /// the `Ui` it built the frame with is what knows how to fill a slot.
150    /// So `kui_slot` hands the declaration to that `Ui` when this is set,
151    /// and fills from `extensions` when it is not. Null on every other
152    /// context: a standalone one, and an extension's own.
153    pub(crate) host_ui: *mut c_void,
154}
155
156impl KuiCtx {
157    pub(crate) fn core(&mut self) -> &mut Core {
158        unsafe { &mut *self.core }
159    }
160
161    /// Takes the core a standalone context owns, for `kui_run_with` to
162    /// open the window on, and leaves the context a fresh
163    /// one so that it stays a context — still the caller's to free, and
164    /// to use, as one that has registered nothing. `None` on a borrowing
165    /// context, whose core is someone else's frame.
166    #[cfg(any(feature = "runner", test))]
167    pub(crate) fn take_core(&mut self) -> Option<Box<Core>> {
168        let taken = self._owned.take()?;
169        let mut fresh = Box::new(Core::new());
170        fresh.set_diagnostics(false);
171        self.core = &mut *fresh;
172        self._owned = Some(fresh);
173        Some(taken)
174    }
175
176    /// A context that borrows someone else's frame instead of owning a
177    /// `Core`: what `kui_run`'s view callback and a C extension's both get.
178    /// Only the builder entry points are meaningful on one - the queues
179    /// below stay empty, because the runner owns event delivery.
180    ///
181    /// The borrow is not in the type (`KuiCtx` is what C holds, and it has
182    /// no lifetime), so the caller keeps it: use the context inside the
183    /// scope this `&mut Core` came from and let it go at the end of it.
184    pub(crate) fn borrowing(core: &mut Core) -> Self {
185        Self {
186            core,
187            _owned: None,
188            events: Vec::new(),
189            last_payload: None,
190            last_edit_text: None,
191            fragment_source: String::new(),
192            fragment_draws: Vec::new(),
193            texture_draws: Vec::new(),
194            draws_current: false,
195            image_pixels: None,
196            last_warnings: Vec::new(),
197            pending_warnings: Vec::new(),
198            last_access: Default::default(),
199            last_runs: Vec::new(),
200            last_announcements: Vec::new(),
201            window_commands: VecDeque::new(),
202            menu_actions: VecDeque::new(),
203            menu_text: String::new(),
204            row_text: String::new(),
205            menu_accel: String::new(),
206            copy_text: String::new(),
207            devtools_key: String::new(),
208            file_request: None,
209            file_filter_text: String::new(),
210            devtools_tab: String::new(),
211            menu_html: String::new(),
212            selection_text: String::new(),
213            selection_html: String::new(),
214            font_families: Vec::new(),
215            system_fonts: Vec::new(),
216            nodes: None,
217            last_window_name: None,
218            slot_name: None,
219            slot_namespace: None,
220            slot_params: None,
221            extensions: Default::default(),
222            last_ext_error: String::new(),
223            host_ui: std::ptr::null_mut(),
224        }
225    }
226
227    /// [`Self::borrowing`] for the windowed runner's view callback, which
228    /// has a whole `Ui` rather than a bare `Core`: same context, plus the
229    /// pointer `kui_slot` needs to reach the runner's extensions. Valid
230    /// for the callback and not one instruction longer, like the borrow
231    /// itself.
232    pub(crate) fn borrowing_in(ui: &mut kui_core::Ui<'_>) -> Self {
233        // `borrowing` keeps the core as a raw pointer, so the borrow of
234        // `ui` it takes ends when it returns and the `Ui` can be kept too.
235        let mut this = Self::borrowing(ui.core());
236        this.host_ui = std::ptr::from_mut(ui).cast();
237        this
238    }
239
240    /// Takes a batch of events the core just produced: the host's own are
241    /// queued for `kui_poll_event`, and one whose origin names a loaded
242    /// extension is delivered to it instead, its replies queued in its
243    /// place. `Extensions::route` is the walk, the
244    /// same one the Rust runner's `route_events` takes.
245    pub(crate) fn absorb(&mut self, events: impl IntoIterator<Item = UiEvent>) {
246        let out = &mut self.events;
247        self.extensions.route(events, |ev| out.push(ev));
248    }
249
250    /// Records a just-opened node's hover hint: the core floats it on
251    /// `kui_close` while the node is hovered (`Core::hint`).
252    pub(crate) fn push_tooltip(&mut self, key: kui_core::Key, hint: KuiStr) {
253        if let Some(h) = opt_str(hint) {
254            self.core().hint(key, h.into_owned());
255        }
256    }
257}
258
259/// An opaque dynamic value: null, bool, int, float, string, list or map.
260///
261/// Payloads a host attaches to nodes (`on_click` and the other tags) and
262/// the payloads events carry back are values. Build one with
263/// `kui_value_*` and read one with [`kui_value_get`], [`kui_value_at`],
264/// [`kui_value_as_str`] and friends. One you built is yours until a call
265/// documented as consuming it takes it; otherwise free it with
266/// [`kui_value_free`]. One the library hands out is borrowed.
267#[repr(transparent)]
268pub struct KuiValue(pub(crate) Value);
269
270/// A borrowed string: `len` bytes of UTF-8 at `ptr`, not NUL-terminated.
271///
272/// `KUI_STR("literal")` makes one in C and `kui_str_eq` compares one to a
273/// C string. A string the library hands out points into memory it owns and
274/// is valid until the next call of the same function on that context.
275/// Invalid UTF-8 going in is replaced. The layout is frozen.
276#[repr(C)]
277#[derive(Clone, Copy)]
278pub struct KuiStr {
279    pub ptr: *const u8,
280    pub len: usize,
281}
282
283/// One axis of a node's size: a `KUI_*` tag and its value.
284#[repr(C)]
285#[derive(Clone, Copy)]
286pub struct KuiSizing {
287    /// `KUI_FIT` (0), `KUI_GROW` (1, `value` the weight), `KUI_FIXED` (2,
288    /// logical px), `KUI_PERCENT` (3, `value` 0..1) or `KUI_CALC` (4,
289    /// `value` an expression number from `kui_size_*`).
290    pub tag: u32,
291    pub value: f32,
292}
293
294/// `KuiSpec.min_w` / `min_h` as the node's own fit size (`KUI_MIN_FIT`).
295pub const KUI_MIN_FIT: f32 = -1.0;
296/// `KuiSpec.min_w` / `min_h` as a declared floor of 0: no floor at all,
297/// not even the content's that a share in an overflowing row gets for an
298/// undeclared (0) one; CSS's `min-width: 0`.
299pub const KUI_MIN_NONE: f32 = -2.0;
300/// Which of a `KuiKeyframe`'s fields are set (its `set` bits).
301pub const KUI_KF_AT: u32 = 1 << 0;
302pub const KUI_KF_WIDTH: u32 = 1 << 1;
303pub const KUI_KF_HEIGHT: u32 = 1 << 2;
304pub const KUI_KF_BG: u32 = 1 << 3;
305pub const KUI_KF_RADIUS: u32 = 1 << 4;
306pub const KUI_KF_OPACITY: u32 = 1 << 5;
307
308/// One keyframe stop (`KuiSpec.keyframes`): a zeroed stop sets nothing.
309/// `set` says which fields count, so 0 stays a legal value for each.
310#[repr(C)]
311#[derive(Clone, Copy)]
312pub struct KuiKeyframe {
313    pub set: u32,
314    /// 0..1 (KUI_KF_AT); unset stops spread evenly, a lone one sits at 1.
315    pub at: f32,
316    pub width: KuiSizing,
317    pub height: KuiSizing,
318    /// 0xRRGGBBAA
319    pub bg: u32,
320    pub radius: f32,
321    /// Group opacity 0..1 (KUI_KF_OPACITY).
322    pub opacity: f32,
323}
324
325/// `KuiGradient.kind`: along a line, or out from a centre.
326pub const KUI_GRADIENT_LINEAR: u32 = 0;
327pub const KUI_GRADIENT_RADIAL: u32 = 1;
328
329/// One stop of a `KuiGradient`: a colour and where along the gradient
330/// it sits, 0..1 — or a negative `at` for a stop spaced evenly between
331/// its neighbours that have one.
332#[repr(C)]
333#[derive(Clone, Copy)]
334pub struct KuiGradientStop {
335    /// 0xRRGGBBAA
336    pub color: u32,
337    pub at: f32,
338}
339
340/// A box's gradient (`KuiSpec.gradient`,
341/// `docs/adr/0042-a-gradient-is-an-image-the-core-paints.md`): linear
342/// along `angle` — turns clockwise from east, in the box's unit square —
343/// or radial out from (`at_x`, `at_y`), fractions of the box, to its
344/// farthest corner. `stops_len` stops, two or more.
345#[repr(C)]
346#[derive(Clone, Copy)]
347pub struct KuiGradient {
348    /// `KUI_GRADIENT_*`.
349    pub kind: u32,
350    pub angle: f32,
351    pub at_x: f32,
352    pub at_y: f32,
353    pub stops: *const KuiGradientStop,
354    pub stops_len: usize,
355}
356
357/// Which of a `KuiEnter`'s fields are set (its `set` bits); 0 = no entrance.
358/// `KuiSpec.float_mode`: in flow, or which rect the float attaches to. The
359/// non-zero values are `kui_core::FLOAT_PRESETS` indices plus one, so zero
360/// can still mean "no float".
361pub const KUI_FLOAT_NONE: u32 = 0;
362pub const KUI_FLOAT_PARENT: u32 = 1;
363pub const KUI_FLOAT_VIEWPORT: u32 = 2;
364
365pub const KUI_ENTER_OFFSET: u32 = 1 << 0;
366pub const KUI_ENTER_WIDTH: u32 = 1 << 1;
367pub const KUI_ENTER_HEIGHT: u32 = 1 << 2;
368pub const KUI_ENTER_BG: u32 = 1 << 3;
369pub const KUI_ENTER_RADIUS: u32 = 1 << 4;
370pub const KUI_ENTER_OPACITY: u32 = 1 << 5;
371
372/// Where a node starts the first frame it is seen (`KuiSpec.enter`): the
373/// slots `set` names ease in from these values over `transition_ms`
374/// instead of snapping. A zeroed struct is no entrance.
375#[repr(C)]
376#[derive(Clone, Copy)]
377pub struct KuiEnter {
378    pub set: u32,
379    /// Logical px the node slides in from (KUI_ENTER_OFFSET).
380    pub dx: f32,
381    pub dy: f32,
382    pub width: KuiSizing,
383    pub height: KuiSizing,
384    /// 0xRRGGBBAA
385    pub bg: u32,
386    pub radius: f32,
387    /// Group opacity 0..1 (KUI_ENTER_OPACITY); 0 fades the subtree in.
388    pub opacity: f32,
389}
390
391/// Everything a box node is built from: size, layout, paint, behaviour
392/// tags and accessibility, as the `kui_open*` family and the stock
393/// widgets read it.
394///
395/// Zero-initialize it and set what you need; a zeroed field is its
396/// documented default. The library reads the whole struct, so build
397/// against the header that matches `kui_abi_version()`. Pointers (`KuiStr`
398/// and `KuiValue`) are borrowed while the node opens and never retained.
399#[repr(C)]
400#[derive(Clone, Copy)]
401pub struct KuiSpec {
402    pub width: KuiSizing,
403    pub height: KuiSizing,
404    /// Clamps applied after sizing resolves; 0 for max means unconstrained,
405    /// 0 for min is undeclared, `KUI_MIN_FIT` the node's own fit size and
406    /// `KUI_MIN_NONE` a declared 0.
407    pub min_w: f32,
408    pub max_w: f32,
409    pub min_h: f32,
410    pub max_h: f32,
411    /// `KUI_COLUMN` (0), `KUI_ROW` (1) or `KUI_TABLE` (2): a column whose
412    /// rows' children line up in columns.
413    pub dir: u32,
414    pub pad_l: f32,
415    pub pad_r: f32,
416    pub pad_t: f32,
417    pub pad_b: f32,
418    pub gap: f32,
419    /// 0 = start, 1 = center, 2 = end
420    pub main_align: u32,
421    pub cross_align: u32,
422    /// 0xRRGGBBAA; 0 = transparent
423    pub bg: u32,
424    pub border_color: u32,
425    pub border_w: f32,
426    pub radius: f32,
427    /// `KUI_CLIP` | `KUI_SCROLL_X` | `KUI_SCROLL_Y`; the same bits the
428    /// binary protocol carries, applied by `NodeSpec::overflow_bits`.
429    pub overflow: u32,
430    /// `KUI_FLOAT_NONE` (in flow), `KUI_FLOAT_PARENT` or `KUI_FLOAT_VIEWPORT`.
431    /// `kui_spec_float_preset` fills this and the fields below from one of
432    /// the named presets.
433    pub float_mode: u32,
434    /// Attach points as align values (0 start, 1 center, 2 end).
435    pub float_anchor_x: u32,
436    pub float_anchor_y: u32,
437    pub float_self_x: u32,
438    pub float_self_y: u32,
439    pub float_dx: f32,
440    pub float_dy: f32,
441    /// Non-zero: flip across the anchor / clamp to stay in the viewport.
442    pub float_fit: u32,
443    /// Non-zero: hover-track this node (kui_is_hovered) without a payload.
444    pub hoverable: u32,
445    /// Window-chrome role: 0 = none, 1 = drag, 2 = close button,
446    /// 3 = minimize button, 4 = maximize button. Chrome nodes emit window
447    /// commands (kui_take_window_command), never events.
448    pub window_role: u32,
449    /// Positive: ease sizing/colors/radius changes over this many ms (the
450    /// node needs a stable key, i.e. kui_open_keyed). Needs kui_set_time.
451    pub transition_ms: f32,
452    /// KUI_EASE_* curve for `transition_ms`.
453    pub easing: u32,
454    /// Non-zero (with `transition_ms`): also ease the node's position, so
455    /// reordered siblings slide into place.
456    pub slide: u32,
457    /// 0xRRGGBBAA background while hovered (or while any node in the same
458    /// hover group is); 0 = none. Implies hover tracking; eases with
459    /// `transition_ms`.
460    pub hover_bg: u32,
461    /// 0xRRGGBBAA background while pressed; 0 = none. Implies hover tracking.
462    pub pressed_bg: u32,
463    /// Hover group name (empty = none): members show hover_bg / pressed_bg
464    /// together. Hashed by the core; the string is not retained.
465    pub hover_group: KuiStr,
466    /// Non-zero: `radius_tl..radius_bl` are the four corner radii and
467    /// `radius` is ignored (zero = uniform `radius` on every corner).
468    pub per_corner: u32,
469    pub radius_tl: f32,
470    pub radius_tr: f32,
471    pub radius_br: f32,
472    pub radius_bl: f32,
473    /// KUI_REPEAT_* direction for `keyframes` (CSS animation-direction).
474    pub repeat: u32,
475    /// Holds the keyframe cycle back by this many ms (CSS animation-delay).
476    pub delay_ms: f32,
477    /// CSS-style stops (`keyframes_len` of them; NULL/0 = none): the slots
478    /// they name cycle over `transition_ms`, forever, without the view
479    /// redrawing. Read while the node opens; not retained.
480    pub keyframes: *const KuiKeyframe,
481    pub keyframes_len: usize,
482    /// Entrance: with `set` non-zero, the named slots ease in from these
483    /// values on the node's first sight (see `KuiEnter`).
484    pub enter: KuiEnter,
485    /// Registered sounds (`kui_sound_add`) played when the node is clicked /
486    /// the pointer enters it; 0 = none. Either makes the node hover-tracked.
487    pub click_sound: u64,
488    pub hover_sound: u64,
489    /// Layout tag (NULL = none): the node's laid-out rect arrives as
490    /// `{kind="layout", x, y, w, h, parent, tag}` on its first frame and
491    /// whenever it changes. Borrowed — cloned while the node opens, so the
492    /// caller keeps ownership and frees it as usual; `kui_value_null()`
493    /// asks for the events without a tag.
494    pub on_layout: *const KuiValue,
495    /// KUI_ROLE_* (0 = unset: the core derives one). What the node is to
496    /// assistive technology; KUI_ROLE_NONE hides it and its subtree.
497    pub role: u32,
498    /// Accessible name (empty = none). Copied while the node opens.
499    pub label: KuiStr,
500    /// Non-zero: a checkbox / radio / switch role is on.
501    pub checked: u32,
502    /// KUI_VALUE_NOW / MIN / MAX bits saying which of the three below are
503    /// set (a slider role's position and range).
504    pub value_set: u32,
505    pub value_now: f32,
506    pub value_min: f32,
507    pub value_max: f32,
508    /// On a KUI_ROLE_LINE of a custom editor: the caret's byte offset into
509    /// the line's text, and the selection's other end (KUI_VALUE_CARET /
510    /// KUI_VALUE_ANCHOR in `value_set` say which are present).
511    /// KUI_VALUE_CARET_SOLID beside KUI_VALUE_CARET says the caret is
512    /// solid — a block caret in a modal editor's normal mode — so
513    /// `kui_has_caret` leaves it out and no blink clock is armed on it,
514    /// while it still anchors the IME and reads to assistive technology.
515    pub caret: u32,
516    pub selection_anchor: u32,
517    /// Non-zero: reachable by Tab (and focused by a click) without a click
518    /// payload or a control role.
519    pub focusable: u32,
520    /// Non-zero: inert — no click, drag or key sink, no hover / pressed /
521    /// focus background, skipped by Tab, reported disabled to assistive
522    /// technology; hover tracking stays so a tooltip can say why.
523    pub disabled: u32,
524    /// 0xRRGGBBAA background while the node holds keyboard-visible focus
525    /// (Tab or assistive technology put it there); 0 = the core's default
526    /// ring.
527    pub focus_bg: u32,
528    /// Hover hint (empty = none), the `tooltip` prop of the other
529    /// bindings: makes the node hover-tracked, becomes its accessible
530    /// description, and floats `kui_core::widgets::tooltip` below it while
531    /// the pointer is over it: as its last child for the `kui_open*`
532    /// family and the `kui_fragment*` doors (backlog RG124), which
533    /// `kui_close` draws, and beside a leaf, anchored to it,
534    /// for the leaf doors (`leaf_spec_of`, backlog RG113).
535    pub tooltip: KuiStr,
536    /// Modal surface (NULL = none): while this node is declared the Tab
537    /// ring is its subtree, everything outside it is inert to the pointer,
538    /// the wheel and assistive technology, and Escape or a press outside
539    /// emits `{kind="dismiss", reason, tag}` on it — the app stops opening
540    /// the node. The last one declared wins (a confirm inside a dialog);
541    /// a modal that must cover the app is a float. Borrowed — cloned
542    /// while the node opens, so the caller keeps ownership;
543    /// `kui_value_null()` asks for the behaviour without a tag.
544    pub modal: *const KuiValue,
545    /// Context menu (NULL = none): a secondary-button press over this node
546    /// emits `{kind="contextmenu", x, y, tag}` on it, at the logical point
547    /// to open the menu at. The press moves no focus, places no caret and
548    /// produces no click. Borrowed — cloned while the node opens, so the
549    /// caller keeps ownership; `kui_value_null()` asks for the behaviour
550    /// without a tag.
551    pub on_context_menu: *const KuiValue,
552    /// KUI_CURSOR_* (0 = unset: the I-beam over text, the arrow otherwise
553    /// — a clickable or draggable node included). The pointer shape while
554    /// the pointer is over this node; a node with only a cursor is
555    /// hover-tracked so it can be found.
556    pub cursor: u32,
557    /// Non-zero: this node is the current one of its set — the shown tab,
558    /// the picked row, the link for the page you are on. A KUI_ROLE_TAB
559    /// reports the state either way; a row or a link reports it only when
560    /// this is set.
561    pub selected: u32,
562    /// KUI_EXPANDED_* (0 = unset: the node does not expand and says
563    /// nothing about it). What a twisty, an accordion header or a menu
564    /// button reads as.
565    pub expanded: u32,
566    /// Non-zero: `opacity` is the node's group opacity (0 without this bit
567    /// means "not set", so a fully transparent subtree stays expressible).
568    pub opacity_set: u32,
569    /// Group opacity 0..1 with `opacity_set`: fades this node and its whole
570    /// subtree. A per-quad alpha multiply, not an offscreen composite, so
571    /// overlapping pieces of one subtree show their seams through the fade;
572    /// layout, hit-testing and the access tree are untouched. Eases with
573    /// `transition_ms`.
574    pub opacity: f32,
575    /// 0xRRGGBBAA drop-shadow color; 0 = no shadow, and nothing else here
576    /// draws without it. The shadow is the node's rounded rect moved by
577    /// `shadow_x`/`shadow_y`, grown by `shadow_spread` and blurred over
578    /// `shadow_blur`, painted behind the node. Outer shadows only, and the
579    /// shape is not knocked out of the middle.
580    pub shadow_color: u32,
581    /// Blur radius (logical px): the edge ramps over this distance and
582    /// reaches this far past the shape. 0 = a hard edge.
583    pub shadow_blur: f32,
584    /// Offset (logical px); positive `shadow_y` casts downward.
585    pub shadow_x: f32,
586    pub shadow_y: f32,
587    /// Grows (negative: shrinks) the shape before blurring (logical px).
588    pub shadow_spread: f32,
589    /// Non-zero: children that don't fit the main axis start a new line
590    /// instead of overflowing or shrinking. Rows only — a column, or a row
591    /// with KUI_OVERFLOW_SCROLL_X, lays out as if this were 0 and raises a
592    /// `wrap-ignored` warning.
593    pub wrap_children: u32,
594    /// Space between wrap lines, across the main axis (`gap` stays the
595    /// space between children along it).
596    pub cross_gap: f32,
597    /// Non-zero: where focus lands when the enclosing `modal` scope is
598    /// entered - the first node in the modal's Tab ring declaring it,
599    /// instead of the ring's first, so a destructive confirm opens on its
600    /// Cancel. Read on entry only; nothing declaring it (or only nodes
601    /// the ring skips) keeps the ring's first node.
602    pub initial_focus: u32,
603    /// Exit: with `set` non-zero and a `transition_ms`, the frame after the
604    /// view stops declaring this node its subtree is copied out of the last
605    /// frame that had it and replayed — frozen where layout left it, on top
606    /// of everything and inert — while the named slots ease from where they
607    /// were to these values (see `KuiEnter`, which an exit reuses: an exit
608    /// is an entrance read the other way).
609    pub exit: KuiEnter,
610    /// KUI_LIVE_* (0 = KUI_LIVE_OFF, the default): when the text inside
611    /// this node changes, a screen reader reads the change without being
612    /// asked. A node that declares it is semantic, so a plain box marked
613    /// live is not elided from the access tree. For a one-off with no node
614    /// behind it, `kui_announce` is the other half.
615    pub live: u32,
616    /// Non-zero, with a non-NULL `on_key` on `kui_open_with`: the sink hears
617    /// releases too, as the same `{kind="key"}` payload with `phase="up"`
618    /// (`text` null, `repeat` false) — for a held-key interaction (WASD,
619    /// press-and-hold, a key that arms a mode while it is down). A key only
620    /// comes up where it went down, and focus leaving while a key is held
621    /// delivers the `up` first. Zero: presses only, which is what a keymap
622    /// wants — one that heard both halves would run every binding twice.
623    pub key_up: u32,
624    /// What a slider role's position reads as, empty for none (ARIA's
625    /// `aria-valuetext`). Without one a reader has only `value_now` and the
626    /// range and says a percentage — 25 in [5..60] is "36 percent" — so a
627    /// value whose unit carries the meaning says it here: "25 minutes". It
628    /// replaces the number in the reading rather than joining it, and a
629    /// nudge announces the new text. Copied while the node opens. It
630    /// arrives back as `KuiAccessNode.value` (KUI_ACCESS_HAS_VALUE), the
631    /// one string slot a node has.
632    pub value_text: KuiStr,
633    /// The accessible description (empty = none): the extra sentence a
634    /// reader says after the name, for what the name cannot say on its own
635    /// — what a button will do, why a control is disabled, what format a
636    /// field wants. `tooltip` is the shorthand that also draws the string
637    /// and hover-tracks the node; this is the description alone, for a hint
638    /// that is spoken and never drawn. Both write the one slot and this one
639    /// is applied second, so it wins over a `tooltip` on the same node. It
640    /// reads only on a node that reaches the access tree — a role, a label,
641    /// a control — since a plain box is elided and takes its description
642    /// with it. Borrowed while the node opens.
643    pub description: KuiStr,
644    /// Non-zero: ask for another frame after this one, every frame this
645    /// node is declared (`animate`). What a `kui_fragment` reading `time`
646    /// needs. Opt-in, because it takes the loop off input-driven; one node
647    /// asking is enough for the window.
648    pub animate: u32,
649    /// Non-zero: paint this node's background in the OS accent colour
650    /// (`kui_env_set_system`), keeping `bg` where the host never said what
651    /// it is. On `kui_button_with` it takes the hover and pressed shades
652    /// and the label colour with it.
653    pub accent: u32,
654    /// Non-zero: this node is a selection scope. The text of every node
655    /// inside it selects as one run, and a press-drag across them takes
656    /// the lot. Declared on the container, not on each label.
657    pub selectable: u32,
658    /// Force-click tag (`on_force_click`): a press that deepens past the
659    /// second stage of a Force Touch trackpad over this node emits
660    /// `{kind:"forceclick", x, y, tag}` on it. Borrowed while the node
661    /// opens, like every other tag.
662    pub on_force_click: *const KuiValue,
663    /// Non-zero: this node's subtree is a focus region, a Tab ring of its
664    /// own that the ring outside never enters and that never leaves.
665    /// Entered on purpose: `kui_focus_region`, a press inside it, or a
666    /// focus on a node in it. Nothing else about the node changes.
667    pub focus_region: u32,
668    /// When this node's scrollbars are drawn: `KUI_SCROLLBAR_*` (the
669    /// `scrollbar` row's index plus one), 0 for the default, which is
670    /// `KUI_SCROLLBAR_VISIBLE`.
671    pub scrollbar: u32,
672    /// The thumb's width at rest, logical px; 0 for the stock 4. Under
673    /// the pointer or dragged it is 2 px wider.
674    pub scrollbar_width: f32,
675    /// The thumb at rest and under the pointer, `0xRRGGBBAA`; 0 (fully
676    /// transparent, like every unset colour) for the theme's `scrollbar`
677    /// and `scrollbar_active`.
678    pub scrollbar_color: u32,
679    pub scrollbar_active_color: u32,
680    /// Non-zero: scroll anchoring on this scrolling node (CSS's
681    /// `overflow-anchor`): the first child in view keeps its place on
682    /// screen when the content before it changes size.
683    pub anchor: u32,
684    /// Scroll tag (`on_scroll`): the wheel over this node emits
685    /// `{kind:"scroll", x, y, dx, dy, lines, tag}` on it instead of
686    /// scrolling anything (`lines` the whole lines a `cells` grid's delta
687    /// covers, null elsewhere), and a drag-select held past a grid's edge
688    /// arrives the same way once a frame. Borrowed while the node opens,
689    /// like every other tag.
690    pub on_scroll: *const KuiValue,
691    /// Drop-zone tag (`on_drop`): files dragged in from the OS over this
692    /// node emit `{kind:"drop", phase, paths, x, y, tag}` on it, `phase`
693    /// one of `enter`, `move`, `leave`, `drop`. A node inside a zone is
694    /// the zone's; a node that is no zone is looked past. Borrowed while
695    /// the node opens, like every other tag. ABI 18.
696    pub on_drop: *const KuiValue,
697    /// Background while dragged files are over this node, `0xRRGGBBAA`;
698    /// 0 for none. Wins over `pressed_bg`, `focus_bg` and `hover_bg`;
699    /// eases with `transition`. ABI 18.
700    pub drop_bg: u32,
701    /// Non-zero: a `KUI_FLOAT_PARENT` float takes its parent's clip, as a
702    /// child does, instead of escaping every ancestor's: cut at a `clip`
703    /// canvas's edge and not hit past it. Read with the parent anchor
704    /// only; still painted as a layer over its in-flow siblings. ABI 19.
705    pub float_clip: u32,
706    /// Width over height (`aspectRatio`); 0 for none. It sizes the axis
707    /// whose sizing is fit: a fit height from the final width, a fit width
708    /// from a fixed height. ABI 19.
709    pub aspect_ratio: f32,
710    /// A checkbox that is neither on nor off (`mixed`): read as mixed
711    /// whatever `checked` says, drawn as a dash by `kui_checkbox`. ABI 19.
712    pub mixed: u32,
713    /// A slider's step (`valueStep`), present when `KUI_VALUE_STEP` is in
714    /// `value_set`. ABI 19.
715    pub value_step: f32,
716    /// A slider's change tag (`onChange`): the core turns a press, a drag,
717    /// the arrows, PageUp / PageDown and Home / End into
718    /// `{kind:"change", value, phase, tag}`. Borrowed while the node
719    /// opens, like every other tag. ABI 19.
720    pub on_change: *const KuiValue,
721    /// Non-zero: paint the background, border, shadow and fragment with each edge
722    /// on a whole physical pixel (`pixelSnap`), so snapped boxes that
723    /// share an edge in layout, and one beside a text's background, meet
724    /// without a seam. A zeroed field is a box drawn where layout put it.
725    /// ABI 20.
726    pub pixel_snap: u32,
727    /// Non-zero: a press on this node or inside it leaves keyboard focus
728    /// where it was (`keepFocus`). ABI 20.
729    pub keep_focus: u32,
730    /// Focus entering or leaving this node's subtree emits `{kind:"focus",
731    /// phase, by, tag}` (`onFocus`). Borrowed while the node opens, like
732    /// every other tag. ABI 20.
733    pub on_focus: *const KuiValue,
734    /// A table's grid rules (`rules`), 0xRRGGBBAA; 0 draws none. ABI 20.
735    pub rules: u32,
736    /// Their width in logical px (`ruleWidth`); 0 is 1. ABI 20.
737    pub rule_w: f32,
738    /// The non-primary buttons as `{kind:"button", phase, button, x, y,
739    /// clicks, tag}` events on the node that claims them, captured from
740    /// press to release (`onButton`). Borrowed while the node opens, like
741    /// every other tag. ABI 20.
742    pub on_button: *const KuiValue,
743    /// Which buttons `on_button` claims, as `KUI_BUTTONS_*` bits; 0 is all
744    /// three. ABI 20.
745    pub buttons: u32,
746    /// Whether a scroll gesture starting over this scroller at its limit
747    /// goes on to the one around it (`overscroll`): `KUI_OVERSCROLL_*`,
748    /// the row's index plus one; 0 is `auto`. ABI 20.
749    pub overscroll: u32,
750    /// Which axes `on_scroll` takes (`scrollAxes`): `KUI_SCROLL_AXES_*`,
751    /// the row's index plus one; 0 is both. ABI 20.
752    pub scroll_axes: u32,
753    /// Non-zero, with `on_key`: the modifier and lock keys arrive as keys
754    /// of their own (`modifierKeys`), with codes "shift", "ctrl", "alt",
755    /// "super", "capslock", "numlock", "scrolllock" and the side in
756    /// `location`. Zero: a modifier is only ever held. ABI 21.
757    pub modifier_keys: u32,
758    /// The clamps as size expressions: a `KUI_FIXED`, `KUI_PERCENT` or
759    /// `KUI_CALC` sizing (`kui_size_*`) here replaces the float of the
760    /// same name, resolved by layout against the parent's content box;
761    /// zeroed (`KUI_FIT`), the float holds. ABI 22.
762    pub min_w_size: KuiSizing,
763    pub max_w_size: KuiSizing,
764    pub min_h_size: KuiSizing,
765    pub max_h_size: KuiSizing,
766    /// How far a spring overshoots (`bounce`), in place of a spring
767    /// easing's own, and making a timed easing a spring; 0 is the
768    /// easing's own, since a spring with none is `KUI_EASE_SMOOTH`.
769    /// ABI 23.
770    pub bounce: f32,
771    /// A gradient painted over `bg`, under the border and the children;
772    /// NULL for none. Read during the call. ABI 24.
773    pub gradient: *const KuiGradient,
774    /// The modifiers `on_scroll` is for (`scrollMods`), as `KUI_KMOD_*`
775    /// bits: with any set, the node hears only a scroll gesture begun
776    /// with one of them held, ahead of every scroller under the pointer.
777    /// 0 is none: a handler like any other. ABI 25.
778    pub scroll_mods: u32,
779    /// Blur what was drawn beneath the node, inside its rounded box, by
780    /// this radius in logical px (`backdropBlur`, backlog F129); 0 is
781    /// none. ABI 26.
782    pub backdrop_blur: f32,
783}
784
785/// One laid-out run of an editor's text (`kui_access_runs`): what a
786/// screen reader reads by character and word. `text` ends with `"\n"`
787/// (counted as a zero-width character) when the line continues into
788/// another. Character positions are relative to `x`. Arrays and strings
789/// are borrowed until the next `kui_access_tree` / `kui_access_runs` on
790/// the context.
791#[repr(C)]
792#[derive(Clone, Copy)]
793pub struct KuiAccessRun {
794    /// The run's own id (a `KuiAccessNode.anchor_run` / `focus_run`, and
795    /// what `kui_input_access_text` takes).
796    pub key: u64,
797    /// The line it belongs to (a buffer line, or a KUI_ROLE_LINE ordinal
798    /// for a custom editor) and its byte range in that line's text.
799    pub line: u32,
800    pub start: u32,
801    pub end: u32,
802    pub text: KuiStr,
803    pub x: f32,
804    pub y: f32,
805    pub w: f32,
806    pub h: f32,
807    pub char_count: u32,
808    pub char_lengths: *const u8,
809    pub char_positions: *const f32,
810    pub char_widths: *const f32,
811    pub word_start_count: u32,
812    pub word_starts: *const u8,
813    pub rtl: u32,
814}
815
816pub const KUI_VALUE_CARET: u32 = 1 << 3;
817pub const KUI_VALUE_ANCHOR: u32 = 1 << 4;
818pub const KUI_VALUE_CARET_SOLID: u32 = 1 << 5;
819pub const KUI_ACCESS_HAS_TEXT_SELECTION: u32 = 1 << 9;
820
821/// One node of the access tree (`kui_access_tree`): what assistive
822/// technology sees. `role` is KUI_ROLE_*, `flags` KUI_ACCESS_HAS_* /
823/// FOCUSED / CHECKED bits saying which optional fields hold, `actions`
824/// the KUI_ACCESS_* bits the node accepts through `kui_input_access`.
825/// Strings are borrowed until the next `kui_access_tree` on the context.
826#[repr(C)]
827#[derive(Clone, Copy)]
828pub struct KuiAccessNode {
829    pub key: u64,
830    /// The nearest semantic ancestor; 0 for the root.
831    pub parent: u64,
832    pub origin: u32,
833    pub role: u32,
834    pub flags: u32,
835    pub actions: u32,
836    pub name: KuiStr,
837    pub description: KuiStr,
838    /// The node's one string value (KUI_ACCESS_HAS_VALUE): an editor's
839    /// text, or a slider's `value_text` — the platform has one slot, and a
840    /// slider that named its reading reads as that instead of its number.
841    pub value: KuiStr,
842    /// Logical px, viewport coordinates, cut to the node's clip (zero-size
843    /// on the clip's edge when wholly clipped).
844    pub x: f32,
845    pub y: f32,
846    pub w: f32,
847    pub h: f32,
848    /// Byte offsets into `value` (KUI_ACCESS_HAS_VALUE / HAS_SELECTION).
849    pub caret: u32,
850    pub selection_start: u32,
851    pub selection_end: u32,
852    /// A slider's position and range (KUI_ACCESS_HAS_NUMBER / MIN / MAX).
853    pub value_now: f32,
854    pub value_min: f32,
855    pub value_max: f32,
856    /// A scroll view's offsets and range (KUI_ACCESS_HAS_SCROLL).
857    pub scroll_x: f32,
858    pub scroll_y: f32,
859    pub scroll_max_x: f32,
860    pub scroll_max_y: f32,
861    /// An editor's caret (`focus_*`) and the selection's other end
862    /// (`anchor_*`) as run positions (KUI_ACCESS_HAS_TEXT_SELECTION):
863    /// a run key from `kui_access_runs` and a character index into it.
864    pub anchor_run: u64,
865    pub anchor_char: u32,
866    pub focus_run: u64,
867    pub focus_char: u32,
868    /// How many runs `kui_access_runs` returns for this node.
869    pub run_count: u32,
870    /// "3 of 7" (KUI_ACCESS_HAS_POS_IN_SET / HAS_SET_SIZE): the item's
871    /// zero-based ordinal among its list's or tab list's items, and the
872    /// count on that container.
873    pub pos_in_set: u32,
874    pub set_size: u32,
875    /// KUI_ORIENTATION_* (0 = unset: this node is not a composite
876    /// container). How the container arranges its items, from its own
877    /// `dir`.
878    pub orientation: u32,
879}
880
881pub const KUI_ACCESS_HAS_VALUE: u32 = 1 << 0;
882pub const KUI_ACCESS_HAS_SELECTION: u32 = 1 << 1;
883pub const KUI_ACCESS_FOCUSED: u32 = 1 << 2;
884pub const KUI_ACCESS_CHECKED_SET: u32 = 1 << 3;
885pub const KUI_ACCESS_CHECKED: u32 = 1 << 4;
886pub const KUI_ACCESS_HAS_NUMBER: u32 = 1 << 5;
887pub const KUI_ACCESS_HAS_MIN: u32 = 1 << 6;
888pub const KUI_ACCESS_HAS_MAX: u32 = 1 << 7;
889pub const KUI_ACCESS_HAS_SCROLL: u32 = 1 << 8;
890/// The node is `disabled`: inert, not a Tab stop (bit 9 is
891/// KUI_ACCESS_HAS_TEXT_SELECTION).
892pub const KUI_ACCESS_DISABLED: u32 = 1 << 10;
893/// The node is the frame's `modal` surface (`aria-modal`): focus and input
894/// are confined to it.
895pub const KUI_ACCESS_MODAL: u32 = 1 << 11;
896/// The node has a selected state at all, and what it is: every
897/// KUI_ROLE_TAB, and a row or link the view marked (see `KuiSpec.selected`).
898pub const KUI_ACCESS_SELECTED_SET: u32 = 1 << 12;
899pub const KUI_ACCESS_SELECTED: u32 = 1 << 13;
900/// The node expands, and whether it is open (see `KuiSpec.expanded`).
901pub const KUI_ACCESS_EXPANDED_SET: u32 = 1 << 14;
902pub const KUI_ACCESS_EXPANDED: u32 = 1 << 15;
903/// `pos_in_set` holds (on an item), `set_size` holds (on its container).
904pub const KUI_ACCESS_HAS_POS_IN_SET: u32 = 1 << 16;
905pub const KUI_ACCESS_HAS_SET_SIZE: u32 = 1 << 17;
906/// A checkbox that is neither on nor off (`KuiSpec.mixed`); set beside
907/// `KUI_ACCESS_CHECKED_SET`, whose `KUI_ACCESS_CHECKED` it outranks.
908pub const KUI_ACCESS_MIXED: u32 = 1 << 20;
909/// The node declared `live` (see `KuiSpec.live`), and which politeness.
910/// Two bits rather than a field, because `KuiAccessNode` is an array the
911/// host allocates and appending to it would be an ABI break.
912pub const KUI_ACCESS_LIVE_POLITE: u32 = 1 << 18;
913pub const KUI_ACCESS_LIVE_ASSERTIVE: u32 = 1 << 19;
914
915/// KUI_ORIENTATION_* is the position in `Orientation::ALL` plus one
916/// (0 = unset: the node is not a composite container).
917pub const KUI_ORIENTATION_HORIZONTAL: u32 = 1;
918pub const KUI_ORIENTATION_VERTICAL: u32 = 2;
919
920pub(crate) fn orientation_code(o: Option<kui_core::Orientation>) -> u32 {
921    o.and_then(|o| kui_core::Orientation::ALL.iter().position(|x| *x == o))
922        .map_or(0, |i| i as u32 + 1)
923}
924
925/// KUI_EXPANDED_* is the position in `schema::EXPANDED` plus one (0 = unset:
926/// the node does not expand).
927pub const KUI_EXPANDED_COLLAPSED: u32 = 1;
928pub const KUI_EXPANDED_EXPANDED: u32 = 2;
929
930/// KUI_SCROLLBAR_* is the position in `schema::SCROLLBARS` plus one (0 =
931/// unset, which is the stock visible bar).
932pub const KUI_SCROLLBAR_VISIBLE: u32 = 1;
933pub const KUI_SCROLLBAR_HIDDEN: u32 = 2;
934pub const KUI_SCROLLBAR_AUTO: u32 = 3;
935
936/// KUI_OVERSCROLL_* is the position in `schema::OVERSCROLLS` plus one (0 =
937/// unset, which is `auto`).
938pub const KUI_OVERSCROLL_AUTO: u32 = 1;
939pub const KUI_OVERSCROLL_CONTAIN: u32 = 2;
940
941/// KUI_SCROLL_AXES_* is the position in `schema::SCROLL_AXES` plus one (0
942/// = unset, which is both).
943pub const KUI_SCROLL_AXES_BOTH: u32 = 1;
944pub const KUI_SCROLL_AXES_X: u32 = 2;
945pub const KUI_SCROLL_AXES_Y: u32 = 3;
946
947/// KUI_LIVE_* is the position in `schema::LIVE` itself, not the position
948/// plus one: unlike a disclosure, a live region's zero *is* a value —
949/// "not a live region" is what an unset field already means, so there is
950/// no unset state to reserve zero for.
951pub const KUI_LIVE_OFF: u32 = 0;
952pub const KUI_LIVE_POLITE: u32 = 1;
953pub const KUI_LIVE_ASSERTIVE: u32 = 2;
954
955/// One queued announcement (`kui_take_announcements`): something to say
956/// once, with no node behind it. `live` is KUI_LIVE_POLITE or
957/// KUI_LIVE_ASSERTIVE — never KUI_LIVE_OFF, which `kui_announce` drops.
958/// `text` borrows the context's buffer and stays valid until the next
959/// `kui_take_announcements` on the same context.
960#[repr(C)]
961#[derive(Clone, Copy)]
962pub struct KuiAnnouncement {
963    pub text: KuiStr,
964    pub live: u32,
965}
966
967pub const KUI_VALUE_NOW: u32 = 1 << 0;
968pub const KUI_VALUE_MIN: u32 = 1 << 1;
969pub const KUI_VALUE_MAX: u32 = 1 << 2;
970/// `KuiSpec.value_step` holds.
971pub const KUI_VALUE_STEP: u32 = 1 << 6;
972
973/// KUI_ROLE_* is the position in `Role::ALL` plus one (0 = unset).
974pub(crate) fn role_code(role: kui_core::Role) -> u32 {
975    kui_core::Role::ALL
976        .iter()
977        .position(|r| *r == role)
978        .map_or(0, |i| i as u32 + 1)
979}
980
981pub(crate) fn role_of_code(code: u32) -> Option<kui_core::Role> {
982    (code > 0)
983        .then(|| kui_core::Role::ALL.get(code as usize - 1).copied())
984        .flatten()
985}
986
987/// What a piece of text measures (`kui_measure_text`), logical px at the
988/// scale of the current or last frame.
989#[repr(C)]
990#[derive(Clone, Copy)]
991pub struct KuiTextMetrics {
992    /// `[out]` reservation; see `KUI_TEXT_METRICS_INIT`.
993    pub size: u32,
994    pub width: f32,
995    pub height: f32,
996    /// Lines after wrapping (capped by `max_lines`).
997    pub lines: u32,
998}
999
1000// Hand-written rather than derived: a derived `Default` would zero `size`,
1001// and a zero `size` is the one value the handshake refuses.
1002impl Default for KuiTextMetrics {
1003    fn default() -> Self {
1004        Self {
1005            size: std::mem::size_of::<Self>() as u32,
1006            width: 0.0,
1007            height: 0.0,
1008            lines: 0,
1009        }
1010    }
1011}
1012
1013// SAFETY: `repr(C)` with `size: u32` first.
1014unsafe impl OutParam for KuiTextMetrics {
1015    const ABI_V1_SIZE: u32 = abi_through!(KuiTextMetrics, lines, u32);
1016    fn size_mut(&mut self) -> &mut u32 {
1017        &mut self.size
1018    }
1019}
1020
1021/// The palette a frame paints with: one `0xRRGGBBAA` per role, derived
1022/// from what the host reported through `kui_env_set_system` unless it
1023/// pinned something else. Written by `kui_theme` (`[out]`, so start from
1024/// `KUI_THEME_INIT`) and read whole by `kui_theme_set` (`[in]`).
1025///
1026/// The roles are `kui_core::schema::THEME_ROLES` field for field, in that
1027/// order; a test pins the two together, and an append here bumps
1028/// `KUI_ABI_VERSION`.
1029#[repr(C)]
1030#[derive(Clone, Copy)]
1031pub struct KuiTheme {
1032    /// `[out]` reservation; see `KUI_THEME_INIT`. Ignored by
1033    /// `kui_theme_set`, which reads the struct the host filled.
1034    pub size: u32,
1035    /// Which base this came from: `KUI_APPEARANCE_UNKNOWN` (0, the dark
1036    /// base without claiming the user chose it) / `_LIGHT` / `_DARK`.
1037    pub appearance: u32,
1038    /// What a disabled control's opacity is multiplied by.
1039    pub disabled_opacity: f32,
1040    pub bg: u32,
1041    pub surface: u32,
1042    pub raised: u32,
1043    pub sunken: u32,
1044    pub border: u32,
1045    pub border_strong: u32,
1046    pub fg: u32,
1047    pub muted: u32,
1048    pub faint: u32,
1049    pub accent: u32,
1050    pub accent_hover: u32,
1051    pub accent_pressed: u32,
1052    pub on_accent: u32,
1053    pub accent_soft: u32,
1054    pub selection: u32,
1055    pub focus_ring: u32,
1056    pub hover: u32,
1057    pub pressed: u32,
1058    pub success: u32,
1059    pub warning: u32,
1060    pub danger: u32,
1061    pub scrollbar: u32,
1062    pub scrollbar_active: u32,
1063}
1064
1065// Hand-written for the same reason `KuiTextMetrics`'s is: a zeroed `size`
1066// is the one value the handshake refuses.
1067impl Default for KuiTheme {
1068    fn default() -> Self {
1069        Self {
1070            size: std::mem::size_of::<Self>() as u32,
1071            appearance: 0,
1072            disabled_opacity: 0.0,
1073            bg: 0,
1074            surface: 0,
1075            raised: 0,
1076            sunken: 0,
1077            border: 0,
1078            border_strong: 0,
1079            fg: 0,
1080            muted: 0,
1081            faint: 0,
1082            accent: 0,
1083            accent_hover: 0,
1084            accent_pressed: 0,
1085            on_accent: 0,
1086            accent_soft: 0,
1087            selection: 0,
1088            focus_ring: 0,
1089            hover: 0,
1090            pressed: 0,
1091            success: 0,
1092            warning: 0,
1093            danger: 0,
1094            scrollbar: 0,
1095            scrollbar_active: 0,
1096        }
1097    }
1098}
1099
1100// SAFETY: `repr(C)` with `size: u32` first.
1101unsafe impl OutParam for KuiTheme {
1102    const ABI_V1_SIZE: u32 = abi_through!(KuiTheme, scrollbar_active, u32);
1103    fn size_mut(&mut self) -> &mut u32 {
1104        &mut self.size
1105    }
1106}
1107
1108/// The sizes the stock widgets are built from (`kui_metrics`,
1109/// `kui_metrics_set`), in logical px before the scale factor:
1110/// `kui_core::schema::METRIC_ROLES` field for field, in that order. An
1111/// append here bumps `KUI_ABI_VERSION`.
1112#[repr(C)]
1113#[derive(Clone, Copy)]
1114pub struct KuiMetrics {
1115    /// `[out]` reservation; see `KUI_METRICS_INIT`. Ignored by
1116    /// `kui_metrics_set`, which reads the struct the host filled.
1117    pub size: u32,
1118    pub control_text: f32,
1119    pub chrome_text: f32,
1120    pub hint_text: f32,
1121    pub radius: f32,
1122    pub radius_inner: f32,
1123    pub control_pad_x: f32,
1124    pub control_pad_y: f32,
1125    pub field_pad_x: f32,
1126    pub field_pad_y: f32,
1127    pub hint_pad_x: f32,
1128    pub hint_pad_y: f32,
1129    pub menu_pad_x: f32,
1130    pub menu_pad_y: f32,
1131    pub menu_width: f32,
1132    pub menu_bar_h: f32,
1133    pub titlebar_h: f32,
1134}
1135
1136impl Default for KuiMetrics {
1137    fn default() -> Self {
1138        Self::of(&kui_core::Metrics::default())
1139    }
1140}
1141
1142impl KuiMetrics {
1143    /// A core `Metrics` as the host reads it, through the role table so
1144    /// the two cannot disagree on a field.
1145    pub(crate) fn of(m: &kui_core::Metrics) -> Self {
1146        let mut out = Self {
1147            size: std::mem::size_of::<Self>() as u32,
1148            control_text: 0.0,
1149            chrome_text: 0.0,
1150            hint_text: 0.0,
1151            radius: 0.0,
1152            radius_inner: 0.0,
1153            control_pad_x: 0.0,
1154            control_pad_y: 0.0,
1155            field_pad_x: 0.0,
1156            field_pad_y: 0.0,
1157            hint_pad_x: 0.0,
1158            hint_pad_y: 0.0,
1159            menu_pad_x: 0.0,
1160            menu_pad_y: 0.0,
1161            menu_width: 0.0,
1162            menu_bar_h: 0.0,
1163            titlebar_h: 0.0,
1164        };
1165        for role in kui_core::schema::METRIC_ROLES {
1166            *out.field_mut(role.name) = (role.get)(m);
1167        }
1168        out
1169    }
1170
1171    /// The host's struct as a core `Metrics`, through the same table.
1172    pub(crate) fn to_core(self) -> kui_core::Metrics {
1173        let mut m = kui_core::Metrics::default();
1174        for role in kui_core::schema::METRIC_ROLES {
1175            (role.set)(&mut m, *self.field(role.name));
1176        }
1177        m
1178    }
1179
1180    fn field(&self, name: &str) -> &f32 {
1181        match name {
1182            "control_text" => &self.control_text,
1183            "chrome_text" => &self.chrome_text,
1184            "hint_text" => &self.hint_text,
1185            "radius" => &self.radius,
1186            "radius_inner" => &self.radius_inner,
1187            "control_pad_x" => &self.control_pad_x,
1188            "control_pad_y" => &self.control_pad_y,
1189            "field_pad_x" => &self.field_pad_x,
1190            "field_pad_y" => &self.field_pad_y,
1191            "hint_pad_x" => &self.hint_pad_x,
1192            "hint_pad_y" => &self.hint_pad_y,
1193            "menu_pad_x" => &self.menu_pad_x,
1194            "menu_pad_y" => &self.menu_pad_y,
1195            "menu_width" => &self.menu_width,
1196            "menu_bar_h" => &self.menu_bar_h,
1197            "titlebar_h" => &self.titlebar_h,
1198            other => panic!("METRIC_ROLES names a metric KuiMetrics lacks: {other}"),
1199        }
1200    }
1201
1202    fn field_mut(&mut self, name: &str) -> &mut f32 {
1203        match name {
1204            "control_text" => &mut self.control_text,
1205            "chrome_text" => &mut self.chrome_text,
1206            "hint_text" => &mut self.hint_text,
1207            "radius" => &mut self.radius,
1208            "radius_inner" => &mut self.radius_inner,
1209            "control_pad_x" => &mut self.control_pad_x,
1210            "control_pad_y" => &mut self.control_pad_y,
1211            "field_pad_x" => &mut self.field_pad_x,
1212            "field_pad_y" => &mut self.field_pad_y,
1213            "hint_pad_x" => &mut self.hint_pad_x,
1214            "hint_pad_y" => &mut self.hint_pad_y,
1215            "menu_pad_x" => &mut self.menu_pad_x,
1216            "menu_pad_y" => &mut self.menu_pad_y,
1217            "menu_width" => &mut self.menu_width,
1218            "menu_bar_h" => &mut self.menu_bar_h,
1219            "titlebar_h" => &mut self.titlebar_h,
1220            other => panic!("METRIC_ROLES names a metric KuiMetrics lacks: {other}"),
1221        }
1222    }
1223}
1224
1225// SAFETY: `repr(C)` with `size: u32` first.
1226unsafe impl OutParam for KuiMetrics {
1227    const ABI_V1_SIZE: u32 = abi_through!(KuiMetrics, titlebar_h, f32);
1228    fn size_mut(&mut self) -> &mut u32 {
1229        &mut self.size
1230    }
1231}
1232
1233/// One diagnostic (`kui_take_warnings`): a silent misconfiguration the
1234/// core noticed. `code` is stable (`grow-weight-ignored`,
1235/// `transition-auto-key`, `duplicate-key`); the strings are borrowed until
1236/// the next `kui_take_warnings` on the same context.
1237#[repr(C)]
1238#[derive(Clone, Copy)]
1239pub struct KuiWarning {
1240    pub code: KuiStr,
1241    pub key: u64,
1242    pub message: KuiStr,
1243}
1244
1245/// One installed or loaded font family (`kui_system_fonts`), as the font
1246/// database read its faces. `family` and `weights` are borrowed until the
1247/// next `kui_system_fonts` on the same context.
1248#[repr(C)]
1249#[derive(Clone, Copy)]
1250pub struct KuiSystemFont {
1251    /// The name `kui_font_add_system` takes.
1252    pub family: KuiStr,
1253    /// The weights its faces come in (400 regular, 700 bold), sorted,
1254    /// each once; `weight_count` of them.
1255    pub weights: *const u16,
1256    pub weight_count: u32,
1257    /// 1 when every face says it is fixed-pitch.
1258    pub monospaced: u32,
1259    /// 1 when it has an italic or an oblique face.
1260    pub italic: u32,
1261}
1262
1263/// Options for `kui_play`. NULL means defaults; a given struct is read
1264/// literally (so `volume` must be set — `KUI_PLAY_INIT` in kui.h does).
1265#[repr(C)]
1266#[derive(Clone, Copy)]
1267pub struct KuiPlay {
1268    /// Linear amplitude, 0..1.
1269    pub volume: f32,
1270    pub looped: u32,
1271    pub fade_in_ms: f32,
1272}
1273
1274/// What a `kui_audio` node declares; read literally (`KUI_AUDIO_INIT`).
1275#[repr(C)]
1276#[derive(Clone, Copy)]
1277pub struct KuiAudio {
1278    /// A registered sound (`kui_sound_add`).
1279    pub src: u64,
1280    /// Linear amplitude, 0..1.
1281    pub volume: f32,
1282    pub looped: u32,
1283    pub paused: u32,
1284    /// Removal releases the playback instead of stopping it: it plays to
1285    /// its end. Zero stops it.
1286    pub finish: u32,
1287}
1288
1289/// One audio command for a host that drives its own device
1290/// (`kui_take_audio_commands`); `kind` is `KUI_AUDIO_*` and says which of
1291/// the other fields mean anything.
1292#[repr(C)]
1293#[derive(Clone, Copy, Default)]
1294pub struct KuiAudioCommand {
1295    pub kind: u32,
1296    pub playback: u64,
1297    pub sound: u64,
1298    /// Linear amplitude (play, set_volume, master_volume).
1299    pub volume: f32,
1300    /// Fade / tween duration in ms (fade-in for play).
1301    pub ms: f32,
1302    pub looped: u32,
1303}
1304
1305/// How a run of text is set: size, line height, colour, family or font,
1306/// wrapping and decoration. A zeroed struct is the default style at size
1307/// 0, so set at least `size`; NULL where a style pointer is taken is the
1308/// default style.
1309#[repr(C)]
1310#[derive(Clone, Copy)]
1311pub struct KuiTextStyle {
1312    pub size: f32,
1313    /// <= 0 picks the default (size * 1.35).
1314    pub line_height: f32,
1315    /// 0xRRGGBBAA; 0 = default foreground
1316    pub color: u32,
1317    /// KUI_FONT_SANS (0, default) / KUI_FONT_SERIF / KUI_FONT_MONO.
1318    pub family: u32,
1319    /// A registered font handle (kui_font_add / kui_font_add_system);
1320    /// non-zero overrides `family`.
1321    pub font: u64,
1322    /// KUI_WRAP_WORD (0, default) / KUI_WRAP_GLYPH / KUI_WRAP_NONE /
1323    /// KUI_WRAP_BREAK_SPACES.
1324    pub wrap: u32,
1325    /// Lay out at most this many lines; 0 = unlimited.
1326    pub max_lines: u32,
1327    /// Non-zero: end the last line with an ellipsis when the text is cut
1328    /// off (a single line unless `max_lines` says otherwise).
1329    pub ellipsis: u32,
1330    /// OpenType features for the shaper, in the spelling every binding
1331    /// shares: `tag=value` pairs separated by spaces or commas, a bare
1332    /// tag meaning 1 and `-tag` 0 (`"liga=0 calt=0"`, `"tnum"`). Empty
1333    /// (a zeroed `KuiStr`) is the font's defaults.
1334    pub features: KuiStr,
1335    /// `KUI_DECO_UNDERLINE` | `KUI_DECO_STRIKETHROUGH`: lines where the
1336    /// face puts them, over every glyph. Paint only.
1337    pub decoration: u32,
1338    /// The underline's own colour as `0xRRGGBBAA`, 0 for the text's;
1339    /// non-zero implies `KUI_DECO_UNDERLINE`. ABI 17.
1340    pub underline_color: u32,
1341    /// `KUI_UNDERLINE_SOLID` / `_WAVY` / `_DOTTED`; a non-solid style
1342    /// implies `KUI_DECO_UNDERLINE`. ABI 17.
1343    pub underline_style: u32,
1344}
1345
1346/// One styled run of a rich-text paragraph (`kui_rich_text`,
1347/// `kui_measure_rich_text`): its text and what differs from the base
1348/// style. Travels as an array, so an append here is an ABI bump.
1349#[repr(C)]
1350#[derive(Clone, Copy)]
1351pub struct KuiSpan {
1352    pub text: KuiStr,
1353    /// 0xRRGGBBAA; 0 = inherit the paragraph color
1354    pub color: u32,
1355    /// `KUI_SPAN_BOLD` | `KUI_SPAN_ITALIC` | `KUI_SPAN_UNDERLINE` |
1356    /// `KUI_SPAN_STRIKETHROUGH`
1357    pub flags: u32,
1358    /// 0xRRGGBBAA behind the span's glyphs alone, one rect per line the
1359    /// span covers; 0 = none. ABI 8.
1360    pub bg: u32,
1361    /// The underline's own colour, 0 for the span's; non-zero implies
1362    /// `KUI_SPAN_UNDERLINE`. ABI 17.
1363    pub underline_color: u32,
1364    /// `KUI_UNDERLINE_*`; non-solid implies `KUI_SPAN_UNDERLINE`. ABI 17.
1365    pub underline_style: u32,
1366    /// The background's corner radius, logical px; 0 is the square
1367    /// background. Above zero, `bg` is joined into one shape with
1368    /// every rounded background of the same colour and radius it meets —
1369    /// on the line above or below, or end to end on its own line, in this
1370    /// text or another — rounded outside where a line reaches past its
1371    /// neighbour and filleted inside where it falls short: a selection
1372    /// over rows is one outline. ABI 20.
1373    pub bg_radius: f32,
1374    /// With `KUI_SPAN_FAMILY` in `flags`, the span's own face, a
1375    /// `KUI_FONT_*`; without it the paragraph's. ABI 26.
1376    pub family: u32,
1377    /// The span's own size, logical px; 0 is the paragraph's. ABI 26.
1378    pub size: f32,
1379    /// A registered font handle, the span's face; non-zero overrides
1380    /// `family`. ABI 26.
1381    pub font: u64,
1382}
1383
1384/// One colour token as `kui_tokens_set` reads it: a name and a value per
1385/// base, `0xRRGGBBAA` each (the same value twice for a colour that does
1386/// not follow the appearance). Travels as an array, so an append here is
1387/// an ABI bump.
1388#[repr(C)]
1389#[derive(Clone, Copy)]
1390pub struct KuiColorToken {
1391    pub name: KuiStr,
1392    pub light: u32,
1393    pub dark: u32,
1394}
1395
1396/// One length token: a name and logical px, before the scale factor.
1397/// Array-carried like `KuiColorToken`.
1398#[repr(C)]
1399#[derive(Clone, Copy)]
1400pub struct KuiLengthToken {
1401    pub name: KuiStr,
1402    pub value: f32,
1403}
1404
1405/// One step of a derived colour token's recipe as `kui_tokens_derive`
1406/// reads it: the verb as one of the `KUI_OP_*` numbers, the number it
1407/// takes, and (for `KUI_OP_MIX` and `KUI_OP_READABLE` only) the colour
1408/// token or role the verb names, empty otherwise. Array-carried, so an
1409/// append here is an ABI bump.
1410#[repr(C)]
1411#[derive(Clone, Copy)]
1412pub struct KuiColorOp {
1413    pub op: u8,
1414    pub t: f32,
1415    pub other: KuiStr,
1416}
1417
1418/// One derived colour token: a name, the colour token or theme role it
1419/// derives from, and its chain of ops in order (none for an alias).
1420/// Array-carried like `KuiColorToken`.
1421#[repr(C)]
1422#[derive(Clone, Copy)]
1423pub struct KuiDerivedToken {
1424    pub name: KuiStr,
1425    pub from: KuiStr,
1426    pub ops: *const KuiColorOp,
1427    pub op_count: usize,
1428}
1429
1430/// The verbs of `KuiColorOp.op`, numbered as `kui_core::ColorOp::VERBS`
1431/// lists them.
1432pub const KUI_OP_LIFT: u8 = 0;
1433pub const KUI_OP_DARKEN: u8 = 1;
1434pub const KUI_OP_RAISE: u8 = 2;
1435pub const KUI_OP_ALPHA: u8 = 3;
1436pub const KUI_OP_MIX: u8 = 4;
1437pub const KUI_OP_READABLE: u8 = 5;
1438
1439/// Where a `kui_reply` from inside `kui_ext_on_event` sends its value: an
1440/// opaque handle the library puts on the event for the callback and takes
1441/// back when it returns. Never allocate, dereference or store one.
1442///
1443/// It carries a function pointer into the host's copy of this library, so
1444/// a plugin linked against a different copy (which a Windows DLL must be)
1445/// still reaches the host. `push` is cleared when the callback returns, so
1446/// a plugin that stored the event and replies later gets `false`.
1447#[repr(C)]
1448pub struct KuiReplySink {
1449    /// Called with the sink and the value; false if the sink is closed.
1450    pub(crate) push: Option<unsafe extern "C" fn(*mut KuiReplySink, *const KuiValue) -> bool>,
1451}
1452
1453/// One event from the UI, as `kui_poll_event` writes it and `kui_run`'s
1454/// `on_event` receives it.
1455///
1456/// `key` is the node that emitted it, `payload` the tag the node was
1457/// declared with plus what the event adds (its `kind`, a pointer
1458/// position, a key's `code`), `origin` 0 for the host's own nodes and an
1459/// extension's index otherwise, `window` which window it came from. An
1460/// `[out]` struct: start from `KUI_EVENT_INIT` so `size` is set.
1461#[repr(C)]
1462pub struct KuiEvent {
1463    /// Set to `sizeof(KuiEvent)` before the call (`KUI_EVENT_INIT` does);
1464    /// comes back as the number of bytes the library filled.
1465    pub size: u32,
1466    pub origin: u16,
1467    pub key: u64,
1468    /// Borrowed until the next `kui_poll_event`/`kui_ctx_free`; NULL if none.
1469    pub payload: *const KuiValue,
1470    /// Which window the event came from; 0 (`KUI_WINDOW_MAIN`) until a
1471    /// second one is opened. Appended in ABI 4; a host reserving the older
1472    /// layout gets a shorter write and never sees it.
1473    pub window: u32,
1474    /// Where `kui_reply` sends a reply to this event, or NULL when there is
1475    /// nowhere to send one (every event a host polls itself). A host has no
1476    /// use for it: it is the library's channel to a plugin, which passes
1477    /// the event straight back. See [`KuiReplySink`]. Appended in ABI 10.
1478    pub reply_sink: *mut KuiReplySink,
1479    /// The key of the slot whose fill drew the node (what `kui_key_of`
1480    /// answers for the slot's full name), or 0 for a node the host drew
1481    /// itself. Appended after `reply_sink` without a bump: a host
1482    /// reserving the older layout never sees it.
1483    pub slot: u64,
1484}
1485
1486impl Default for KuiEvent {
1487    fn default() -> Self {
1488        Self {
1489            size: std::mem::size_of::<Self>() as u32,
1490            origin: 0,
1491            key: 0,
1492            payload: std::ptr::null(),
1493            window: 0,
1494            reply_sink: std::ptr::null_mut(),
1495            slot: 0,
1496        }
1497    }
1498}
1499
1500// SAFETY: `repr(C)` with `size: u32` first.
1501unsafe impl OutParam for KuiEvent {
1502    /// Through `payload`: the last field ABI 1 shipped, and so still the
1503    /// floor now that `window` follows it.
1504    const ABI_V1_SIZE: u32 = abi_through!(KuiEvent, payload, *const KuiValue);
1505    fn size_mut(&mut self) -> &mut u32 {
1506        &mut self.size
1507    }
1508}
1509
1510/// What a declared window is (`kui_window_declare`), and what an `Open`
1511/// command carries back out inside [`KuiWindowCommand`]. Read literally,
1512/// so start from `KUI_WINDOW_CONFIG_INIT` (a normal, activating 640x480
1513/// window) or pass NULL for exactly that.
1514#[repr(C)]
1515#[derive(Clone, Copy, Default)]
1516pub struct KuiWindowConfig {
1517    /// `KUI_WINDOW_KIND_*`; 0 is a normal window.
1518    pub kind: u32,
1519    /// Initial inner size, logical px. Zero means the default.
1520    pub width: f32,
1521    pub height: f32,
1522    /// Whether opening it takes OS focus. Start a popup from
1523    /// `KUI_WINDOW_POPUP_INIT`, which clears it: a popup that takes focus
1524    /// blurs the field that opened it.
1525    pub activates: u32,
1526    /// `KUI_WINDOW_KIND_POPUP` only: the rect the popup is placed against,
1527    /// in the **declaring window's** logical coordinates — the `x`/`y`/`w`/
1528    /// `h` an `onLayout` event already reports for the field or button the
1529    /// menu belongs to. The host resolves it to screen coordinates against
1530    /// that window's own position. Ignored by a normal window, and read on
1531    /// the opening edge with the rest of the config.
1532    pub anchor_x: f32,
1533    pub anchor_y: f32,
1534    pub anchor_w: f32,
1535    pub anchor_h: f32,
1536}
1537
1538/// `KUI_WINDOW_KIND_NORMAL`: a regular top-level window.
1539pub const KUI_WINDOW_KIND_NORMAL: u32 = 0;
1540/// `KUI_WINDOW_KIND_POPUP`: a borderless, taskbar-less menu surface owned
1541/// by the window that declared it, placed against `anchor_*` in screen
1542/// coordinates and closed when its owner closes.
1543pub const KUI_WINDOW_KIND_POPUP: u32 = 1;
1544
1545pub(crate) fn window_config_of(c: Option<&KuiWindowConfig>) -> WindowConfig {
1546    let Some(c) = c else {
1547        return WindowConfig::default();
1548    };
1549    let size = if c.width > 0.0 && c.height > 0.0 {
1550        Size::new(c.width, c.height)
1551    } else {
1552        WindowConfig::DEFAULT_SIZE
1553    };
1554    WindowConfig {
1555        // A kind this build does not have reads as a normal window, so a
1556        // host built against a later header degrades to a window rather
1557        // than to nothing. `kui_window_declare` raises `unknown-window-kind`
1558        // on the way past, so the degradation is reported and not silent.
1559        kind: match c.kind {
1560            KUI_WINDOW_KIND_POPUP => WindowKind::Popup,
1561            _ => WindowKind::Normal,
1562        },
1563        size,
1564        activates: c.activates != 0,
1565        anchor: Rect::new(c.anchor_x, c.anchor_y, c.anchor_w, c.anchor_h),
1566    }
1567}
1568
1569/// How `kui_run_with` opens its window: the options a Rust host's
1570/// `Launcher` has, as one struct. Read literally, so start from
1571/// `KUI_RUN_CONFIG_INIT` (every zero) or pass NULL for exactly that: a
1572/// 960x640 native window, unbounded, antialiasing chosen by the GPU,
1573/// diagnostics as the build has them.
1574#[repr(C)]
1575#[derive(Clone, Copy, Default, Debug, PartialEq)]
1576pub struct KuiRunConfig {
1577    /// Initial inner size, logical px; zero is the default. Clamped into
1578    /// the bounds below, as the OS would. `KUI_WINDOW=WxH` in the
1579    /// environment still overrides.
1580    pub width: f32,
1581    pub height: f32,
1582    /// Smallest and largest inner size the user may resize to, logical
1583    /// px; a zero side is unbounded, so a lone `min_h` stands. A max
1584    /// below its min loses to it, as on the OS side.
1585    pub min_w: f32,
1586    pub min_h: f32,
1587    pub max_w: f32,
1588    pub max_h: f32,
1589    /// `KUI_CHROME_*`: native decorations, the custom titlebar a
1590    /// `kui_titlebar` draws, or none.
1591    pub chrome: u32,
1592    /// `KUI_TEXT_AA_*`; `KUI_TEXT_AA=gray|subpixel` in the environment
1593    /// still overrides, for an A/B by hand.
1594    pub text_aa: u32,
1595    /// `KUI_DIAG_*`: whether the core runs its checks and the runner
1596    /// prints them to stderr. The window's setting, over whatever
1597    /// `kui_set_diagnostics` set on the context handed in; the default is
1598    /// the build's — on in a debug build, off in release — as `kui_run`
1599    /// always had it.
1600    pub diagnostics: u32,
1601    /// Frames queued ahead of the one on screen; zero is the default.
1602    /// `KUI_FRAME_LATENCY` in the environment still overrides. ABI 19.
1603    pub frame_latency: u32,
1604    /// `KUI_BACKDROP_*`: what shows through the window's transparent
1605    /// pixels where the platform can (`Launcher::backdrop`); 0 is opaque.
1606    /// What the window got is `kui_ctx_backdrop`. ABI 26.
1607    pub backdrop: u32,
1608}
1609
1610/// `KUI_CHROME_NATIVE`: the OS's decorations.
1611pub const KUI_CHROME_NATIVE: u32 = 0;
1612/// `KUI_CHROME_CUSTOM`: undecorated; the view draws a `kui_titlebar` and
1613/// the runner synthesizes edge resizing and double-click maximize.
1614pub const KUI_CHROME_CUSTOM: u32 = 1;
1615/// `KUI_CHROME_BORDERLESS`: no decorations and no chrome expectations.
1616pub const KUI_CHROME_BORDERLESS: u32 = 2;
1617/// `KUI_TEXT_AA_AUTO`: LCD subpixel coverage when the GPU can blend per
1618/// channel, grayscale otherwise.
1619pub const KUI_TEXT_AA_AUTO: u32 = 0;
1620/// `KUI_TEXT_AA_GRAYSCALE`.
1621pub const KUI_TEXT_AA_GRAYSCALE: u32 = 1;
1622/// `KUI_TEXT_AA_SUBPIXEL`.
1623pub const KUI_TEXT_AA_SUBPIXEL: u32 = 2;
1624/// `KUI_DIAG_DEFAULT`: the build's — on in debug, off in release.
1625pub const KUI_DIAG_DEFAULT: u32 = 0;
1626/// `KUI_DIAG_ON`.
1627pub const KUI_DIAG_ON: u32 = 1;
1628/// `KUI_DIAG_OFF`.
1629pub const KUI_DIAG_OFF: u32 = 2;
1630
1631/// A `KuiRunConfig` read: what `kui_run_with` tells the launcher, in the
1632/// launcher's own terms. Separate from the launcher so the reading is a
1633/// test without a window.
1634#[cfg(any(feature = "runner", test))]
1635#[derive(Debug, PartialEq, Default)]
1636pub(crate) struct RunOptions {
1637    pub size: Option<(f64, f64)>,
1638    pub min_size: Option<(f64, f64)>,
1639    pub max_size: Option<(f64, f64)>,
1640    /// `KUI_CHROME_*`, checked.
1641    pub chrome: u32,
1642    /// `KUI_TEXT_AA_*`, checked.
1643    pub text_aa: u32,
1644    /// `None` is the build's default.
1645    pub diagnostics: Option<bool>,
1646    /// `None` is the launcher's default.
1647    pub frame_latency: Option<u32>,
1648    /// The backdrop asked for, checked.
1649    pub backdrop: kui_core::Backdrop,
1650}
1651
1652/// A max side left at zero is unbounded: a bound no display reaches, as
1653/// Node's `maxWidth` alone is.
1654#[cfg(any(feature = "runner", test))]
1655pub(crate) const UNBOUNDED_SIZE: f64 = 65_535.0;
1656
1657/// Reads a config the way `kui_window_declare` reads its own — literally,
1658/// NULL for the defaults — except that a word this build does not have is
1659/// refused with its reason rather than degraded: a window that opened
1660/// native when asked for the custom chrome would draw its titlebar under
1661/// the OS's.
1662#[cfg(any(feature = "runner", test))]
1663pub(crate) fn run_options_of(c: Option<&KuiRunConfig>) -> Result<RunOptions, String> {
1664    let Some(c) = c else {
1665        return Ok(RunOptions::default());
1666    };
1667    let side = |v: f32, what: &str| -> Result<f64, String> {
1668        if v.is_finite() && v >= 0.0 {
1669            Ok(f64::from(v))
1670        } else {
1671            Err(format!(
1672                "KuiRunConfig.{what} must be a finite, non-negative size, not {v}"
1673            ))
1674        }
1675    };
1676    let (w, h) = (side(c.width, "width")?, side(c.height, "height")?);
1677    let size =
1678        match (w > 0.0, h > 0.0) {
1679            (true, true) => Some((w, h)),
1680            (false, false) => None,
1681            _ => return Err(
1682                "KuiRunConfig: width and height go together (a min or max side may stand alone)"
1683                    .into(),
1684            ),
1685        };
1686    let (min_w, min_h) = (side(c.min_w, "min_w")?, side(c.min_h, "min_h")?);
1687    let min_size = (min_w > 0.0 || min_h > 0.0).then_some((min_w, min_h));
1688    let (max_w, max_h) = (side(c.max_w, "max_w")?, side(c.max_h, "max_h")?);
1689    let unbounded = |v: f64| if v > 0.0 { v } else { UNBOUNDED_SIZE };
1690    let max_size = (max_w > 0.0 || max_h > 0.0).then_some((unbounded(max_w), unbounded(max_h)));
1691    if c.chrome > KUI_CHROME_BORDERLESS {
1692        return Err(format!(
1693            "KuiRunConfig.chrome must be KUI_CHROME_NATIVE, KUI_CHROME_CUSTOM or KUI_CHROME_BORDERLESS, not {}",
1694            c.chrome
1695        ));
1696    }
1697    if c.text_aa > KUI_TEXT_AA_SUBPIXEL {
1698        return Err(format!(
1699            "KuiRunConfig.text_aa must be KUI_TEXT_AA_AUTO, KUI_TEXT_AA_GRAYSCALE or KUI_TEXT_AA_SUBPIXEL, not {}",
1700            c.text_aa
1701        ));
1702    }
1703    let diagnostics = match c.diagnostics {
1704        KUI_DIAG_DEFAULT => None,
1705        KUI_DIAG_ON => Some(true),
1706        KUI_DIAG_OFF => Some(false),
1707        other => {
1708            return Err(format!(
1709                "KuiRunConfig.diagnostics must be KUI_DIAG_DEFAULT, KUI_DIAG_ON or KUI_DIAG_OFF, not {other}"
1710            ));
1711        }
1712    };
1713    let Some(backdrop) = kui_core::Backdrop::from_code(c.backdrop) else {
1714        return Err(format!(
1715            "KuiRunConfig.backdrop must be KUI_BACKDROP_OPAQUE, KUI_BACKDROP_TRANSPARENT, KUI_BACKDROP_BLUR or KUI_BACKDROP_TINTED, not {}",
1716            c.backdrop
1717        ));
1718    };
1719    Ok(RunOptions {
1720        size,
1721        min_size,
1722        max_size,
1723        chrome: c.chrome,
1724        text_aa: c.text_aa,
1725        diagnostics,
1726        frame_latency: (c.frame_latency > 0).then_some(c.frame_latency),
1727        backdrop,
1728    })
1729}
1730
1731fn window_config_to_c(c: WindowConfig) -> KuiWindowConfig {
1732    KuiWindowConfig {
1733        kind: match c.kind {
1734            WindowKind::Normal => KUI_WINDOW_KIND_NORMAL,
1735            WindowKind::Popup => KUI_WINDOW_KIND_POPUP,
1736        },
1737        width: c.size.w,
1738        height: c.size.h,
1739        activates: c.activates as u32,
1740        anchor_x: c.anchor.x,
1741        anchor_y: c.anchor.y,
1742        anchor_w: c.anchor.w,
1743        anchor_h: c.anchor.h,
1744    }
1745}
1746
1747/// `KUI_DISMISS_OUTSIDE`, `KUI_DISMISS_ESCAPE`: why a window was asked to
1748/// go away (`kui_window_dismissed`).
1749pub const KUI_DISMISS_OUTSIDE: u32 = 0;
1750pub const KUI_DISMISS_ESCAPE: u32 = 1;
1751
1752/// `KUI_OPTION_AS_ALT_NONE`, `_LEFT`, `_RIGHT`, `_BOTH`: which Option
1753/// keys act as Alt on macOS (`kui_set_option_as_alt`).
1754pub const KUI_OPTION_AS_ALT_NONE: u32 = 0;
1755pub const KUI_OPTION_AS_ALT_LEFT: u32 = 1;
1756pub const KUI_OPTION_AS_ALT_RIGHT: u32 = 2;
1757pub const KUI_OPTION_AS_ALT_BOTH: u32 = 3;
1758
1759/// `KUI_CMD_START_DRAG`, `KUI_CMD_CLOSE`, `KUI_CMD_MINIMIZE`,
1760/// `KUI_CMD_TOGGLE_MAXIMIZE`: the verbs chrome nodes issue.
1761pub const KUI_CMD_START_DRAG: u32 = 1;
1762pub const KUI_CMD_CLOSE: u32 = 2;
1763pub const KUI_CMD_MINIMIZE: u32 = 3;
1764pub const KUI_CMD_TOGGLE_MAXIMIZE: u32 = 4;
1765/// `KUI_CMD_OPEN`: the declared set gained a window; `config` says what.
1766pub const KUI_CMD_OPEN: u32 = 5;
1767/// `KUI_CMD_SET_SIZE`: the app asked for a size (`kui_set_window_size`);
1768/// `width`/`height` carry it. `KUI_CMD_FOCUS`: it asked for focus.
1769pub const KUI_CMD_SET_SIZE: u32 = 6;
1770pub const KUI_CMD_FOCUS: u32 = 7;
1771/// `KUI_CMD_REDRAW`: draw `window` again, because another window's input
1772/// changed what it shows. A host that redraws every window on every event
1773/// may ignore it.
1774pub const KUI_CMD_REDRAW: u32 = 8;
1775
1776/// One window command (`kui_take_window_command`): what a chrome node
1777/// asked for, or what the declared window set's diff decided. Plain data
1778/// (an `Open` carries no title; the window's first frame declares one
1779/// through `kui_window_title`), so nothing borrowed enters a host's drain
1780/// loop. An `[out]` struct: start from `KUI_WINDOW_COMMAND_INIT`.
1781#[repr(C)]
1782pub struct KuiWindowCommand {
1783    /// Set to `sizeof(KuiWindowCommand)` before the call
1784    /// (`KUI_WINDOW_COMMAND_INIT` does); comes back as the bytes filled.
1785    pub size: u32,
1786    /// `KUI_CMD_*`.
1787    pub kind: u32,
1788    /// Which window: the one the chrome node was drawn in, or for
1789    /// `KUI_CMD_OPEN` the id the core assigned the new window — what its
1790    /// events will carry in `KuiEvent.window`.
1791    pub window: u32,
1792    /// `KUI_CMD_OPEN` only: which frontend's declaration won (0 = the host,
1793    /// 1+ = an extension), so a host can refuse an extension's window.
1794    pub origin: u16,
1795    /// `KUI_CMD_OPEN` only: the config from the declaration that opened it.
1796    pub config: KuiWindowConfig,
1797    /// `KUI_CMD_SET_SIZE` only: the size asked for, logical px. Appended in
1798    /// ABI 6; only the host's own `kui_set_window_size` produces the verb
1799    /// that fills them.
1800    pub width: f32,
1801    pub height: f32,
1802    /// `KUI_CMD_OPEN` only: the window whose frame declared this one. For a
1803    /// `KUI_WINDOW_KIND_POPUP` it is the owner — the surface `config`'s
1804    /// `anchor_*` is measured against, the one to parent it to, and the one
1805    /// whose closing closes it. (The core closes it either way: an owner's
1806    /// declarations leave the declared set with it, so the same drain
1807    /// carries the popup's `KUI_CMD_CLOSE`.)
1808    pub owner: u32,
1809}
1810
1811impl Default for KuiWindowCommand {
1812    fn default() -> Self {
1813        Self {
1814            size: std::mem::size_of::<Self>() as u32,
1815            kind: 0,
1816            window: 0,
1817            origin: 0,
1818            config: KuiWindowConfig::default(),
1819            width: 0.0,
1820            height: 0.0,
1821            owner: 0,
1822        }
1823    }
1824}
1825
1826// SAFETY: `repr(C)` with `size: u32` first.
1827unsafe impl OutParam for KuiWindowCommand {
1828    /// Through `config`: the whole struct as it first shipped, in ABI 5.
1829    /// ABI 6's `width`/`height` and ABI 7's `owner` sit past it, so the
1830    /// floor never moved for those — but ABI 7 also grew `KuiWindowConfig`
1831    /// itself, by the four anchor floats a popup is placed against, and a
1832    /// field appended *inside* an embedded struct moves everything after
1833    /// it. So the floor is 16 bytes higher than the whole ABI-6 struct was,
1834    /// an ABI-6 host's reservation is refused rather than short-written
1835    /// (`out_accepts`), and `kui_abi_version()` is what catches that before
1836    /// it looks like an empty queue. The size handshake bounds the damage;
1837    /// only the version check prevents it.
1838    const ABI_V1_SIZE: u32 = abi_through!(KuiWindowCommand, config, KuiWindowConfig);
1839    fn size_mut(&mut self) -> &mut u32 {
1840        &mut self.size
1841    }
1842}
1843
1844pub(crate) fn window_command_to_c(cmd: WindowCommand) -> KuiWindowCommand {
1845    let mut size = Size::new(0.0, 0.0);
1846    let mut owner = 0;
1847    let (kind, origin, config) = match cmd {
1848        WindowCommand::StartDrag(_) => (KUI_CMD_START_DRAG, 0, KuiWindowConfig::default()),
1849        WindowCommand::Close(_) => (KUI_CMD_CLOSE, 0, KuiWindowConfig::default()),
1850        WindowCommand::Minimize(_) => (KUI_CMD_MINIMIZE, 0, KuiWindowConfig::default()),
1851        WindowCommand::ToggleMaximize(_) => {
1852            (KUI_CMD_TOGGLE_MAXIMIZE, 0, KuiWindowConfig::default())
1853        }
1854        WindowCommand::Open {
1855            owner: o,
1856            origin,
1857            config,
1858            ..
1859        } => {
1860            owner = o.0;
1861            (KUI_CMD_OPEN, origin.0, window_config_to_c(config))
1862        }
1863        WindowCommand::SetSize { size: s, .. } => {
1864            size = s;
1865            (KUI_CMD_SET_SIZE, 0, KuiWindowConfig::default())
1866        }
1867        WindowCommand::Focus(_) => (KUI_CMD_FOCUS, 0, KuiWindowConfig::default()),
1868        WindowCommand::Redraw(_) => (KUI_CMD_REDRAW, 0, KuiWindowConfig::default()),
1869    };
1870    KuiWindowCommand {
1871        kind,
1872        window: cmd.window().0,
1873        origin,
1874        config,
1875        width: size.w,
1876        height: size.h,
1877        owner,
1878        ..Default::default()
1879    }
1880}
1881
1882/// One quad of a frame's draw list, in physical pixels: a rounded
1883/// rectangle, border, shadow, glyph, image or segment, told apart by
1884/// `kind`. A renderer draws them in order, every one with the same
1885/// instanced pipeline; `KuiDrawData` hands out the array.
1886#[repr(C)]
1887#[derive(Clone, Copy)]
1888pub struct KuiQuad {
1889    pub x: f32,
1890    pub y: f32,
1891    pub w: f32,
1892    pub h: f32,
1893    pub color: [f32; 4],
1894    pub border_color: [f32; 4],
1895    /// Corner radii (physical px), clockwise from the top-left.
1896    pub radius: [f32; 4],
1897    pub border_w: f32,
1898    /// KUI_QUAD_SHADOW only: blur radius (physical px), which is also how
1899    /// far `x`/`y`/`w`/`h` is inflated past the shape being blurred.
1900    pub blur: f32,
1901    /// KUI_QUAD_*
1902    pub kind: u32,
1903    /// Which entry of `KuiDrawData::clips` clips this quad; entry zero
1904    /// clips nothing.
1905    pub clip: u32,
1906    /// Atlas texels: x, y, w, h. `KUI_QUAD_SEGMENT`: the endpoints as
1907    /// float bits (see `kui_core::Quad::segment_ends`).
1908    pub uv: [u32; 4],
1909}
1910
1911/// One clip a frame's quads name, in physical pixels. Mirrors
1912/// `kui_core::Clip`, so `KuiDrawData::clips` is a cast and not a copy.
1913#[repr(C)]
1914#[derive(Clone, Copy)]
1915pub struct KuiClip {
1916    /// Clip rect: x, y, w, h. Pixels outside are transparent.
1917    pub rect: [f32; 4],
1918    /// Corner radii (physical px), clockwise from the top-left: pixels
1919    /// outside the rounded clip are transparent too. All zero — every clip
1920    /// of a frame with no rounded clipper — is the plain rect clip.
1921    pub radius: [f32; 4],
1922}
1923
1924/// One `KUI_QUAD_FRAGMENT`'s draw, addressed by that quad's `uv[0]`.
1925#[repr(C)]
1926#[derive(Clone, Copy)]
1927pub struct KuiFragmentDraw {
1928    /// The registered handle, for `kui_fragment_source` and for keying a
1929    /// renderer's pipeline cache.
1930    pub fragment: u64,
1931    /// What the node declared, zero-padded to sixteen.
1932    pub params: [f32; 16],
1933    /// Where the draw's `image` is: `KUI_FRAGMENT_IMAGE_NONE`,
1934    /// `_ATLAS` (bind the atlas, as for any fragment) or `_TEXTURE` (bind
1935    /// the texture `image_texture` names, as for a `KUI_QUAD_TEXTURE`
1936    /// quad). Added in ABI 15.
1937    pub image_source: u32,
1938    /// The `textures` index the draw reads, when `image_source` is
1939    /// `KUI_FRAGMENT_IMAGE_TEXTURE`; 0 otherwise.
1940    pub image_texture: u32,
1941    /// The texel rect the shader is given as `FragmentIn::image`, in the
1942    /// atlas or in that texture; zero with no image.
1943    pub image_uv: [u32; 4],
1944}
1945
1946/// `KuiFragmentDraw::image_source`: no image.
1947pub const KUI_FRAGMENT_IMAGE_NONE: u32 = 0;
1948/// `KuiFragmentDraw::image_source`: the image is in the atlas.
1949pub const KUI_FRAGMENT_IMAGE_ATLAS: u32 = 1;
1950/// `KuiFragmentDraw::image_source`: the image has a texture of its own.
1951pub const KUI_FRAGMENT_IMAGE_TEXTURE: u32 = 2;
1952
1953/// One `KUI_QUAD_TEXTURE`'s draw, addressed by that quad's `uv[0]`.
1954#[repr(C)]
1955#[derive(Clone, Copy)]
1956pub struct KuiTextureDraw {
1957    /// The image handle, for `kui_image_pixels` and for keying a
1958    /// renderer's texture cache.
1959    pub image: u64,
1960    /// Moves with every `kui_image_update`; a renderer that uploaded this
1961    /// revision has nothing to do.
1962    pub rev: u32,
1963    pub width: u32,
1964    pub height: u32,
1965    /// The texel rect to show, `[x, y, w, h]` in the image's own texels —
1966    /// the whole image, or the crop a `fit = cover` made.
1967    pub uv: [u32; 4],
1968}
1969
1970/// The finished frame's draw list, as `kui_draw_data` writes it: the
1971/// quads, the clips they index, the glyph atlas to mirror as a texture,
1972/// and the side lists for fragments and texture-backed images.
1973///
1974/// Everything is in physical pixels. The pointers are valid until the
1975/// next `kui_frame_begin` on the context. An `[out]` struct: start from
1976/// `KUI_DRAW_DATA_INIT`.
1977#[repr(C)]
1978pub struct KuiDrawData {
1979    /// `[out]` reservation; see `KUI_DRAW_DATA_INIT`.
1980    pub size: u32,
1981    pub quads: *const KuiQuad,
1982    pub quad_count: usize,
1983    pub viewport_w: f32,
1984    pub viewport_h: f32,
1985    pub scale: f32,
1986    /// RGBA, atlas_size * atlas_size * 4 bytes.
1987    pub atlas_pixels: *const u8,
1988    /// Can grow or shrink between frames: a page extended for one frame
1989    /// goes back to its size at the next. Size the texture to it, not to
1990    /// the largest seen.
1991    pub atlas_size: u32,
1992    /// Re-upload the atlas texture when either of these changes/sets.
1993    pub atlas_dirty: bool,
1994    pub atlas_epoch: u64,
1995    /// One per `KUI_QUAD_FRAGMENT` quad, indexed by its `uv[0]`; null and
1996    /// zero on a frame that draws none. Added in ABI 9.
1997    pub fragments: *const KuiFragmentDraw,
1998    pub fragment_count: usize,
1999    /// The frame clock in seconds, for a fragment's `time`.
2000    pub time: f32,
2001    /// The clips the quads index through `KuiQuad::clip`. Never empty on a
2002    /// frame that drew anything: entry zero clips nothing. Added in
2003    /// ABI 11.
2004    pub clips: *const KuiClip,
2005    pub clip_count: usize,
2006    /// One per `KUI_QUAD_TEXTURE` quad, indexed by its `uv[0]`; null and
2007    /// zero on a frame that draws none. Added in ABI 14.
2008    pub textures: *const KuiTextureDraw,
2009    pub texture_count: usize,
2010}
2011
2012impl Default for KuiDrawData {
2013    fn default() -> Self {
2014        Self {
2015            size: std::mem::size_of::<Self>() as u32,
2016            quads: std::ptr::null(),
2017            quad_count: 0,
2018            viewport_w: 0.0,
2019            viewport_h: 0.0,
2020            scale: 1.0,
2021            atlas_pixels: std::ptr::null(),
2022            atlas_size: 0,
2023            atlas_dirty: false,
2024            atlas_epoch: 0,
2025            fragments: std::ptr::null(),
2026            fragment_count: 0,
2027            time: 0.0,
2028            clips: std::ptr::null(),
2029            clip_count: 0,
2030            textures: std::ptr::null(),
2031            texture_count: 0,
2032        }
2033    }
2034}
2035
2036// SAFETY: `repr(C)` with `size: u32` first.
2037unsafe impl OutParam for KuiDrawData {
2038    const ABI_V1_SIZE: u32 = abi_through!(KuiDrawData, atlas_epoch, u64);
2039    fn size_mut(&mut self) -> &mut u32 {
2040        &mut self.size
2041    }
2042}
2043
2044/// What the last layout resolved for a scroll container (`kui_scroll_geometry`):
2045/// its own box, its content size and the clamped offset, all logical px in
2046/// viewport coordinates.
2047#[repr(C)]
2048#[derive(Clone, Copy)]
2049pub struct KuiScrollGeometry {
2050    /// `[out]` reservation; see `KUI_SCROLL_GEOMETRY_INIT`.
2051    pub size: u32,
2052    /// The container's box, as the last layout placed and sized it.
2053    pub x: f32,
2054    pub y: f32,
2055    pub w: f32,
2056    pub h: f32,
2057    /// Its laid-out content, padding included.
2058    pub content_w: f32,
2059    pub content_h: f32,
2060    /// Where it is scrolled to: the retained offset clamped to the travel
2061    /// below, so it is always a position within the content.
2062    pub offset_x: f32,
2063    pub offset_y: f32,
2064    /// How far the offset can travel; zero on an axis that does not scroll.
2065    pub max_offset_x: f32,
2066    pub max_offset_y: f32,
2067}
2068
2069impl Default for KuiScrollGeometry {
2070    fn default() -> Self {
2071        Self {
2072            size: std::mem::size_of::<Self>() as u32,
2073            x: 0.0,
2074            y: 0.0,
2075            w: 0.0,
2076            h: 0.0,
2077            content_w: 0.0,
2078            content_h: 0.0,
2079            offset_x: 0.0,
2080            offset_y: 0.0,
2081            max_offset_x: 0.0,
2082            max_offset_y: 0.0,
2083        }
2084    }
2085}
2086
2087/// One cell of a `kui_cells` grid: a Unicode scalar, colours as
2088/// `0xRRGGBBAA` (a `bg` of 0 is none), `KUI_CELL_*` attribute bits.
2089/// Travels as an array, so a change here is an ABI bump.
2090#[repr(C)]
2091#[derive(Clone, Copy, Default)]
2092pub struct KuiCell {
2093    pub ch: u32,
2094    pub fg: u32,
2095    pub bg: u32,
2096    pub flags: u32,
2097    /// The underline's own colour (SGR 58), 0 for `fg`. ABI 17.
2098    pub ul: u32,
2099}
2100
2101/// What choosing a context-menu row left for the host
2102/// (`kui_take_menu_action`): the clipboard, which is the host's in this
2103/// library. `KUI_MENU_ACTION_SET_CLIPBOARD` carries the text to put there;
2104/// `KUI_MENU_ACTION_PASTE` carries nothing and asks for what is there,
2105/// which the host delivers back with `kui_input_paste` (or
2106/// `kui_input_commit`); `KUI_MENU_ACTION_SET_CLIPBOARD_SECRET` carries a
2107/// secret to put there marked concealed and transient.
2108#[repr(C)]
2109#[derive(Clone, Copy)]
2110pub struct KuiMenuAction {
2111    /// `[out]` reservation; see `KUI_MENU_ACTION_INIT`.
2112    pub size: u32,
2113    /// A `KUI_MENU_ACTION_*` kind.
2114    pub kind: u32,
2115    /// Borrowed until the next `kui_take_menu_action` on this context.
2116    pub text: KuiStr,
2117    /// The same selection with the formatting the core knows about, for a
2118    /// host offering a second clipboard flavour.
2119    /// Empty when there is none to carry — and never a *replacement* for
2120    /// `text`: a clipboard whose only flavour is HTML pastes markup into
2121    /// every plain-text field on the machine.
2122    pub html: KuiStr,
2123    /// `KUI_MENU_ACTION_LOOK_UP` only: where to anchor the panel — the
2124    /// baseline origin of the selection's first line, logical viewport
2125    /// px. Zero for every other kind.
2126    pub x: f32,
2127    pub y: f32,
2128}
2129
2130impl Default for KuiMenuAction {
2131    fn default() -> Self {
2132        Self {
2133            size: std::mem::size_of::<Self>() as u32,
2134            kind: 0,
2135            text: KuiStr {
2136                ptr: std::ptr::null(),
2137                len: 0,
2138            },
2139            html: KuiStr {
2140                ptr: std::ptr::null(),
2141                len: 0,
2142            },
2143            x: 0.0,
2144            y: 0.0,
2145        }
2146    }
2147}
2148
2149// SAFETY: `repr(C)` with `size: u32` first.
2150unsafe impl OutParam for KuiMenuAction {
2151    const ABI_V1_SIZE: u32 = abi_through!(KuiMenuAction, y, f32);
2152    fn size_mut(&mut self) -> &mut u32 {
2153        &mut self.size
2154    }
2155}
2156
2157/// Where a point landed in the text a keyed node drew (`kui_text_hit`):
2158/// a byte offset into that text, across the node's text runs in order,
2159/// and the visual (wrapped) line it is on.
2160#[repr(C)]
2161#[derive(Clone, Copy)]
2162pub struct KuiTextHit {
2163    /// `[out]` reservation; see `KUI_TEXT_HIT_INIT`.
2164    pub size: u32,
2165    pub line: u32,
2166    pub byte: u64,
2167}
2168
2169impl Default for KuiTextHit {
2170    fn default() -> Self {
2171        Self {
2172            size: std::mem::size_of::<Self>() as u32,
2173            line: 0,
2174            byte: 0,
2175        }
2176    }
2177}
2178
2179// SAFETY: `repr(C)` with `size: u32` first.
2180unsafe impl OutParam for KuiTextHit {
2181    const ABI_V1_SIZE: u32 = abi_through!(KuiTextHit, byte, u64);
2182    fn size_mut(&mut self) -> &mut u32 {
2183        &mut self.size
2184    }
2185}
2186
2187/// The rect a node was laid out at (`kui_layout_of`): logical px in
2188/// viewport coordinates, the `layout` event's numbers without the event.
2189#[repr(C)]
2190#[derive(Clone, Copy)]
2191pub struct KuiLayoutRect {
2192    /// `[out]` reservation; see `KUI_LAYOUT_RECT_INIT`.
2193    pub size: u32,
2194    pub x: f32,
2195    pub y: f32,
2196    pub w: f32,
2197    pub h: f32,
2198}
2199
2200impl Default for KuiLayoutRect {
2201    fn default() -> Self {
2202        Self {
2203            size: std::mem::size_of::<Self>() as u32,
2204            x: 0.0,
2205            y: 0.0,
2206            w: 0.0,
2207            h: 0.0,
2208        }
2209    }
2210}
2211
2212// SAFETY: `repr(C)` with `size: u32` first.
2213unsafe impl OutParam for KuiLayoutRect {
2214    const ABI_V1_SIZE: u32 = abi_through!(KuiLayoutRect, h, f32);
2215    fn size_mut(&mut self) -> &mut u32 {
2216        &mut self.size
2217    }
2218}
2219
2220/// A caret rect (`kui_caret_rect`): logical px in viewport coordinates,
2221/// zero wide, one line tall.
2222#[repr(C)]
2223#[derive(Clone, Copy)]
2224pub struct KuiCaretRect {
2225    /// `[out]` reservation; see `KUI_CARET_RECT_INIT`.
2226    pub size: u32,
2227    pub x: f32,
2228    pub y: f32,
2229    pub w: f32,
2230    pub h: f32,
2231}
2232
2233impl Default for KuiCaretRect {
2234    fn default() -> Self {
2235        Self {
2236            size: std::mem::size_of::<Self>() as u32,
2237            x: 0.0,
2238            y: 0.0,
2239            w: 0.0,
2240            h: 0.0,
2241        }
2242    }
2243}
2244
2245// SAFETY: `repr(C)` with `size: u32` first.
2246unsafe impl OutParam for KuiCaretRect {
2247    const ABI_V1_SIZE: u32 = abi_through!(KuiCaretRect, h, f32);
2248    fn size_mut(&mut self) -> &mut u32 {
2249        &mut self.size
2250    }
2251}
2252
2253// SAFETY: `repr(C)` with `size: u32` first.
2254unsafe impl OutParam for KuiScrollGeometry {
2255    const ABI_V1_SIZE: u32 = abi_through!(KuiScrollGeometry, max_offset_y, f32);
2256    fn size_mut(&mut self) -> &mut u32 {
2257        &mut self.size
2258    }
2259}
2260
2261// ---------------------------------------------------------------------------
2262// The plain-constant enums the header spells and the entry points read.
2263//
2264// Each of these used to be a literal at the one site that read it (`kind =
2265// 3`, `flags & 8`), with the header the only place the number had a name.
2266// Named here so `mod abi_parity` can pin every one of them to the header
2267// by name, the way it pins `KUI_CMD_*`: a value renumbered on either side
2268// fails the C build instead of meaning something else at runtime.
2269
2270/// `KUI_SPAN_*`: the flags on a `KuiSpan`.
2271pub const KUI_SPAN_BOLD: u32 = 1 << 0;
2272pub const KUI_SPAN_ITALIC: u32 = 1 << 1;
2273pub const KUI_SPAN_UNDERLINE: u32 = 1 << 2;
2274pub const KUI_SPAN_STRIKETHROUGH: u32 = 1 << 3;
2275/// `KUI_SPAN_FAMILY`: `KuiSpan.family` names the span's face (ABI 26).
2276pub const KUI_SPAN_FAMILY: u32 = 1 << 4;
2277
2278/// `KUI_UNDERLINE_*`: an underline's shape, `KuiTextStyle.underline_style`
2279/// and `KuiSpan.underline_style`.
2280pub const KUI_UNDERLINE_SOLID: u32 = 0;
2281pub const KUI_UNDERLINE_WAVY: u32 = 1;
2282pub const KUI_UNDERLINE_DOTTED: u32 = 2;
2283
2284/// `KUI_KMOD_*`: the modifier bits `kui_input_key_down` and its siblings
2285/// take, and `kui_input_modifiers` reports — the core's own
2286/// `KeyMods::bits`, which is also what the conformance corpus's
2287/// `modifiers` step spells, so the header and the corpus cannot drift.
2288pub const KUI_KMOD_SHIFT: u32 = kui_core::KeyMods::SHIFT;
2289pub const KUI_KMOD_CTRL: u32 = kui_core::KeyMods::CTRL;
2290pub const KUI_KMOD_ALT: u32 = kui_core::KeyMods::ALT;
2291pub const KUI_KMOD_SUPER: u32 = kui_core::KeyMods::SUPER;
2292
2293/// `KUI_KLOCK_*` and `KUI_KLOC_*`: which lock keys were on and which of a
2294/// key's twins it was, in the same word as the `KUI_KMOD_*` bits
2295/// `kui_input_key_down` and its siblings take. Zero is no lock on and the
2296/// standard key.
2297pub const KUI_KLOCK_CAPS: u32 = kui_core::KeyLocks::CAPS;
2298pub const KUI_KLOCK_NUM: u32 = kui_core::KeyLocks::NUM;
2299pub const KUI_KLOC_LEFT: u32 = 1 << kui_core::KeyLocation::SHIFT;
2300pub const KUI_KLOC_RIGHT: u32 = 2 << kui_core::KeyLocation::SHIFT;
2301pub const KUI_KLOC_NUMPAD: u32 = 3 << kui_core::KeyLocation::SHIFT;
2302/// `KUI_KLAYOUT_NONLATIN`: the press was typed on a layout that writes no
2303/// Latin, so the US key stands in for its ASCII too. Zero judges each key
2304/// by itself.
2305pub const KUI_KLAYOUT_NONLATIN: u32 = kui_core::LayoutScript::NON_LATIN;
2306
2307/// `KUI_EDIT_*`: the flags `kui_text_edit` takes. `WRAP` is the `wrap`
2308/// row declared on a field (the mode is `KuiTextStyle.wrap`, whose zero
2309/// is `KUI_WRAP_WORD`, so the style alone cannot say): the field folds to
2310/// its width the way a document does and keeps a field's keyboard.
2311pub const KUI_EDIT_MULTILINE: u32 = 1 << 0;
2312pub const KUI_EDIT_AUTOFOCUS: u32 = 1 << 1;
2313pub const KUI_EDIT_WRAP: u32 = 1 << 2;
2314
2315/// `KUI_MOD_*`: the editing modifiers `kui_input_key` takes — extend the
2316/// selection, move by word, move by document.
2317pub const KUI_MOD_SHIFT: u32 = 1 << 0;
2318pub const KUI_MOD_WORD: u32 = 1 << 1;
2319pub const KUI_MOD_DOC: u32 = 1 << 2;
2320
2321/// `KUI_KEY_*`: the editing keys `kui_input_key` takes, in the header's
2322/// order — which is not `EditKey`'s declaration order, so the table is
2323/// the pin rather than a cast. `edit_key_of` reads it and `mod
2324/// abi_parity` emits it.
2325pub const KUI_EDIT_KEYS: [(&str, EditKey); 16] = [
2326    ("KUI_KEY_LEFT", EditKey::Left),
2327    ("KUI_KEY_RIGHT", EditKey::Right),
2328    ("KUI_KEY_UP", EditKey::Up),
2329    ("KUI_KEY_DOWN", EditKey::Down),
2330    ("KUI_KEY_HOME", EditKey::Home),
2331    ("KUI_KEY_END", EditKey::End),
2332    ("KUI_KEY_PAGE_UP", EditKey::PageUp),
2333    ("KUI_KEY_PAGE_DOWN", EditKey::PageDown),
2334    ("KUI_KEY_BACKSPACE", EditKey::Backspace),
2335    ("KUI_KEY_DELETE", EditKey::Delete),
2336    ("KUI_KEY_ENTER", EditKey::Enter),
2337    ("KUI_KEY_TAB", EditKey::Tab),
2338    ("KUI_KEY_SELECT_ALL", EditKey::SelectAll),
2339    ("KUI_KEY_ESCAPE", EditKey::Escape),
2340    ("KUI_KEY_UNDO", EditKey::Undo),
2341    ("KUI_KEY_REDO", EditKey::Redo),
2342];
2343
2344/// `KUI_MOUSE_*`: `kui_input_mouse_button`'s button, which is
2345/// `MouseButton::code` — the core owns the numbering, this is its name.
2346pub const KUI_MOUSE_PRIMARY: u32 = 0;
2347pub const KUI_MOUSE_SECONDARY: u32 = 1;
2348pub const KUI_MOUSE_MIDDLE: u32 = 2;
2349pub const KUI_MOUSE_OTHER: u32 = 3;
2350
2351/// `KUI_MENU_*`: a `KuiMenuItem.role`, the position in `MenuRole::ALL`.
2352pub const KUI_MENU_CUSTOM: u32 = 0;
2353pub const KUI_MENU_SEPARATOR: u32 = 1;
2354pub const KUI_MENU_CUT: u32 = 2;
2355pub const KUI_MENU_COPY: u32 = 3;
2356pub const KUI_MENU_PASTE: u32 = 4;
2357pub const KUI_MENU_SELECT_ALL: u32 = 5;
2358pub const KUI_MENU_LOOK_UP: u32 = 6;
2359
2360/// `KUI_MENU_ITEM_*`: the flags `kui_menu_bar_item` and `kui_menu_item`
2361/// report on a row.
2362pub const KUI_MENU_ITEM_ENABLED: u32 = 1 << 0;
2363pub const KUI_MENU_ITEM_CHECKED: u32 = 1 << 1;
2364/// `KUI_MENU_ITEM_SUBMENU`: the row opens rows of its own (backlog F128).
2365pub const KUI_MENU_ITEM_SUBMENU: u32 = 1 << 2;
2366
2367/// `KUI_MENU_ACTION_*`: a `KuiMenuAction.kind`.
2368pub const KUI_MENU_ACTION_SET_CLIPBOARD: u32 = 0;
2369pub const KUI_MENU_ACTION_PASTE: u32 = 1;
2370pub const KUI_MENU_ACTION_LOOK_UP: u32 = 2;
2371/// A secret for the clipboard, to write marked concealed and transient
2372/// (`kui_set_clipboard_secret`). A host that does not know the kind drops
2373/// the copy, which for a secret is the safe way to fail.
2374pub const KUI_MENU_ACTION_SET_CLIPBOARD_SECRET: u32 = 3;
2375
2376/// `KUI_PASTE_*`: the pasteboard's markers on a paste's answer
2377/// (`kui_input_paste`).
2378pub const KUI_PASTE_CONCEALED: u32 = 1 << 0;
2379pub const KUI_PASTE_TRANSIENT: u32 = 1 << 1;
2380
2381/// `KUI_BUTTONS_*`: the buttons `KuiSpec.on_button` claims
2382/// (`KuiSpec.buttons`); none set is all three.
2383pub const KUI_BUTTONS_SECONDARY: u32 = kui_core::Buttons::SECONDARY.bits();
2384pub const KUI_BUTTONS_MIDDLE: u32 = kui_core::Buttons::MIDDLE.bits();
2385pub const KUI_BUTTONS_OTHER: u32 = kui_core::Buttons::OTHER.bits();
2386
2387/// `KUI_OWED_*`: the bits of what `kui_owed` returns, `kui_animating` by
2388/// kind.
2389pub const KUI_OWED_TRANSITION: u32 = 1 << 0;
2390pub const KUI_OWED_CYCLE: u32 = 1 << 1;
2391pub const KUI_OWED_DEPART: u32 = 1 << 2;
2392pub const KUI_OWED_REQUESTED: u32 = 1 << 3;
2393pub const KUI_OWED_AUTOSCROLL: u32 = 1 << 4;
2394pub const KUI_OWED_SCROLL: u32 = 1 << 5;
2395
2396/// `KUI_FRAME_CAUSE_*`: the bits of what `kui_frame_cause` returns and
2397/// `kui_note_frame_cause` takes.
2398pub const KUI_FRAME_CAUSE_POINTER_MOVE: u32 = kui_core::FrameCause::POINTER_MOVE.bits();
2399pub const KUI_FRAME_CAUSE_POINTER_LEAVE: u32 = kui_core::FrameCause::POINTER_LEAVE.bits();
2400pub const KUI_FRAME_CAUSE_BUTTON: u32 = kui_core::FrameCause::BUTTON.bits();
2401pub const KUI_FRAME_CAUSE_WHEEL: u32 = kui_core::FrameCause::WHEEL.bits();
2402pub const KUI_FRAME_CAUSE_KEY: u32 = kui_core::FrameCause::KEY.bits();
2403pub const KUI_FRAME_CAUSE_MODIFIERS: u32 = kui_core::FrameCause::MODIFIERS.bits();
2404pub const KUI_FRAME_CAUSE_TEXT: u32 = kui_core::FrameCause::TEXT.bits();
2405pub const KUI_FRAME_CAUSE_PREEDIT: u32 = kui_core::FrameCause::PREEDIT.bits();
2406pub const KUI_FRAME_CAUSE_ACCESS: u32 = kui_core::FrameCause::ACCESS.bits();
2407pub const KUI_FRAME_CAUSE_FILE_DRAG: u32 = kui_core::FrameCause::FILE_DRAG.bits();
2408pub const KUI_FRAME_CAUSE_FILES: u32 = kui_core::FrameCause::FILES.bits();
2409pub const KUI_FRAME_CAUSE_FIRST: u32 = kui_core::FrameCause::FIRST.bits();
2410pub const KUI_FRAME_CAUSE_WAKE: u32 = kui_core::FrameCause::WAKE.bits();
2411pub const KUI_FRAME_CAUSE_HOST: u32 = kui_core::FrameCause::HOST.bits();
2412pub const KUI_FRAME_CAUSE_RESIZE: u32 = kui_core::FrameCause::RESIZE.bits();
2413pub const KUI_FRAME_CAUSE_SCALE: u32 = kui_core::FrameCause::SCALE.bits();
2414pub const KUI_FRAME_CAUSE_FOCUS: u32 = kui_core::FrameCause::FOCUS.bits();
2415pub const KUI_FRAME_CAUSE_OCCLUSION: u32 = kui_core::FrameCause::OCCLUSION.bits();
2416pub const KUI_FRAME_CAUSE_APPEARANCE: u32 = kui_core::FrameCause::APPEARANCE.bits();
2417pub const KUI_FRAME_CAUSE_CARET: u32 = kui_core::FrameCause::CARET.bits();
2418pub const KUI_FRAME_CAUSE_RETRY: u32 = kui_core::FrameCause::RETRY.bits();
2419pub const KUI_FRAME_CAUSE_OVERDUE: u32 = kui_core::FrameCause::OVERDUE.bits();
2420pub const KUI_FRAME_CAUSE_DEVICE: u32 = kui_core::FrameCause::DEVICE.bits();
2421pub const KUI_FRAME_CAUSE_AFTER_FRAME: u32 = kui_core::FrameCause::AFTER_FRAME.bits();
2422pub const KUI_FRAME_CAUSE_ELSEWHERE: u32 = kui_core::FrameCause::ELSEWHERE.bits();
2423pub const KUI_FRAME_CAUSE_MENU: u32 = kui_core::FrameCause::MENU.bits();
2424pub const KUI_FRAME_CAUSE_AUDIO: u32 = kui_core::FrameCause::AUDIO.bits();
2425pub const KUI_FRAME_CAUSE_SMOKE: u32 = kui_core::FrameCause::SMOKE.bits();
2426pub const KUI_FRAME_CAUSE_OWED: u32 = kui_core::FrameCause::OWED.bits();
2427
2428/// `KUI_COPY_*`: what `kui_request_copy` returns.
2429pub const KUI_COPY_READY: u32 = 0;
2430pub const KUI_COPY_ASKED: u32 = 1;
2431pub const KUI_COPY_NOTHING: u32 = 2;
2432
2433/// `KUI_AUDIO_*`: a `KuiAudioCommand.kind`.
2434pub const KUI_AUDIO_PLAY: u32 = 1;
2435pub const KUI_AUDIO_STOP: u32 = 2;
2436pub const KUI_AUDIO_SET_VOLUME: u32 = 3;
2437pub const KUI_AUDIO_PAUSE: u32 = 4;
2438pub const KUI_AUDIO_RESUME: u32 = 5;
2439pub const KUI_AUDIO_MASTER_VOLUME: u32 = 6;
2440pub const KUI_AUDIO_UNLOAD: u32 = 7;
2441
2442/// `KUI_WINDOW_NONE`: a `KuiSpec.window_role` that is no chrome role.
2443pub const KUI_WINDOW_NONE: u32 = 0;