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