Skip to main content

ui/
win.rs

1// win -- multi-window runtime: tool windows and dialog windows built by the same App::frame
2// as the main window, each with its own UI state                       [src/ui/ui_win.s]
3//
4// App-facing:
5//   window_begin(id, title, open, flags, rect) -> true: emit the content now (always pair
6//        with window_end; see WF_* / WinRect; open / rect: shared with the library)
7//   window_end()
8//   window_place(id, &rect)              move / resize (copied into the window's WINRECT)
9//   window_rect(id) -> Option<WinRect>   current placement (None = unknown window)
10//   window_state(id) -> UWS_*            UWS_OPEN | UWS_VISIBLE | UWS_ACTIVE | UWS_MINIMIZED |
11//                                        UWS_FULLSCREEN
12//   window_fullscreen(id, on)            (L7) over the whole monitor without a frame, or back;
13//                                        UIC_FULLSCREEN when the platform or the user changes it
14//   window_activate(id)                  give it the keyboard (raises it)
15//   window_snap(id, area, dist)          its edges snap to the area's while the user moves it
16//   window_destroy(id)                   free its state and buffers (created again when shown)
17//   window_current() -> id               id of the window being built (0 = main)
18//   window_modal() -> id                 id of the topmost modal window or 0
19//   sync_hook_set(f)                     f() -> true while background jobs run (`sync`)
20// Platform: hit_flags_win(win, x, y), win_hooks_set(on) (OS windows through the plat_win_*
21// API, the asm's uiw_plat_hooks table), uiw_hit_pw (the platform's hit hook), uiw_pw /
22// uiw_from_pw (UI index <-> platform window).
23// Runtime (runtime.rs, ctx.rs, chrome.rs, auto.rs, drop.rs): the uiw_* functions below.
24//
25// How it works: the per-window state of every module lives in WinParts (lib.rs); `Ui::wp`
26// is the live window's.  uiw_use(i) swaps the live parts into the record of the window
27// that was live and window i's parts out of its record (core::mem::swap of a Box), so every
28// widget, popup, dialog and the dirty-region code work unchanged in whichever window is
29// live.  Outside of the passes and of the event dispatch the main window (index 0) is live.
30// Platform operations are never done inside window_begin: they are recorded in Rec::want
31// and done by uiw_settle after the frame settled (headless runs: none -- the windows only
32// exist as framebuffers).
33//
34// The asm keeps the app's `&open` and `&rect` pointers in the window record and writes
35// through them later (close button, Esc, placement by the user); here the app passes a
36// Shared<T> (Rc<Cell<T>>) and the record keeps a clone of it.
37
38use alloc::boxed::Box;
39use alloc::rc::Rc;
40use alloc::string::String;
41use alloc::vec::Vec;
42use core::cell::Cell;
43
44use super::consts::*;
45use super::ctx::Hit;
46use super::layout::px2dip_at;
47use super::{Ui, WinParts};
48use gfx::{Surface, FMT_XRGB};
49use plat::Event;
50use rt::sync::SpinLock;
51
52// fixed: a window index is a bit in u32 masks and Event::win (WIN_MAX, plat's PW_MAX); the
53// stacks below are nesting depths of window_begin, which real apps keep at two or three
54const WSTACK_MAX: usize = 16; // window_begin records inside each other (entered or not)
55const NEST_MAX: u32 = 4; // windows entered inside each other (warns)
56const DEF_W: i32 = 280;
57const DEF_H: i32 = 400;
58const WSE_ENTERED: u32 = 0x10000; // stack entry: prev | index << 8 | WSE_ENTERED
59const TITLE_MAX: usize = 256;
60const SIZE_MAX_DIP: i32 = 8192; // largest client width / height of a window (DIP)
61const FWDQ_MAX: usize = super::ctx::KEYQ_MAX; // (U3) keys a borrowing tool window hands on per pass (all)
62const FWDT_MAX: usize = super::ctx::TEXTQ_MAX; // (U3) typed codepoints handed on per pass (all)
63// (U3) popup slots (items outside their window): fixed, plat has PPW_MAX popup windows;
64// more open at once are drawn clipped to their window (limit_hit)
65const EXT_MAX: usize = 4;
66const POPUP_ITEMS: u32 = 7; // (U3) popup_ext items: 6 popups + the tooltip
67
68/// chrome ids of window i: WCH_BASE | i << 4 | part
69pub(crate) const WCH_BASE: u32 = 0xFFFF_7800;
70pub(crate) const WCH_CLOSE: u32 = 1; // close button
71pub(crate) const WCH_CONTENT: u32 = 2; // content box
72pub(crate) const WCH_FOOTER: u32 = 3; // dialog footer (dialog_buttons in a WF_DIALOG window)
73pub(crate) const WCH_BTN: u32 = 4; // footer buttons 4, 5, 6
74pub(crate) const WCH_ROLL: u32 = 7; // (form.rs) the scroll view of content taller than a phone's window
75/// root box id of window i (> 0): WROOT_BASE | i
76pub(crate) const WROOT_BASE: u32 = 0xFFFF_7F00;
77
78// window record state bits (UWI_*)
79pub(crate) const UWI_OPEN: u32 = 1; // *open was set at the last window_begin
80pub(crate) const UWI_VISIBLE: u32 = 2; // shown (built and rendered every frame)
81pub(crate) const UWI_CREATED: u32 = 16; // its WinParts and framebuffer exist
82pub(crate) const UWI_SEEN: u32 = 32; // window_begin ran in the current pass
83pub(crate) const UWI_SHOWN: u32 = 64; // was shown before (placement done)
84pub(crate) const UWI_NEWSHOW: u32 = 128; // became visible in this frame
85pub(crate) const UWI_BUILDING: u32 = 256; // between window_begin and window_end
86pub(crate) const UWI_FRESH: u32 = 512; // shown again: its focus starts empty
87pub(crate) const UWI_DEFPOS: u32 = 1024; // placed by the default rule (re-centred while its height settles)
88
89// pending platform operations (UWW_*)
90const UWW_SHOW: u32 = 1;
91const UWW_HIDE: u32 = 2;
92const UWW_RECT: u32 = 4;
93const UWW_TITLE: u32 = 8;
94const UWW_ACTIVATE: u32 = 16;
95const UWW_CREATE: u32 = 64;
96const UWW_FS: u32 = 128; // (L7) full screen on / off (Rec::fs)
97
98/// a value the app shares with the library: the asm passes its address and the library
99/// keeps it (a window's `open` flag, its WINRECT, a content dialog's `open` flag)
100pub struct Shared<T: Copy>(Rc<Cell<T>>);
101
102impl<T: Copy> Shared<T> {
103    pub fn new(v: T) -> Shared<T> {
104        Shared(Rc::new(Cell::new(v)))
105    }
106    pub fn get(&self) -> T {
107        self.0.get()
108    }
109    pub fn set(&self, v: T) {
110        self.0.set(v)
111    }
112}
113
114impl<T: Copy> Clone for Shared<T> {
115    fn clone(&self) -> Shared<T> {
116        Shared(self.0.clone())
117    }
118}
119
120impl<T: Copy + Default> Default for Shared<T> {
121    fn default() -> Shared<T> {
122        Shared::new(T::default())
123    }
124}
125
126/// a window record (UW_*); index 0 = the main window
127pub(crate) struct Rec {
128    /// app id (0 for the main window; 0 in a free record)
129    pub(crate) id: u32,
130    /// WF_*
131    pub(crate) flags: u32,
132    /// UWI_*
133    pub(crate) state: u32,
134    /// owner window index
135    pub(crate) owner: u32,
136    /// frame of the last window_begin
137    pub(crate) seen: u32,
138    /// stacking order (larger = above)
139    pub(crate) z: u32,
140    /// the app's open flag (None = always open)
141    pub(crate) openp: Option<Shared<i32>>,
142    /// WINRECT: the app's, or `own` when the app passes none
143    pub(crate) rect: Shared<WinRect>,
144    pub(crate) own: Shared<WinRect>,
145    /// the window's per-window state while another window is live (None: live, or no
146    /// state yet)
147    pub(crate) save: Option<Box<WinParts>>,
148    /// WINRECT as last applied
149    pub(crate) applied: WinRect,
150    /// UWW_* platform operations not done yet
151    pub(crate) want: u32,
152    /// library title bar height in DIP (0 = none)
153    pub(crate) tbh: i32,
154    /// natural content height in DIP (WF_AUTOHEIGHT)
155    pub(crate) autoh: i32,
156    /// screen position (px) reported by the platform
157    pub(crate) sx: i32,
158    pub(crate) sy: i32,
159    /// the title copy (a change is passed on to the platform)
160    pub(crate) title: String,
161    /// window_snap: area x, y, w, h (DIP of the owner's client area), distance (DIP; 0 = off)
162    pub(crate) snap: [i32; 5],
163    /// (L7) full screen: asked by the app or last reported by the platform (window_state
164    /// shows it); the platform's own state when it was last asked (a change of its own is
165    /// UIC_FULLSCREEN)
166    pub(crate) fs: bool,
167    pub(crate) fs_plat: bool,
168}
169
170impl Default for Rec {
171    fn default() -> Rec {
172        let own = Shared::default();
173        Rec {
174            id: 0,
175            flags: 0,
176            state: 0,
177            owner: 0,
178            seen: 0,
179            z: 0,
180            openp: None,
181            rect: own.clone(),
182            own,
183            save: None,
184            applied: WinRect::default(),
185            want: 0,
186            tbh: 0,
187            autoh: 0,
188            sx: 0,
189            sy: 0,
190            title: String::new(),
191            snap: [0; 5],
192            fs: false,
193            fs_plat: false,
194        }
195    }
196}
197
198// (U3) a popup slot: an item of the owner's popups outside its framebuffer
199#[derive(Default)]
200struct Ex {
201    /// owner window index + 1 (0 = free)
202    owner: u32,
203    /// popup_ext item
204    item: u32,
205    /// x, y, w, h (px, owner coordinates)
206    rect: [i32; 4],
207    /// used by the owner's last render
208    used: bool,
209    /// the platform shows it
210    shown: bool,
211    /// (§6) its natural place (px, owner) when it was shown -- popup_placed answers for it --
212    /// and that place as the popup code computed it (the key of uiw_popup_nudge)
213    nat: [i32; 2],
214    key: [i32; 2],
215    sf: Surface,
216}
217
218/// state shared by all windows
219pub(crate) struct S {
220    pub(crate) recs: [Rec; WIN_MAX],
221    /// window with the keyboard
222    pub(crate) active: u32,
223    /// topmost modal window (0 = none)
224    pub(crate) modal: u32,
225    /// bit mask of inert windows
226    pub(crate) inert: u32,
227    /// window under the pointer (-1 = none)
228    pub(crate) ptr: i32,
229    /// windows that got input since the last pass
230    pub(crate) edges: u32,
231    /// bit mask of window records in use (not the main window)
232    pub(crate) used: u32,
233    /// the live window takes no accelerators (a dialog window's pass end)
234    pub(crate) noaccel: bool,
235    stack: [u32; WSTACK_MAX],
236    sp: u32,
237    over: u32,
238    /// OS windows (the runtime runs with a display: the asm's uiw_plat_hooks are set)
239    hooks: bool,
240    synchook: Option<fn() -> bool>,
241    zc: u32,
242    // (U3) key forwarding of tool windows (kfocus: the window that borrows the keys + 1;
243    // kbnav.rs sets it for F6 / Ctrl+F6)
244    pub(crate) kfocus: u32,
245    fwd: u32,
246    fwd_to: u32,
247    fwdq: Vec<u32>,
248    fwdt: Vec<u32>,
249    // (U3) popup slots
250    ex: [Ex; EXT_MAX],
251    /// (§6) headless: the virtual screen (the script's `screen`): x, y, w, h in DIP of the main
252    /// window's client area (w 0: none, the main window's client area holds the popups)
253    pub(crate) screen: [i32; 4],
254    /// OS windows disabled by uiw_enable_sync (bit = index)
255    disabled: u32,
256    /// uiw_follow_owner nesting
257    follow: u32,
258    /// OS windows placed at their WINRECT position (bit = index)
259    placed: u32,
260    /// uiw_owner_origin: the position is known
261    origin_ok: bool,
262    /// UI window -> platform window (0xFF none) and back
263    u2p: [u8; WIN_MAX],
264    p2u: [u8; WIN_MAX],
265    /// the framebuffer of a window without state (uiw_var's zeroes)
266    zero_fb: Surface,
267    /// winstate.rs: saved tool windows window_begin has not met yet; the main window's last
268    /// placement while it was neither maximized nor minimized
269    pub(crate) saved: Vec<super::winstate::Saved>,
270    pub(crate) norm: super::winstate::MainState,
271    pub(crate) cand: Option<super::winstate::MainState>,
272}
273
274impl Default for S {
275    fn default() -> S {
276        S {
277            recs: Default::default(),
278            active: 0,
279            modal: 0,
280            inert: 0,
281            ptr: 0,
282            edges: 0,
283            used: 0,
284            noaccel: false,
285            stack: [0; WSTACK_MAX],
286            sp: 0,
287            over: 0,
288            hooks: false,
289            synchook: None,
290            zc: 0,
291            kfocus: 0,
292            fwd: 0,
293            fwd_to: 0,
294            fwdq: Vec::new(),
295            fwdt: Vec::new(),
296            ex: Default::default(),
297            screen: [0; 4],
298            disabled: 0,
299            follow: 0,
300            placed: 0,
301            origin_ok: false,
302            u2p: [0xFF; WIN_MAX],
303            p2u: [0xFF; WIN_MAX],
304            zero_fb: Surface::new(0, 0, FMT_XRGB),
305            saved: Vec::new(),
306            norm: Default::default(),
307            cand: None,
308        }
309    }
310}
311
312// ------------------------------------------------------------------ the platform's hit hook
313
314// what the platform's non-client hit tests (WM_NCHITTEST, click activation) see of the UI:
315// they run inside window procedures without the UI context, so every window's hit list of
316// its last pass is published at the end of the pass (uiw_publish_hits)
317struct Published {
318    hits: [Vec<Hit>; WIN_MAX],
319    modal: [u32; WIN_MAX],
320    /// windows that exist and are shown (uiw_drawable)
321    drawable: u32,
322    p2u: [u8; WIN_MAX],
323    /// right-to-left: the width each window is mirrored across (0 = not mirrored)
324    mirror: [i32; WIN_MAX],
325    /// drag and drop (dnd.rs): the drop targets of each window's last pass, what a window
326    /// takes outside them, the kinds of the app's own drag while the platform carries it
327    zones: [Vec<super::dnd::Zone>; WIN_MAX],
328    accept: u32,
329    own: u32,
330    /// window_snap for the move hook (uiw_moving_pw): owner index, area x, y, w, h (px from
331    /// the owner's platform client origin), distance (px; 0 = off)
332    snap: [[i32; 6]; WIN_MAX],
333}
334
335static PUBLISHED: SpinLock<Published> = SpinLock::new(Published {
336    hits: [const { Vec::new() }; WIN_MAX],
337    modal: [0; WIN_MAX],
338    drawable: 1,
339    p2u: [0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
340    mirror: [0; WIN_MAX],
341    zones: [const { Vec::new() }; WIN_MAX],
342    accept: plat::DT_FILES,
343    own: 0,
344    snap: [[0; 6]; WIN_MAX],
345});
346
347/// uiw_hit_pw(pw, x, y) -> HB_* flags at a client point (px) of platform window pw in the
348/// last pass (the platform's non-client hit tests and click activation)
349pub(crate) fn uiw_hit_pw(pw: i32, x: i32, y: i32) -> u32 {
350    if !(0..WIN_MAX as i32).contains(&pw) {
351        return 0;
352    }
353    let p = PUBLISHED.lock();
354    let i = p.p2u[pw as usize];
355    if i == 0xFF || (p.drawable >> i) & 1 == 0 {
356        return 0;
357    }
358    let i = i as usize;
359    // the hits are logical: a mirrored window's point too
360    let x = if p.mirror[i] > 0 { p.mirror[i] - 1 - x } else { x };
361    super::ctx::hit_find_list(&p.hits[i], p.modal[i], x, y).1
362}
363
364/// uiw_drag_pw(pw, x, y, types) -> (accepted DT_* kinds, DE_* effects) of a drag offering
365/// `types` at a client point (px) of platform window pw (the platform's drag hook, dnd.rs):
366/// the drop target there with the effects it took (drop_effect_set), else what the windows
367/// take (drop_accept) with every effect
368pub(crate) fn uiw_drag_pw(pw: i32, x: i32, y: i32, types: u32) -> (u32, u32) {
369    if !(0..WIN_MAX as i32).contains(&pw) {
370        return (0, 0);
371    }
372    let p = PUBLISHED.lock();
373    let i = p.p2u[pw as usize];
374    if i == 0xFF || (p.drawable >> i) & 1 == 0 {
375        return (0, 0);
376    }
377    let i = i as usize;
378    let x = if p.mirror[i] > 0 { p.mirror[i] - 1 - x } else { x }; // logical
379    super::dnd::drag_answer(&p.zones[i], &p.hits[i], p.modal[i], p.accept, p.own, x, y, types)
380}
381
382/// (dnd.rs) what the drag hook needs besides the targets: the kinds the windows take, the
383/// kinds of the app's own drag while the platform carries it (0 = none)
384pub(crate) fn uiw_publish_dnd(accept: u32, own: u32) {
385    let mut p = PUBLISHED.lock();
386    p.accept = accept;
387    p.own = own;
388}
389
390/// uiw_moving_pw(pw, &rect): (the platform's move hook, inside its modal move loop) the user
391/// drags platform window pw to rect (x, y, w, h of its client area, screen px): x, y snap to
392/// the area window_snap gave
393pub(crate) fn uiw_moving_pw(pw: i32, r: &mut [i32; 4]) {
394    if !(0..WIN_MAX as i32).contains(&pw) {
395        return;
396    }
397    let (s, opw) = {
398        let p = PUBLISHED.lock();
399        let i = p.p2u[pw as usize];
400        if i == 0xFF {
401            return;
402        }
403        let s = p.snap[i as usize];
404        if s[5] == 0 {
405            return;
406        }
407        (s, p.p2u.iter().position(|&u| u as i32 == s[0]))
408    };
409    // the owner's client origin on the screen; unknown (Wayland): the platform moves a child
410    // window (WST_CHILD) and gives its position relative to the owner
411    let o = opw.and_then(|o| plat::win_get_rect(o as i32)).unwrap_or([0; 4]);
412    let a = [o[0] + s[1], o[1] + s[2], s[3], s[4]];
413    (r[0], r[1]) = snap_to(r[0], r[1], r[2], r[3], a, s[5]);
414}
415
416/// snap_to(x, y, w, h, area, dist) -> (x, y): a rectangle at x, y moved so that its edges sit
417/// on the area's edges they are at most dist away from (left / right, top / bottom: the
418/// nearer one); an axis snaps only while the rectangle overlaps the area (with dist) on the
419/// other axis.  Any one unit.
420fn snap_to(x: i32, y: i32, w: i32, h: i32, a: [i32; 4], d: i32) -> (i32, i32) {
421    let axis = |p: i32, s: i32, lo: i32, len: i32| {
422        let near = (p - lo).abs(); // leading edge to the area's start
423        let far = (p + s - lo - len).abs(); // trailing edge to its end
424        if near <= d && near <= far {
425            lo
426        } else if far <= d {
427            lo + len - s
428        } else {
429            p
430        }
431    };
432    let mut r = (x, y);
433    if y < a[1] + a[3] + d && y + h > a[1] - d {
434        r.0 = axis(x, w, a[0], a[2]);
435    }
436    if x < a[0] + a[2] + d && x + w > a[0] - d {
437        r.1 = axis(y, h, a[1], a[3]);
438    }
439    r
440}
441
442/// (Wayland) the room (DIP) a popup taller than its window leaves on the screen for panels, docks
443/// and the top bar, which the platform does not tell (uiw_popup_screen_h)
444const POPUP_PANEL_ROOM: i32 = 96;
445
446/// dip2px_at(dip, dpi) -> round(dip * dpi / 96)
447fn dip2px_at(dip: i32, dpi: i32) -> i32 {
448    let v = dip.wrapping_mul(dpi);
449    if v < 0 { (v - 48) / 96 } else { (v + 48) / 96 }
450}
451
452// blit_copy(dst, src, x, y): src at x, y, clipped (gfx_blit_copy)
453fn blit_copy(dst: &mut Surface, src: &Surface, x: i32, y: i32) {
454    let x0 = x.max(0);
455    let y0 = y.max(0);
456    let x1 = (x + src.w).min(dst.w);
457    let y1 = (y + src.h).min(dst.h);
458    if x1 <= x0 || y1 <= y0 {
459        return;
460    }
461    let n = (x1 - x0) as usize * 4;
462    for yy in y0..y1 {
463        let d = yy as usize * dst.stride as usize + x0 as usize * 4;
464        let s = (yy - y) as usize * src.stride as usize + (x0 - x) as usize * 4;
465        dst.px[d..d + n].copy_from_slice(&src.px[s..s + n]);
466    }
467}
468
469impl Ui {
470    // ------------------------------------------------------------------ records
471
472    /// uiw_index(id) -> index of the window with app id `id` (0 -> 0), -1 if none
473    pub(crate) fn uiw_index(&self, id: u32) -> i32 {
474        if id == 0 {
475            return 0;
476        }
477        (1..WIN_MAX).find(|&i| self.m.win.recs[i].id == id).map_or(-1, |i| i as i32)
478    }
479
480    /// uiw_rec(i).id: the app id of window i (0 = the main window)
481    pub(crate) fn uiw_rec_id(&self, i: u32) -> u32 {
482        self.m.win.recs[(i as usize) & (WIN_MAX - 1)].id
483    }
484
485    /// uiw_title(i): the window's title copy
486    pub(crate) fn uiw_title(&self, i: u32) -> &str {
487        &self.m.win.recs[(i as usize) & (WIN_MAX - 1)].title
488    }
489
490    /// any window record besides the main window in use (g_uiw_used != 0)
491    pub(crate) fn uiw_any_used(&self) -> bool {
492        self.m.win.used != 0
493    }
494
495    /// uiw_init: window records, window 0
496    pub(crate) fn uiw_init(&mut self) {
497        let w = &mut self.m.win;
498        w.recs[0].state = UWI_OPEN | UWI_VISIBLE | UWI_CREATED | UWI_SHOWN;
499        let r = &w.recs[0];
500        r.rect.set(WinRect { flags: WRF_ABS, ..r.rect.get() });
501        self.wp.lay.root_id = 1;
502        // platform windows: only the main window
503        w.u2p = [0xFF; WIN_MAX];
504        w.p2u = [0xFF; WIN_MAX];
505        w.u2p[0] = 0;
506        w.p2u[0] = 0;
507        self.uiw_cur = 0;
508        w.active = 0;
509        w.modal = 0;
510        w.ptr = 0;
511    }
512
513    /// uiw_use(i): make window i live: the live parts go to the record of the window that
514    /// was live, window i's parts become live
515    pub(crate) fn uiw_use(&mut self, i: u32) {
516        let i = (i as usize) & (WIN_MAX - 1);
517        let cur = self.uiw_cur as usize & (WIN_MAX - 1);
518        if i == cur {
519            return;
520        }
521        let Some(mut b) = self.m.win.recs[i].save.take() else {
522            return; // no such window: stay
523        };
524        core::mem::swap(&mut self.wp, &mut *b);
525        self.m.win.recs[cur].save = Some(b);
526        self.uiw_cur = i as u32;
527        // per-window platform calls of this pass (cursor, state, move / resize) go to it
528        plat::win_use(self.uiw_pw(i as u32));
529    }
530
531    /// uiw_var: the per-window parts of window i (the live ones if i is live; None for a
532    /// window without state)
533    pub(crate) fn uiw_parts(&self, i: u32) -> Option<&WinParts> {
534        let i = (i as usize) & (WIN_MAX - 1);
535        if i == self.uiw_cur as usize {
536            Some(&self.wp)
537        } else {
538            self.m.win.recs[i].save.as_deref()
539        }
540    }
541
542    pub(crate) fn uiw_parts_mut(&mut self, i: u32) -> Option<&mut WinParts> {
543        let i = (i as usize) & (WIN_MAX - 1);
544        if i == self.uiw_cur as usize {
545            Some(&mut self.wp)
546        } else {
547            self.m.win.recs[i].save.as_deref_mut()
548        }
549    }
550
551    /// the dpi of window i (uiw_var(i, &g_ui_dpi); 0 for a window without state)
552    pub(crate) fn win_dpi(&self, i: u32) -> i32 {
553        self.uiw_parts(i).map_or(0, |p| p.rt.dpi)
554    }
555
556    /// the framebuffer of window i (uiw_var(i, &g_fb))
557    pub(crate) fn win_fb(&self, i: u32) -> &Surface {
558        match self.uiw_parts(i) {
559            Some(p) => &p.rt.fb,
560            None => &self.m.win.zero_fb,
561        }
562    }
563
564    // the focus of window i (uiw_var(i, &g_focus))
565    fn win_focus(&self, i: u32) -> u32 {
566        self.uiw_parts(i).map_or(0, |p| p.ctx.focus)
567    }
568
569    /// uiw_take_newshow(i): the window became visible in this frame (clears the bit)
570    pub(crate) fn uiw_take_newshow(&mut self, i: u32) -> bool {
571        let r = &mut self.m.win.recs[(i as usize) & (WIN_MAX - 1)];
572        let n = r.state & UWI_NEWSHOW != 0;
573        r.state &= !UWI_NEWSHOW;
574        n
575    }
576
577    // ------------------------------------------------------------------ per-pass
578
579    /// uiw_frame_begin: start of a pass, no window begun yet
580    pub(crate) fn uiw_frame_begin(&mut self) {
581        let w = &mut self.m.win;
582        if w.used != 0 {
583            for r in w.recs.iter_mut() {
584                r.state &= !UWI_SEEN;
585            }
586        }
587        w.sp = 0;
588        w.over = 0;
589    }
590
591    /// uiw_frame_end: end of a pass over all windows: modal window and inert windows
592    pub(crate) fn uiw_frame_end(&mut self) {
593        if self.m.win.used == 0 {
594            return; // the main window only
595        }
596        if self.uiw_modal_update() {
597            self.repass = true;
598        }
599    }
600
601    /// uiw_unwind: after App::frame close windows the app left open (window_begin without
602    /// window_end) so the main window's pass ends in the main window
603    pub(crate) fn uiw_unwind(&mut self) {
604        while self.m.win.sp != 0 {
605            self.warn("ui_window_begin without ui_window_end");
606            self.window_end();
607        }
608        self.m.win.over = 0;
609        self.uiw_use(0);
610    }
611
612    // uiw_modal_update -> the modal window changed: the topmost visible WF_MODAL window;
613    // every window but it and the windows it owns is inert
614    fn uiw_modal_update(&mut self) -> bool {
615        let mut modal = 0u32;
616        let mut mz: i32 = -1;
617        // interactive runs without OS windows: the secondary windows cannot be seen, so
618        // none may block the main window
619        if self.headless || self.m.win.hooks {
620            for i in 1..WIN_MAX {
621                let r = &self.m.win.recs[i];
622                if r.state & (UWI_VISIBLE | UWI_CREATED) != UWI_VISIBLE | UWI_CREATED || r.flags & WF_MODAL == 0 {
623                    continue;
624                }
625                if r.z as i32 > mz {
626                    mz = r.z as i32;
627                    modal = i as u32;
628                }
629            }
630        }
631        // inert mask
632        let mut inert = 0u32;
633        if modal != 0 {
634            for i in 0..WIN_MAX as u32 {
635                if i != modal && !self.uiw_owned_by(i, modal) {
636                    inert |= 1 << i;
637                }
638            }
639        }
640        self.m.win.inert = inert;
641        if modal == self.m.win.modal {
642            return false;
643        }
644        self.m.win.modal = modal;
645        if modal != 0 {
646            self.uiw_activate(modal); // the modal window takes the keyboard
647        }
648        true
649    }
650
651    // uiw_owned_by(i, owner): `owner` owns window i (directly or further up)
652    fn uiw_owned_by(&self, i: u32, owner: u32) -> bool {
653        let mut i = i;
654        for _ in 0..WIN_MAX {
655            if i == 0 {
656                return false;
657            }
658            i = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].owner;
659            if i == owner {
660                return true;
661            }
662        }
663        false
664    }
665
666    /// uiw_inert_now: the live window is inert (a modal window is open elsewhere)
667    pub(crate) fn uiw_inert_now(&self) -> bool {
668        (self.m.win.inert >> (self.uiw_cur & 31)) & 1 != 0
669    }
670
671    /// uiw_border -> border thickness (px) of the live window: a hairline around secondary
672    /// windows with the library frame, 0 for the main window
673    pub(crate) fn uiw_border(&mut self) -> i32 {
674        let cur = self.uiw_cur;
675        let r = &self.m.win.recs[(cur as usize) & (WIN_MAX - 1)];
676        if cur == 0 || r.flags & WF_SYSFRAME != 0 || r.fs {
677            return 0;
678        }
679        self.hair()
680    }
681
682    /// uiw_escape: Esc no widget took in the live window: UIC_ESCAPE (arg = window id), in
683    /// a dialog window a close request
684    pub(crate) fn uiw_escape(&mut self) {
685        let r = &self.m.win.recs[(self.uiw_cur as usize) & (WIN_MAX - 1)];
686        let (id, flags) = (r.id, r.flags);
687        let mut cmd = UIC_ESCAPE;
688        if id != 0 && flags & WF_DIALOG != 0 {
689            if flags & WF_NOCLOSE != 0 {
690                return;
691            }
692            cmd = UIC_WINCLOSE;
693        }
694        self.cmd_run(cmd, id);
695    }
696
697    /// uiw_focus_arrived: behave met the id of a pending focus(id): it gets the focus in
698    /// the live window; asked from another window, that window keeps its old focus and the
699    /// live one is activated
700    pub(crate) fn uiw_focus_arrived(&mut self) {
701        let id = self.focus_req;
702        self.focus_req = 0;
703        self.wp.ctx.focus = id;
704        self.wp.ctx.focus_visible = true;
705        let from = self.focus_req_win;
706        if from == self.uiw_cur {
707            return;
708        }
709        let old = self.focus_req_old;
710        if let Some(p) = self.uiw_parts_mut(from) {
711            p.ctx.focus = old;
712        }
713        self.repass = true;
714        let cur = self.uiw_cur;
715        if cur != self.m.win.active {
716            self.uiw_activate(cur);
717        }
718    }
719
720    /// uiw_clear_edges: after the first pass of a batch: the input edges of every window
721    /// that got input are consumed (the main window always)
722    pub(crate) fn uiw_clear_edges(&mut self) {
723        self.dnd_clear_edges(); // DS_DROP / DS_END were seen
724        self.input_clear_edges(); // main window (live)
725        let e = self.m.win.edges & !1;
726        self.m.win.edges = 0;
727        if e == 0 {
728            return;
729        }
730        for i in 1..WIN_MAX as u32 {
731            if (e >> i) & 1 != 0 && self.uiw_drawable(i) {
732                self.uiw_use(i);
733                self.input_clear_edges();
734            }
735        }
736        self.uiw_use(0);
737    }
738
739    /// uiw_drawable(i): the window exists and is shown (the main window: always)
740    /// the title bar of a window with these WF_* flags that draws its own (DIP): the style's, higher
741    /// with a finger, and the window grows with it (lay out what lies below it with this height)
742    pub fn window_title_height(&self, flags: u32) -> i32 {
743        let h = self.style.metric(if flags & WF_TOOL != 0 { SM_WIN_TBH_TOOL } else { SM_WIN_TBH_DIALOG });
744        if self.m.touch.on { h.max(super::touch::TITLE_DIP) } else { h } // (a finger moves it there)
745    }
746
747    pub(crate) fn uiw_drawable(&self, i: u32) -> bool {
748        let s = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].state;
749        s & (UWI_VISIBLE | UWI_CREATED) == UWI_VISIBLE | UWI_CREATED
750    }
751
752    // ------------------------------------------------------------------ window_begin / end
753
754    /// ui_window_begin(id, title, open, flags, rect) -> true: the window is shown, emit its
755    /// content now (window_end always follows).  open None = always open; rect None: the
756    /// library keeps the placement.  The owner is the window being built.
757    #[cfg_attr(jfgui_dev, track_caller)]
758    pub fn window_begin(&mut self, id: u32, title: &str, open: Option<&Shared<i32>>, flags: u32, rect: Option<&Shared<WinRect>>) -> bool {
759        let _src = self.src_scope();
760        let mut flags = flags;
761        let sp = self.m.win.sp;
762        if sp as usize >= WSTACK_MAX {
763            self.m.win.over += 1;
764            self.limit_hit(super::ctx::LIM_WINNEST);
765            return false;
766        }
767        let owner = self.uiw_cur; // the window being built
768        self.m.win.stack[sp as usize & (WSTACK_MAX - 1)] = owner; // not entered (yet)
769        self.m.win.sp += 1;
770        if id == 0 {
771            self.warn("ui_window_begin: window id 0");
772            return false;
773        }
774        // windows entered inside each other
775        if self.uiw_depth() >= NEST_MAX {
776            self.warn(&alloc::format!("window {}: windows nested too deeply", id));
777            return false;
778        }
779        // the state of the last run (winstate.rs): the open flag before it is looked at
780        if !self.m.win.saved.is_empty() {
781            self.ws_open(id, flags, open);
782        }
783        let mut i = self.uiw_index(id);
784        if i < 0 {
785            // a window that was never shown needs no record while it is closed
786            if let Some(o) = open {
787                if o.get() == 0 {
788                    return false;
789                }
790            }
791            i = self.uiw_alloc();
792            if i < 0 {
793                self.warn(&alloc::format!("window {}: too many windows ({} besides the main window)", id, WIN_MAX - 1));
794                return false;
795            }
796            self.m.win.recs[i as usize].id = id;
797            self.m.win.used |= 1 << i;
798        }
799        let i = i as u32;
800        let iu = i as usize;
801        if self.m.win.recs[iu].state & (UWI_SEEN | UWI_BUILDING) != 0 {
802            self.warn(&alloc::format!("window {} begun twice in one pass", id));
803            return false;
804        }
805        let frame = self.frame;
806        {
807            let r = &mut self.m.win.recs[iu];
808            r.state |= UWI_SEEN;
809            r.seen = frame;
810        }
811        // no system frame (Wayland without decorations): the library draws it
812        if flags & WF_SYSFRAME != 0 && !self.uiw_sysframe_ok() {
813            flags &= !WF_SYSFRAME;
814        }
815        // title bar height (the style's, DIP; (E7) an app window draws its own)
816        let tbh = if flags & (WF_SYSFRAME | WF_APP) != 0 || self.m.win.recs[iu].fs {
817            0
818        } else {
819            self.window_title_height(flags)
820        };
821        {
822            let r = &mut self.m.win.recs[iu];
823            r.flags = flags;
824            r.owner = owner;
825            r.openp = open.cloned();
826            r.rect = match rect {
827                Some(p) => p.clone(),
828                None => r.own.clone(),
829            };
830            r.tbh = tbh;
831            // title (a copy; a change is passed on to the platform)
832            let t = super::basic::cstr(title);
833            let mut n = t.len().min(TITLE_MAX - 1);
834            while !t.is_char_boundary(n) {
835                n -= 1;
836            }
837            if r.title != t[..n] {
838                r.title = String::from(&t[..n]);
839                r.want |= UWW_TITLE;
840            }
841        }
842        // the placement of the last run (winstate.rs)
843        if !self.m.win.saved.is_empty() {
844            self.ws_rect(i, id);
845        }
846        // open?
847        let is_open = open.is_none_or(|o| o.get() != 0);
848        if !is_open {
849            let r = &mut self.m.win.recs[iu];
850            r.state &= !UWI_OPEN;
851            if r.state & UWI_VISIBLE != 0 {
852                self.uiw_hide(i);
853            }
854            return false;
855        }
856        self.m.win.recs[iu].state |= UWI_OPEN;
857        if self.m.win.recs[iu].state & UWI_CREATED == 0 && !self.uiw_create(i) {
858            self.warn(&alloc::format!("window {}: out of memory", id));
859            return false;
860        }
861        if self.m.win.recs[iu].state & UWI_VISIBLE == 0 {
862            self.uiw_show(i);
863        }
864        // enter the window
865        let top = (self.m.win.sp - 1) as usize & (WSTACK_MAX - 1);
866        self.m.win.stack[top] = i << 8 | owner | WSE_ENTERED;
867        self.uiw_use(i);
868        self.m.win.recs[iu].state |= UWI_BUILDING;
869        if self.m.win.recs[iu].state & UWI_FRESH != 0 {
870            self.m.win.recs[iu].state &= !UWI_FRESH;
871            // a window shown (again) starts without focus; a dialog window with the focus on
872            // its first control (kbnav.rs)
873            self.wp.ctx.focus = 0;
874            self.wp.ctx.focus_visible = false;
875            self.wp.ctx.active = 0;
876            self.wp.ctx.kb.autofocus = flags & WF_DIALOG != 0;
877        }
878        self.uiw_apply_rect(i);
879        let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
880        self.uiw_pass_begin(w, h);
881        self.win_chrome_begin(i);
882        true
883    }
884
885    // uiw_depth -> windows entered on the window_begin stack
886    fn uiw_depth(&self) -> u32 {
887        let n = (self.m.win.sp as usize).min(WSTACK_MAX);
888        self.m.win.stack[..n].iter().filter(|&&e| e & WSE_ENTERED != 0).count() as u32
889    }
890
891    // uiw_alloc -> index of a free window record (1..WIN_MAX-1) or -1; when every record is
892    // in use, the hidden window seen longest ago is freed for it
893    fn uiw_alloc(&mut self) -> i32 {
894        if let Some(i) = (1..WIN_MAX).find(|&i| self.m.win.recs[i].id == 0) {
895            return i as i32;
896        }
897        // recycle: a window that is not shown, not being built, not begun in this pass
898        let mut cand: i32 = -1;
899        let mut oldest = u32::MAX;
900        for i in 1..WIN_MAX {
901            let r = &self.m.win.recs[i];
902            if r.state & (UWI_VISIBLE | UWI_BUILDING | UWI_SEEN) != 0 {
903                continue;
904            }
905            if r.seen < oldest {
906                oldest = r.seen;
907                cand = i as i32;
908            }
909        }
910        if cand < 0 {
911            return -1;
912        }
913        let id = self.m.win.recs[cand as usize].id;
914        self.window_destroy(id);
915        cand
916    }
917
918    /// ui_window_end: close window_begin (also when it returned false)
919    #[cfg_attr(jfgui_dev, track_caller)]
920    pub fn window_end(&mut self) {
921        let _src = self.src_scope();
922        if self.m.win.over != 0 {
923            self.m.win.over -= 1;
924            return;
925        }
926        if self.m.win.sp == 0 {
927            self.warn("ui_window_end without ui_window_begin");
928            return;
929        }
930        let sp = (self.m.win.sp - 1) & (WSTACK_MAX as u32 - 1);
931        self.m.win.sp = sp;
932        let e = self.m.win.stack[sp as usize];
933        if e & WSE_ENTERED == 0 {
934            return;
935        }
936        let i = (e >> 8) & 255; // index
937        let back = e & 255; // window to return to
938        let iu = (i as usize) & (WIN_MAX - 1);
939        let h = self.win_chrome_end(i); // natural content height (DIP)
940        let flags = self.m.win.recs[iu].flags;
941        if flags & WF_AUTOHEIGHT != 0 {
942            self.m.win.recs[iu].autoh = h;
943        }
944        // resize handles of the library frame
945        if flags & (WF_RESIZABLE | WF_SYSFRAME) == WF_RESIZABLE {
946            self.edge_grips();
947        }
948        self.m.win.noaccel = flags & WF_DIALOG != 0;
949        self.uiw_pass_end();
950        self.m.win.noaccel = false;
951        self.m.win.recs[iu].state &= !UWI_BUILDING;
952        self.uiw_use(back);
953    }
954
955    // uiw_create(i) -> ok: per-window state and buffers of a new window; its UI state
956    // starts empty, its dpi is the owner's
957    fn uiw_create(&mut self, i: u32) -> bool {
958        let iu = (i as usize) & (WIN_MAX - 1);
959        let owner = self.m.win.recs[iu].owner;
960        let dpi = self.win_dpi(owner);
961        let mut p = Box::new(WinParts::new());
962        p.rt.dpi = dpi;
963        p.ctx.mx = -1;
964        p.ctx.my = -1;
965        p.lay.root_id = i | WROOT_BASE;
966        p.cmd = super::cmd::W::new(&super::cmd::CAPS_SMALL);
967        p.dirty.full = true; // dirty_force_full
968        self.m.win.recs[iu].save = Some(p);
969        let r = &mut self.m.win.recs[iu];
970        r.state |= UWI_CREATED;
971        r.want |= UWW_CREATE;
972        true
973    }
974
975    // uiw_show(i): the window becomes visible: default size / placement the first time,
976    // stacking order, activation (dialogs and plain windows; not tool / WF_NOACTIVATE)
977    fn uiw_show(&mut self, i: u32) {
978        let iu = (i as usize) & (WIN_MAX - 1);
979        let mut rc = self.m.win.recs[iu].rect.get();
980        if rc.w <= 0 {
981            rc.w = DEF_W;
982        }
983        if rc.h <= 0 {
984            rc.h = DEF_H;
985        }
986        self.m.win.recs[iu].rect.set(rc);
987        if rc.flags & WRF_POS == 0 {
988            self.uiw_place_default(i);
989            self.m.win.recs[iu].state |= UWI_DEFPOS;
990        }
991        self.m.win.zc = self.m.win.zc.wrapping_add(1);
992        let zc = self.m.win.zc;
993        let r = &mut self.m.win.recs[iu];
994        r.state |= UWI_VISIBLE | UWI_NEWSHOW | UWI_SHOWN | UWI_FRESH;
995        r.want = (r.want & !UWW_HIDE) | UWW_SHOW;
996        r.z = zc;
997        if r.flags & (WF_TOOL | WF_NOACTIVATE) == 0 {
998            self.uiw_activate(i);
999        }
1000    }
1001
1002    /// uiw_hide(i): the window disappears (its state and buffers stay)
1003    pub(crate) fn uiw_hide(&mut self, i: u32) {
1004        let iu = (i as usize) & (WIN_MAX - 1);
1005        let r = &mut self.m.win.recs[iu];
1006        r.state &= !(UWI_VISIBLE | UWI_NEWSHOW);
1007        r.want = (r.want & !UWW_SHOW) | UWW_HIDE;
1008        let mut owner = r.owner;
1009        if self.m.win.ptr == i as i32 {
1010            self.m.win.ptr = -1;
1011        }
1012        if self.m.win.active != i {
1013            return;
1014        }
1015        // the keyboard returns to the nearest visible owner
1016        let mut n = WIN_MAX;
1017        loop {
1018            let o = owner;
1019            if self.uiw_drawable(o) || o == 0 {
1020                break;
1021            }
1022            owner = self.m.win.recs[(o as usize) & (WIN_MAX - 1)].owner;
1023            n -= 1;
1024            if n == 0 {
1025                owner = 0;
1026                break;
1027            }
1028        }
1029        self.uiw_activate(owner);
1030    }
1031
1032    /// uiw_activate(i): the window gets the keyboard and goes on top
1033    pub(crate) fn uiw_activate(&mut self, i: u32) {
1034        let i = i & (WIN_MAX as u32 - 1);
1035        if self.m.win.active == i {
1036            return;
1037        }
1038        // interactive runs without OS windows: the keyboard stays with the main window
1039        if i != 0 && !self.headless && !self.m.win.hooks {
1040            return;
1041        }
1042        self.m.win.active = i;
1043        self.m.win.recs[i as usize].want |= UWW_ACTIVATE;
1044        if i != 0 {
1045            self.m.win.zc = self.m.win.zc.wrapping_add(1);
1046            self.m.win.recs[i as usize].z = self.m.win.zc;
1047        }
1048        self.invalidate();
1049    }
1050
1051    // uiw_place_default(i): first placement (the same on every platform and headless):
1052    // dialogs centred on the owner, other windows at the owner's client origin + (24 + 32k,
1053    // 48 + 32k) DIP, k = windows of that owner placed before; written back with WRF_POS
1054    fn uiw_place_default(&mut self, i: u32) {
1055        let iu = (i as usize) & (WIN_MAX - 1);
1056        let (owner, flags, tbh) = {
1057            let r = &self.m.win.recs[iu];
1058            (r.owner, r.flags, r.tbh)
1059        };
1060        let mut rc = self.m.win.recs[iu].rect.get();
1061        if flags & WF_DIALOG == 0 {
1062            // k = shown windows of the same owner (not dialogs)
1063            let mut k = 0;
1064            for j in 1..WIN_MAX {
1065                if j == iu {
1066                    continue;
1067                }
1068                let r = &self.m.win.recs[j];
1069                if r.id != 0 && r.owner == owner && r.state & UWI_SHOWN != 0 && r.flags & WF_DIALOG == 0 {
1070                    k += 1;
1071                }
1072            }
1073            rc.x = k * 32 + 24;
1074            rc.y = k * 32 + 48;
1075        } else {
1076            // owner outer rectangle relative to its client origin, in DIP
1077            let (ow, oh, oy) = self.uiw_outer_dip(owner);
1078            rc.x = (ow - rc.w) >> 1;
1079            rc.y = ((oh - (tbh + rc.h)) >> 1) + oy;
1080        }
1081        rc.flags |= WRF_POS;
1082        self.m.win.recs[iu].rect.set(rc);
1083    }
1084
1085    // (form.rs) uiw_keep_on_screen(i, rect): a phone's or a tablet's window is at most as large as the
1086    // app's screen, and one the main window owns lies inside it (what does not fit rolls: chrome.rs)
1087    fn uiw_keep_on_screen(&self, iu: usize, rc: &mut WinRect) {
1088        if iu == 0 {
1089            return;
1090        }
1091        let (owner, tbh) = (self.m.win.recs[iu].owner, self.m.win.recs[iu].tbh);
1092        let (mw, mh, _) = self.uiw_outer_dip(0);
1093        let edge = 2; // the window's border, both sides (DIP, rounded up)
1094        rc.w = rc.w.min(mw - edge).max(1);
1095        rc.h = rc.h.min(mh - tbh - edge).max(1);
1096        if owner == 0 && rc.flags & WRF_POS != 0 {
1097            rc.x = rc.x.min(mw - edge - rc.w).max(0);
1098            rc.y = rc.y.min(mh - edge - rc.h).max(tbh);
1099        }
1100    }
1101
1102    // uiw_outer_dip(i) -> (outer width, outer height (DIP), y of the outer top edge relative
1103    // to the client origin (-title bar height; main window: 0))
1104    fn uiw_outer_dip(&self, i: u32) -> (i32, i32, i32) {
1105        if i == 0 {
1106            // the main window: its framebuffer
1107            let fb = self.win_fb(0);
1108            let dpi = self.win_dpi(0);
1109            return (px2dip_at(fb.w, dpi), px2dip_at(fb.h, dpi), 0);
1110        }
1111        let r = &self.m.win.recs[(i as usize) & (WIN_MAX - 1)];
1112        let rc = r.rect.get();
1113        (rc.w, rc.h + r.tbh, -r.tbh)
1114    }
1115
1116    // uiw_apply_rect(i): (the window is live) apply its WINRECT when it changed:
1117    // framebuffer size = client + title bar + border at the window's dpi
1118    fn uiw_apply_rect(&mut self, i: u32) {
1119        let iu = (i as usize) & (WIN_MAX - 1);
1120        let mut rc = self.m.win.recs[iu].rect.get();
1121        if rc.w <= 0 {
1122            rc.w = DEF_W;
1123        }
1124        if rc.h <= 0 {
1125            rc.h = DEF_H;
1126        }
1127        if rc.w > SIZE_MAX_DIP {
1128            rc.w = SIZE_MAX_DIP;
1129        }
1130        if rc.h > SIZE_MAX_DIP {
1131            rc.h = SIZE_MAX_DIP;
1132        }
1133        if self.mobile() {
1134            self.uiw_keep_on_screen(iu, &mut rc);
1135        }
1136        self.m.win.recs[iu].rect.set(rc);
1137        if !self.wp.rt.fb.px.is_empty() && rc == self.m.win.recs[iu].applied {
1138            return;
1139        }
1140        let tbh = {
1141            let r = &mut self.m.win.recs[iu];
1142            r.applied = rc;
1143            r.want |= UWW_RECT;
1144            r.tbh
1145        };
1146        let b2 = self.uiw_border() * 2; // 2 borders
1147        let w = self.dp(rc.w) + b2; // framebuffer width
1148        let h = self.dp(rc.h + tbh) + b2; // framebuffer height
1149        if !self.wp.rt.fb.px.is_empty() && w == self.wp.rt.fb.w && h == self.wp.rt.fb.h {
1150            return;
1151        }
1152        if self.wp.rt.fb.px.is_empty() {
1153            self.wp.rt.fb = Surface::new(w, h, FMT_XRGB);
1154        } else {
1155            self.wp.rt.fb.resize(w, h);
1156        }
1157        self.dirty_force_full();
1158    }
1159
1160    // ------------------------------------------------------------------ settle / sync
1161
1162    /// uiw_settle -> the frame must be built again: after the settled pass, windows that
1163    /// were not begun are hidden, WF_AUTOHEIGHT windows take their content height, platform
1164    /// operations are done
1165    pub(crate) fn uiw_settle(&mut self) -> bool {
1166        let mut changed = false;
1167        if self.m.win.used != 0 {
1168            for i in 1..WIN_MAX as u32 {
1169                let iu = i as usize;
1170                let (state, flags, autoh) = {
1171                    let r = &self.m.win.recs[iu];
1172                    (r.state, r.flags, r.autoh)
1173                };
1174                if state & UWI_VISIBLE == 0 {
1175                    continue;
1176                }
1177                if state & UWI_SEEN == 0 {
1178                    self.uiw_hide(i);
1179                    changed = true;
1180                    continue;
1181                }
1182                if flags & WF_AUTOHEIGHT == 0 || autoh <= 0 {
1183                    continue;
1184                }
1185                let mut rc = self.m.win.recs[iu].rect.get();
1186                if rc.h == autoh {
1187                    continue;
1188                }
1189                rc.h = autoh;
1190                self.m.win.recs[iu].rect.set(rc);
1191                changed = true;
1192                // a dialog placed by the default rule stays centred while its height settles
1193                if state & UWI_DEFPOS != 0 && flags & WF_DIALOG != 0 {
1194                    self.uiw_place_default(i);
1195                }
1196            }
1197            changed |= self.uiw_modal_update();
1198        }
1199        // platform operations once the windows stopped changing
1200        if !changed {
1201            self.uiw_sync();
1202        }
1203        changed
1204    }
1205
1206    // uiw_sync: pending platform operations (nothing headless)
1207    fn uiw_sync(&mut self) {
1208        if !self.m.win.hooks || self.headless {
1209            return; // no OS windows (headless, or no platform support yet): nothing to do
1210        }
1211        self.uiw_enable_sync(1); // windows a closed modal window disabled first (before it is hidden)
1212        for i in 0..WIN_MAX as u32 {
1213            let iu = i as usize;
1214            let want = self.m.win.recs[iu].want;
1215            if want == 0 {
1216                continue;
1217            }
1218            self.m.win.recs[iu].want = 0;
1219            if i != 0 {
1220                if want & UWW_CREATE != 0 {
1221                    let (flags, owner) = (self.m.win.recs[iu].flags, self.m.win.recs[iu].owner);
1222                    self.uwp_create(i, flags, owner);
1223                }
1224                if want & UWW_RECT != 0 && !self.m.win.recs[iu].fs {
1225                    // (a window in full screen keeps the monitor; its place comes back after)
1226                    let rc = self.m.win.recs[iu].rect.get();
1227                    self.uwp_rect(i, &rc);
1228                }
1229                if want & UWW_TITLE != 0 {
1230                    let t = self.m.win.recs[iu].title.clone();
1231                    self.uwp_title(i, &t);
1232                }
1233                if want & UWW_SHOW != 0 {
1234                    // shown (and activated) after its first present: uiw_sync_show
1235                    self.m.win.recs[iu].want |= want & (UWW_SHOW | UWW_ACTIVATE | UWW_FS);
1236                    continue;
1237                }
1238                if want & UWW_HIDE != 0 {
1239                    self.uwp_show(i, false);
1240                }
1241            }
1242            if i == 0 && want & UWW_RECT != 0 {
1243                // window_place(0, ...)
1244                let rc = self.m.win.recs[0].rect.get();
1245                self.uwp_rect(0, &rc);
1246            }
1247            if want & UWW_ACTIVATE != 0 {
1248                self.uwp_activate(i);
1249            }
1250            if want & UWW_FS != 0 {
1251                self.uwp_fullscreen(i);
1252            }
1253        }
1254    }
1255
1256    // (L7) uwp_fullscreen(i): the platform window follows Rec::fs
1257    fn uwp_fullscreen(&mut self, i: u32) {
1258        let pw = self.uiw_pw(i);
1259        if pw < 0 {
1260            return;
1261        }
1262        let on = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].fs;
1263        plat::win_cmd(pw, if on { plat::WCMD_FULLSCREEN } else { plat::WCMD_WINDOWED });
1264        // the platform's answer (Windows at once, the others with their next configure)
1265        let now = plat::win_state(pw) & plat::WST_FULLSCREEN != 0;
1266        self.m.win.recs[(i as usize) & (WIN_MAX - 1)].fs_plat = now;
1267    }
1268
1269    /// (L7) uiw_fs_check(i): after a resize of window i: the platform's full-screen state
1270    /// changed by itself (the browser's Esc, the window manager, the user): the window's state
1271    /// follows and the app gets UIC_FULLSCREEN
1272    pub(crate) fn uiw_fs_check(&mut self, i: u32) {
1273        if !self.uiw_os() {
1274            return;
1275        }
1276        let iu = (i as usize) & (WIN_MAX - 1);
1277        let on = self.uiw_plat_state(i) & plat::WST_FULLSCREEN != 0;
1278        if on == self.m.win.recs[iu].fs_plat {
1279            return;
1280        }
1281        self.m.win.recs[iu].fs_plat = on;
1282        if on != self.m.win.recs[iu].fs {
1283            self.uiw_fs_changed(i, on);
1284        }
1285    }
1286
1287    /// (L7) uiw_fs_changed(i, on): window i is (no longer) in full screen without the app
1288    /// asking (the platform, a script's `system fullscreen`): UIC_FULLSCREEN (arg = its id)
1289    pub(crate) fn uiw_fs_changed(&mut self, i: u32, on: bool) {
1290        let iu = (i as usize) & (WIN_MAX - 1);
1291        let r = &mut self.m.win.recs[iu];
1292        r.fs = on;
1293        r.want &= !UWW_FS;
1294        let id = r.id;
1295        self.cmd_run(UIC_FULLSCREEN, id);
1296        self.invalidate();
1297    }
1298
1299    /// uiw_sync_show: after render: windows waiting to be shown appear (and are activated)
1300    /// now that their framebuffer has content
1301    pub(crate) fn uiw_sync_show(&mut self) {
1302        if !self.m.win.hooks || self.headless {
1303            return;
1304        }
1305        for i in 1..WIN_MAX as u32 {
1306            let iu = i as usize;
1307            let r = &self.m.win.recs[iu];
1308            if r.want & UWW_SHOW == 0 || r.state & UWI_VISIBLE == 0 {
1309                continue;
1310            }
1311            self.m.win.recs[iu].want &= !UWW_SHOW;
1312            self.uwp_show(i, true);
1313            if self.m.win.recs[iu].want & UWW_ACTIVATE != 0 {
1314                self.m.win.recs[iu].want &= !UWW_ACTIVATE;
1315                self.uwp_activate(i);
1316            }
1317        }
1318        self.uiw_enable_sync(2); // a new modal window is shown: the windows below it are disabled now
1319    }
1320
1321    /// ui_win_hooks_set: secondary windows become OS windows (the runtime calls it when it
1322    /// runs with a display; without it they are built but only exist as framebuffers)
1323    pub(crate) fn win_hooks_set(&mut self, on: bool) {
1324        self.m.win.hooks = on;
1325    }
1326
1327    /// ui_sync_hook_set(f): f() -> true while background jobs are pending (the script
1328    /// command `sync` waits for false)
1329    pub fn sync_hook_set(&mut self, f: Option<fn() -> bool>) {
1330        self.m.win.synchook = f;
1331    }
1332
1333    /// uiw_sync_pending -> background jobs are pending (false without a hook)
1334    pub(crate) fn uiw_sync_pending(&mut self) -> bool {
1335        match self.m.win.synchook {
1336            Some(f) => f(),
1337            None => false,
1338        }
1339    }
1340
1341    /// uiw_present(n): the live window's framebuffer changed in n dirty rectangles (drects;
1342    /// -1 = everything): show it through the GPU (gpu_out.rs) or hand it to the platform
1343    pub(crate) fn uiw_present(&mut self, n: i32) {
1344        let cur = self.uiw_cur;
1345        let pw = if self.headless {
1346            // a script's `gpu on`: real windows, else nothing to show
1347            if !self.gp_scripted() {
1348                return;
1349            }
1350            self.gp_script_pw(cur)
1351        } else if cur == 0 {
1352            0
1353        } else if !self.m.win.hooks {
1354            return;
1355        } else {
1356            self.uiw_pw(cur) // uwp_present(index, &fb, rects, n)
1357        };
1358        if pw < 0 {
1359            return;
1360        }
1361        // (form.rs) the system's bars take the colours at the app's edges
1362        #[cfg(target_os = "android")]
1363        if cur == 0 && !self.headless {
1364            plat::host_edges(super::form::edge_colors(&self.wp.rt.fb));
1365        }
1366        if self.gp_present(cur, pw, n) {
1367            return;
1368        }
1369        let rects = if n < 0 { None } else { Some(&self.drects[..n as usize]) };
1370        plat::win_present(pw, &Ui::frame_of(&self.wp.rt.fb), rects);
1371        self.gp_cpu_done(cur, pw);
1372    }
1373
1374    /// (the platform's hit hook) publish the hit list of the live window's last pass for
1375    /// the non-client hit tests, which run without the UI context (uiw_hit_pw).  Called at
1376    /// the end of uiw_pass_end.
1377    pub(crate) fn uiw_publish_hits(&mut self) {
1378        if self.headless {
1379            return;
1380        }
1381        let cur = (self.uiw_cur as usize) & (WIN_MAX - 1);
1382        let mut drawable = 0;
1383        for i in 0..WIN_MAX as u32 {
1384            if self.uiw_drawable(i) {
1385                drawable |= 1 << i;
1386            }
1387        }
1388        let c = &self.wp.ctx;
1389        let mut p = PUBLISHED.lock();
1390        p.hits[cur].clear();
1391        p.hits[cur].extend_from_slice(&c.hits[c.hit_cur ^ 1]);
1392        p.zones[cur].clear();
1393        p.zones[cur].extend_from_slice(&c.zones[c.hit_cur ^ 1]);
1394        p.modal[cur] = c.modal_prev;
1395        p.mirror[cur] = if self.rtl { self.wp.rt.fb.w } else { 0 };
1396        p.drawable = drawable;
1397        p.p2u = self.m.win.p2u;
1398    }
1399
1400    // publish_state: the window map and the shown windows for the hit hook
1401    fn publish_state(&self) {
1402        if self.headless {
1403            return;
1404        }
1405        let mut drawable = 0;
1406        for i in 0..WIN_MAX as u32 {
1407            if self.uiw_drawable(i) {
1408                drawable |= 1 << i;
1409            }
1410        }
1411        let mut p = PUBLISHED.lock();
1412        p.drawable = drawable;
1413        p.p2u = self.m.win.p2u;
1414    }
1415
1416    // ------------------------------------------------------------------ events
1417
1418    /// uiw_input_target(ev) -> the window an input event goes to, -1 = drop it.  Keys go
1419    /// to the keyboard window; from a tool window without a focused text box (and without
1420    /// WF_KEYS) they go on to its owner; with a modal window open every key of an inert
1421    /// window goes to the modal one.  A pointer event in another window than the last one
1422    /// first leaves that window (E_LEAVE).  Headless runs activate windows on a press like
1423    /// the Windows window manager.
1424    pub(crate) fn uiw_input_target(&mut self, ev: &Event) -> i32 {
1425        let w = ev.win as u32;
1426        if w as usize >= WIN_MAX || (w != 0 && !self.uiw_drawable(w)) {
1427            return -1; // a dead or hidden window
1428        }
1429        let t = match ev.typ {
1430            plat::E_KEY_DOWN | plat::E_KEY_UP | plat::E_TEXT | plat::E_IME_COMMIT | plat::E_PREEDIT => self.uiw_key_target(w),
1431            plat::E_LEAVE => {
1432                if self.m.win.ptr == w as i32 {
1433                    self.m.win.ptr = -1;
1434                }
1435                w
1436            }
1437            plat::E_FOCUS_OUT => w,
1438            _ => {
1439                // pointer: another window than the last one -> that one is left first
1440                let last = self.m.win.ptr;
1441                if last != w as i32 {
1442                    self.m.win.ptr = w as i32;
1443                    if last >= 0 && self.uiw_drawable(last as u32) {
1444                        self.uiw_use(last as u32);
1445                        let leave = Event { typ: plat::E_LEAVE, btn: 0, mods: ev.mods, win: last as u8, x: -1, y: -1, data: 0 };
1446                        self.input_event(&leave);
1447                        self.m.win.edges |= 1 << last;
1448                    }
1449                }
1450                if ev.typ == plat::E_DOWN {
1451                    self.uiw_kfocus_press(w, ev.x, ev.y);
1452                    self.uiw_popups_dismiss(w); // (U3) popups of other windows close
1453                    if self.headless {
1454                        self.uiw_click_activate(w, ev.x, ev.y);
1455                    }
1456                }
1457                w
1458            }
1459        };
1460        let t = t & (WIN_MAX as u32 - 1);
1461        self.m.win.edges |= 1 << t;
1462        t as i32
1463    }
1464
1465    // uiw_key_target(i) -> the window that gets a key / text event sent to window i
1466    fn uiw_key_target(&mut self, i: u32) -> u32 {
1467        let mut b = i;
1468        if b != 0 && !self.uiw_drawable(b) {
1469            b = self.m.win.active; // a gone window: the keyboard window
1470        }
1471        if (self.m.win.inert >> (b & 31)) & 1 != 0 {
1472            return self.m.win.modal & (WIN_MAX as u32 - 1);
1473        }
1474        // (U3) a tool window whose (non-text) widget got the focus by a press borrows the
1475        // keys of its owner: its widgets see them first, the rest goes on to the owner
1476        // (uiw_fwd_take / uiw_fwd_inject)
1477        let kf = self.m.win.kfocus;
1478        if kf != 0 {
1479            let t = kf - 1;
1480            if self.uiw_borrows(t) && (b == t || b == self.m.win.recs[(t as usize) & (WIN_MAX - 1)].owner) {
1481                self.m.win.fwd |= 1 << (t & 31);
1482                return t & (WIN_MAX as u32 - 1);
1483            }
1484        }
1485        let mut n = WIN_MAX;
1486        loop {
1487            if b == 0 {
1488                break;
1489            }
1490            let r = &self.m.win.recs[(b as usize) & (WIN_MAX - 1)];
1491            if r.flags & WF_TOOL == 0 || r.flags & WF_KEYS != 0 {
1492                break;
1493            }
1494            let owner = r.owner;
1495            if self.uiw_focus_is_text(b) {
1496                break;
1497            }
1498            // (U3) a focused widget of the tool window: it borrows the key (the rest goes on)
1499            if self.uiw_borrows(b) {
1500                self.m.win.fwd |= 1 << (b & 31);
1501                break;
1502            }
1503            b = owner;
1504            n -= 1;
1505            if n == 0 {
1506                break;
1507            }
1508        }
1509        b & (WIN_MAX as u32 - 1)
1510    }
1511
1512    /// uiw_keys_window -> the window a key would go to now (uiw_key_target without its side
1513    /// effects; the dump's "keys", the script's `expect window ID keys`)
1514    pub(crate) fn uiw_keys_window(&self) -> u32 {
1515        let mut b = self.m.win.active & (WIN_MAX as u32 - 1);
1516        if (self.m.win.inert >> b) & 1 != 0 {
1517            return self.m.win.modal & (WIN_MAX as u32 - 1);
1518        }
1519        let kf = self.m.win.kfocus;
1520        if kf != 0 {
1521            let t = kf - 1;
1522            if self.uiw_borrows(t) && (b == t || b == self.m.win.recs[(t as usize) & (WIN_MAX - 1)].owner) {
1523                return t & (WIN_MAX as u32 - 1);
1524            }
1525        }
1526        for _ in 0..WIN_MAX {
1527            if b == 0 {
1528                break;
1529            }
1530            let r = &self.m.win.recs[(b as usize) & (WIN_MAX - 1)];
1531            if r.flags & WF_TOOL == 0 || r.flags & WF_KEYS != 0 || self.uiw_focus_is_text(b) || self.uiw_borrows(b) {
1532                break;
1533            }
1534            b = r.owner;
1535        }
1536        b & (WIN_MAX as u32 - 1)
1537    }
1538
1539    // uiw_borrows(i): tool window i (shown, not inert, without WF_KEYS) has a focused
1540    // widget, so it may borrow its owner's keys (U3)
1541    fn uiw_borrows(&self, i: u32) -> bool {
1542        if i == 0 || i as usize >= WIN_MAX || !self.uiw_drawable(i) || (self.m.win.inert >> i) & 1 != 0 {
1543            return false;
1544        }
1545        if self.m.win.recs[i as usize].flags & (WF_TOOL | WF_KEYS) != WF_TOOL {
1546            return false;
1547        }
1548        self.win_focus(i) != 0
1549    }
1550
1551    // uiw_kfocus_press(i, x, y): a press in window i: in a tool window (not on a text box)
1552    // that window borrows the keys from now on while one of its widgets has the focus; a
1553    // press anywhere else gives the keys their normal way again (U3)
1554    fn uiw_kfocus_press(&mut self, i: u32, x: i32, y: i32) {
1555        self.m.win.kfocus = 0;
1556        self.m.kbnav.tool = false; // (kbnav.rs: a press ends the keys F6 gave a tool window)
1557        if i == 0 || self.m.win.recs[(i as usize) & (WIN_MAX - 1)].flags & (WF_TOOL | WF_KEYS) != WF_TOOL {
1558            return;
1559        }
1560        if self.hit_flags_win(i, x, y) & HB_TEXT != 0 {
1561            return; // (a text box activates the window)
1562        }
1563        self.m.win.kfocus = i + 1;
1564    }
1565
1566    /// uiw_fwd_take: (start of uiw_pass_end of the live window) a window that borrowed keys
1567    /// this frame and has no open popup hands the keys and typed text its widgets did not
1568    /// consume to its owner (injected by uiw_fwd_inject after the pass) (U3)
1569    pub(crate) fn uiw_fwd_take(&mut self) {
1570        let cur = self.uiw_cur;
1571        if cur as usize >= WIN_MAX || (self.m.win.fwd >> cur) & 1 == 0 {
1572            return;
1573        }
1574        self.m.win.fwd &= !(1 << cur);
1575        if self.popup_count() != 0 {
1576            return; // menus / lists of the window keep them
1577        }
1578        self.m.win.fwd_to = self.m.win.recs[cur as usize].owner & (WIN_MAX as u32 - 1);
1579        let n = FWDQ_MAX - self.m.win.fwdq.len().min(FWDQ_MAX);
1580        let k = self.kb_take_rest(n); // (kbnav.rs: Tab, F6, arrows in groups... stay here)
1581        self.m.win.fwdq.extend_from_slice(&k);
1582        let n = FWDT_MAX - self.m.win.fwdt.len().min(FWDT_MAX);
1583        let t = self.text_take(n);
1584        self.m.win.fwdt.extend_from_slice(&t);
1585    }
1586
1587    /// uiw_fwd_inject -> keys were handed on (after the first pass of a batch, whose edges
1588    /// were just cleared): they are the owner's input for the next pass, which the caller
1589    /// runs and clears again (U3)
1590    pub(crate) fn uiw_fwd_inject(&mut self) -> bool {
1591        self.m.win.fwd = 0;
1592        if self.m.win.fwdq.is_empty() && self.m.win.fwdt.is_empty() {
1593            return false;
1594        }
1595        let to = self.m.win.fwd_to;
1596        let mut r = false;
1597        if self.uiw_drawable(to) {
1598            self.uiw_use(to);
1599            let q = core::mem::take(&mut self.m.win.fwdq);
1600            self.keys_inject(&q);
1601            let t = core::mem::take(&mut self.m.win.fwdt);
1602            self.text_inject(&t);
1603            self.uiw_use(0);
1604            self.m.win.edges |= 1 << to;
1605            self.repass = true;
1606            r = true;
1607        }
1608        self.m.win.fwdq.clear();
1609        self.m.win.fwdt.clear();
1610        r
1611    }
1612
1613    /// uiw_fwd_arm(i): (U3) typed text for window i outside an input event (ctx.rs
1614    /// text_refill): what the widgets of a borrowing tool window leave goes on to its owner,
1615    /// as uiw_key_target arranges for typed text
1616    pub(crate) fn uiw_fwd_arm(&mut self, i: u32) {
1617        if self.uiw_borrows(i) && !self.uiw_focus_is_text(i) {
1618            self.m.win.fwd |= 1 << (i & 31);
1619        }
1620    }
1621
1622    // uiw_focus_is_text(i): the focused widget of window i was a text input (or kept every key,
1623    // HB_KEYS) in the last pass
1624    fn uiw_focus_is_text(&self, i: u32) -> bool {
1625        let Some(p) = self.uiw_parts(i) else { return false };
1626        let f = p.ctx.focus;
1627        if f == 0 {
1628            return false;
1629        }
1630        let list = &p.ctx.hits[p.ctx.hit_cur ^ 1];
1631        match list.iter().rev().find(|h| h.id == f) {
1632            Some(h) => h.flags as u32 & HB_TEXT != 0 || h.keys,
1633            None => false,
1634        }
1635    }
1636
1637    // uiw_click_activate(i, x, y): headless window manager: a press activates the window,
1638    // except tool / WF_NOACTIVATE windows, which only take the keyboard for a text box;
1639    // inert windows (a modal window is open) are not activated
1640    fn uiw_click_activate(&mut self, i: u32, x: i32, y: i32) {
1641        if (self.m.win.inert >> (i & 31)) & 1 != 0 {
1642            return;
1643        }
1644        if i != 0 {
1645            let f = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].flags;
1646            if f & (WF_TOOL | WF_NOACTIVATE) != 0 {
1647                if f & WF_NOACTIVATE != 0 || self.hit_flags_win(i, x, y) & HB_TEXT == 0 {
1648                    return;
1649                }
1650            }
1651        }
1652        self.uiw_activate(i);
1653    }
1654
1655    /// ui_hit_flags_win(i, x, y) -> HB_* flags of the topmost hit box at the point (px) of
1656    /// window i in its last pass (0 = none); no state is swapped
1657    pub(crate) fn hit_flags_win(&mut self, i: u32, x: i32, y: i32) -> u32 {
1658        let i = i & (WIN_MAX as u32 - 1);
1659        if !self.uiw_drawable(i) {
1660            return 0;
1661        }
1662        let Some(p) = self.uiw_parts(i) else { return 0 };
1663        let c = &p.ctx;
1664        super::ctx::hit_find_list(&c.hits[c.hit_cur ^ 1], c.modal_prev, x, y).1
1665    }
1666
1667    /// uiw_moved_passes -> pointer moves (or leaving) in some window need a pass
1668    pub(crate) fn uiw_moved_passes(&mut self) -> bool {
1669        if self.m.win.used == 0 {
1670            // the main window only
1671            if !self.wp.ctx.moved {
1672                return false;
1673            }
1674            let r = self.needs_move_pass();
1675            self.wp.ctx.moved = false;
1676            return r;
1677        }
1678        let mut r = false;
1679        for i in 0..WIN_MAX as u32 {
1680            if !self.uiw_drawable(i) || !self.uiw_parts(i).is_some_and(|p| p.ctx.moved) {
1681                continue;
1682            }
1683            self.uiw_use(i);
1684            r |= self.needs_move_pass();
1685            self.wp.ctx.moved = false;
1686        }
1687        self.uiw_use(0);
1688        r
1689    }
1690
1691    /// uiw_resize(i, w, h): the window's client area got this size (E_RESIZE)
1692    pub(crate) fn uiw_resize(&mut self, app: &mut dyn super::App, i: u32, w: i32, h: i32) {
1693        if i != 0 {
1694            if i as usize >= WIN_MAX || !self.uiw_drawable(i) || w <= 0 || h <= 0 {
1695                return;
1696            }
1697            // the new client size in DIP goes into the WINRECT (the user resized it)
1698            let iu = i as usize;
1699            let dpi = self.win_dpi(i);
1700            self.uiw_use(i);
1701            let b2 = self.uiw_border() * 2;
1702            let tbh = self.m.win.recs[iu].tbh;
1703            let mut rc = self.m.win.recs[iu].rect.get();
1704            rc.w = px2dip_at(w - b2, dpi);
1705            rc.h = (px2dip_at(h - b2, dpi) - tbh).max(1);
1706            rc.flags |= WRF_CHANGED;
1707            if !self.headless {
1708                rc.flags &= !WRF_MAX;
1709                if self.uiw_plat_state(i) & plat::WST_MAXIMIZED != 0 {
1710                    rc.flags |= WRF_MAX;
1711                }
1712            }
1713            self.m.win.recs[iu].rect.set(rc);
1714            self.m.win.recs[iu].applied = rc;
1715        }
1716        self.uiw_use(i);
1717        self.resize(app, w, h);
1718        self.uiw_use(0);
1719        if i == 0 {
1720            self.ws_track(); // the normal size of the main window (winstate.rs)
1721        }
1722        self.uiw_fs_check(i); // (L7) full screen left (or entered) by the platform
1723    }
1724
1725    /// uiw_dpi(i, dpi): the window's dpi changed (E_DPI, script `scale`): full redraw; the
1726    /// main window also gets UIC_DPI, other windows are laid out again at the new dpi
1727    pub(crate) fn uiw_dpi(&mut self, i: u32, dpi: i32) {
1728        if i as usize >= WIN_MAX || !self.uiw_drawable(i) || dpi < 24 {
1729            return;
1730        }
1731        self.uiw_use(i);
1732        self.wp.rt.dpi = dpi;
1733        self.dirty_force_full();
1734        if i == 0 {
1735            self.cmd_run(UIC_DPI, dpi as u32);
1736        } else {
1737            self.m.win.recs[i as usize].applied.w = -1; // apply the rect again
1738        }
1739        self.uiw_use(0);
1740        self.invalidate();
1741    }
1742
1743    /// uiw_focus_in(i): the OS gave the window the keyboard (E_FOCUS_IN)
1744    pub(crate) fn uiw_focus_in(&mut self, i: u32) {
1745        if i as usize >= WIN_MAX || !self.uiw_drawable(i) || i == self.m.win.active {
1746            return;
1747        }
1748        self.uiw_activate(i);
1749    }
1750
1751    /// uiw_winpos(i, x, y): the platform reports the window's screen position (E_WINPOS:
1752    /// client origin of the main window, outer top-left of the others, px): kept in the
1753    /// WINRECT (main window: WRF_ABS px; others: DIP relative to the owner's client origin)
1754    pub(crate) fn uiw_winpos(&mut self, i: u32, x: i32, y: i32) {
1755        if i as usize >= WIN_MAX {
1756            return;
1757        }
1758        let iu = i as usize;
1759        if self.m.win.recs[iu].state == 0 {
1760            return;
1761        }
1762        self.m.win.recs[iu].sx = x;
1763        self.m.win.recs[iu].sy = y;
1764        let mut rc = self.m.win.recs[iu].rect.get();
1765        if i == 0 {
1766            rc.x = x;
1767            rc.y = y;
1768            rc.flags |= WRF_ABS | WRF_POS;
1769            let a = &mut self.m.win.recs[0].applied;
1770            a.flags = rc.flags;
1771            a.x = x;
1772            a.y = y;
1773        } else {
1774            // owner client origin on the screen
1775            let (ox, oy) = self.uiw_owner_origin(i);
1776            let odpi = self.win_dpi(self.m.win.recs[iu].owner);
1777            rc.x = px2dip_at(x - ox, odpi);
1778            rc.y = px2dip_at(y - oy, odpi);
1779            rc.flags |= WRF_POS | WRF_CHANGED;
1780            let r = &mut self.m.win.recs[iu];
1781            r.applied.x = rc.x;
1782            r.applied.y = rc.y;
1783            r.applied.flags = rc.flags;
1784            r.state &= !UWI_DEFPOS;
1785        }
1786        self.m.win.recs[iu].rect.set(rc);
1787        if i == 0 {
1788            self.ws_track(); // the normal position of the main window (winstate.rs)
1789        }
1790        // windows it owns stay where they are: their offsets change
1791        self.uiw_follow_owner(i);
1792    }
1793
1794    /// uiw_close_request(i): the user wants to close the window (E_CLOSE, close button):
1795    /// App::command(UIC_WINCLOSE, id); false closes it (uiw_cmd_done)
1796    pub(crate) fn uiw_close_request(&mut self, i: u32) {
1797        if i == 0 || i as usize >= WIN_MAX {
1798            return;
1799        }
1800        let r = &self.m.win.recs[i as usize];
1801        if r.id == 0 || r.flags & WF_NOCLOSE != 0 {
1802            return;
1803        }
1804        let id = r.id;
1805        self.cmd_run(UIC_WINCLOSE, id);
1806        self.invalidate();
1807    }
1808
1809    /// uiw_cmd_done(id, arg, result): after App::command: UIC_WINCLOSE that the app did not
1810    /// handle (false) closes the window: *open = 0, hidden
1811    pub(crate) fn uiw_cmd_done(&mut self, id: u32, arg: u32, r: bool) {
1812        if id != UIC_WINCLOSE || r {
1813            return;
1814        }
1815        let i = self.uiw_index(arg);
1816        if i <= 0 {
1817            return;
1818        }
1819        let rec = &self.m.win.recs[i as usize];
1820        if let Some(o) = &rec.openp {
1821            o.set(0);
1822        }
1823        if rec.state & UWI_VISIBLE != 0 {
1824            self.uiw_hide(i as u32);
1825        }
1826    }
1827
1828    // ------------------------------------------------------------------ app API
1829
1830    /// ui_window_place(id, &rect): set the window's placement (copied into its WINRECT;
1831    /// applied when it is built next).  id 0: the main window (headless: its size)
1832    pub fn window_place(&mut self, id: u32, rect: &WinRect) {
1833        let i = self.uiw_index(id);
1834        if i < 0 {
1835            return;
1836        }
1837        let iu = i as usize;
1838        self.m.win.recs[iu].state &= !UWI_DEFPOS;
1839        self.m.win.recs[iu].rect.set(*rect);
1840        if i == 0 {
1841            // main window: the client size (headless), the OS window (uwp_rect)
1842            if !self.headless {
1843                self.m.win.recs[0].want |= UWW_RECT;
1844            } else if rect.w > 0 && rect.h > 0 {
1845                let (w, h) = (self.dp(rect.w), self.dp(rect.h));
1846                // ui_resize without App::frame (the asm's resize runs the passes only in
1847                // the script runner; ui_resize itself sets need)
1848                self.resize_noapp(w, h);
1849            }
1850        }
1851        self.invalidate();
1852    }
1853
1854    /// ui_window_snap(id, area, dist): while the user moves window id, its outer edges snap
1855    /// to the edges of `area` (x, y, w, h in DIP of the owner's client area, like its
1856    /// WINRECT) once they are at most `dist` DIP away -- both axes at a corner; dist 0 turns
1857    /// it off.  Live where the platform lets the UI see the move (Windows: WM_MOVING) and for
1858    /// scripts (movewin); a window manager that moves the windows itself (X11, Wayland) and
1859    /// the browser do not snap.  Call it in every frame the area may change (after the
1860    /// window's first window_begin).
1861    pub fn window_snap(&mut self, id: u32, area: [i32; 4], dist: i32) {
1862        let i = self.uiw_index(id);
1863        if i <= 0 {
1864            return;
1865        }
1866        let iu = i as usize;
1867        let s = [area[0], area[1], area[2], area[3], dist.max(0)];
1868        self.m.win.recs[iu].snap = s;
1869        if self.headless {
1870            return;
1871        }
1872        // px from the owner's platform client origin: below a library title bar of the owner
1873        // (uiw_owner_origin)
1874        let owner = self.m.win.recs[iu].owner;
1875        let dpi = self.win_dpi(owner);
1876        let mut p = [0; 6];
1877        if s[4] != 0 && dpi >= 24 {
1878            let tb = if owner != 0 { dip2px_at(self.m.win.recs[(owner as usize) & (WIN_MAX - 1)].tbh, dpi) } else { 0 };
1879            p = [owner as i32, dip2px_at(s[0], dpi), tb + dip2px_at(s[1], dpi), dip2px_at(s[2], dpi), dip2px_at(s[3], dpi), dip2px_at(s[4], dpi)];
1880        }
1881        PUBLISHED.lock().snap[iu] = p;
1882    }
1883
1884    /// uiw_snap_dip(i, x, y) -> (x, y): a user move of window i to x, y (DIP of the owner)
1885    /// snapped like the platform's move hook does it (window_snap; scripts: movewin)
1886    pub(crate) fn uiw_snap_dip(&self, i: u32, x: i32, y: i32) -> (i32, i32) {
1887        let iu = (i as usize) & (WIN_MAX - 1);
1888        let r = &self.m.win.recs[iu];
1889        let s = r.snap;
1890        if s[4] == 0 {
1891            return (x, y);
1892        }
1893        // the outer size: the framebuffer (title bar, border), before it exists the WINRECT
1894        let rc = r.rect.get();
1895        let (fb, dpi) = (self.win_fb(i), self.win_dpi(i));
1896        let (w, h) = if fb.w > 0 && dpi >= 24 { (px2dip_at(fb.w, dpi), px2dip_at(fb.h, dpi)) } else { (rc.w, rc.h + r.tbh) };
1897        snap_to(x, y, w, h, [s[0], s[1], s[2], s[3]], s[4])
1898    }
1899
1900    /// ui_window_rect(id) -> the window's placement (None = unknown window)
1901    pub fn window_rect(&self, id: u32) -> Option<WinRect> {
1902        let i = self.uiw_index(id);
1903        if i < 0 {
1904            return None;
1905        }
1906        Some(self.m.win.recs[i as usize].rect.get())
1907    }
1908
1909    /// ui_window_state(id) -> UWS_OPEN | UWS_VISIBLE | UWS_ACTIVE | UWS_MINIMIZED |
1910    /// UWS_FULLSCREEN (0 unknown)
1911    pub fn window_state(&mut self, id: u32) -> u32 {
1912        let i = self.uiw_index(id);
1913        if i < 0 {
1914            return 0;
1915        }
1916        let i = i as u32;
1917        let st = self.m.win.recs[i as usize].state;
1918        let mut s = 0;
1919        if st & UWI_OPEN != 0 {
1920            s |= UWS_OPEN;
1921        }
1922        if st & (UWI_VISIBLE | UWI_CREATED) != UWI_VISIBLE | UWI_CREATED {
1923            return s;
1924        }
1925        s |= UWS_VISIBLE;
1926        if i == self.m.win.active {
1927            s |= UWS_ACTIVE;
1928        }
1929        if self.uiw_plat_state(i) & plat::WST_MINIMIZED != 0 {
1930            s |= UWS_MINIMIZED;
1931        }
1932        if self.m.win.recs[i as usize].fs {
1933            s |= UWS_FULLSCREEN;
1934        }
1935        s
1936    }
1937
1938    /// (L7) window_fullscreen(id, on): show the window (0 = the main window, else the app's
1939    /// window id) over its whole monitor without a frame, or back to its place and frame as
1940    /// before.  window_state reports UWS_FULLSCREEN at once; the platform follows after the
1941    /// frame: Windows a borderless window over the window's monitor (the old placement and
1942    /// style come back), X11 _NET_WM_STATE_FULLSCREEN, Wayland xdg_toplevel set_fullscreen /
1943    /// unset_fullscreen, the browser its Fullscreen API (only from the user's gesture: a key or
1944    /// a click that just happened).  A tool window drops its library title bar and border
1945    /// meanwhile.  When the platform or the user leaves (or enters) full screen by itself (Esc
1946    /// in the browser, the window manager), the state follows and UIC_FULLSCREEN arrives (arg
1947    /// = the window's id).  Headless runs only record it (scripts: `expect window ID
1948    /// fullscreen`, `system fullscreen off`).  Nothing happens on Android and for Wayland tool
1949    /// windows drawn inside their owner (WST_CHILD).  F11 is the app's key for it (GUIDE).
1950    pub fn window_fullscreen(&mut self, id: u32, on: bool) {
1951        let i = self.uiw_index(id);
1952        if i < 0 {
1953            return;
1954        }
1955        let iu = i as usize;
1956        if self.m.win.recs[iu].fs == on {
1957            return;
1958        }
1959        if on && self.uiw_os() && self.uiw_plat_state(i as u32) & plat::WST_CHILD != 0 {
1960            return; // a subsurface of its owner: no full screen of its own
1961        }
1962        if cfg!(any(target_os = "android", target_os = "ios")) {
1963            return; // the app has the whole screen already
1964        }
1965        self.m.win.recs[iu].fs = on;
1966        self.m.win.recs[iu].want |= UWW_FS;
1967        self.invalidate();
1968    }
1969
1970    /// ui_window_activate(id): give the window the keyboard and put it on top
1971    pub fn window_activate(&mut self, id: u32) {
1972        let i = self.uiw_index(id);
1973        if i < 0 || !self.uiw_drawable(i as u32) {
1974            return;
1975        }
1976        self.uiw_activate(i as u32);
1977    }
1978
1979    /// ui_window_destroy(id): hide the window and free its state and buffers (not while it
1980    /// is being built)
1981    pub fn window_destroy(&mut self, id: u32) {
1982        let i = self.uiw_index(id);
1983        if i <= 0 {
1984            return;
1985        }
1986        let i = i as u32;
1987        let iu = i as usize;
1988        if self.m.win.recs[iu].state & UWI_BUILDING != 0 || i == self.uiw_cur {
1989            self.warn("ui_window_destroy: the window is being built");
1990            return;
1991        }
1992        if self.m.win.recs[iu].state & UWI_VISIBLE != 0 {
1993            self.uiw_hide(i);
1994        }
1995        if self.m.win.recs[iu].state & UWI_CREATED != 0 {
1996            self.m.win.recs[iu].save = None; // its WinParts (command lists, dirty grid, fb)
1997            if self.m.win.hooks && !self.headless {
1998                self.uwp_destroy(i);
1999            }
2000        }
2001        self.m.win.used &= !(1 << i);
2002        self.m.win.recs[iu] = Rec::default();
2003        if !self.headless {
2004            PUBLISHED.lock().snap[iu] = [0; 6];
2005        }
2006        self.uiw_modal_update();
2007        self.publish_state();
2008    }
2009
2010    /// ui_window_current -> id of the window being built (0 = main window)
2011    pub fn window_current(&self) -> u32 {
2012        self.uiw_rec_id(self.uiw_cur)
2013    }
2014
2015    /// ui_window_modal -> id of the topmost modal window, 0 = none
2016    pub fn window_modal(&self) -> u32 {
2017        let m = self.m.win.modal;
2018        if m == 0 { 0 } else { self.uiw_rec_id(m) }
2019    }
2020
2021    // ui_resize without the passes a headless ui_resize runs (no App here): a new
2022    // framebuffer size of the live window; the frame is built at the next opportunity
2023    fn resize_noapp(&mut self, w: i32, h: i32) {
2024        if w <= 0 || h <= 0 || (w == self.wp.rt.fb.w && h == self.wp.rt.fb.h) {
2025            return;
2026        }
2027        self.wp.rt.fb.resize(w, h);
2028        self.dirty_force_full();
2029        self.need = true;
2030    }
2031
2032    // ------------------------------------------------------------------ automation helpers
2033
2034    /// uiw_desk_pos(i) -> outer top-left of window i on the virtual desktop (DIP; the main
2035    /// window's client origin is 0, 0)
2036    pub(crate) fn uiw_desk_pos(&self, i: u32) -> (i32, i32) {
2037        let (mut x, mut y) = (0, 0);
2038        let mut b = i;
2039        for _ in 0..WIN_MAX {
2040            if b == 0 {
2041                break;
2042            }
2043            let r = &self.m.win.recs[(b as usize) & (WIN_MAX - 1)];
2044            let rc = r.rect.get();
2045            x += rc.x;
2046            y += rc.y;
2047            b = r.owner;
2048            if b == 0 {
2049                break;
2050            }
2051            // the owner's client starts below its title bar
2052            y += self.m.win.recs[(b as usize) & (WIN_MAX - 1)].tbh;
2053        }
2054        (x, y)
2055    }
2056
2057    /// uiw_user_rect(i, x, y, w, h): the user moved (w = 0) or resized (x = i32::MIN) the
2058    /// window: WINRECT updated with WRF_CHANGED, the framebuffer follows (headless)
2059    pub(crate) fn uiw_user_rect(&mut self, i: u32, x: i32, y: i32, w: i32, h: i32) {
2060        if i == 0 || i as usize >= WIN_MAX {
2061            return;
2062        }
2063        let iu = i as usize;
2064        let mut rc = self.m.win.recs[iu].rect.get();
2065        if x != i32::MIN {
2066            rc.x = x;
2067            rc.y = y;
2068            rc.flags |= WRF_POS | WRF_CHANGED;
2069            self.m.win.recs[iu].state &= !UWI_DEFPOS;
2070        }
2071        if w > 0 {
2072            rc.w = w;
2073            if h > 0 {
2074                rc.h = h;
2075                rc.flags |= WRF_CHANGED;
2076            }
2077        }
2078        self.m.win.recs[iu].rect.set(rc);
2079        self.m.win.recs[iu].want |= UWW_RECT;
2080        self.invalidate();
2081    }
2082
2083    // ------------------------------------------------------------------ OS windows (platform glue)
2084    // The UI windows become plat::win_* windows (the asm's uiw_plat_hooks table, set by the
2085    // runtime when it runs with a display).  UI index i and platform window index may
2086    // differ: u2p / p2u map them (0xFF = none); events are translated in process_events.
2087    // Positions: a WINRECT offset (DIP of the owner) is added to the owner's client origin
2088    // (its platform client origin + its library title bar).
2089
2090    /// uiw_index + uiw_pw: (UI index of app window id (-1 none, 0 the main window), its
2091    /// platform window (-1 none))  // pn-core
2092    pub fn window_pw(&self, id: u32) -> (i32, i32) {
2093        let i = self.uiw_index(id);
2094        if i < 0 {
2095            return (i, -1);
2096        }
2097        (i, self.uiw_pw(i as u32))
2098    }
2099
2100    /// uiw_pw(i) -> platform window of UI window i (-1 = none)
2101    pub(crate) fn uiw_pw(&self, i: u32) -> i32 {
2102        let p = self.m.win.u2p[(i as usize) & (WIN_MAX - 1)];
2103        if p == 0xFF { -1 } else { p as i32 }
2104    }
2105
2106    /// uiw_from_pw(pw) -> UI window of platform window pw (-1 = none)
2107    pub(crate) fn uiw_from_pw(&self, pw: i32) -> i32 {
2108        if !(0..WIN_MAX as i32).contains(&pw) {
2109            return -1;
2110        }
2111        let u = self.m.win.p2u[pw as usize];
2112        if u == 0xFF { -1 } else { u as i32 }
2113    }
2114
2115    // uiw_os: the windows are OS windows (a display, hooks set)
2116    fn uiw_os(&self) -> bool {
2117        !self.headless && self.m.win.hooks
2118    }
2119
2120    /// uiw_plat_state(i) -> WST_* of the window's OS window (0: headless / none)
2121    pub(crate) fn uiw_plat_state(&self, i: u32) -> u32 {
2122        if !self.uiw_os() {
2123            return 0;
2124        }
2125        let pw = self.uiw_pw(i);
2126        if pw < 0 { 0 } else { plat::win_state(pw) }
2127    }
2128
2129    /// uiw_app_active: the app is active (headless: always)
2130    pub(crate) fn uiw_app_active(&self) -> bool {
2131        if self.headless {
2132            return true;
2133        }
2134        plat::win_state(0) & plat::WST_ACTIVE != 0
2135    }
2136
2137    // uiw_sysframe_ok: WF_SYSFRAME gets a system frame (not: Wayland without server-side
2138    // decorations, WST_CSD)
2139    fn uiw_sysframe_ok(&self) -> bool {
2140        if !self.uiw_os() {
2141            return true;
2142        }
2143        plat::win_state(0) & plat::WST_CSD == 0
2144    }
2145
2146    // uiw_owner_origin(i) -> screen position (px) of the client origin of the window's
2147    // owner as the UI sees it (below a library title bar); OS windows ask the platform,
2148    // headless runs use the last reported position.  origin_ok = false when the platform
2149    // does not know it (Wayland; X11 before the window manager placed it).  A child window
2150    // (WST_CHILD: a Wayland subsurface) is placed relative to its owner: origin 0, 0
2151    fn uiw_owner_origin(&mut self, i: u32) -> (i32, i32) {
2152        let owner = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].owner;
2153        let ou = (owner as usize) & (WIN_MAX - 1);
2154        let (mut x, mut y) = (self.m.win.recs[ou].sx, self.m.win.recs[ou].sy);
2155        self.m.win.origin_ok = true;
2156        if self.uiw_os() {
2157            self.m.win.origin_ok = false;
2158            let own_pw = self.uiw_pw(i);
2159            let pw = self.uiw_pw(owner);
2160            if own_pw >= 0 && plat::win_state(own_pw) & plat::WST_CHILD != 0 {
2161                self.m.win.origin_ok = true;
2162                x = 0;
2163                y = 0;
2164            } else if pw >= 0 {
2165                if let Some(r) = plat::win_get_rect(pw) {
2166                    self.m.win.origin_ok = true;
2167                    x = r[0];
2168                    y = r[1];
2169                }
2170            }
2171        }
2172        if owner != 0 {
2173            // the owner's client starts below its title bar
2174            let dpi = self.win_dpi(owner);
2175            y += dip2px_at(self.m.win.recs[ou].tbh, dpi);
2176        }
2177        (x, y)
2178    }
2179
2180    // uiw_attached(i): the centre of the window (its WINRECT) lies inside the client area
2181    // of its owner: such a window is docked to the owner and moves with it (paint.net
2182    // style); a window dragged out of its owner stays where it is on the screen
2183    fn uiw_attached(&self, i: u32) -> bool {
2184        let r = &self.m.win.recs[(i as usize) & (WIN_MAX - 1)];
2185        let rc = r.rect.get();
2186        let owner = r.owner;
2187        let odpi = self.win_dpi(owner);
2188        if odpi < 24 {
2189            return false;
2190        }
2191        let fb = self.win_fb(owner);
2192        let ow = px2dip_at(fb.w, odpi); // owner client width (DIP)
2193        let oh = px2dip_at(fb.h, odpi) - self.m.win.recs[(owner as usize) & (WIN_MAX - 1)].tbh;
2194        let cx = (rc.w >> 1) + rc.x; // centre (DIP from the owner's client origin)
2195        if cx < 0 || cx >= ow {
2196            return false;
2197        }
2198        let cy = ((rc.h + r.tbh) >> 1) + rc.y;
2199        cy >= 0 && cy < oh
2200    }
2201
2202    // uiw_follow_owner(i): window i moved: the windows it owns that are docked to it
2203    // (uiw_attached) keep their WINRECT offsets and are moved along; the others stay where
2204    // they are on the screen (Windows, X11), so their offsets are taken again from the
2205    // platform
2206    fn uiw_follow_owner(&mut self, i: u32) {
2207        if !self.uiw_os() || self.m.win.follow as usize >= WIN_MAX {
2208            return; // (ownership has no cycles; a guard)
2209        }
2210        self.m.win.follow += 1;
2211        for j in 1..WIN_MAX as u32 {
2212            if j == i {
2213                continue;
2214            }
2215            let r = &self.m.win.recs[j as usize];
2216            if r.id == 0 || r.owner != i || !self.uiw_drawable(j) {
2217                continue;
2218            }
2219            if (self.m.win.placed >> j) & 1 == 0 || self.uiw_attached(j) {
2220                // its owner's position is known now (or it is docked): the WINRECT
2221                // position is applied (Win32 already moved it live during the drag: a
2222                // no-op then)
2223                self.m.win.recs[j as usize].want |= UWW_RECT;
2224                self.invalidate();
2225                continue;
2226            }
2227            let pw = self.uiw_pw(j);
2228            if pw < 0 {
2229                continue;
2230            }
2231            if let Some(r) = plat::win_get_rect(pw) {
2232                self.uiw_winpos(j, r[0], r[1]);
2233            }
2234        }
2235        self.m.win.follow -= 1;
2236    }
2237
2238    // uiw_enable_sync(phase): OS windows follow the inert mask: phase 1 enables windows
2239    // that are no longer inert (before a modal window disappears), phase 2 disables inert
2240    // windows (after a new modal window was shown)
2241    fn uiw_enable_sync(&mut self, phase: u32) {
2242        if !self.m.win.hooks {
2243            return;
2244        }
2245        for i in 0..WIN_MAX as u32 {
2246            if self.uiw_pw(i) < 0 {
2247                continue;
2248            }
2249            let want = (self.m.win.inert >> i) & 1 == 0; // enabled
2250            let now = (self.m.win.disabled >> i) & 1 == 0;
2251            if want == now {
2252                continue;
2253            }
2254            if phase == 1 {
2255                if !want {
2256                    continue; // phase 1: enabling only
2257                }
2258                self.m.win.disabled &= !(1 << i);
2259            } else {
2260                if want {
2261                    continue; // phase 2: disabling only
2262                }
2263                self.m.win.disabled |= 1 << i;
2264            }
2265            self.uwp_enable(i, want);
2266        }
2267    }
2268
2269    // ---- the hook table: UI window index -> plat::win_*
2270
2271    // uwp_create(i, flags, owner) -> ok: the OS window (hidden) for a new UI window, as
2272    // large as its framebuffer
2273    fn uwp_create(&mut self, i: u32, flags: u32, owner: u32) -> bool {
2274        let i = i & (WIN_MAX as u32 - 1);
2275        let opw = self.uiw_pw(owner);
2276        if opw < 0 {
2277            return false; // the owner has no OS window
2278        }
2279        let dpi = self.win_dpi(i);
2280        let fb = self.win_fb(i);
2281        let (w, h) = (px2dip_at(fb.w, dpi), px2dip_at(fb.h, dpi));
2282        let title = self.m.win.recs[i as usize].title.clone();
2283        // (E7) an app window: a top-level window of its own where the platform has more than one
2284        // (Windows, X11, Wayland), else an owned one (macOS)
2285        let mut pw = -1;
2286        if flags & WF_APP != 0 && !cfg!(any(target_arch = "wasm32", target_os = "android", target_os = "ios")) {
2287            pw = plat::win_create(&title, w, h, flags & plat::PWF_MASK, plat::PW_NOOWNER, plat::PW_DEFAULT, plat::PW_DEFAULT);
2288        }
2289        if pw < 0 {
2290            pw = plat::win_create(&title, w, h, flags & plat::PWF_MASK, opw, plat::PW_DEFAULT, plat::PW_DEFAULT);
2291        }
2292        if pw < 0 || pw as usize >= WIN_MAX {
2293            return false;
2294        }
2295        self.m.win.u2p[i as usize] = pw as u8;
2296        self.m.win.p2u[pw as usize] = i as u8;
2297        self.m.win.disabled &= !(1 << i);
2298        if flags & WF_RESIZABLE != 0 {
2299            let tbh = self.m.win.recs[i as usize].tbh;
2300            plat::win_set_min_size(pw, 120, tbh + 48);
2301        }
2302        self.publish_state();
2303        // it may be on a monitor with another dpi
2304        let d = plat::win_dpi(pw);
2305        if d != dpi {
2306            self.uiw_dpi(i, d);
2307        }
2308        true
2309    }
2310
2311    // uwp_destroy(i): close its OS window
2312    fn uwp_destroy(&mut self, i: u32) {
2313        let i = i & (WIN_MAX as u32 - 1);
2314        let pw = self.uiw_pw(i);
2315        if pw < 0 {
2316            return;
2317        }
2318        self.gp_window_gone(i); // its swapchain before its window
2319        let pw = pw as usize & (WIN_MAX - 1);
2320        self.m.win.p2u[pw] = 0xFF;
2321        self.m.win.u2p[i as usize] = 0xFF;
2322        self.m.win.disabled &= !(1 << i);
2323        self.m.win.placed &= !(1 << i);
2324        self.publish_state();
2325        plat::win_destroy(pw as i32);
2326    }
2327
2328    // uwp_show(i, show): tool / WF_NOACTIVATE windows appear without taking the keyboard
2329    fn uwp_show(&mut self, i: u32, show: bool) {
2330        let pw = self.uiw_pw(i);
2331        if pw < 0 {
2332            return;
2333        }
2334        self.gp_window_show(i, show);
2335        let mut mode = plat::PWS_HIDE;
2336        if show {
2337            mode = plat::PWS_SHOW;
2338            if self.m.win.recs[(i as usize) & (WIN_MAX - 1)].flags & (WF_TOOL | WF_NOACTIVATE) != 0 {
2339                mode = plat::PWS_SHOWNA;
2340            }
2341        }
2342        plat::win_show(pw, mode);
2343    }
2344
2345    // uwp_rect(i, &rect): place / size the OS window: secondary windows at the owner's
2346    // client origin + the offset (when WRF_POS), as large as the framebuffer; the main
2347    // window (window_place(0)): screen px position, DIP client size, WRF_MAX
2348    fn uwp_rect(&mut self, i: u32, rc: &WinRect) {
2349        let pw = self.uiw_pw(i);
2350        if pw < 0 {
2351            return;
2352        }
2353        if i != 0 {
2354            let (fw, fh) = {
2355                let fb = self.win_fb(i);
2356                (fb.w, fb.h)
2357            };
2358            let (mut x, mut y) = (plat::PW_KEEP, plat::PW_KEEP);
2359            if rc.flags & WRF_POS != 0 {
2360                let owner = self.m.win.recs[(i as usize) & (WIN_MAX - 1)].owner;
2361                let odpi = self.win_dpi(owner);
2362                let (ox, oy) = self.uiw_owner_origin(i);
2363                if self.m.win.origin_ok {
2364                    self.m.win.placed |= 1 << i;
2365                    x = ox + dip2px_at(rc.x, odpi);
2366                    y = oy + dip2px_at(rc.y, odpi);
2367                }
2368                // not known yet: placed when it is (uiw_winpos of the owner)
2369            }
2370            plat::win_set_rect(pw, x, y, fw, fh);
2371            return;
2372        }
2373        let dpi = self.win_dpi(0);
2374        let st = plat::win_state(pw);
2375        if rc.flags & WRF_MAX != 0 {
2376            if st & plat::WST_MAXIMIZED == 0 {
2377                plat::win_cmd(pw, plat::WCMD_MAXIMIZE);
2378            }
2379            return;
2380        }
2381        if st & plat::WST_MAXIMIZED != 0 {
2382            plat::win_cmd(pw, plat::WCMD_RESTORE);
2383        }
2384        let (mut x, mut y) = (plat::PW_KEEP, plat::PW_KEEP);
2385        if rc.flags & WRF_POS != 0 {
2386            x = rc.x;
2387            y = rc.y;
2388        }
2389        let w = if rc.w > 0 { dip2px_at(rc.w, dpi) } else { plat::PW_KEEP };
2390        let h = if rc.h > 0 { dip2px_at(rc.h, dpi) } else { plat::PW_KEEP };
2391        plat::win_set_rect(pw, x, y, w, h);
2392        // a size change of our own is not reported back as E_RESIZE (Win32 WW_SELF): take
2393        // the client size the platform has now (U3)
2394        if w == plat::PW_KEEP && h == plat::PW_KEEP {
2395            return;
2396        }
2397        let (cw, ch) = plat::win_size(pw);
2398        if cw <= 0 || ch <= 0 {
2399            return;
2400        }
2401        self.resize_noapp(cw, ch); // (the main window is live here)
2402    }
2403
2404    // uwp_title(i, s)
2405    fn uwp_title(&mut self, i: u32, s: &str) {
2406        let pw = self.uiw_pw(i);
2407        if pw >= 0 {
2408            plat::win_set_title(pw, s);
2409        }
2410    }
2411
2412    // uwp_activate(i)
2413    fn uwp_activate(&mut self, i: u32) {
2414        let pw = self.uiw_pw(i);
2415        if pw >= 0 {
2416            plat::win_activate(pw);
2417        }
2418    }
2419
2420    // uwp_enable(i, on)
2421    fn uwp_enable(&mut self, i: u32, on: bool) {
2422        let pw = self.uiw_pw(i);
2423        if pw >= 0 {
2424            plat::win_enable(pw, on);
2425        }
2426    }
2427
2428    // ------------------------------------------------------------------ popup windows (U3, §6)
2429    // Popups (flyouts, menus, combo lists, the calendar, context menus) and tooltips of every
2430    // window may leave its framebuffer: the popup code's bounds ask uiw_popup_area for the
2431    // screen (OS windows: the work area of the monitor; flyouts flip above / to the left
2432    // where it ends) or the virtual desktop (headless: the script's `screen`, without one the
2433    // main window's client area -- the main window's popups then stay inside).  Every such
2434    // item gets a slot after the owner's render: a surface with the owner's popup and tooltip
2435    // layers replayed moved (render_popup).  With OS windows the platform shows it as a
2436    // borderless, non-activating popup window over the owner (plat::popup); its input comes
2437    // back as events of the owner (owner coordinates), so hit tests, focus and light dismiss
2438    // work as inside the window.  Wayland does not tell where a window is: popups that fit
2439    // stay inside it, the others get the compositor's placement (plat::popup_anchor: below the
2440    // anchor, flipped when there is no room) and the UI lays them out where it put them
2441    // (uiw_popup_nudge).  Headless runs composite the slots on the virtual desktop (`shot
2442    // desktop`, uiw_popups_blit).
2443
2444    /// uiw_popup_area -> Some((x0, y0, x1, y1), known) (px, coordinates of the live window) when
2445    /// the live window's popups may leave it, else None.  known false: the platform does not
2446    /// tell where the window is (Wayland) -- a wide area that the compositor keeps on screen
2447    pub(crate) fn uiw_popup_area(&mut self) -> Option<([i32; 4], bool)> {
2448        let cur = self.uiw_cur;
2449        if cur as usize >= WIN_MAX {
2450            return None;
2451        }
2452        let mut a = [0i32; 4];
2453        let mut known = true;
2454        if self.uiw_os() {
2455            // OS windows: the work area of the monitor, relative to the window
2456            let pw = self.uiw_pw(cur);
2457            if pw < 0 {
2458                return None;
2459            }
2460            match (plat::win_get_rect(pw), plat::win_work_area(pw)) {
2461                (Some(wr), Some(wa)) => {
2462                    a[0] = wa[0] - wr[0];
2463                    a[2] = a[0] + wa[2];
2464                    a[1] = wa[1] - wr[1];
2465                    a[3] = a[1] + wa[3];
2466                }
2467                _ => {
2468                    // position unknown (Wayland): wide; the compositor keeps the popup on screen
2469                    a = [-4096, -4096, self.wp.rt.fb.w + 4096, self.wp.rt.fb.h + 4096];
2470                    known = false;
2471                }
2472            }
2473        } else {
2474            // headless: the virtual screen, else the main window's client (DIP)
2475            let scr = self.m.win.screen;
2476            let [sx, sy, sw, sh] = if scr[2] > 0 && scr[3] > 0 {
2477                scr
2478            } else if cur == 0 {
2479                return None;
2480            } else {
2481                let mdpi = self.win_dpi(0);
2482                let fb = self.win_fb(0);
2483                [0, 0, px2dip_at(fb.w, mdpi), px2dip_at(fb.h, mdpi)]
2484            };
2485            let (wx, wy) = self.uiw_desk_pos(cur); // outer top-left of the window (DIP)
2486            let dpi = self.wp.rt.dpi;
2487            a[0] = dip2px_at(sx - wx, dpi);
2488            a[1] = dip2px_at(sy - wy, dpi);
2489            a[2] = dip2px_at(sx + sw - wx, dpi);
2490            a[3] = dip2px_at(sy + sh - wy, dpi);
2491        }
2492        // never less than the window itself
2493        a[0] = a[0].min(0);
2494        a[1] = a[1].min(0);
2495        a[2] = a[2].max(self.wp.rt.fb.w);
2496        a[3] = a[3].max(self.wp.rt.fb.h);
2497        Some((a, known))
2498    }
2499
2500    /// uiw_popup_screen_h -> the tallest popup (px of the live window) that leaves the window
2501    /// where the platform does not tell where the window is (Wayland): the lowest output's
2502    /// height less POPUP_PANEL_ROOM for panels (the compositor slides the popup on screen, so a
2503    /// longer one scrolls), never less than the window; None where the area is known
2504    pub(crate) fn uiw_popup_screen_h(&mut self) -> Option<i32> {
2505        if !self.uiw_os() || self.uiw_cur as usize >= WIN_MAX {
2506            return None;
2507        }
2508        let pw = self.uiw_pw(self.uiw_cur);
2509        if pw < 0 || (plat::win_get_rect(pw).is_some() && plat::win_work_area(pw).is_some()) {
2510            return None;
2511        }
2512        // monitor units are the compositor's logical units: DIP of the window's scale
2513        let h = plat::monitors().iter().map(|m| m.work[3]).filter(|&h| h > 0).min()?;
2514        Some(dip2px_at((h - POPUP_PANEL_ROOM).max(h / 2), self.wp.rt.dpi).max(self.wp.rt.fb.h))
2515    }
2516
2517    /// uiw_popup_nudge(item, key) -> (dx, dy) px: how far the platform moved item `item` of the
2518    /// live window from its natural place, computed as key (x, y, physical px) -- Wayland
2519    /// compositors flip / slide popups to keep them on the screen; the popup is then laid out
2520    /// there.  (0, 0): not moved, or the platform has not placed it yet
2521    pub(crate) fn uiw_popup_nudge(&self, item: u32, key: [i32; 2]) -> (i32, i32) {
2522        if !self.uiw_os() {
2523            return (0, 0);
2524        }
2525        let own = self.uiw_cur + 1;
2526        let Some(k) = (0..EXT_MAX).find(|&k| {
2527            let e = &self.m.win.ex[k];
2528            e.owner == own && e.item == item && e.shown && e.key == key
2529        }) else {
2530            return (0, 0);
2531        };
2532        let nat = self.m.win.ex[k].nat;
2533        match plat::popup_placed(k as u32) {
2534            Some(p) => (p[0] - nat[0], p[1] - nat[1]),
2535            None => (0, 0),
2536        }
2537    }
2538
2539    /// uiw_popups_render(i): after the render of window i (live): the slots of its items
2540    /// outside the framebuffer, re-rendered when the window changed or the item moved;
2541    /// shown / moved / presented by the platform (OS windows), slots no longer used hidden.
2542    /// The main window (index 0): while a secondary window is shown, EVERY popup gets a
2543    /// slot, so menus and lists of the main window stay above the tool windows (headless:
2544    /// `shot desktop` blits them last).
2545    pub(crate) fn uiw_popups_render(&mut self, i: u32) {
2546        if i as usize >= WIN_MAX {
2547            return;
2548        }
2549        // the main window: every popup while a secondary window is shown, else (§6) those
2550        // outside the window
2551        let all = i == 0 && (1..WIN_MAX as u32).any(|j| self.uiw_drawable(j));
2552        let own = i + 1;
2553        // nothing used yet this round
2554        for e in self.m.win.ex.iter_mut() {
2555            if e.owner == own {
2556                e.used = false;
2557            }
2558        }
2559        for item in 0..POPUP_ITEMS {
2560            let rect = if all { self.popup_rect(item) } else { self.popup_ext(item) };
2561            let Some(rect) = rect else { continue };
2562            // the slot of this item, else a free one
2563            let k = match (0..EXT_MAX).find(|&k| self.m.win.ex[k].owner == own && self.m.win.ex[k].item == item) {
2564                Some(k) => k,
2565                None => match (0..EXT_MAX).find(|&k| self.m.win.ex[k].owner == 0) {
2566                    Some(k) => {
2567                        let e = &mut self.m.win.ex[k];
2568                        e.owner = own;
2569                        e.item = item;
2570                        e.shown = false;
2571                        e.rect[2] = 0;
2572                        k
2573                    }
2574                    None => {
2575                        self.limit_hit(super::ctx::LIM_POPUP_SLOTS); // all slots taken: drawn clipped
2576                        continue;
2577                    }
2578                },
2579            };
2580            // changed: the window rendered something, or the item moved / resized / is new
2581            let mut changed = self.wp.rt.present_n != 0;
2582            {
2583                let e = &mut self.m.win.ex[k];
2584                e.used = true;
2585                if e.rect != rect {
2586                    changed = true;
2587                    e.rect = rect;
2588                }
2589                if !e.shown {
2590                    changed = true;
2591                }
2592            }
2593            if !changed {
2594                continue;
2595            }
2596            // the surface: popup + tooltip layers of the owner, moved
2597            let mut sf = core::mem::take(&mut self.m.win.ex[k].sf);
2598            if sf.w != rect[2] || sf.h != rect[3] || sf.px.is_empty() {
2599                sf = Surface::new(rect[2], rect[3], FMT_XRGB);
2600                if sf.px.is_empty() {
2601                    self.m.win.ex[k].owner = 0;
2602                    continue;
2603                }
2604            }
2605            self.dc.begin(&sf);
2606            let c = self.color(T_FLYOUT) | 0xFF00_0000;
2607            self.dc.set_color(c);
2608            self.dc.fill(&mut sf, 0, 0, rect[2], rect[3]);
2609            self.render_popup(&mut sf, -rect[0], -rect[1]);
2610            self.dc.end();
2611            if self.uiw_os() {
2612                let pw = self.uiw_pw(i);
2613                if pw >= 0 {
2614                    // (§6) how it hangs at its anchor (Wayland places it from that)
2615                    let (anchor, kind, nat, key) =
2616                        self.popup_place(item).unwrap_or(([rect[0], rect[1], 1, 1], plat::PA_FREE, [rect[0], rect[1]], [rect[0], rect[1]]));
2617                    plat::popup_anchor(k as u32, anchor, kind, nat);
2618                    plat::popup(k as u32, pw, Some(rect), Some(&Ui::frame_of(&sf)));
2619                    let e = &mut self.m.win.ex[k];
2620                    e.shown = true;
2621                    e.nat = nat;
2622                    e.key = key;
2623                }
2624            }
2625            self.m.win.ex[k].sf = sf;
2626        }
2627        self.uiw_popups_drop_ex(i, true);
2628    }
2629
2630    /// uiw_popups_drop(i): window i is not drawn: its popup slots go away
2631    pub(crate) fn uiw_popups_drop(&mut self, i: u32) {
2632        self.uiw_popups_drop_ex(i, false);
2633    }
2634
2635    // uiw_popups_drop_ex(i, unused_only): free the slots of window i (with unused_only:
2636    // those its last render did not use); shown ones are hidden first
2637    fn uiw_popups_drop_ex(&mut self, i: u32, unused_only: bool) {
2638        let own = i + 1;
2639        for k in 0..EXT_MAX {
2640            let e = &self.m.win.ex[k];
2641            if e.owner != own || (unused_only && e.used) {
2642                continue;
2643            }
2644            if e.shown && self.uiw_os() {
2645                plat::popup(k as u32, 0, None, None); // hide
2646            }
2647            let e = &mut self.m.win.ex[k];
2648            e.sf = Surface::default();
2649            e.owner = 0;
2650            e.shown = false;
2651            e.used = false;
2652        }
2653    }
2654
2655    /// uiw_popups_blit(sf, i, x0, y0, dpi): (headless `shot desktop`) the popup slots of
2656    /// window i onto sf, a desktop whose top-left is x0, y0 (DIP)
2657    pub(crate) fn uiw_popups_blit(&mut self, sf: &mut Surface, i: u32, x0: i32, y0: i32, dpi: i32) {
2658        let own = i + 1;
2659        let (wx, wy) = self.uiw_desk_pos(i);
2660        let wdpi = self.win_dpi(i);
2661        for k in 0..EXT_MAX {
2662            let e = &self.m.win.ex[k];
2663            if e.owner != own || !e.used || e.sf.px.is_empty() {
2664                continue;
2665            }
2666            let x = dip2px_at(px2dip_at(e.rect[0], wdpi) + wx - x0, dpi);
2667            let y = dip2px_at(px2dip_at(e.rect[1], wdpi) + wy - y0, dpi);
2668            blit_copy(sf, &e.sf, x, y);
2669        }
2670    }
2671
2672    /// uiw_popups_dismiss(i): a press in window i: popups of every other window close
2673    /// (light dismiss across windows; a press on an OS popup window counts as its owner)
2674    pub(crate) fn uiw_popups_dismiss(&mut self, i: u32) {
2675        let mut swapped = false;
2676        for j in 0..WIN_MAX as u32 {
2677            if j == i || (j != 0 && !self.uiw_drawable(j)) {
2678                continue;
2679            }
2680            self.uiw_use(j);
2681            swapped = true;
2682            if self.popup_count() != 0 {
2683                self.popup_close();
2684                self.invalidate();
2685            }
2686        }
2687        if swapped {
2688            self.uiw_use(0);
2689        }
2690    }
2691}