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