Skip to main content

ui/
ctx.rs

1// ctx -- UI context: input state, passes, hit testing, hot / active / focus, state slots and
2// animations, deferred commands and timers, semantic records, clip, layers, modal,
3// disabled regions, warnings                                            [src/ui/ui_ctx.s]
4//
5// App-facing:
6//   now() -> ms                     milliseconds (virtual clock in headless runs)
7//   key(vk) -> bool                 vk pressed this frame and not yet consumed (consumes it)
8//   key_peek(vk) / key_down(vk)     the same without consuming / while the key is held
9//   mods() -> MOD_*   mouse() -> (x, y) px (-1 outside)   pen() -> (pressure, PTR_*)
10//   focus(id) / focused() -> id     give / ask the keyboard focus
11//   cmd_run(id, arg)                queue a command for App::command (runs after the frame)
12//   timer(id, ms)                   one-shot timer -> App::command(UIC_TIMER, id)
13//   anim(id, target, ms) -> value   animated value 0..256 towards target
14//   warn(msg)                       "UIWARN f<frame>: ..." on stderr; scripts and jfgui_DEBUG
15//                                   also warn once about an id two widgets of one pass share
16//                                   (compound widgets take id + 1 .. or (id << 3 | k) parts)
17// Widget building blocks:
18//   behave(id, hbflags) -> EV_*     hit box on last_rect + pointer / keyboard behaviour
19//   hit(id, hbflags)                hit box only
20//   hit_rect(id, x, y, w, h, hb)    behave on an explicit rectangle (px)
21//   slot(id) -> &mut Slot           persistent per-id state record (per window)
22//   sem(kind, id, label, value)     semantic record (state from the last behave)
23//   clip_push(x, y, w, h) / clip_pop()   clip drawing and hit boxes (px)
24//   layer_set(l) -> previous        modal(layer)   disabled_push() / disabled_pop()
25// Runtime (runtime.rs, win.rs): frame_begin / frame_end (a pass over all windows),
26// uiw_pass_begin / uiw_pass_end (one window), input_event, input_clear_edges, hot_update.
27
28use alloc::collections::BTreeMap;
29use alloc::string::String;
30use alloc::vec::Vec;
31
32use super::consts::*;
33use super::keys::*;
34use super::Ui;
35use plat::Event;
36
37// The queues and stacks of a pass grow as needed (Vec: amortised growth, the capacity stays,
38// so the steady state allocates nothing).  Their ceilings only guard against runaway memory
39// (an app queueing in a loop); what a ceiling holds back is never dropped silently:
40// limit_hit counts it (Ui::limits) and warns the first time (UIWARN; scripts: expect_warn).
41
42/// keys one pass sees (the platform gives every key a pass of its own; more come from a tool
43/// window handing on what its widgets left, or the script command `keys`)
44pub(crate) const KEYQ_MAX: usize = 1024;
45/// typed codepoints a pass sees at most (one input method commit); more waits for the next
46/// passes (Ui::text_more), nothing typed is dropped
47pub(crate) const TEXTQ_MAX: usize = 4096;
48/// pointer positions of one pass for strokes (a 1 kHz pen during a long frame)
49pub(crate) const PTS_MAX: usize = 1 << 16;
50/// commands waiting for App::command (the UIC_TIMER of due timers among them)
51pub(crate) const CMDQ_MAX: usize = 1 << 16;
52/// timers waiting at once (distinct ids, the library's caret, tool tip, toast ... included)
53pub(crate) const TIMERS_MAX: usize = 4096;
54/// clip_push levels inside each other (the render's clip stack has no limit)
55pub(crate) const UICLIP_MAX: usize = 1024;
56/// hit boxes (and drop targets) of one window in one pass
57pub(crate) const HITS_MAX: usize = 1 << 16;
58/// semantic records of one pass (all windows) and the bytes of their labels
59pub(crate) const SEMS_MAX: usize = 1 << 16;
60pub(crate) const SEMTEXT_MAX: usize = 4 << 20;
61
62/// the ceilings limit_hit reports (Ui::limits[LIM_*]); fixed ones say next to their constant
63/// why they stay fixed
64pub(crate) const LIM_KEYS: usize = 0;
65pub(crate) const LIM_CMDS: usize = 1;
66pub(crate) const LIM_TIMERS: usize = 2;
67pub(crate) const LIM_POINTS: usize = 3;
68pub(crate) const LIM_HITS: usize = 4;
69pub(crate) const LIM_ZONES: usize = 5;
70pub(crate) const LIM_SEMS: usize = 6;
71pub(crate) const LIM_SEMTEXT: usize = 7;
72pub(crate) const LIM_CLIP: usize = 8;
73pub(crate) const LIM_WINNEST: usize = 9;
74pub(crate) const LIM_POPUP_SLOTS: usize = 10;
75pub(crate) const LIM_POPUPS: usize = 11;
76pub(crate) const LIM_FLYOUTS: usize = 12;
77pub(crate) const LIM_MENU_ROWS: usize = 13;
78pub(crate) const LIM_MENUBAR: usize = 14;
79pub(crate) const LIM_SCROLLS: usize = 15;
80pub(crate) const LIM_LISTS: usize = 16;
81pub(crate) const LIM_EXPANDERS: usize = 17;
82pub(crate) const LIM_WRAP: usize = 18;
83pub(crate) const LIM_N: usize = 19;
84const LIM_TEXT: [&str; LIM_N] = [
85    "more than 1024 keys in one pass: the rest dropped",
86    "more than 65536 commands waiting: the rest dropped",
87    "more than 4096 timers at once: new ones dropped",
88    "more than 65536 pointer positions in one pass: the rest dropped",
89    "more than 65536 hit boxes in one window pass: the rest cannot be hit",
90    "more than 65536 drop targets in one window pass: the rest take no drops",
91    "more than 65536 widget records in one pass: the rest missing (dump, expect, screen readers)",
92    "more than 4 MiB of record labels in one pass: the rest without labels",
93    "clip_push nested more than 1024 deep: deeper levels clip no hit boxes",
94    "window_begin nested more than 16 deep: deeper windows not shown",
95    "more than 4 popups outside their windows: the rest drawn clipped to the window",
96    "popups nested more than 6 deep: the deepest one replaced",
97    "flyouts nested more than 8 deep: deeper ones not shown",
98    "a menu with more than 250 rows: the rest not shown",
99    "a menu bar with more than 16 menus: the rest not shown",
100    "scroll views nested more than 8 deep: deeper ones share one record",
101    "lists nested more than 4 deep: deeper ones share one record",
102    "expanders nested more than 16 deep: deeper ones lose their content box",
103    "a wrapped text of more than 65536 lines: the rest not shown",
104];
105
106/// hit record of a pass (clipped, physical px; x1 / y1 exclusive)  [HR_*]
107#[derive(Clone, Copy, Default)]
108pub(crate) struct Hit {
109    pub(crate) id: u32,
110    /// Additional region emitted by the same logical widget call (e.g. a text line).
111    pub(crate) continuation: bool,
112    /// HB_* (cursor in bits 12..15)
113    pub(crate) flags: u16,
114    /// HB_KEYS: every key is the widget's while it has the focus
115    pub(crate) keys: bool,
116    /// (E3) the cursor of ui.cursor_next (any CUR_*; 0: the shape in flags)
117    pub(crate) cursor: u8,
118    /// the axes along which a finger belongs to the widget (touch.rs, AX_*)
119    pub(crate) grab: u8,
120    pub(crate) layer: u8,
121    #[allow(dead_code)]
122    pub(crate) kind: u8,
123    /// keyboard (kbnav.rs): the focus group (0 = none) and the pane (0 = the window's root)
124    pub(crate) group: u32,
125    pub(crate) pane: u32,
126    pub(crate) x0: i32,
127    pub(crate) y0: i32,
128    pub(crate) x1: i32,
129    pub(crate) y1: i32,
130}
131
132/// (E2) a key as it went down or up, for a canvas that passes the keyboard on (a web page's
133/// keydown / keyup events): ui.key_event_take
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
135#[non_exhaustive]
136pub struct KeyEvent {
137    /// VK_*
138    pub vk: u32,
139    /// MOD_* at the time
140    pub mods: u32,
141    /// down (true) or up
142    pub down: bool,
143    /// a down of the system's auto-repeat while the key is held
144    pub repeat: bool,
145    /// the key's place on the keyboard, whatever the layout: Windows' scan code (0xE0.. for
146    /// extended keys), Linux's evdev code; 0 where the platform does not tell (macOS, the
147    /// browser, Android)
148    pub scancode: u32,
149    /// KL_STANDARD, KL_LEFT / KL_RIGHT (the two Shift, Ctrl, Alt, Windows keys), KL_NUMPAD
150    pub location: u32,
151}
152
153// (E2) a KeyEvent of the batch: the index of its down in keyq (-1: an up), taken
154#[derive(Clone, Copy)]
155pub(crate) struct RawKey {
156    pub(crate) ev: KeyEvent,
157    pub(crate) kq: i32,
158    pub(crate) taken: bool,
159}
160
161/// semantic record (dump / expect)  [SR_*]
162#[derive(Clone, Default)]
163pub struct Sem {
164    pub id: u32,
165    pub kind: u16,
166    /// SS_*
167    pub state: u16,
168    /// x, y, w, h physical px
169    pub rect: [i32; 4],
170    pub label: Option<String>,
171    pub value: i32,
172    pub max: i32,
173    /// the lower end of a slider's range (accessibility)
174    pub min: i32,
175    pub layer: u8,
176    pub depth: u8,
177    /// window index (0 = the main window)
178    pub win: u8,
179    /// what the record says beyond kind, label and value (its own 64-bit id, a parent, a
180    /// description, cells, states ...): set through the record's setters (id64, parent, desc ...;
181    /// section 19 of the guide); None for the records of the built-in widgets
182    pub ax: Option<alloc::boxed::Box<SemAx>>,
183}
184
185/// What a semantic record says beyond its kind, label and value (Sem::ax): its setters fill it
186/// (`ui.sem_rect(..).id64(7).parent(3).desc("..")`); a web page's accessibility tree needs it.
187#[derive(Clone, Debug, Default, PartialEq)]
188#[non_exhaustive]
189pub struct SemAx {
190    /// the record's own id (below 2^62): a screen reader's requests for it come back with it
191    /// (ui.a11y_request_take); 0: none
192    pub id: u64,
193    /// the record it hangs under in the accessibility tree: one with this id64 (or, without one,
194    /// this widget id) published in the same frame and window; 0: by its rectangle
195    pub parent: u64,
196    /// its name when the label is something else (a text box's or drop-down's label is its text)
197    pub name: Option<String>,
198    /// a description beside the name (a hint, a page's aria-describedby)
199    pub desc: Option<String>,
200    /// a link's address
201    pub url: Option<String>,
202    /// the text a screen reader says for a value (a slider's "Monday", a page's aria-valuetext)
203    pub value_text: Option<String>,
204    /// AS_*
205    pub states: u32,
206    /// its position in its set (1-based) and the set's size (0: unknown)
207    pub set: [u32; 2],
208    /// the level of a heading, tree item, list item or row (1 the top; 0: none / the value)
209    pub level: u32,
210    /// a cell's row, column (0-based), row span, column span (spans 0: no place)
211    pub cell: [u32; 4],
212    /// a table's rows and columns (0: unknown)
213    pub grid: [u32; 2],
214    /// LIVE_* (0: as its kind says)
215    pub live: u32,
216    /// value, minimum, maximum, step with fractions (a meter at 0.4 of 1); None: Sem::value / min
217    /// / max
218    pub range: Option<[f64; 4]>,
219    /// a focused text box's selection: anchor, caret in characters (a page's field; the built-in
220    /// boxes report theirs themselves)
221    pub sel: Option<[u32; 2]>,
222}
223
224/// persistent per-id state (slot)  [SL_*]
225#[derive(Clone, Copy, Default)]
226pub struct Slot {
227    pub seen: u32,
228    pub afrom: u16,
229    pub ato: u16,
230    pub at0: u32,
231    pub adur: u32,
232    pub measw: i32,
233    pub meash: i32,
234    pub fixed: i32,
235    pub wsum: i32,
236    pub scrollx: i32,
237    pub scrolly: i32,
238    pub aux: i32,
239}
240
241/// ease(curve, t) -> the eased progress: t and the result in 0..256 (EASE_OUT_BACK overshoots
242/// above 256 on the way)
243pub fn ease(curve: u32, t: i32) -> i32 {
244    let t = t.clamp(0, 256);
245    let u = 256 - t;
246    match curve {
247        EASE_LINEAR => t,
248        EASE_IN => (t * t) >> 8,
249        EASE_IN_OUT => {
250            // 4 t^3 below the middle, 1 - (-2 t + 2)^3 / 2 above it
251            if t < 128 {
252                ((4 * t * t >> 8) * t) >> 8
253            } else {
254                let v = 2 * u;
255                256 - (((v * v >> 8) * v) >> 9)
256            }
257        }
258        EASE_OUT_CUBIC => 256 - (((u * u >> 8) * u) >> 8),
259        EASE_OUT_BACK => {
260            // 1 + c3 (t - 1)^3 + c1 (t - 1)^2 with c1 = 1.70158, c3 = c1 + 1 (in 1/256)
261            let x = t - 256;
262            let x2 = x * x >> 8;
263            let x3 = x2 * x >> 8;
264            256 + (x3 * 691 >> 8) + (x2 * 436 >> 8)
265        }
266        // EASE_OUT: 1 - (1 - t)^2 (anim's curve)
267        _ => 256 - ((u * u) >> 8),
268    }
269}
270
271/// the per-window part of the context (ui_ctx_wb0 .. ui_ctx_wb1)
272pub(crate) struct W {
273    pub(crate) last_rect: [i32; 4],
274    /// x0, y0, x1, y1
275    pub(crate) uiclip: [i32; 4],
276    pub(crate) uiclip_stack: Vec<[i32; 4]>,
277    /// clip_push levels beyond UICLIP_MAX (their clip_pop restore nothing)
278    pub(crate) uiclip_over: u32,
279    /// two lists: current / previous pass (hit_cur selects the current one)
280    pub(crate) hits: [Vec<Hit>; 2],
281    /// the drop targets of the same two passes (dnd.rs)
282    pub(crate) zones: [Vec<super::dnd::Zone>; 2],
283    /// the views a finger can roll and the rectangles of ui.touch_drag (x0, y0, x1, y1) of the
284    /// same two passes (touch.rs)
285    pub(crate) pans: [Vec<super::touch::PanView>; 2],
286    pub(crate) grabs: [Vec<[i32; 4]>; 2],
287    pub(crate) keyq: Vec<u32>,
288    /// (E2) the keys as they went down and up in this batch (key_event_take)
289    pub(crate) rawq: Vec<RawKey>,
290    pub(crate) textq: Vec<u32>,
291    pub(crate) hit_cur: usize,
292    pub(crate) mx: i32,
293    pub(crate) my: i32,
294    pub(crate) mbtn: u32,
295    pub(crate) pressed: u32,
296    pub(crate) released: u32,
297    pub(crate) wheel_x: i32,
298    pub(crate) wheel_y: i32,
299    pub(crate) moved: bool,
300    pub(crate) press_x: i32,
301    pub(crate) press_y: i32,
302    pub(crate) press_ms: u32, // the clock when the left button made a widget active
303    pub(crate) hot: u32,
304    pub(crate) hot_flags: u32,
305    pub(crate) active: u32,
306    pub(crate) active_seen: bool,
307    pub(crate) ractive: u32,
308    pub(crate) focus: u32,
309    pub(crate) focus_visible: bool,
310    pub(crate) press_used: bool,
311    pub(crate) rpress_used: bool,
312    pub(crate) last_click_id: u32,
313    pub(crate) last_click_ms: u32,
314    pub(crate) last_state: u32,
315    pub(crate) modal_cur: u32,
316    pub(crate) modal_prev: u32,
317    pub(crate) disabled_depth: u32,
318    pub(crate) cmd_layer_ui: u32,
319    /// keyboard navigation of this window (kbnav.rs)
320    pub(crate) kb: super::kbnav::W,
321    /// the state slots of this window's ids (Ui::slot): a box's measurements, a view's scroll
322    /// position, animations -- the same id in two windows has a slot in each
323    pub(crate) slots: BTreeMap<u32, Slot>,
324}
325
326impl W {
327    /// a window before its first pass (pointer outside: -1, -1)
328    pub(crate) fn new() -> W {
329        W {
330            last_rect: [0; 4],
331            uiclip: [0; 4],
332            uiclip_stack: Vec::new(),
333            uiclip_over: 0,
334            hits: [Vec::new(), Vec::new()],
335            zones: [Vec::new(), Vec::new()],
336            pans: [Vec::new(), Vec::new()],
337            grabs: [Vec::new(), Vec::new()],
338            keyq: Vec::new(),
339            rawq: Vec::new(),
340            textq: Vec::new(),
341            hit_cur: 0,
342            mx: -1,
343            my: -1,
344            mbtn: 0,
345            pressed: 0,
346            released: 0,
347            wheel_x: 0,
348            wheel_y: 0,
349            moved: false,
350            press_x: 0,
351            press_y: 0,
352            press_ms: 0,
353            hot: 0,
354            hot_flags: 0,
355            active: 0,
356            active_seen: false,
357            ractive: 0,
358            focus: 0,
359            focus_visible: false,
360            press_used: false,
361            rpress_used: false,
362            last_click_id: 0,
363            last_click_ms: 0,
364            last_state: 0,
365            modal_cur: 0,
366            modal_prev: 0,
367            disabled_depth: 0,
368            cmd_layer_ui: 0,
369            kb: Default::default(),
370            slots: BTreeMap::new(),
371        }
372    }
373}
374
375/// hit_find_list(list, modal_layer, x, y) -> (id, flags) of the topmost hit box at the
376/// point: layers from the top down to modal_layer, last record first; HB_BLOCK /
377/// HB_DISABLED boxes answer id 0.  (-1, -1) = outside the window: none.
378pub(crate) fn hit_find_list(list: &[Hit], modal_layer: u32, x: i32, y: i32) -> (u32, u32) {
379    match hit_find(list, modal_layer, x, y) {
380        Some(h) => {
381            let flags = h.flags as u32;
382            (if flags & (HB_BLOCK | HB_DISABLED) != 0 { 0 } else { h.id }, flags)
383        }
384        None => (0, 0),
385    }
386}
387
388// the topmost hit box at the point (see hit_find_list)
389pub(crate) fn hit_find(list: &[Hit], modal_layer: u32, x: i32, y: i32) -> Option<&Hit> {
390    // outside the window = (-1, -1) (E_LEAVE); other negative points are real: popups of a
391    // secondary window may lie left of / above it
392    if x == -1 && y == -1 {
393        return None;
394    }
395    let mut layer = L_COUNT as i32 - 1;
396    while layer >= modal_layer as i32 {
397        for h in list.iter().rev() {
398            if h.layer as i32 != layer || x < h.x0 || x >= h.x1 || y < h.y0 || y >= h.y1 {
399                continue;
400            }
401            return Some(h);
402        }
403        layer -= 1;
404    }
405    None
406}
407
408// (E3) the cursor a hit box shows: cursor_next's, else the shape of its flags; the arrow over a
409// title bar's drag region
410pub(crate) fn hit_cursor(h: Option<&Hit>) -> u32 {
411    match h {
412        Some(h) if h.flags as u32 & HB_DRAG != 0 => CUR_ARROW,
413        Some(h) if h.cursor != 0 => h.cursor as u32,
414        Some(h) => (h.flags as u32 >> HB_CURSOR_SHIFT) & 15,
415        None => CUR_ARROW,
416    }
417}
418
419impl Ui {
420    // ------------------------------------------------------------------ small queries
421
422    /// ui_now: milliseconds since start (virtual clock in headless runs)
423    pub fn now(&self) -> u32 {
424        self.now_ms
425    }
426
427    /// ui_mods: MOD_* bits of the current input
428    pub fn mods(&self) -> u32 {
429        self.mods
430    }
431
432    /// ui_mouse: pointer position in px (-1 when outside)
433    pub fn mouse(&self) -> (i32, i32) {
434        (self.wp.ctx.mx, self.wp.ctx.my)
435    }
436
437    /// ui_pen: (pressure 0 = none / 1..UI_PRESSURE_ONE, PTR_*) of the last pointer event
438    pub fn pen(&self) -> (u32, u32) {
439        (self.mpress, self.mptype)
440    }
441
442    /// ui_focus(id): give the keyboard focus to a widget (focus ring shown).  The id is
443    /// looked up in whichever window emits it in the next pass (that window is activated).
444    pub fn focus(&mut self, id: u32) {
445        self.focus_req_old = self.wp.ctx.focus;
446        self.focus_req = id;
447        self.focus_req_win = self.uiw_cur;
448        self.focus_req_frame = self.frame;
449        self.wp.ctx.focus = id;
450        self.wp.ctx.focus_visible = true;
451        self.repass = true;
452    }
453
454    /// the keyboard focus to a widget of this window without the focus ring: the keyboard goes
455    /// back to it after a pointer gesture (a ring only follows keyboard navigation)
456    pub fn focus_quiet(&mut self, id: u32) {
457        self.wp.ctx.focus = id;
458        self.wp.ctx.focus_visible = false;
459        self.repass = true;
460    }
461
462    /// ui_focus_req_expire: after the passes, a focus id no window emitted is dropped
463    pub(crate) fn focus_req_expire(&mut self) {
464        if self.focus_req_frame < self.frame {
465            self.focus_req = 0;
466        }
467    }
468
469    /// ui_focused: id of the focused widget (0 = none)
470    pub fn focused(&self) -> u32 {
471        self.wp.ctx.focus
472    }
473
474    // ui_key_find: index of an unconsumed queue entry for vk
475    fn key_find(&self, vk: u32) -> Option<usize> {
476        self.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk)
477    }
478
479    /// ui_key(vk): pressed this frame and not yet consumed (consumes it)
480    pub fn key(&mut self, vk: u32) -> bool {
481        let k = self.key_find(vk);
482        match self.text_key_filter(k) {
483            Some(i) => {
484                self.wp.ctx.keyq[i] |= 0x8000_0000;
485                true
486            }
487            None => false,
488        }
489    }
490
491    /// Capture an exact application shortcut before text widgets handle it. Unlike key(),
492    /// this bypasses the focused textbox's key reservation. Use only for an editing
493    /// surface whose history/commands the application owns, and not while a modal draft
494    /// dialog owns input. Other shortcuts and the normal key() behavior are unchanged.
495    pub fn capture_shortcut(&mut self, vk: u32, mods: u32) -> bool {
496        if let Some(i) = self.wp.ctx.keyq.iter().position(|&k| {
497            (k as i32) >= 0 && k & 0xffff == vk && (k >> 16) == mods
498        }) {
499            self.wp.ctx.keyq[i] |= 0x8000_0000;
500            true
501        } else {
502            false
503        }
504    }
505
506    /// ui_key_peek(vk): pressed this frame and unconsumed (not consumed)
507    pub fn key_peek(&mut self, vk: u32) -> bool {
508        let k = self.key_find(vk);
509        self.text_key_filter(k).is_some()
510    }
511
512    /// ui_key_down(vk): held (mods: VK_SHIFT / VK_CONTROL / VK_MENU)
513    pub fn key_down(&self, vk: u32) -> bool {
514        self.keydown[(vk & 255) as usize] != 0
515    }
516
517    /// ui_key_first: the first unconsumed key this frame (VK | mods << 16) or 0
518    pub fn key_first(&self) -> u32 {
519        self.wp.ctx.keyq.iter().copied().find(|&k| (k as i32) >= 0).unwrap_or(0)
520    }
521
522    /// key_take -> the first unconsumed key this frame (VK | mods << 16), consumed; 0 when none
523    /// is left.  A canvas with HB_KEYS takes every key with it while it has the focus (`ev &
524    /// EV_FOCUS`, call it until 0); the keys a focused text box keeps stay with the box, and
525    /// while a popup of the window is open (a menu, a flyout) the keys are its.
526    pub fn key_take(&mut self) -> u32 {
527        if self.popup_count() != 0 {
528            return 0;
529        }
530        for i in 0..self.wp.ctx.keyq.len() {
531            let k = self.wp.ctx.keyq[i];
532            if (k as i32) >= 0 && self.text_key_filter(Some(i)).is_some() {
533                self.wp.ctx.keyq[i] |= 0x8000_0000;
534                return k;
535            }
536        }
537        0
538    }
539
540    /// (E2) the next key as it went down or up (KeyEvent: the key, modifiers, down / up, auto-
541    /// repeat, scancode, left / right / number block), consumed; None when none is left.  For a
542    /// canvas with HB_KEYS that passes the keyboard on as it is (a web page's keydown and keyup):
543    /// call it while the canvas has the focus (`ev & EV_FOCUS`) until None, instead of key_take
544    /// (a down taken here is taken for key_take too, and one key_take took is not here).  The
545    /// typed text comes through text_take as before.  None while a popup of the window is open
546    pub fn key_event_take(&mut self) -> Option<KeyEvent> {
547        if self.popup_count() != 0 {
548            return None;
549        }
550        for i in 0..self.wp.ctx.rawq.len() {
551            let r = self.wp.ctx.rawq[i];
552            if r.taken {
553                continue;
554            }
555            self.wp.ctx.rawq[i].taken = true;
556            if r.kq >= 0 {
557                let k = r.kq as usize;
558                // its down: unless taken already or kept by a focused text box
559                if self.wp.ctx.keyq.get(k).is_none_or(|&q| (q as i32) < 0) || self.text_key_filter(Some(k)).is_none() {
560                    continue;
561                }
562                self.wp.ctx.keyq[k] |= 0x8000_0000;
563            }
564            return Some(r.ev);
565        }
566        None
567    }
568
569    /// ui_text_take(cap): the typed codepoints (at most cap), consumed; when cap leaves some,
570    /// they come in the next pass (nothing typed is lost)
571    pub fn text_take(&mut self, cap: usize) -> Vec<u32> {
572        let c = &mut self.wp.ctx;
573        let n = c.textq.len().min(cap);
574        let v: Vec<u32> = c.textq.drain(..n).collect();
575        if n > 0 && !c.textq.is_empty() {
576            let rest = core::mem::take(&mut c.textq);
577            self.text_defer(rest);
578        }
579        self.wp.ctx.textq.clear(); // (cap 0 takes nothing: the text goes)
580        v
581    }
582
583    // text_defer(rest): typed text of the live window for the next pass, before the text
584    // that waits already (it came later)
585    fn text_defer(&mut self, rest: Vec<u32>) {
586        let w = self.uiw_cur;
587        match self.text_more.first_mut() {
588            Some((m, q)) if *m == w => drop(q.splice(0..0, rest)),
589            _ => self.text_more.insert(0, (w, rest)),
590        }
591    }
592
593    // text_push(cp): a typed codepoint for the live window; beyond TEXTQ_MAX (or behind text
594    // that waits already) it waits for the next pass (text_refill)
595    fn text_push(&mut self, cp: u32) {
596        if self.text_more.is_empty() && self.wp.ctx.textq.len() < TEXTQ_MAX {
597            self.wp.ctx.textq.push(cp);
598            return;
599        }
600        let w = self.uiw_cur;
601        match self.text_more.last_mut() {
602            Some((m, q)) if *m == w => q.push(cp),
603            _ => self.text_more.push((w, alloc::vec![cp])),
604        }
605    }
606
607    /// text_refill -> true: the next part of the text that waits (text_more) is in its
608    /// window's queue for the next passes (run_passes); text of a window that is gone goes
609    pub(crate) fn text_refill(&mut self) -> bool {
610        while let Some((w, q)) = self.text_more.first() {
611            let w = *w;
612            if q.is_empty() || (w != 0 && !self.uiw_drawable(w)) {
613                self.text_more.remove(0);
614                continue;
615            }
616            let cur = self.uiw_cur;
617            self.uiw_use(w);
618            let q = &mut self.text_more[0].1;
619            let n = q.len().min(TEXTQ_MAX.saturating_sub(self.wp.ctx.textq.len()));
620            self.wp.ctx.textq.extend(q.drain(..n));
621            if q.is_empty() {
622                self.text_more.remove(0);
623            }
624            self.m.win.edges |= 1 << (w & (WIN_MAX as u32 - 1)); // cleared after the first pass
625            self.uiw_fwd_arm(w);
626            self.uiw_use(cur);
627            return true;
628        }
629        false
630    }
631
632    /// ui_keys_take_rest(cap): (U3, key forwarding of tool windows) the unconsumed keys of
633    /// this frame (VK | mods << 16), consumed
634    #[allow(dead_code)] // ui-win: key forwarding of tool windows (U3)
635    pub(crate) fn keys_take_rest(&mut self, cap: usize) -> Vec<u32> {
636        let mut out = Vec::new();
637        for k in self.wp.ctx.keyq.iter_mut() {
638            if (*k as i32) < 0 {
639                continue;
640            }
641            if out.len() >= cap {
642                break;
643            }
644            out.push(*k);
645            *k |= 0x8000_0000;
646        }
647        out
648    }
649
650    /// ui_keys_inject(keys): (U3) append keys to the live window's queue (the forwarded keys
651    /// of a tool window; the script command `keys`)
652    pub(crate) fn keys_inject(&mut self, keys: &[u32]) {
653        for &k in keys {
654            self.key_push(k & 0x7FFF_FFFF);
655        }
656    }
657
658    // key_push(k): a key (VK | mods << 16) for the live window's pass
659    // (E2) a key as it went down or up (KEYQ_MAX of them per batch)
660    fn raw_push(&mut self, ev: KeyEvent, kq: i32) {
661        if self.wp.ctx.rawq.len() < KEYQ_MAX {
662            self.wp.ctx.rawq.push(RawKey { ev, kq, taken: false });
663        }
664    }
665
666    fn key_push(&mut self, k: u32) {
667        if self.wp.ctx.keyq.len() < KEYQ_MAX {
668            self.wp.ctx.keyq.push(k);
669        } else {
670            self.limit_hit(LIM_KEYS);
671        }
672    }
673
674    /// limit_hit(lim): a ceiling (LIM_*) held something back: counted in Ui::limits, a
675    /// UIWARN the first time
676    pub(crate) fn limit_hit(&mut self, lim: usize) {
677        let n = self.limits[lim];
678        self.limits[lim] = n.saturating_add(1);
679        if n == 0 {
680            self.warn(LIM_TEXT[lim]);
681        }
682    }
683
684    /// ui_text_inject(cps): (U3) append typed codepoints to the live window's text queue
685    #[allow(dead_code)] // ui-win: key forwarding of tool windows (U3)
686    pub(crate) fn text_inject(&mut self, cps: &[u32]) {
687        // text of this pass: before any that waits; what does not fit, in the next pass
688        let n = TEXTQ_MAX.saturating_sub(self.wp.ctx.textq.len()).min(cps.len());
689        self.wp.ctx.textq.extend_from_slice(&cps[..n]);
690        if n < cps.len() {
691            self.text_defer(cps[n..].to_vec());
692        }
693    }
694
695    // ------------------------------------------------------------------ input feed
696
697    /// ui_input_event(ev) -> 0 nothing, 1 an edge (its own pass), 2 text (batched)
698    pub(crate) fn input_event(&mut self, ev: &Event) -> u32 {
699        let mut e = *ev;
700        self.rtl_input(&mut e); // a mirrored window: logical pointer and arrows
701        let ev = &e;
702        self.mods = ev.mods as u32;
703        match ev.typ {
704            plat::E_MOVE => {
705                self.wp.ctx.mx = ev.x;
706                self.wp.ctx.my = ev.y;
707                self.pen_decode(ev.data);
708                self.pts_add();
709                self.wp.ctx.moved = true;
710                self.dnd_moved(); // a drag that leaves the window goes to the platform
711                0
712            }
713            plat::E_DOWN => {
714                if ev.x != -1 {
715                    self.wp.ctx.mx = ev.x;
716                    self.wp.ctx.my = ev.y;
717                }
718                self.pen_decode(ev.data);
719                let b = (ev.btn & 31) as u32;
720                let c = &mut self.wp.ctx;
721                c.mbtn |= 1 << b;
722                c.pressed |= 1 << b;
723                c.press_x = c.mx;
724                c.press_y = c.my;
725                self.mpts.clear();
726                self.mprs.clear();
727                self.pts_add();
728                1
729            }
730            plat::E_UP => {
731                self.dnd_input(ev); // the release of a drag: the drop
732                let c = &mut self.wp.ctx;
733                if ev.x != -1 {
734                    c.mx = ev.x;
735                    c.my = ev.y;
736                }
737                let b = (ev.btn & 31) as u32;
738                c.mbtn &= !(1 << b);
739                c.released |= 1 << b;
740                1
741            }
742            plat::E_WHEEL => {
743                let c = &mut self.wp.ctx;
744                c.wheel_y = c.wheel_y.wrapping_add(ev.data as u16 as i16 as i32);
745                c.wheel_x = c.wheel_x.wrapping_add((ev.data as i32) >> 16);
746                1
747            }
748            plat::E_KEY_DOWN => {
749                if self.dnd_input(ev) {
750                    return 1; // Esc ended a drag
751                }
752                let vk = ev.data & 0xFFFF;
753                self.keydown[(vk & 255) as usize] = 1;
754                let n = self.wp.ctx.keyq.len();
755                self.key_push((vk | self.mods << 16) & 0x7FFF_FFFF);
756                // (E2) as it went down
757                let kq = if self.wp.ctx.keyq.len() > n { n as i32 } else { -1 };
758                let ev = KeyEvent { vk, mods: self.mods, down: true, repeat: ev.data >> 31 != 0, scancode: ev.x as u32, location: ev.y as u32 };
759                self.raw_push(ev, kq);
760                1
761            }
762            plat::E_KEY_UP => {
763                let k = ev.data & 255;
764                self.keydown[k as usize] = 0;
765                // (E2) as it went up: a pass of its own for a canvas that takes the keyboard
766                let keys = self.focus_has_keys();
767                let ev = KeyEvent { vk: ev.data & 0xFFFF, mods: self.mods, down: false, repeat: false, scancode: ev.x as u32, location: ev.y as u32 };
768                self.raw_push(ev, -1);
769                // (popup agent) Alt release: a pass for the menu bar's Alt tap
770                (k == VK_MENU || keys) as u32
771            }
772            plat::E_TEXT => {
773                self.text_push(ev.data);
774                2 // text batches with following text
775            }
776            plat::E_PREEDIT => 1,
777            plat::E_IME_COMMIT => {
778                if let Some(text) = plat::ime_commit_take(ev.data) {
779                    self.hr_commit(&text); // (hotreload.rs) the commit's text into its record
780                    for c in text.chars() {
781                        self.text_push(c as u32);
782                    }
783                }
784                1 // atomic input, handled in its own pass
785            }
786            plat::E_LEAVE => {
787                let c = &mut self.wp.ctx;
788                c.mx = -1;
789                c.my = -1;
790                c.moved = true;
791                0
792            }
793            plat::E_FOCUS_OUT => {
794                self.wp.ctx.mbtn = 0;
795                self.keydown = [0; 256];
796                1
797            }
798            _ => 0,
799        }
800    }
801
802    // pen_decode(data): (PN1) Event::data of a pointer event -> pressure, type, tilt
803    fn pen_decode(&mut self, d: u32) {
804        self.mpress = d & plat::PD_PRESSURE;
805        self.mptype = (d >> plat::PD_TYPE_SHIFT) & 3;
806        self.mtilt[0] = ((d << (32 - plat::PD_TILTY_SHIFT)) as i32) >> 25;
807        self.mtilt[1] = (d as i32) >> plat::PD_TILTY_SHIFT;
808        if self.mptype == PTR_TOUCH {
809            self.mtilt = [0, 0]; // (those bits hold a finger's time)
810        }
811    }
812
813    // pts_add: remember the pointer position for stroke-like widgets (one window's points)
814    fn pts_add(&mut self) {
815        if self.uiw_cur != self.mpts_win {
816            self.mpts_win = self.uiw_cur;
817            self.mpts.clear();
818            self.mprs.clear();
819        }
820        if self.mpts.len() < PTS_MAX {
821            self.mpts.push([self.wp.ctx.mx, self.wp.ctx.my]);
822            self.mprs.push(self.mpress);
823        } else {
824            self.limit_hit(LIM_POINTS);
825        }
826    }
827
828    /// ui_input_clear_edges: after the first pass of a batch the edges are consumed
829    pub(crate) fn input_clear_edges(&mut self) {
830        let c = &mut self.wp.ctx;
831        c.pressed = 0;
832        c.released = 0;
833        c.wheel_x = 0;
834        c.wheel_y = 0;
835        c.keyq.clear();
836        c.rawq.clear();
837        c.textq.clear();
838        c.moved = false;
839        // keep the last pointer position as the start of the next stroke segment
840        if self.uiw_cur == self.mpts_win && !self.mpts.is_empty() {
841            let n = self.mpts.len();
842            let (p, r) = (self.mpts[n - 1], self.mprs[n - 1]);
843            self.mpts.clear();
844            self.mprs.clear();
845            self.mpts.push(p);
846            self.mprs.push(r);
847        }
848    }
849
850    /// ui_set_time(ms)
851    pub(crate) fn set_time(&mut self, ms: u32) {
852        self.now_ms = ms;
853        if self.headless {
854            plat::task::clock_set(Some(ms as u64 * 1000)); // task timers follow the virtual clock
855        }
856    }
857
858    // ------------------------------------------------------------------ hit testing
859
860    /// ui_hot_find(x, y) -> (id, flags) at the point in the previous pass (0 in a window
861    /// made inert by a modal window)
862    pub(crate) fn hot_find(&self, x: i32, y: i32) -> (u32, u32) {
863        if self.uiw_inert_now() {
864            return (0, 0);
865        }
866        let c = &self.wp.ctx;
867        hit_find_list(&c.hits[c.hit_cur ^ 1], c.modal_prev, x, y)
868    }
869
870    /// (E3) the cursor shape at a point (px) of the current window in the previous pass: the
871    /// topmost hit box's (CUR_*: ui.cursor_next's, else the shape of its flags; CUR_CUSTOM for
872    /// the custom cursor), CUR_ARROW where there is none
873    pub fn cursor_at(&self, x: i32, y: i32) -> u32 {
874        let c = &self.wp.ctx;
875        hit_cursor(hit_find(&c.hits[c.hit_cur ^ 1], c.modal_prev, x, y))
876    }
877
878    /// ui_hit_flags_at(x, y): HB_* flags of the topmost hit box at a point of the main
879    /// window (previous pass) -- non-client hit testing of the platform
880    pub fn hit_flags_at(&mut self, x: i32, y: i32) -> u32 {
881        self.hit_flags_win(0, x, y)
882    }
883
884    /// ui_hot_update -> true if the hot widget changed (sets the cursor too)
885    pub(crate) fn hot_update(&mut self) -> bool {
886        let (id, flags) = self.hot_find(self.wp.ctx.mx, self.wp.ctx.my);
887        // cursor: the active widget keeps its cursor
888        if self.wp.ctx.active == 0 {
889            let shape = if self.uiw_inert_now() { CUR_ARROW } else { self.cursor_at(self.wp.ctx.mx, self.wp.ctx.my) };
890            let s = self.cursor_resolve(shape);
891            plat::set_cursor(s);
892        }
893        let c = &mut self.wp.ctx;
894        let changed = id != c.hot;
895        c.hot = id;
896        c.hot_flags = flags;
897        changed
898    }
899
900    /// ui_needs_move_pass: a pointer move must be answered with a pass
901    pub(crate) fn needs_move_pass(&mut self) -> bool {
902        self.hot_update() || self.wp.ctx.active != 0 || self.wp.ctx.hot_flags & HB_MOVES != 0 || self.dnd_follows_pointer()
903    }
904
905    /// ui_hit(id, hbflags): register last_rect (clipped) as a hit box of the current layer
906    pub fn hit(&mut self, id: u32, hbflags: u32) {
907        let c = &mut self.wp.ctx;
908        let (group, pane, notab) = c.kb.hit_keys(hbflags);
909        if c.hits[c.hit_cur].len() >= HITS_MAX {
910            self.limit_hit(LIM_HITS);
911            return;
912        }
913        let c = &mut self.wp.ctx;
914        let list = &mut c.hits[c.hit_cur];
915        let r = c.last_rect;
916        let k = c.uiclip;
917        let cursor = core::mem::take(&mut self.wp.lay.next_cursor).min(255) as u8;
918        list.push(Hit {
919            id,
920            continuation: false,
921            flags: (hbflags | notab) as u16,
922            keys: hbflags & HB_KEYS != 0,
923            cursor,
924            grab: super::touch::hit_grab(hbflags),
925            layer: c.cmd_layer_ui as u8,
926            kind: 0,
927            group,
928            pane,
929            x0: r[0].max(k[0]),
930            y0: r[1].max(k[1]),
931            x1: r[0].wrapping_add(r[2]).min(k[2]),
932            y1: r[1].wrapping_add(r[3]).min(k[3]),
933        });
934    }
935
936    /// ui_behave(id, hbflags) -> EV_*: the standard pointer / keyboard behaviour of a
937    /// widget occupying last_rect
938    pub fn behave(&mut self, id: u32, hbflags: u32) -> u32 {
939        // a pending focus(id) for this widget (it may live in another window)
940        if self.focus_req == id && id != 0 {
941            self.uiw_focus_arrived();
942        }
943        let mut flags = hbflags;
944        // inside a disabled box everything is disabled
945        if self.wp.ctx.disabled_depth != 0 {
946            flags |= HB_DISABLED;
947        }
948        self.hit(id, flags);
949        self.wp.ctx.last_state = 0;
950        if flags & HB_DISABLED != 0 {
951            self.wp.ctx.last_state = SS_DISABLED as u32;
952            return 0;
953        }
954        let now = self.now_ms;
955        let lim = self.dp(4);
956        let dbl_ms = self.dbl_ms; // the system's double-click time
957        let mut ev = 0;
958        let c = &mut self.wp.ctx;
959        if c.hot == id {
960            ev |= EV_HOVER;
961            if c.pressed & (1 << BTN_LEFT) != 0 && !c.press_used {
962                c.press_used = true;
963                c.active = id;
964                c.press_ms = now;
965                if flags & HB_FOCUS != 0 {
966                    c.focus = id;
967                    c.focus_visible = false;
968                }
969                if c.last_click_id == id && now.wrapping_sub(c.last_click_ms) <= dbl_ms {
970                    ev |= EV_DOUBLE;
971                    c.last_click_id = 0;
972                } else {
973                    c.last_click_id = id;
974                    c.last_click_ms = now;
975                }
976            }
977            if c.pressed & (1 << BTN_RIGHT) != 0 && !c.rpress_used {
978                c.rpress_used = true;
979                c.ractive = id;
980            }
981        }
982        // active (held) widget
983        if c.active == id {
984            c.active_seen = true;
985            if c.mbtn & (1 << BTN_LEFT) != 0 {
986                ev |= EV_HELD;
987                // dragged more than 4 DIP?
988                let dx = c.mx.wrapping_sub(c.press_x).wrapping_abs();
989                let dy = c.my.wrapping_sub(c.press_y).wrapping_abs();
990                if dx > lim || dy > lim {
991                    ev |= EV_DRAG;
992                }
993            } else {
994                c.active = 0;
995                ev |= EV_COMMIT;
996                if ev & EV_HOVER != 0 {
997                    ev |= EV_ACT;
998                }
999            }
1000        }
1001        if c.ractive == id && c.mbtn & (1 << BTN_RIGHT) == 0 {
1002            c.ractive = 0;
1003            if ev & EV_HOVER != 0 {
1004                ev |= EV_RCLICK;
1005            }
1006        }
1007        if c.focus == id {
1008            ev |= EV_FOCUS;
1009            if flags & (HB_TEXT | HB_OWNKEYS | HB_KEYS) == 0 && flags & HB_FOCUS != 0 && (self.key(VK_SPACE) || flags & HB_SPACE == 0 && self.key(VK_RETURN)) {
1010                ev |= EV_ACT;
1011                self.wp.ctx.focus_visible = true;
1012            }
1013        }
1014        // the menu key / Shift+F10 that no widget took: a right click of the focused widget
1015        // (kbnav.rs)
1016        if self.m.kbnav.ctx == id && id != 0 {
1017            ev |= self.kb_ctx_take();
1018        }
1019        // a click of a screen reader (a11y.rs) acts like Space
1020        if self.m.a11y.pending() && flags & HB_TEXT == 0 && self.a11y_click(id) {
1021            ev |= EV_ACT;
1022        }
1023        // an action usually changes app state that was already drawn: build the frame
1024        // again so everything shows the new state
1025        if ev & (EV_ACT | EV_COMMIT | EV_DOUBLE | EV_RCLICK) != 0 {
1026            self.repass = true;
1027        }
1028        // semantic state for the dump
1029        let mut st = 0;
1030        if ev & EV_HOVER != 0 {
1031            st |= SS_HOVER;
1032        }
1033        if ev & EV_HELD != 0 {
1034            st |= SS_PRESSED;
1035        }
1036        if ev & EV_FOCUS != 0 {
1037            st |= SS_FOCUSED;
1038        }
1039        self.wp.ctx.last_state = st as u32;
1040        ev
1041    }
1042
1043    /// ui_hit_rect(id, x, y, w, h, hbflags) -> EV_*: behave on an explicit rectangle (px)
1044    pub fn hit_rect(&mut self, id: u32, x: i32, y: i32, w: i32, h: i32, hbflags: u32) -> u32 {
1045        self.wp.ctx.last_rect = [x, y, w, h];
1046        self.behave(id, hbflags)
1047    }
1048
1049    /// ui_focus_ring(ev): the focus visual should be drawn for the widget that just
1050    /// called behave (its events)
1051    pub fn focus_ring(&self, ev: u32) -> bool {
1052        ev & EV_FOCUS != 0 && self.wp.ctx.focus_visible
1053    }
1054
1055    // ------------------------------------------------------------------ state slots
1056
1057    /// ui_slot(id): persistent per-id state of the window being built (created on first use):
1058    /// every window keeps its own slots, so the same id in two windows (a title bar's, a
1059    /// dialog's box) measures and scrolls in each by itself; outside the passes the main
1060    /// window's.  A window's slots go with its state (window_destroy)
1061    pub fn slot(&mut self, id: u32) -> &mut Slot {
1062        let frame = self.frame;
1063        let s = self.wp.ctx.slots.entry(id).or_default();
1064        s.seen = frame;
1065        s
1066    }
1067
1068    /// ui_anim(id, target, ms) -> current value (0..65535, usually 0..256) moving towards
1069    /// target (ease-out)
1070    pub fn anim(&mut self, id: u32, target: u32, ms: u32) -> u32 {
1071        self.anim_ex(id, target, ms, EASE_OUT)
1072    }
1073
1074    /// anim_ex(id, target, ms, curve) -> current value moving towards target along an
1075    /// EASE_* curve (EASE_OUT_BACK may overshoot; the value stays >= 0)
1076    pub fn anim_ex(&mut self, id: u32, target: u32, ms: u32, curve: u32) -> u32 {
1077        let now = self.now_ms;
1078        let off = self.anim_off;
1079        // animation slots live beside widget slots; aux keeps the curve
1080        let s = self.slot(id | 0x4000_0000);
1081        s.aux = curve as i32;
1082        // value: interpolated along the curve
1083        fn value(s: &Slot, now: u32) -> u32 {
1084            let to = s.ato as u32;
1085            let dur = s.adur;
1086            if dur == 0 {
1087                return to;
1088            }
1089            let el = now.wrapping_sub(s.at0);
1090            if el >= dur {
1091                return to;
1092            }
1093            let t = ((el << 8) / dur) as i32;
1094            let e = ease(s.aux as u32, t);
1095            let from = s.afrom as i32;
1096            let d = ((to as i32 - from).wrapping_mul(e)) >> 8;
1097            (from + d).max(0) as u32
1098        }
1099        if off || s.adur == 0 {
1100            // first use: start at the target
1101            s.afrom = target as u16;
1102            s.ato = target as u16;
1103            s.adur = if ms == 0 { 1 } else { ms };
1104            return target;
1105        }
1106        if s.ato as u32 != target {
1107            // new target: start from the current value
1108            let v = value(s, now);
1109            s.afrom = v as u16;
1110            s.ato = target as u16;
1111            s.at0 = now;
1112            s.adur = ms;
1113        }
1114        let v = value(s, now);
1115        if v != s.ato as u32 {
1116            self.animating = true;
1117        }
1118        v
1119    }
1120
1121    /// anim_color(id, argb, ms, curve) -> the colour on its way to argb (a transition: hover
1122    /// fills, state colours); the first call starts at argb
1123    pub fn anim_color(&mut self, id: u32, argb: u32, ms: u32, curve: u32) -> u32 {
1124        let now = self.now_ms;
1125        let off = self.anim_off;
1126        // colour slots: measw = from, meash = to, at0 / adur the time, aux the curve
1127        let s = self.slot(id | 0x2000_0000);
1128        let at = |s: &Slot| -> u32 {
1129            let el = now.wrapping_sub(s.at0);
1130            if s.adur == 0 || el >= s.adur {
1131                return s.meash as u32;
1132            }
1133            let t = ((el << 8) / s.adur) as i32;
1134            super::theme::color_mix(s.measw as u32, s.meash as u32, ease(s.aux as u32, t).clamp(0, 256))
1135        };
1136        if off || s.adur == 0 {
1137            s.measw = argb as i32;
1138            s.meash = argb as i32;
1139            s.adur = ms.max(1);
1140            s.at0 = now;
1141            return argb;
1142        }
1143        if s.meash as u32 != argb {
1144            let c = at(s);
1145            s.measw = c as i32;
1146            s.meash = argb as i32;
1147            s.at0 = now;
1148            s.adur = ms.max(1);
1149            s.aux = curve as i32;
1150        }
1151        let c = at(s);
1152        if c != argb {
1153            self.animating = true;
1154        }
1155        c
1156    }
1157
1158    // ------------------------------------------------------------------ commands, timers
1159
1160    /// ui_cmd_run(id, arg): queue a command for App::command after this frame (in order)
1161    pub fn cmd_run(&mut self, id: u32, arg: u32) {
1162        if self.cmdq.len() < CMDQ_MAX {
1163            self.cmdq.push_back((id, arg));
1164        } else {
1165            self.limit_hit(LIM_CMDS);
1166        }
1167    }
1168
1169    /// ui_cmd_take -> the oldest queued command
1170    pub(crate) fn cmd_take(&mut self) -> Option<(u32, u32)> {
1171        self.cmdq.pop_front()
1172    }
1173
1174    /// ui_timer(id, ms): App::command(UIC_TIMER, id) after ms (replaces a timer with the
1175    /// same id)
1176    pub fn timer(&mut self, id: u32, ms: u32) {
1177        let deadline = self.now_ms.wrapping_add(ms);
1178        // set again: behind the others (of equal deadlines the one set first fires first)
1179        if let Some(i) = self.timers.iter().position(|t| t.0 == id) {
1180            self.timers.remove(i);
1181        } else if self.timers.len() >= TIMERS_MAX {
1182            self.limit_hit(LIM_TIMERS);
1183            return;
1184        }
1185        self.timers.push((id, deadline));
1186    }
1187
1188    /// ui_timer_due -> (id of the expired timer with the earliest deadline (removed; of
1189    /// equal deadlines the one set first) or -1, the next deadline ms or -1)
1190    pub(crate) fn timer_due(&mut self) -> (u32, u32) {
1191        match self.timer_scan() {
1192            (Some(i), next) => (self.timers.remove(i).0, next),
1193            (None, next) => (u32::MAX, next),
1194        }
1195    }
1196
1197    /// timer_scan -> (index of the expired timer timer_due takes next, the earliest deadline
1198    /// still ahead or -1)
1199    pub(crate) fn timer_scan(&self) -> (Option<usize>, u32) {
1200        let now = self.now_ms;
1201        let (mut due, mut due_at) = (None, 0i32);
1202        let (mut next, mut left) = (u32::MAX, u32::MAX);
1203        for (i, &(_, d)) in self.timers.iter().enumerate() {
1204            let rel = d.wrapping_sub(now) as i32; // ms until the deadline (wraps with the clock)
1205            if rel <= 0 {
1206                if due.is_none() || rel < due_at {
1207                    due = Some(i);
1208                    due_at = rel;
1209                }
1210            } else if (rel as u32) < left {
1211                left = rel as u32;
1212                next = d;
1213            }
1214        }
1215        (due, next)
1216    }
1217
1218    // ------------------------------------------------------------------ semantic records
1219
1220    /// g_last_rect = 0, g_last_state = 0: the apps' hidden probe records (JFPainter's *_sem
1221    /// helpers zero both before ui_sem; later records of the pass see the zeroes too)  // pn-core
1222    pub fn last_reset(&mut self) {
1223        self.wp.ctx.last_rect = [0; 4];
1224        self.wp.ctx.last_state = 0;
1225    }
1226
1227    /// g_last_rect = rect, g_last_state = state (apps that build a widget from drawing calls
1228    /// set the record's rectangle and state before ui_sem)  // pn-core
1229    pub fn last_set(&mut self, rect: [i32; 4], state: u32) {
1230        self.wp.ctx.last_rect = rect;
1231        self.wp.ctx.last_state = state;
1232    }
1233
1234    /// g_last_state: the SS_* state of the last behave  // pn-core
1235    pub fn last_state(&self) -> u32 {
1236        self.wp.ctx.last_state
1237    }
1238
1239    /// g_mbtn / g_pressed / g_released: mouse buttons held / pressed / released this frame
1240    /// (bit 1 << BTN_*)  // pn-core
1241    pub fn mbtn(&self) -> u32 {
1242        self.wp.ctx.mbtn
1243    }
1244    pub fn pressed(&self) -> u32 {
1245        self.wp.ctx.pressed
1246    }
1247    pub fn released(&self) -> u32 {
1248        self.wp.ctx.released
1249    }
1250
1251    /// ui_sem(kind, id, label, value) -> the record (rect = last_rect, state from the last
1252    /// behave); the label is copied
1253    #[cfg_attr(jfgui_dev, track_caller)]
1254    pub fn sem(&mut self, kind: u16, id: u32, label: Option<&str>, value: i32) -> &mut Sem {
1255        if self.sems.len() >= SEMS_MAX {
1256            self.limit_hit(LIM_SEMS);
1257            return &mut self.sem_dummy;
1258        }
1259        let c = &self.wp.ctx;
1260        let mut r = Sem {
1261            id,
1262            kind,
1263            state: c.last_state as u16,
1264            rect: self.phys_rect(c.last_rect), // window px (mirrored right-to-left)
1265            label: None,
1266            value,
1267            max: 0,
1268            min: 0,
1269            layer: c.cmd_layer_ui as u8,
1270            depth: self.wp.lay.boxes.len() as u8,
1271            win: self.uiw_cur as u8,
1272            ax: None,
1273        };
1274        if let Some(l) = label {
1275            // copy the label into the frame text arena (it may live in a scratch buffer)
1276            let l = l.split('\0').next().unwrap_or("");
1277            let n = self.semtext_len + l.len() + 1;
1278            if n <= SEMTEXT_MAX {
1279                self.semtext_len = n;
1280                r.label = Some(String::from(l));
1281            } else {
1282                self.limit_hit(LIM_SEMTEXT);
1283            }
1284        }
1285        self.sems.push(r);
1286        let n = self.sems.len();
1287        self.src_sem(n - 1); // the inspector's source of the record (inspect_src.rs)
1288        &mut self.sems[n - 1]
1289    }
1290
1291    /// sem_rect(kind, id, rect, label, value) -> the record: a semantic record of content the app
1292    /// draws itself (a link or heading of a web page inside a canvas) at rect (px of the window, as
1293    /// last_rect), without a hit box of its own and without a state; last_rect and the state of
1294    /// the last widget stay.  Scripts find it by its label (`click "Label"` presses at its middle,
1295    /// on the hit box that lies there: the canvas), screen readers read it (section 19 of the
1296    /// guide).  Publish the records of what is visible, after the canvas.
1297    #[cfg_attr(jfgui_dev, track_caller)]
1298    pub fn sem_rect(&mut self, kind: u16, id: u32, rect: [i32; 4], label: Option<&str>, value: i32) -> &mut Sem {
1299        let (lr, ls) = (self.wp.ctx.last_rect, self.wp.ctx.last_state);
1300        self.wp.ctx.last_rect = rect;
1301        self.wp.ctx.last_state = 0;
1302        let n = self.sems.len();
1303        self.sem(kind, id, label, value);
1304        self.wp.ctx.last_rect = lr;
1305        self.wp.ctx.last_state = ls;
1306        if self.sems.len() > n {
1307            &mut self.sems[n]
1308        } else {
1309            &mut self.sem_dummy
1310        }
1311    }
1312
1313    // ------------------------------------------------------------------ clipping, layers
1314
1315    /// ui_clip_push(x, y, w, h): px; intersects drawing and hit clipping
1316    pub fn clip_push(&mut self, x: i32, y: i32, w: i32, h: i32) {
1317        self.draw_clip(x, y, w, h);
1318        let c = &mut self.wp.ctx;
1319        if c.uiclip_stack.len() >= UICLIP_MAX {
1320            c.uiclip_over += 1;
1321            self.limit_hit(LIM_CLIP);
1322            return;
1323        }
1324        c.uiclip_stack.push(c.uiclip);
1325        let k = &mut c.uiclip;
1326        k[0] = x.max(k[0]);
1327        k[1] = y.max(k[1]);
1328        k[2] = x.wrapping_add(w).min(k[2]);
1329        k[3] = y.wrapping_add(h).min(k[3]);
1330    }
1331
1332    /// ui_clip_pop
1333    pub fn clip_pop(&mut self) {
1334        self.draw_unclip();
1335        let c = &mut self.wp.ctx;
1336        if c.uiclip_over != 0 {
1337            c.uiclip_over -= 1; // a level beyond UICLIP_MAX: it clipped nothing
1338        } else if let Some(k) = c.uiclip_stack.pop() {
1339            c.uiclip = k;
1340        }
1341    }
1342
1343    /// ui_layer_set(layer) -> previous: switch the command layer and hit layer together
1344    pub fn layer_set(&mut self, layer: u32) -> u32 {
1345        let prev = self.wp.ctx.cmd_layer_ui;
1346        self.wp.ctx.cmd_layer_ui = layer & 3;
1347        self.cmd_layer(layer);
1348        prev
1349    }
1350
1351    // ------------------------------------------------------------------ passes
1352    //
1353    // A pass builds every window: frame_begin (shared state), then per window
1354    // uiw_pass_begin .. content .. uiw_pass_end (window 0 by the runtime, the others inside
1355    // window_begin / window_end), then frame_end.
1356
1357    /// ui_begin_pass(w, h): start recording a frame for a w x h px window (one window)
1358    pub fn begin_pass(&mut self, w: i32, h: i32) {
1359        self.frame_begin();
1360        self.uiw_pass_begin(w, h);
1361    }
1362
1363    /// ui_end_pass -> another pass is needed (one window)
1364    pub fn end_pass(&mut self) -> bool {
1365        self.uiw_pass_end();
1366        self.frame_end()
1367    }
1368
1369    /// ui_frame_begin: start of a pass over all windows
1370    pub(crate) fn frame_begin(&mut self) {
1371        self.repass = false;
1372        self.sems.clear();
1373        self.semtext_len = 0;
1374        self.animating = false;
1375        self.frame = self.frame.wrapping_add(1);
1376        if self.m.ins.on {
1377            self.m.ins.boxes.clear(); // the inspector's boxes of this pass (inspect.rs)
1378        }
1379        self.src_clear(); // ... and the records' sources (inspect_src.rs)
1380        self.rtl_update();
1381        self.ids_frame_begin();
1382        self.fonts_frame(); // the fallback follows the UI language
1383        self.uiw_frame_begin();
1384    }
1385
1386    /// ui_frame_end -> another pass is needed before the frame is final
1387    pub(crate) fn frame_end(&mut self) -> bool {
1388        self.uiw_frame_end();
1389        #[cfg(target_os = "android")]
1390        self.ime_frame_end();
1391        self.pw_frame_end(); // a password box's IP_PASSWORD ends with it
1392        self.repass
1393    }
1394
1395    /// uiw_pass_begin(w, h): start recording the live window (w x h px): empty command
1396    /// lists and hit list, background, root layout box
1397    pub(crate) fn uiw_pass_begin(&mut self, w: i32, h: i32) {
1398        if self.uiw_cur == 0 {
1399            self.form_update(w, h); // the device of this frame (form.rs)
1400        }
1401        self.cmd_reset();
1402        let c = &mut self.wp.ctx;
1403        c.cmd_layer_ui = 0;
1404        c.press_used = false;
1405        c.rpress_used = false;
1406        c.active_seen = false;
1407        c.modal_cur = 0;
1408        c.disabled_depth = 0;
1409        c.kb.pass_begin();
1410        c.uiclip_stack.clear();
1411        c.uiclip_over = 0;
1412        c.uiclip = [0, 0, w, h];
1413        let cur = c.hit_cur;
1414        c.hits[cur].clear();
1415        self.wp.chrome.cap_n = 0; // the caption buttons' boxes of this pass (chrome.rs)
1416        self.wp.chrome.tb_rect = None;
1417        let c = &mut self.wp.ctx;
1418        c.zones[cur].clear();
1419        c.pans[cur].clear();
1420        c.grabs[cur].clear();
1421        self.hot_update();
1422        self.dnd_pass_begin(); // the drop target under a drag (previous pass)
1423        // window background (style)
1424        let st = self.style;
1425        st.window_bg(self, w, h);
1426        // root layout box (secondary windows: inside their 1 px border)
1427        let b = self.uiw_border();
1428        self.layout_root(b, b, w - b - b, h - b - b);
1429    }
1430
1431    /// uiw_pass_end: finish the live window's pass: runtime menus and accelerators, layout,
1432    /// Tab, Esc, modal layer, hit lists
1433    pub(crate) fn uiw_pass_end(&mut self) {
1434        self.uiw_fwd_take(); // (U3) keys a tool window borrowed and left
1435        self.accel_dispatch(); // (popup agent) runtime menus, stale popups, accelerators
1436        self.layout_end_root();
1437        // an active widget that vanished releases the pointer
1438        if !self.wp.ctx.active_seen {
1439            self.wp.ctx.active = 0;
1440        }
1441        // Tab / Shift+Tab, focus groups (arrows), F6 / Ctrl+F6 (panes, windows), F8
1442        // (splitters), the menu key (kbnav.rs)
1443        self.kb_nav();
1444        // Esc not consumed by a widget: UIC_ESCAPE (dialog windows: a close request)
1445        if self.key(VK_ESCAPE) {
1446            self.uiw_escape();
1447        }
1448        let c = &mut self.wp.ctx;
1449        if c.modal_cur != c.modal_prev {
1450            c.modal_prev = c.modal_cur;
1451            self.repass = true;
1452        }
1453        if self.dup_check {
1454            self.dup_ids();
1455        }
1456        self.dnd_pass_end(); // the label next to the pointer of a drag
1457        // swap hit lists
1458        self.wp.ctx.hit_cur ^= 1;
1459        self.uiw_publish_hits(); // (the platform's hit hook; see win.rs)
1460    }
1461
1462    // dup_ids: an id that two widgets of this pass share (they would share hover, press
1463    // and focus: a click on one acts on the other) -- one UIWARN per id
1464    fn dup_ids(&mut self) {
1465        let c = &self.wp.ctx;
1466        // (a widget registering the same rectangle twice is still one widget)
1467        let mut ids: Vec<(u32, i32, i32, i32, i32)> =
1468            c.hits[c.hit_cur].iter().filter(|h| h.id != 0 && !h.continuation).map(|h| (h.id, h.x0, h.y0, h.x1, h.y1)).collect();
1469        ids.sort_unstable();
1470        ids.dedup();
1471        let dups: Vec<((u32, i32, i32, i32, i32), (u32, i32, i32, i32, i32))> = ids.windows(2).filter(|w| w[0].0 == w[1].0).map(|w| (w[0], w[1])).collect();
1472        for (a, b) in dups {
1473            if self.dup_warned.insert(a.0) {
1474                let name = self.id_name(a.0).map_or(String::new(), |n| alloc::format!(" (\"{}\")", n));
1475                self.warn(&alloc::format!("widget id {:#X}{} used twice in one pass (at {},{} and {},{})", a.0, name, a.1, a.2, b.1, b.2));
1476            }
1477        }
1478    }
1479
1480    /// ui_repass: request another pass before this frame is shown
1481    pub fn repass(&mut self) {
1482        self.repass = true;
1483    }
1484
1485    /// ui_modal(layer): a modal element (dialog) is open on `layer` this pass
1486    pub fn modal(&mut self, layer: u32) {
1487        if layer as i32 > self.wp.ctx.modal_cur as i32 {
1488            self.wp.ctx.modal_cur = layer;
1489        }
1490    }
1491
1492    /// ui_modal_active -> layer of the modal element of the last pass (0 = none)
1493    pub fn modal_active(&self) -> u32 {
1494        self.wp.ctx.modal_prev
1495    }
1496
1497    /// ui_disabled_push / ui_disabled_pop: widgets in between are disabled
1498    pub fn disabled_push(&mut self) {
1499        self.wp.ctx.disabled_depth += 1;
1500    }
1501
1502    pub fn disabled_pop(&mut self) {
1503        if self.wp.ctx.disabled_depth != 0 {
1504            self.wp.ctx.disabled_depth -= 1;
1505        }
1506    }
1507
1508    /// ui_warn: "UIWARN f<frame>: <msg>" on stderr
1509    pub fn warn(&mut self, msg: &str) {
1510        self.warns = self.warns.wrapping_add(1);
1511        let s = alloc::format!("UIWARN f{}: {}\n", self.frame, msg);
1512        plat::write_stderr(s.as_bytes());
1513        plat::log_quiet(plat::LOG_WARN, "ui", msg); // the ring (crash reports) and the log file
1514    }
1515}