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;