Skip to main content

ui/
desk.rs

1// desk -- windows inside an area of a window: the app's programs in windows of their own
2// (title bar with icon, title and buttons, moving, resizing at every edge and corner,
3// maximising, minimising, z-order, snapping, keyboard, screen readers) and a taskbar; the
4// state is the app's DeskState
5//
6//   desk_begin(id, &mut st, wins)     the area takes the next place (without next_size it fills
7//                                     the box); wins describe every open window each frame, in
8//                                     the order of the taskbar (DeskWin: key != 0, title, icon,
9//                                     DWF_* flags, minimum and first size)
10//   desk_next(&mut st) -> Some(key)   the next window to fill, from the bottom of the z-order to
11//                                     the top (minimised ones are left out): emit its content (a
12//                                     vertical box that clips) and call it again until None
13//   desk_end(&mut st) -> Vec<DeskEv>  what the user did: close requests (the app takes the
14//                                     window out of its list, or asks first), activation, moves,
15//                                     resizes, state changes (also those of the taskbar)
16//   desk_taskbar_begin(id)            a taskbar of desk `id`: a row with the style's surface;
17//   desk_taskbar_end()                the app's own items (a start button, a clock) go before
18//                                     and after desk_taskbar.  Between desk_begin and desk_end
19//                                     without next_size / next_rect it takes the strip the
20//                                     area keeps free (DeskState::set_reserve)
21//   desk_taskbar(id, &mut st, flags)  a button per window of desk `id` (icon, title; the
22//                                     active one pressed): a click activates, a second click
23//                                     minimises, a right click (menu key, Shift+F10) opens
24//                                     Wiederherstellen / Minimieren / Maximieren / Schließen;
25//                                     what does not fit goes into a "Weitere Fenster" menu at
26//                                     the end; DTB_ICONS: icons only
27//   DeskState                         plain data: the windows' places, the z-order, maximised /
28//                                     minimised / snapped, the active one -- save() -> text,
29//                                     load(text), activate / minimize / maximize / restore /
30//                                     toggle_max / snap / cycle / set_rect / forget, order /
31//                                     active / state / rect / contains, set_reserve
32//
33// The windows: a press anywhere on one brings it to the front and makes it active (drawn as
34// active, its content gets the keys).  The title bar moves it (a maximised or snapped one comes
35// back to its size under the pointer); while it is dragged its edges stick to the area's edges,
36// and with the pointer at the area's left or right edge it snaps to that half, at the top edge
37// it maximises (a preview shows where).  Every edge and corner resizes it down to its minimum
38// size; a double click on the title bar maximises or restores it; the icon and a right click on
39// the title bar open its menu.  Esc cancels a drag.  The area keeps every window reachable: when it shrinks, at least
40// KEEP DIP of a window and its whole title bar stay inside (shown there, the place in the state
41// stays).  Maximised windows fill the area minus the reserved strip (a taskbar inside it); new
42// windows are cascaded from the top left.  Places are logical (DIP from the area's start
43// corner): a mirrored (RTL) window shows them mirrored, the title bar's buttons at the left.
44// Keyboard (while the focus is inside the area, or nowhere): Alt+F6 / Alt+Shift+F6 the next /
45// previous window (the active one goes to the back), Alt+F10 maximises / restores the active
46// one, Alt+F9 minimises it, Alt+- opens its menu.  Tab never enters the windows behind the
47// active one (their widgets are no Tab stops); each window is an F6 pane, and a focus that
48// arrives in a window behind (F6, a screen reader, focus(id)) brings it to the front.  The
49// active window gets the keyboard focus back where it was (else its first Tab stop).
50// Screen readers: each shown window is a dialog record named by its title (" (maximiert)"
51// after it when it is), the active one selected; its title bar, buttons and content are inside
52// it, whatever the windows above cover.  The taskbar is a group "Taskleiste" with a button per
53// window (" (minimiert)" after a minimised one's title, the active one pressed).
54// Structural changes (buttons, menus, keys) are applied at desk_end, followed by another pass;
55// drags change the place at once.  Widget ids are parts of the area id (pid).
56
57use alloc::string::String;
58use alloc::vec::Vec;
59
60use super::consts::*;
61use super::keys::*;
62use super::{Sp, Ui};
63
64/// DeskWin flags: no close button (the app closes the window itself)
65pub const DWF_NOCLOSE: u32 = 1;
66/// no minimise button (and no minimising by keys or the taskbar)
67pub const DWF_NOMIN: u32 = 2;
68/// no maximise button (no double click, no snapping to the top edge)
69pub const DWF_NOMAX: u32 = 4;
70/// a fixed size: no resize grips, no maximising, no snapping to a half
71pub const DWF_FIXED: u32 = 8;
72/// a new window starts maximised (an IDE)
73pub const DWF_MAXIMIZED: u32 = 16;
74/// no taskbar button
75pub const DWF_NOTASK: u32 = 32;
76/// DeskState::state: maximised (with DW_MIN: it comes back maximised)
77pub const DW_MAX: u32 = 1;
78/// minimised (hidden until restored)
79pub const DW_MIN: u32 = 2;
80/// snapped to the start (left) / end (right) half of the area (mirrored windows show it the
81/// other way round)
82pub const DW_LEFT: u32 = 4;
83pub const DW_RIGHT: u32 = 8;
84/// desk_taskbar flags: icons only
85pub const DTB_ICONS: u32 = 1;
86
87// metrics (DIP)
88const DEF_W: i32 = 400; // a new window without DeskWin::size
89const DEF_H: i32 = 280;
90const MIN_W: i32 = 160; // the smallest window without DeskWin::min
91const MIN_H: i32 = 96;
92const CASCADE: i32 = 28;
93const KEEP: i32 = 64; // of a window's width that stays inside the area
94const MAGNET: i32 = 10; // window edges this close to the area's edges stick to them
95const ZONE: i32 = 4; // the pointer this close to the area's edge (or beyond) snaps
96const GRIP_OUT: i32 = 4; // resize bands: outside the frame
97const GRIP_IN: i32 = 2; // ... and inside it
98const SIZE_MAX: i32 = 16384;
99const TASK_H: i32 = 40; // a taskbar without a size
100const TASK_MIN: i32 = 96; // the narrowest taskbar button (with a title)
101const TASK_MAX: i32 = 200;
102const TASK_GAP: i32 = 2;
103const OVER_W: i32 = 32; // the "Weitere Fenster" button
104
105// edges of a resize
106const E_LEFT: u32 = 1;
107const E_TOP: u32 = 2;
108const E_RIGHT: u32 = 4;
109const E_BOTTOM: u32 = 8;
110// the eight grips: edges, cursor (left to right)
111const GRIPS: [(u32, u32); 8] = [
112    (E_TOP, CUR_SIZE_NS),
113    (E_BOTTOM, CUR_SIZE_NS),
114    (E_LEFT, CUR_SIZE_WE),
115    (E_RIGHT, CUR_SIZE_WE),
116    (E_TOP | E_LEFT, CUR_SIZE_NWSE),
117    (E_TOP | E_RIGHT, CUR_SIZE_NESW),
118    (E_BOTTOM | E_LEFT, CUR_SIZE_NESW),
119    (E_BOTTOM | E_RIGHT, CUR_SIZE_NWSE),
120];
121
122// id parts (pid)
123const P_AREA: u32 = 1;
124const P_WIN: u32 = 2;
125const P_BOX: u32 = 3;
126const P_TITLE: u32 = 4;
127const P_ICON: u32 = 5;
128const P_MIN: u32 = 6;
129const P_MAX: u32 = 7;
130const P_CLOSE: u32 = 8;
131const P_CONTENT: u32 = 9;
132const P_PANE: u32 = 10;
133const P_MENU: u32 = 11;
134const P_BAR: u32 = 12;
135const P_TASK: u32 = 13;
136const P_TBTN: u32 = 14;
137const P_TMENU: u32 = 15;
138const P_OVER: u32 = 16;
139const P_OITEM: u32 = 17;
140const P_GRIP: u32 = 20; // .. + 7
141const P_MITEM: u32 = 32; // .. + 3: the window menu's rows
142const P_TITEM: u32 = 40; // .. + 3: the taskbar menu's rows
143
144// a widget id of desk `desk`: part `part` of window `a`
145fn pid(desk: u32, part: u32, a: u32) -> u32 {
146    let mut h = desk.wrapping_mul(0x9E37_79B1) ^ part.wrapping_mul(0x85EB_CA6B) ^ a.wrapping_mul(0xC2B2_AE35) ^ 0x5D35_E7A1;
147    h ^= h >> 15;
148    h = h.wrapping_mul(0x2C1B_3C6D);
149    h ^= h >> 12;
150    h | 0x8000_0000
151}
152
153fn inside(r: [i32; 4], p: [i32; 2]) -> bool {
154    p[0] >= r[0] && p[0] < r[0] + r[2] && p[1] >= r[1] && p[1] < r[1] + r[3]
155}
156
157/// a window as the app describes it to desk_begin every frame
158#[derive(Clone, Copy, Debug)]
159pub struct DeskWin<'a> {
160    /// != 0 and stable (the saved state refers to it)
161    pub key: u32,
162    /// shown in its title bar, its taskbar button and to screen readers
163    pub title: &'a str,
164    /// IC_* of its title bar and taskbar button (0 = none)
165    pub icon: u32,
166    /// DWF_*
167    pub flags: u32,
168    /// the smallest content size (DIP, without the title bar; 0 = the library's)
169    pub min: [i32; 2],
170    /// the size a new window gets (DIP, without the title bar; 0 = the library's)
171    pub size: [i32; 2],
172}
173
174impl<'a> DeskWin<'a> {
175    /// a window with the library's sizes and every button
176    pub const fn new(key: u32, title: &'a str, icon: u32) -> DeskWin<'a> {
177        DeskWin { key, title, icon, flags: 0, min: [0, 0], size: [0, 0] }
178    }
179    /// add DWF_* flags
180    pub const fn flags(mut self, f: u32) -> DeskWin<'a> {
181        self.flags |= f;
182        self
183    }
184    /// the smallest content size (DIP)
185    pub const fn min(mut self, w: i32, h: i32) -> DeskWin<'a> {
186        self.min = [w, h];
187        self
188    }
189    /// the content size of a new window (DIP); DWF_FIXED windows keep it
190    pub const fn size(mut self, w: i32, h: i32) -> DeskWin<'a> {
191        self.size = [w, h];
192        self
193    }
194}
195
196/// what desk_end reports
197#[derive(Clone, Copy, Debug, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum DeskEv {
200    /// the user wants to close the window (close button, menu, taskbar): take it out of the
201    /// list of windows (DeskState keeps its place until forget), or ask first
202    Close(u32),
203    /// it became the active window
204    Activate(u32),
205    /// the user moved it (a drag ended)
206    Move(u32),
207    /// the user resized it
208    Resize(u32),
209    /// minimised, maximised, restored or snapped (DeskState::state)
210    State(u32),
211}
212
213// ------------------------------------------------------------------ the state (DeskState)
214
215#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
216struct Win {
217    key: u32,
218    /// the normal place: x, y from the area's start corner, outer width and height (DIP)
219    rect: [i32; 4],
220    max: bool,
221    min: bool,
222    /// 0 none, 1 the start half, 2 the end half
223    snap: u8,
224}
225
226impl Win {
227    fn state(&self) -> u32 {
228        let mut s = 0;
229        if self.max {
230            s |= DW_MAX;
231        }
232        if self.min {
233            s |= DW_MIN;
234        }
235        match self.snap {
236            1 => s |= DW_LEFT,
237            2 => s |= DW_RIGHT,
238            _ => {}
239        }
240        s
241    }
242}
243
244/// the windows of an area: kept by the app and passed to desk_begin / desk_next / desk_end /
245/// desk_taskbar.  Plain data: save() / load() turn it into a few lines of text (`desk 1`, then
246/// `win KEY X Y W H STATE` from the bottom of the z-order to the top -- x, y from the area's
247/// start corner, outer size, all DIP; STATE the DW_* bits -- and `active KEY`; unknown lines
248/// are skipped).  The reserved strip is no part of it.
249#[derive(Clone, Default, Debug, PartialEq, Eq)]
250pub struct DeskState {
251    /// from the bottom of the z-order to the top
252    wins: Vec<Win>,
253    active: u32,
254    /// new windows placed so far (the cascade)
255    cascade: i32,
256    /// the strip the area keeps free: start, top, end, bottom (DIP; not saved)
257    reserve: [i32; 4],
258}
259
260impl DeskState {
261    /// no windows: every window goes cascaded to the front in its first frame
262    pub fn new() -> DeskState {
263        DeskState::default()
264    }
265
266    fn find(&self, key: u32) -> Option<usize> {
267        self.wins.iter().position(|w| w.key == key)
268    }
269
270    // windows the state does not know yet: cascaded (or maximised), on top, active
271    fn adopt(&mut self, wins: &[DeskWin], area: [i32; 2], tbh: i32) {
272        for d in wins {
273            if d.key == 0 || self.find(d.key).is_some() {
274                continue;
275            }
276            let (mw, mh) = min_size(d, tbh);
277            let w = (if d.size[0] > 0 { d.size[0] } else { DEF_W }).max(mw);
278            let h = (if d.size[1] > 0 { d.size[1] + tbh } else { DEF_H }).max(mh);
279            // the next step of the cascade; from the start again where it would leave the area
280            // (not before the area has its size)
281            let mut k = self.cascade;
282            let o = CASCADE / 2 + k * CASCADE;
283            if k > 0 && area[0] > 0 && area[1] > 0 && (o + w > area[0] || o + h > area[1]) {
284                k = 0;
285                self.cascade = 0;
286            }
287            self.cascade += 1;
288            let x = CASCADE / 2 + k * CASCADE;
289            let y = CASCADE / 2 + k * CASCADE;
290            let max = d.flags & DWF_MAXIMIZED != 0 && d.flags & (DWF_NOMAX | DWF_FIXED) == 0;
291            self.wins.push(Win { key: d.key, rect: [x, y, w, h], max, min: false, snap: 0 });
292            self.active = d.key;
293        }
294    }
295
296    /// forget every window: each goes to its first place in the next frame
297    pub fn reset(&mut self) {
298        let reserve = self.reserve;
299        *self = DeskState { reserve, ..DeskState::default() };
300    }
301
302    /// the strip the area keeps free for a taskbar inside it (DIP: start, top, end, bottom):
303    /// maximised and snapped windows leave it out, the title bars stay above it; not saved
304    pub fn set_reserve(&mut self, insets: [i32; 4]) {
305        self.reserve = insets.map(|v| v.clamp(0, SIZE_MAX));
306    }
307
308    /// the reserved strip (set_reserve)
309    pub fn reserve(&self) -> [i32; 4] {
310        self.reserve
311    }
312
313    /// the state knows the window
314    pub fn contains(&self, key: u32) -> bool {
315        self.find(key).is_some()
316    }
317
318    /// the windows from the bottom of the z-order to the top (minimised ones too)
319    pub fn order(&self) -> Vec<u32> {
320        self.wins.iter().map(|w| w.key).collect()
321    }
322
323    /// the active window (0: none)
324    pub fn active(&self) -> u32 {
325        self.active
326    }
327
328    /// DW_* bits of the window (0: a normal window, or an unknown one)
329    pub fn state(&self, key: u32) -> u32 {
330        self.find(key).map_or(0, |i| self.wins[i].state())
331    }
332
333    /// the normal place of the window: x, y from the area's start corner, outer width and
334    /// height (DIP) -- what it comes back to from maximised, snapped or minimised
335    pub fn rect(&self, key: u32) -> Option<[i32; 4]> {
336        self.find(key).map(|i| self.wins[i].rect)
337    }
338
339    /// set the normal place of a window (DIP; see rect); a maximised or snapped one shows it
340    /// once restored
341    pub fn set_rect(&mut self, key: u32, r: [i32; 4]) {
342        if let Some(i) = self.find(key) {
343            self.wins[i].rect = [r[0].clamp(-SIZE_MAX, SIZE_MAX), r[1].clamp(-SIZE_MAX, SIZE_MAX), r[2].clamp(1, SIZE_MAX), r[3].clamp(1, SIZE_MAX)];
344        }
345    }
346
347    /// bring the window to the front and make it active (a minimised one comes back)
348    pub fn activate(&mut self, key: u32) {
349        let Some(i) = self.find(key) else { return };
350        let mut w = self.wins.remove(i);
351        w.min = false;
352        self.wins.push(w);
353        self.active = key;
354    }
355
356    // the topmost window that is not minimised becomes active (0: none)
357    fn activate_top(&mut self) {
358        self.active = self.wins.iter().rev().find(|w| !w.min).map_or(0, |w| w.key);
359    }
360
361    /// minimise the window (it keeps its place in the z-order; the topmost other one becomes
362    /// active)
363    pub fn minimize(&mut self, key: u32) {
364        let Some(i) = self.find(key) else { return };
365        self.wins[i].min = true;
366        if self.active == key || self.active == 0 {
367            self.activate_top();
368        }
369    }
370
371    /// maximise the window (and activate it)
372    pub fn maximize(&mut self, key: u32) {
373        if let Some(i) = self.find(key) {
374            self.wins[i].max = true;
375            self.wins[i].snap = 0;
376            self.activate(key);
377        }
378    }
379
380    /// a minimised window comes back (as it was: maximised stays maximised); else a maximised
381    /// or snapped one goes back to its normal place; it becomes active
382    pub fn restore(&mut self, key: u32) {
383        let Some(i) = self.find(key) else { return };
384        let w = &mut self.wins[i];
385        if w.min {
386            w.min = false;
387        } else {
388            w.max = false;
389            w.snap = 0;
390        }
391        self.activate(key);
392    }
393
394    /// maximise the window, or restore a maximised (or snapped) one
395    pub fn toggle_max(&mut self, key: u32) {
396        let Some(i) = self.find(key) else { return };
397        let w = self.wins[i];
398        if w.max || w.snap != 0 {
399            self.wins[i].max = false;
400            self.wins[i].snap = 0;
401            self.activate(key);
402        } else {
403            self.maximize(key);
404        }
405    }
406
407    /// snap the window to the start (DW_LEFT) or end (DW_RIGHT) half of the area, 0: back to
408    /// its normal place
409    pub fn snap(&mut self, key: u32, half: u32) {
410        let Some(i) = self.find(key) else { return };
411        let w = &mut self.wins[i];
412        w.max = false;
413        w.min = false;
414        w.snap = match half {
415            DW_LEFT => 1,
416            DW_RIGHT => 2,
417            _ => 0,
418        };
419        self.activate(key);
420    }
421
422    /// the next window (forward: the active one goes to the back and the one below it comes to
423    /// the front; back: the one at the back comes to the front) among the windows that are not
424    /// minimised -> the window now active
425    pub fn cycle(&mut self, forward: bool) -> Option<u32> {
426        self.cycle_in(forward, &|_| true)
427    }
428
429    // cycle among the windows `keep` accepts
430    fn cycle_in(&mut self, forward: bool, keep: &dyn Fn(u32) -> bool) -> Option<u32> {
431        let shown: Vec<usize> = (0..self.wins.len()).filter(|&i| !self.wins[i].min && keep(self.wins[i].key)).collect();
432        if shown.is_empty() {
433            return None;
434        }
435        if shown.len() > 1 {
436            if forward {
437                let top = *shown.last()?;
438                let w = self.wins.remove(top);
439                self.wins.insert(shown[0], w);
440            } else {
441                let w = self.wins.remove(shown[0]);
442                self.wins.push(w);
443            }
444        }
445        self.active = self.wins.iter().rev().find(|w| !w.min && keep(w.key)).map_or(0, |w| w.key);
446        Some(self.active)
447    }
448
449    /// forget a window (a closed one: it comes back cascaded)
450    pub fn forget(&mut self, key: u32) {
451        if let Some(i) = self.find(key) {
452            self.wins.remove(i);
453            if self.active == key {
454                self.activate_top();
455            }
456        }
457    }
458
459    /// the state as text (see DeskState)
460    pub fn save(&self) -> String {
461        use core::fmt::Write;
462        let mut o = String::from("desk 1\n");
463        for w in &self.wins {
464            let r = w.rect;
465            let _ = writeln!(o, "win {} {} {} {} {} {}", w.key, r[0], r[1], r[2], r[3], w.state());
466        }
467        let _ = writeln!(o, "active {}", self.active);
468        o
469    }
470
471    /// a state saved with save -> None when the text is no desk state (version 1)
472    pub fn load(text: &str) -> Option<DeskState> {
473        let mut lines = text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#'));
474        let mut w = lines.next()?.split_whitespace();
475        if w.next()? != "desk" || w.next()? != "1" {
476            return None;
477        }
478        let mut st = DeskState::default();
479        let mut active = 0;
480        for l in lines {
481            let mut w = l.split_whitespace();
482            let kind = w.next().unwrap_or("");
483            let v: Vec<i64> = w.map(|x| x.parse::<i64>().unwrap_or(i64::MIN)).collect();
484            if v.contains(&i64::MIN) {
485                continue;
486            }
487            let int = |i: usize| v.get(i).copied().unwrap_or(0).clamp(-(SIZE_MAX as i64), SIZE_MAX as i64) as i32;
488            match kind {
489                "win" if v.len() >= 6 => {
490                    let key = v[0].clamp(0, u32::MAX as i64) as u32;
491                    if key == 0 || st.find(key).is_some() {
492                        continue;
493                    }
494                    let s = v[5].clamp(0, 15) as u32;
495                    let snap = if s & DW_LEFT != 0 { 1 } else if s & DW_RIGHT != 0 { 2 } else { 0 };
496                    let rect = [int(1), int(2), int(3).max(1), int(4).max(1)];
497                    st.wins.push(Win { key, rect, max: s & DW_MAX != 0, min: s & DW_MIN != 0, snap });
498                }
499                "active" if !v.is_empty() => active = v[0].clamp(0, u32::MAX as i64) as u32,
500                _ => {}
501            }
502        }
503        st.cascade = st.wins.len() as i32;
504        if st.find(active).is_some_and(|i| !st.wins[i].min) {
505            st.active = active;
506        } else {
507            st.activate_top();
508        }
509        Some(st)
510    }
511}
512
513// the smallest outer size of a window (DIP; tbh: its title bar)
514fn min_size(d: &DeskWin, tbh: i32) -> (i32, i32) {
515    let w = if d.min[0] > 0 { d.min[0] } else { MIN_W };
516    let h = if d.min[1] > 0 { d.min[1] + tbh } else { MIN_H.max(tbh * 2) };
517    (w.clamp(1, SIZE_MAX), h.clamp(tbh + 1, SIZE_MAX))
518}
519
520// ------------------------------------------------------------------ the runtime part
521
522// the frame's description of a window
523struct Info {
524    key: u32,
525    title: String,
526    icon: u32,
527    flags: u32,
528    /// the smallest outer size (DIP)
529    min: [i32; 2],
530}
531
532// a window being moved (edges 0) or resized
533#[derive(Clone, Copy)]
534struct Drag {
535    key: u32,
536    edges: u32,
537    /// the pointer at the start (px of the window, logical)
538    ptr0: [i32; 2],
539    /// the place at the start and now (DIP from the area's start corner)
540    rect0: [i32; 4],
541    cur: [i32; 4],
542    /// where a release puts it: 0 there, DW_LEFT / DW_RIGHT a half, DW_MAX maximised
543    zone: u32,
544    /// it was maximised or snapped (the drag restored it)
545    was: bool,
546}
547
548#[derive(Clone, Copy)]
549enum Act {
550    Activate(u32),
551    Min(u32),
552    ToggleMax(u32),
553    Restore(u32),
554    Close(u32),
555    /// the window menu at its icon (true: the first row focused)
556    Menu(u32),
557    Cycle(bool),
558}
559
560/// one window area's state between the calls (and the passes)
561#[derive(Default)]
562pub(crate) struct Rt {
563    id: u32,
564    infos: Vec<Info>,
565    /// the area and its part without the reserved strip (logical px)
566    area: [i32; 4],
567    work: [i32; 4],
568    /// the windows of this pass, bottom to top, their outer rectangles (px) and the range of
569    /// their hit boxes in the pass's list
570    order: Vec<u32>,
571    shown: Vec<(u32, [i32; 4], usize, usize)>,
572    /// ... of the last pass (a press finds its window there)
573    shown_prev: Vec<(u32, [i32; 4], usize, usize)>,
574    next: usize,
575    /// the window desk_next opened (0: none), whether its widgets are no Tab stops, the hit
576    /// count before its content, its outer rectangle and whether it has grips
577    open: u32,
578    open_notab: bool,
579    hits0: usize,
580    open_rect: [i32; 4],
581    open_place: [i32; 4],
582    open_grips: bool,
583    /// the index of its record in Ui::sems (its content is counted into Sem::max at its end)
584    open_rec: usize,
585    drag: Option<Drag>,
586    acts: Vec<Act>,
587    evs: Vec<DeskEv>,
588    /// the widget focused last in each window
589    wfocus: Vec<(u32, u32)>,
590    /// the window whose content gets the focus when it is emitted next, the focus visual then
591    focus_into: u32,
592    focus_kb: bool,
593    /// the focus was inside the area in this pass / the last one; the focus at the last
594    /// desk_end
595    focus_here: bool,
596    focus_in: bool,
597    last_focus: u32,
598    /// the active window at the last desk_end (Activate events, the focus follows)
599    last_active: u32,
600    /// the window menu opened by a right click (anchored to the pointer)
601    menu_point: u32,
602    /// the taskbar's menu opened by a right click
603    tmenu_point: u32,
604}
605
606impl Rt {
607    fn info(&self, key: u32) -> Option<&Info> {
608        self.infos.iter().find(|i| i.key == key)
609    }
610    fn flags(&self, key: u32) -> u32 {
611        self.info(key).map_or(0, |i| i.flags)
612    }
613    fn present(&self, key: u32) -> bool {
614        self.infos.iter().any(|i| i.key == key)
615    }
616}
617
618/// the window areas of the app (Mods)
619#[derive(Default)]
620pub(crate) struct S {
621    desks: Vec<Rt>,
622    /// the areas between desk_begin and desk_end
623    cur: Vec<u32>,
624}
625
626// a library text (German source; i18n::tr)
627fn tr(s: &'static str) -> &'static str {
628    super::i18n::tr(s)
629}
630
631// the name of a window for screen readers and the taskbar: its title, its state after it
632fn named(title: &str, state: u32) -> String {
633    if state & DW_MIN != 0 {
634        alloc::format!("{} ({})", title, tr("minimiert"))
635    } else if state & DW_MAX != 0 {
636        alloc::format!("{} ({})", title, tr("maximiert"))
637    } else {
638        String::from(title)
639    }
640}
641
642impl Ui {
643    fn dw_take(&mut self, id: u32) -> Rt {
644        let d = &mut self.m.desk.desks;
645        match d.iter().position(|r| r.id == id) {
646            Some(i) => d.swap_remove(i),
647            None => Rt { id, ..Rt::default() },
648        }
649    }
650
651    fn dw_put(&mut self, rt: Rt) {
652        self.m.desk.desks.push(rt);
653    }
654
655    // the title bar height (DIP): the style's dialog window title bar
656    fn dw_tbh(&self) -> i32 {
657        let v = self.style.metric(SM_WIN_TBH_DIALOG);
658        if v > 0 { v } else { 32 }
659    }
660
661    // vk with exactly these modifiers pressed this pass (consumed; a text box keeps its keys)
662    fn dw_key(&mut self, vk: u32, mods: u32) -> bool {
663        let m3 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
664        let k = self.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & m3 == mods);
665        match self.text_key_filter(k) {
666            Some(i) => {
667                self.wp.ctx.keyq[i] |= 0x8000_0000;
668                true
669            }
670            None => false,
671        }
672    }
673
674    // px -> DIP, rounded the same way on both sides of 0 (deltas, places left of the area)
675    fn dw_dip(&self, px: i32) -> i32 {
676        super::layout::px2dip_at(px, self.wp.rt.dpi)
677    }
678
679    // DIP from the area's start corner -> logical px of the window
680    fn dw_px(&self, rt: &Rt, r: [i32; 4]) -> [i32; 4] {
681        [rt.area[0] + self.dp(r[0]), rt.area[1] + self.dp(r[1]), self.dp(r[2]), self.dp(r[3])]
682    }
683
684    // the work area (the area without the reserved strip) in DIP from the area's start corner
685    fn dw_work(&self, rt: &Rt) -> [i32; 4] {
686        let w = rt.work;
687        [self.dw_dip(w[0] - rt.area[0]), self.dw_dip(w[1] - rt.area[1]), self.dw_dip(w[2]), self.dw_dip(w[3])]
688    }
689
690    // a normal place kept reachable inside the work area (DIP)
691    fn dw_reach(&self, rt: &Rt, r: [i32; 4]) -> [i32; 4] {
692        let wk = self.dw_work(rt);
693        let tbh = self.dw_tbh();
694        let keep = KEEP.min(r[2]);
695        let x = r[0].min(wk[0] + wk[2] - keep).max(wk[0] + keep - r[2]);
696        let y = r[1].min(wk[1] + wk[3] - tbh).max(wk[1]);
697        [x, y, r[2], r[3]]
698    }
699
700    // where a window shows (DIP from the area's start corner) and whether it fills the work
701    // area (maximised: no frame); a drag shows its own place
702    fn dw_place(&self, rt: &Rt, w: &Win) -> ([i32; 4], bool) {
703        if let Some(d) = rt.drag.filter(|d| d.key == w.key) {
704            return (d.cur, false);
705        }
706        let wk = self.dw_work(rt);
707        if w.max {
708            return (wk, true);
709        }
710        match w.snap {
711            1 => ([wk[0], wk[1], wk[2] / 2, wk[3]], false),
712            2 => ([wk[0] + wk[2] / 2, wk[1], wk[2] - wk[2] / 2, wk[3]], false),
713            _ => (self.dw_reach(rt, w.rect), false),
714        }
715    }
716
717    // ------------------------------------------------------------------ app API
718
719    /// desk_begin(id, &mut st, wins): open the window area (always pair with desk_end; fill it
720    /// with desk_next).  It takes the next place of the box (the whole rest when no size was
721    /// set); wins: every open window, each frame (titles and icons may change), in the order
722    /// the taskbar shows them.
723    pub fn desk_begin(&mut self, id: u32, st: &mut DeskState, wins: &[DeskWin]) {
724        let mut rt = self.dw_take(id);
725        let cur = &mut self.m.desk.cur;
726        cur.retain(|&c| c != id); // (a desk_end the app left out)
727        cur.push(id);
728        let tbh = self.dw_tbh();
729        rt.infos = wins
730            .iter()
731            .filter(|d| d.key != 0)
732            .map(|d| {
733                let (mw, mh) = min_size(d, tbh);
734                Info { key: d.key, title: String::from(d.title), icon: d.icon, flags: d.flags, min: [mw, mh] }
735            })
736            .collect();
737        let l = &self.wp.lay;
738        if l.next_w == 0 && l.next_h == 0 && l.next_rect_set == 0 {
739            self.next_size(SIZE_FILL, SIZE_FILL);
740        }
741        self.box_begin(pid(id, P_AREA, 0), BOX_V | BOX_FIXED | BOX_CLIP, 0);
742        rt.area = self.box_rect();
743        let a = rt.area;
744        let rv = st.reserve.map(|v| self.dp(v));
745        rt.work = [a[0] + rv[0], a[1] + rv[1], (a[2] - rv[0] - rv[2]).max(1), (a[3] - rv[1] - rv[3]).max(1)];
746        // new windows cascade inside the work area (0: not measured yet in a first pass)
747        let wk = rt.work;
748        let dims = if a[2] > 0 && a[3] > 0 { [self.dw_dip(wk[2]), self.dw_dip(wk[3])] } else { [0, 0] };
749        st.adopt(wins, dims, tbh);
750        // the background
751        let sh = self.style.shell();
752        let mut sp = Sp::default();
753        sp.place(a);
754        sh.desk_bg(self, &sp);
755        // a drag follows the pointer, ends with the release; Esc cancels it
756        self.dw_drag_update(&mut rt, st);
757        // a press on a window brings it to the front
758        self.dw_press(&mut rt, st);
759        self.dw_sync_active(&mut rt, st, false);
760        rt.shown_prev = core::mem::take(&mut rt.shown);
761        rt.order = st.wins.iter().filter(|w| !w.min && rt.present(w.key)).map(|w| w.key).collect();
762        // (the focus memory of windows that went away)
763        let infos = &rt.infos;
764        rt.wfocus.retain(|e| infos.iter().any(|i| i.key == e.0));
765        rt.next = 0;
766        rt.open = 0;
767        rt.focus_here = false;
768        self.dw_put(rt);
769    }
770
771    /// desk_next(&mut st) -> the next window to fill (None: done), from the back to the
772    /// front; its content goes into a vertical box (it clips) until the next call
773    pub fn desk_next(&mut self, st: &mut DeskState) -> Option<u32> {
774        let id = *self.m.desk.cur.last()?;
775        let mut rt = self.dw_take(id);
776        self.dw_close(&mut rt, st);
777        let mut out = None;
778        if rt.next < rt.order.len() {
779            let key = rt.order[rt.next];
780            rt.next += 1;
781            self.dw_window(&mut rt, st, key);
782            out = Some(key);
783        }
784        self.dw_put(rt);
785        out
786    }
787
788    /// desk_end(&mut st) -> what the user did in this pass (and the taskbar since the last
789    /// desk_end): close requests, activation, moves, resizes, state changes
790    pub fn desk_end(&mut self, st: &mut DeskState) -> Vec<DeskEv> {
791        let Some(id) = self.m.desk.cur.pop() else { return Vec::new() };
792        let mut rt = self.dw_take(id);
793        self.dw_close(&mut rt, st);
794        // the keys, while the focus is inside the area (or nowhere)
795        let focus = self.wp.ctx.focus;
796        if rt.drag.is_none() && (rt.focus_here || rt.focus_in || focus == 0) {
797            let a = st.active;
798            if self.dw_key(VK_F6, MOD_ALT) {
799                rt.acts.push(Act::Cycle(true));
800            }
801            if self.dw_key(VK_F6, MOD_ALT | MOD_SHIFT) {
802                rt.acts.push(Act::Cycle(false));
803            }
804            if a != 0 && rt.present(a) {
805                let f = rt.flags(a);
806                if f & (DWF_NOMAX | DWF_FIXED) == 0 && self.dw_key(VK_F10, MOD_ALT) {
807                    rt.acts.push(Act::ToggleMax(a));
808                }
809                if f & DWF_NOMIN == 0 && self.dw_key(VK_F9, MOD_ALT) {
810                    rt.acts.push(Act::Min(a));
811                }
812                if self.dw_key(VK_OEM_MINUS, MOD_ALT) || self.dw_key(VK_SUBTRACT, MOD_ALT) {
813                    rt.acts.push(Act::Menu(a));
814                }
815            }
816        }
817        let acts = core::mem::take(&mut rt.acts);
818        let mut by_focus = false;
819        for a in acts {
820            by_focus |= self.dw_apply(&mut rt, st, a);
821        }
822        self.dw_sync_active(&mut rt, st, by_focus);
823        self.box_end(); // the area
824        rt.focus_in = rt.focus_here;
825        rt.last_focus = self.wp.ctx.focus;
826        let evs = core::mem::take(&mut rt.evs);
827        self.dw_put(rt);
828        evs
829    }
830
831    // ------------------------------------------------------------------ input
832
833    // the active window changed (by anyone): Activate, and its content gets the focus (unless
834    // the focus arriving there activated it)
835    fn dw_sync_active(&mut self, rt: &mut Rt, st: &mut DeskState, by_focus: bool) {
836        // a window the app does not describe (closed, the state keeps its place) is no
837        // active one
838        if st.active != 0 && (!rt.present(st.active) || st.state(st.active) & DW_MIN != 0) {
839            st.active = st.wins.iter().rev().find(|w| !w.min && rt.present(w.key)).map_or(0, |w| w.key);
840        }
841        if st.active == rt.last_active {
842            return;
843        }
844        rt.last_active = st.active;
845        if st.active == 0 {
846            return;
847        }
848        rt.evs.push(DeskEv::Activate(st.active));
849        if !by_focus {
850            rt.focus_into = st.active;
851        }
852        self.repass = true;
853    }
854
855    // a press of any button on a window (the window whose hit box took it in the last pass:
856    // also its grips outside the frame) brings it to the front
857    fn dw_press(&mut self, rt: &mut Rt, st: &mut DeskState) {
858        let c = &self.wp.ctx;
859        if c.pressed & (1 << BTN_LEFT | 1 << BTN_RIGHT | 1 << BTN_MIDDLE) == 0 || c.modal_prev > L_BASE {
860            return;
861        }
862        let p = if c.pressed & (1 << BTN_LEFT) != 0 { [c.press_x, c.press_y] } else { [c.mx, c.my] };
863        if !inside(rt.area, p) {
864            return;
865        }
866        // a popup (a menu, or the layer that closes them) over the point takes the press
867        let hits = &c.hits[c.hit_cur ^ 1];
868        let at = |h: &super::ctx::Hit| p[0] >= h.x0 && p[0] < h.x1 && p[1] >= h.y0 && p[1] < h.y1;
869        if hits.iter().any(|h| h.layer as u32 > L_BASE && at(h)) {
870            return;
871        }
872        let Some(top) = hits.iter().rposition(at) else { return };
873        let Some(&(key, ..)) = rt.shown_prev.iter().find(|e| top >= e.2 && top < e.3) else { return };
874        if st.active != key || st.wins.last().is_some_and(|w| w.key != key) {
875            st.activate(key);
876            self.repass = true;
877        }
878    }
879
880    // follow the pointer with the window being dragged; the release commits it
881    fn dw_drag_update(&mut self, rt: &mut Rt, st: &mut DeskState) {
882        let Some(mut d) = rt.drag else { return };
883        if !st.contains(d.key) || !rt.present(d.key) {
884            rt.drag = None;
885            return;
886        }
887        // (also while a text box has the focus: the drag holds the pointer)
888        if self.kb_chord(VK_ESCAPE, 0) {
889            rt.drag = None;
890            self.wp.ctx.active = 0; // the release is no click
891            self.repass = true;
892            return;
893        }
894        let (mx, my) = self.mouse();
895        let held = self.wp.ctx.mbtn & (1 << BTN_LEFT) != 0;
896        let (dx, dy) = (self.dw_dip(mx - d.ptr0[0]), self.dw_dip(my - d.ptr0[1]));
897        let wk = self.dw_work(rt);
898        let r0 = d.rect0;
899        let min = rt.info(d.key).map_or([MIN_W, MIN_H], |i| i.min);
900        let mut r = r0;
901        d.zone = 0;
902        if d.edges == 0 {
903            r[0] = r0[0] + dx;
904            r[1] = r0[1] + dy;
905            // the edges stick to the work area's
906            let snap = |v: i32, len: i32, lo: i32, hi: i32| {
907                if (v - lo).abs() <= MAGNET {
908                    lo
909                } else if (v + len - hi).abs() <= MAGNET {
910                    hi - len
911                } else {
912                    v
913                }
914            };
915            r[0] = snap(r[0], r[2], wk[0], wk[0] + wk[2]);
916            r[1] = snap(r[1], r[3], wk[1], wk[1] + wk[3]);
917            r = self.dw_reach(rt, r);
918            // the pointer at the area's edges: a half or the whole area
919            let f = rt.flags(d.key);
920            let a = rt.area;
921            let z = self.dp(ZONE);
922            if f & DWF_FIXED == 0 {
923                if mx <= a[0] + z {
924                    d.zone = DW_LEFT;
925                } else if mx >= a[0] + a[2] - 1 - z {
926                    d.zone = DW_RIGHT;
927                }
928            }
929            if d.zone == 0 && my <= a[1] + z && f & (DWF_NOMAX | DWF_FIXED) == 0 {
930                d.zone = DW_MAX;
931            }
932        } else {
933            // the dragged edges, inside the work area, not below the minimum size
934            let (x1, y1) = (r0[0] + r0[2], r0[1] + r0[3]);
935            if d.edges & E_LEFT != 0 {
936                let x = (r0[0] + dx).max(wk[0].min(r0[0])).min(x1 - min[0]);
937                r[0] = x;
938                r[2] = x1 - x;
939            }
940            if d.edges & E_RIGHT != 0 {
941                r[2] = (r0[2] + dx).min((wk[0] + wk[2]).max(x1) - r0[0]).max(min[0]);
942            }
943            if d.edges & E_TOP != 0 {
944                let y = (r0[1] + dy).max(wk[1].min(r0[1])).min(y1 - min[1]);
945                r[1] = y;
946                r[3] = y1 - y;
947            }
948            if d.edges & E_BOTTOM != 0 {
949                r[3] = (r0[3] + dy).min((wk[1] + wk[3]).max(y1) - r0[1]).max(min[1]);
950            }
951        }
952        if held {
953            if r != d.cur || rt.drag.is_some_and(|o| o.zone != d.zone) {
954                self.repass = true;
955            }
956            d.cur = r;
957            rt.drag = Some(d);
958            return;
959        }
960        // released: the new place
961        rt.drag = None;
962        self.repass = true;
963        let Some(i) = st.find(d.key) else { return };
964        let w = &mut st.wins[i];
965        if d.zone != 0 && d.edges == 0 {
966            // (the normal place stays what it was)
967            w.max = d.zone == DW_MAX;
968            w.snap = match d.zone {
969                DW_LEFT => 1,
970                DW_RIGHT => 2,
971                _ => 0,
972            };
973            rt.evs.push(DeskEv::State(d.key));
974            return;
975        }
976        w.rect = r;
977        w.max = false;
978        w.snap = 0;
979        if d.was {
980            rt.evs.push(DeskEv::State(d.key));
981        }
982        if r != r0 || d.was {
983            rt.evs.push(if d.edges == 0 { DeskEv::Move(d.key) } else { DeskEv::Resize(d.key) });
984        }
985    }
986
987    // start moving (edges 0) or resizing window key from its place r (DIP), the pointer at the
988    // press
989    fn dw_drag_start(&mut self, rt: &mut Rt, st: &DeskState, key: u32, edges: u32, r: [i32; 4]) {
990        let Some(i) = st.find(key) else { return };
991        let w = st.wins[i];
992        let (px, py) = (self.wp.ctx.press_x, self.wp.ctx.press_y);
993        let mut rect0 = r;
994        let was = edges == 0 && (w.max || w.snap != 0);
995        if was {
996            // back to the normal size under the pointer, at the same share of the title bar
997            let n = w.rect;
998            let (ax, ay) = (self.dw_dip(px - rt.area[0]), self.dw_dip(py - rt.area[1]));
999            let share = if r[2] > 0 { ((ax - r[0]) as i64 * 1024 / r[2] as i64) as i32 } else { 512 };
1000            let x = ax - (n[2] as i64 * share as i64 / 1024) as i32;
1001            let y = ay - (ay - r[1]).min(self.dw_tbh() / 2);
1002            rect0 = self.dw_reach(rt, [x, y, n[2], n[3]]);
1003        }
1004        rt.drag = Some(Drag { key, edges, ptr0: [px, py], rect0, cur: rect0, zone: 0, was });
1005        self.wp.ctx.last_click_id = 0; // a drag is no first click of a double click
1006        self.repass = true;
1007    }
1008
1009    // ------------------------------------------------------------------ a window
1010
1011    // the frame, the title bar, the buttons, the menu and the content box of window key
1012    fn dw_window(&mut self, rt: &mut Rt, st: &mut DeskState, key: u32) {
1013        let id = rt.id;
1014        let Some(i) = st.find(key) else { return };
1015        let w = st.wins[i];
1016        let (title, icon, flags) = rt.info(key).map_or((String::new(), 0, 0), |f| (f.title.clone(), f.icon, f.flags));
1017        let active = st.active == key;
1018        let (place, max) = self.dw_place(rt, &w);
1019        let r = self.dw_px(rt, place);
1020        let sh = self.style.shell();
1021        let stl = self.style;
1022        // where a release of the drag puts it
1023        if let Some(d) = rt.drag.filter(|d| d.key == key && d.zone != 0) {
1024            let wk = self.dw_work(rt);
1025            let p = match d.zone {
1026                DW_LEFT => [wk[0], wk[1], wk[2] / 2, wk[3]],
1027                DW_RIGHT => [wk[0] + wk[2] / 2, wk[1], wk[2] - wk[2] / 2, wk[3]],
1028                _ => wk,
1029            };
1030            let mut sp = Sp { value: (d.zone == DW_MAX) as i32, ..Sp::default() };
1031            sp.place(self.dw_px(rt, p));
1032            sh.desk_snap(self, &sp);
1033        }
1034        // the record: everything of the window lies in its box, one level deeper (a11y.rs)
1035        let state = if max { DW_MAX } else { w.state() & !DW_MIN };
1036        let label = named(&title, state);
1037        self.wp.ctx.last_rect = r;
1038        self.wp.ctx.last_state = if active { SS_SELECTED as u32 } else { 0 };
1039        rt.open_rec = self.sems.len();
1040        let rec = self.sem(K_DIALOG, pid(id, P_WIN, key), Some(&label), state as i32);
1041        rec.min = super::a11y::SEM_SCOPE;
1042        self.next_rect_px(r[0], r[1], r[2], r[3]);
1043        self.box_begin(pid(id, P_BOX, key), BOX_V | BOX_FIXED, 0);
1044        // the window takes the pointer: nothing below it is hit
1045        self.wp.ctx.last_rect = r;
1046        let h0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1047        self.hit(pid(id, P_WIN, key), HB_BLOCK);
1048        rt.shown.push((key, r, h0, usize::MAX));
1049        let mut sp = Sp { value: active as i32, flags: max as u32, ..Sp::default() };
1050        sp.place(r);
1051        sh.desk_window(self, &sp);
1052        // the title bar: icon and title, the buttons at the end (the style's SM_CAP_LEFT: at the
1053        // start, close first)
1054        let b = if max { 0 } else { self.hair() };
1055        let tbh = self.dp(self.dw_tbh()).min((r[3] - b * 2).max(0));
1056        let bar = [r[0] + b, r[1] + b, (r[2] - b * 2).max(0), tbh];
1057        let capw = {
1058            let v = stl.metric(SM_CAP_W_TOOL);
1059            self.dp(if v > 0 { v } else { 40 })
1060        };
1061        let left = stl.metric(SM_CAP_LEFT) != 0;
1062        let mut btns: Vec<u32> = Vec::new();
1063        if flags & DWF_NOMIN == 0 {
1064            btns.push(P_MIN);
1065        }
1066        if flags & (DWF_NOMAX | DWF_FIXED) == 0 {
1067            btns.push(P_MAX);
1068        }
1069        if flags & DWF_NOCLOSE == 0 {
1070            if left {
1071                btns.insert(0, P_CLOSE);
1072            } else {
1073                btns.push(P_CLOSE);
1074            }
1075        }
1076        let nb = (btns.len() as i32).min(((bar[2] - self.dp(24)) / capw.max(1)).max(0));
1077        let bw = capw * nb;
1078        let bx = if left { bar[0] } else { bar[0] + bar[2] - bw };
1079        let pad = {
1080            let v = stl.metric(SM_WC_TITLE_PAD);
1081            self.dp(if v > 0 { v } else { 12 })
1082        };
1083        let tx = if left { bar[0] + bw + pad } else { bar[0] + pad };
1084        let tw = (bar[2] - bw - pad * 2).max(0);
1085        let mut tsp = Sp { label: Some(&title), icon, value: active as i32, ..Sp::default() };
1086        tsp.place(bar);
1087        tsp.s[0] = tx;
1088        tsp.s[1] = tw;
1089        sh.desk_titlebar(self, &tsp);
1090        let trect = if left { [bar[0] + bw, bar[1], bar[2] - bw, tbh] } else { [bar[0], bar[1], bar[2] - bw, tbh] };
1091        let tid = pid(id, P_TITLE, key);
1092        let ev = self.hit_rect(tid, trect[0], trect[1], trect[2], trect[3], 0);
1093        if ev & EV_HELD != 0 && ev & EV_DRAG != 0 && rt.drag.is_none() {
1094            self.dw_drag_start(rt, st, key, 0, place);
1095        } else if ev & EV_DOUBLE != 0 && flags & (DWF_NOMAX | DWF_FIXED) == 0 {
1096            rt.acts.push(Act::ToggleMax(key));
1097        }
1098        let menu = pid(id, P_MENU, key);
1099        if ev & EV_RCLICK != 0 {
1100            self.popup_open(menu);
1101            rt.menu_point = menu;
1102        }
1103        self.wp.ctx.last_rect = trect;
1104        self.wp.ctx.last_state = 0;
1105        self.sem(K_TITLEBAR, tid, Some(&title), 0);
1106        // the icon opens the window menu (it hangs below the icon)
1107        let isz = self.dp(16);
1108        let irect = [tx - pad / 2, bar[1], isz + pad, tbh];
1109        if icon != 0 && tw > isz {
1110            let iev = self.hit_rect(pid(id, P_ICON, key), irect[0], irect[1], irect[2], irect[3], 0);
1111            if iev & EV_ACT != 0 {
1112                self.popup_open(menu);
1113                rt.menu_point = 0;
1114            }
1115        } else {
1116            self.wp.ctx.last_rect = irect;
1117        }
1118        self.dw_menu(rt, st, key, flags, menu);
1119        // the buttons
1120        for (k, &part) in btns.iter().take(nb as usize).enumerate() {
1121            let x = bx + capw * k as i32;
1122            let bid = pid(id, part, key);
1123            let (hb, ic, lab) = match part {
1124                P_MIN => (HB_MIN, IC_CHROME_MIN, tr("Minimieren")),
1125                P_MAX if state & DW_MAX != 0 || (w.snap != 0 && !max) => (HB_MAX, IC_CHROME_RESTORE, tr("Wiederherstellen")),
1126                P_MAX => (HB_MAX, IC_CHROME_MAX, tr("Maximieren")),
1127                _ => (HB_CLOSE, IC_CHROME_CLOSE, tr("Schließen")),
1128            };
1129            let bev = self.hit_rect(bid, x, bar[1], capw, tbh, 0);
1130            let mut bsp = Sp { ev: bev, flags: hb, icon: ic, value: active as i32, ..Sp::default() };
1131            bsp.place([x, bar[1], capw, tbh]);
1132            sh.desk_button(self, &bsp);
1133            // named with the window's title (the buttons of every window can be told apart)
1134            self.sem(K_BUTTON, bid, Some(&alloc::format!("{}: {}", lab, title)), 0);
1135            if bev & EV_ACT != 0 {
1136                rt.acts.push(match part {
1137                    P_MIN => Act::Min(key),
1138                    P_MAX => Act::ToggleMax(key),
1139                    _ => Act::Close(key),
1140                });
1141            }
1142        }
1143        // the content
1144        let c = [r[0] + b, r[1] + b + tbh, (r[2] - b * 2).max(0), (r[3] - b * 2 - tbh).max(0)];
1145        self.next_rect_px(c[0], c[1], c[2], c[3]);
1146        self.box_begin(pid(id, P_CONTENT, key), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1147        self.focus_pane_begin(pid(id, P_PANE, key));
1148        rt.open_notab = !active;
1149        if !active {
1150            self.kb_notab(true); // only the active window has Tab stops
1151        }
1152        rt.open = key;
1153        rt.hits0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1154        rt.open_rect = r;
1155        rt.open_place = place;
1156        rt.open_grips = !max && flags & DWF_FIXED == 0;
1157    }
1158
1159    // the window menu of window key (popup `menu`): Wiederherstellen, Minimieren, Maximieren,
1160    // Schließen
1161    fn dw_menu(&mut self, rt: &mut Rt, st: &DeskState, key: u32, flags: u32, menu: u32) {
1162        let fl = if rt.menu_point == menu { FL_POINT } else { FL_BELOW };
1163        if self.flyout_begin(menu, fl) {
1164            let s = st.state(key);
1165            let id = rt.id;
1166            let rows: [(&'static str, bool, Act); 3] = [
1167                (tr("Wiederherstellen"), s & (DW_MAX | DW_MIN | DW_LEFT | DW_RIGHT) != 0, Act::Restore(key)),
1168                (tr("Minimieren"), flags & DWF_NOMIN == 0 && s & DW_MIN == 0, Act::Min(key)),
1169                (tr("Maximieren"), flags & (DWF_NOMAX | DWF_FIXED) == 0 && s & DW_MAX == 0, Act::ToggleMax(key)),
1170            ];
1171            for (k, (label, on, act)) in rows.into_iter().enumerate() {
1172                if !on {
1173                    self.next_flags(ST_DISABLED);
1174                }
1175                if self.menu_item(pid(id, P_MITEM + k as u32, key), label) & EV_ACT != 0 && on {
1176                    rt.acts.push(act);
1177                }
1178            }
1179            self.menu_sep();
1180            if flags & DWF_NOCLOSE != 0 {
1181                self.next_flags(ST_DISABLED);
1182            }
1183            if self.menu_item(pid(id, P_MITEM + 3, key), tr("Schließen")) & EV_ACT != 0 && flags & DWF_NOCLOSE == 0 {
1184                rt.acts.push(Act::Close(key));
1185            }
1186        } else if rt.menu_point == menu {
1187            rt.menu_point = 0;
1188        }
1189        self.flyout_end();
1190    }
1191
1192    // close what desk_next opened: the focus, the resize grips (they win over the content's
1193    // edge), the window's box
1194    fn dw_close(&mut self, rt: &mut Rt, st: &mut DeskState) {
1195        let key = core::mem::take(&mut rt.open);
1196        if key == 0 {
1197            return;
1198        }
1199        let id = rt.id;
1200        if rt.open_notab {
1201            self.kb_notab(false);
1202        }
1203        self.focus_pane_end();
1204        self.box_end(); // the content
1205        // the focus inside the window: remembered; arriving in a window behind, it brings it
1206        // to the front
1207        let focus = self.wp.ctx.focus;
1208        let (here, first, remembered) = {
1209            let c = &self.wp.ctx;
1210            let hits = &c.hits[c.hit_cur];
1211            let from = rt.hits0.min(hits.len());
1212            let mine = &hits[from..];
1213            let here = focus != 0 && mine.iter().any(|h| h.id == focus);
1214            let ok = |h: &&super::ctx::Hit| h.flags as u32 & (HB_FOCUS | HB_DISABLED) == HB_FOCUS && !h.continuation;
1215            let first = mine.iter().find(|h| ok(h) && h.flags as u32 & HB_NOTAB == 0).or_else(|| mine.iter().find(ok)).map(|h| h.id);
1216            let want = rt.wfocus.iter().find(|e| e.0 == key).map_or(0, |e| e.1);
1217            let remembered = if want != 0 && mine.iter().any(|h| h.id == want && ok(&h)) { Some(want) } else { None };
1218            (here, first, remembered)
1219        };
1220        if here {
1221            rt.focus_here = true;
1222            match rt.wfocus.iter_mut().find(|e| e.0 == key) {
1223                Some(e) => e.1 = focus,
1224                None => rt.wfocus.push((key, focus)),
1225            }
1226            if rt.focus_into == key {
1227                rt.focus_into = 0;
1228            }
1229            if st.active != key && focus != rt.last_focus && rt.drag.is_none() {
1230                rt.acts.push(Act::Activate(key));
1231            }
1232        } else if rt.focus_into == key {
1233            rt.focus_into = 0;
1234            match remembered.or(first) {
1235                Some(f) => {
1236                    let kb = rt.focus_kb;
1237                    self.focus(f);
1238                    self.wp.ctx.focus_visible = kb;
1239                    rt.focus_here = true;
1240                }
1241                // nothing to focus: the keys go to no window behind it
1242                None if rt.focus_in => self.wp.ctx.focus = 0,
1243                None => {}
1244            }
1245            rt.focus_kb = false;
1246        }
1247        // the resize grips: bands along the edges (mostly outside the frame), squares at the
1248        // corners
1249        if rt.open_grips {
1250            let r = rt.open_rect;
1251            let (o, n) = (self.dp(GRIP_OUT), self.dp(GRIP_IN));
1252            let cs = {
1253                let v = self.style.metric(SM_GRIP_CORNER);
1254                self.dp(if v > 0 { v } else { 12 })
1255            };
1256            let (x0, y0, x1, y1) = (r[0] - o, r[1] - o, r[0] + r[2] + o, r[1] + r[3] + o);
1257            let place = rt.open_place;
1258            for (k, &(edges, cur)) in GRIPS.iter().enumerate() {
1259                let g = match edges {
1260                    E_TOP => [x0 + cs, y0, x1 - x0 - cs * 2, o],
1261                    E_BOTTOM => [x0 + cs, y1 - o - n, x1 - x0 - cs * 2, o + n],
1262                    E_LEFT => [x0, y0 + cs, o + n, y1 - y0 - cs * 2],
1263                    E_RIGHT => [x1 - o - n, y0 + cs, o + n, y1 - y0 - cs * 2],
1264                    _ => {
1265                        let gx = if edges & E_LEFT != 0 { x0 } else { x1 - cs };
1266                        let gy = if edges & E_TOP != 0 { y0 } else { y1 - cs };
1267                        [gx, gy, cs, cs]
1268                    }
1269                };
1270                // right to left: the logical corners show mirrored, their arrows too
1271                let cur = match cur {
1272                    CUR_SIZE_NWSE if self.rtl => CUR_SIZE_NESW,
1273                    CUR_SIZE_NESW if self.rtl => CUR_SIZE_NWSE,
1274                    c => c,
1275                };
1276                let gid = pid(id, P_GRIP + k as u32, key);
1277                let ev = self.hit_rect(gid, g[0], g[1], g[2], g[3], cur << HB_CURSOR_SHIFT);
1278                if ev & EV_HELD != 0 && rt.drag.is_none() {
1279                    self.dw_drag_start(rt, st, key, edges, place);
1280                }
1281            }
1282        }
1283        self.box_end(); // the window
1284        let h1 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1285        if let Some(e) = rt.shown.iter_mut().rev().find(|e| e.0 == key) {
1286            e.3 = h1;
1287        }
1288        // the window's record holds the records emitted since (of its OS window): a11y.rs
1289        let at = rt.open_rec;
1290        if at < self.sems.len() && self.sems[at].id == pid(id, P_WIN, key) {
1291            let win = self.sems[at].win;
1292            let n = self.sems[at + 1..].iter().filter(|s| s.win == win).count();
1293            self.sems[at].max = n as i32;
1294        }
1295    }
1296
1297    // ------------------------------------------------------------------ changes
1298
1299    // apply one change -> true when the focus arriving in a window activated it
1300    fn dw_apply(&mut self, rt: &mut Rt, st: &mut DeskState, a: Act) -> bool {
1301        self.repass = true;
1302        match a {
1303            Act::Activate(k) => {
1304                st.activate(k);
1305                return true;
1306            }
1307            Act::Min(k) => {
1308                if st.state(k) & DW_MIN == 0 {
1309                    st.minimize(k);
1310                    rt.evs.push(DeskEv::State(k));
1311                }
1312            }
1313            Act::ToggleMax(k) => {
1314                st.toggle_max(k);
1315                rt.evs.push(DeskEv::State(k));
1316            }
1317            Act::Restore(k) => {
1318                st.restore(k);
1319                rt.evs.push(DeskEv::State(k));
1320            }
1321            Act::Close(k) => rt.evs.push(DeskEv::Close(k)),
1322            Act::Menu(k) => {
1323                let menu = pid(rt.id, P_MENU, k);
1324                self.popup_open(menu);
1325                self.pp_flag(menu, super::popup::PPF_FOCUS);
1326                self.wp.ctx.focus_visible = true;
1327                rt.menu_point = 0;
1328            }
1329            Act::Cycle(fwd) => {
1330                // only the windows the app describes take part
1331                let before = st.active;
1332                let infos = &rt.infos;
1333                if st.cycle_in(fwd, &|k| infos.iter().any(|i| i.key == k)).is_some() && st.active != before {
1334                    rt.focus_kb = true;
1335                }
1336            }
1337        }
1338        false
1339    }
1340
1341    // ------------------------------------------------------------------ taskbar
1342
1343    /// desk_taskbar_begin(id): open a taskbar for desk `id` (always pair with
1344    /// desk_taskbar_end): a row with the style's taskbar surface; the app's items and
1345    /// desk_taskbar go inside.  Inside desk_begin .. desk_end without next_size / next_rect it
1346    /// takes the area's reserved strip (DeskState::set_reserve: the bottom one, else the top
1347    /// one), else the next place, TASK_H DIP high unless next_size says otherwise.
1348    pub fn desk_taskbar_begin(&mut self, id: u32) {
1349        let l = &self.wp.lay;
1350        let no_size = l.next_w == 0 && l.next_h == 0 && l.next_rect_set == 0;
1351        let open = self.m.desk.cur.contains(&id);
1352        let mut placed = false;
1353        if no_size && open {
1354            if let Some(rt) = self.m.desk.desks.iter().find(|r| r.id == id) {
1355                let (a, w) = (rt.area, rt.work);
1356                let bottom = a[1] + a[3] - (w[1] + w[3]);
1357                let top = w[1] - a[1];
1358                if bottom > 0 {
1359                    self.next_rect_px(a[0], w[1] + w[3], a[2], bottom);
1360                    placed = true;
1361                } else if top > 0 {
1362                    self.next_rect_px(a[0], a[1], a[2], top);
1363                    placed = true;
1364                }
1365            }
1366        }
1367        if no_size && !placed {
1368            self.next_size(0, TASK_H);
1369        }
1370        self.box_begin(pid(id, P_BAR, 0), BOX_H | BOX_XCENTER | BOX_BLOCK | pad(4), 4);
1371        let mut sp = Sp::default();
1372        sp.place(self.wp.ctx.last_rect);
1373        self.style.shell().taskbar(self, &sp);
1374        self.wp.ctx.last_state = 0;
1375        self.sem(K_BOX, pid(id, P_BAR, 0), Some(tr("Taskleiste")), 0);
1376        self.focus_pane_begin(pid(id, P_BAR, 0)); // F6 reaches it
1377    }
1378
1379    /// desk_taskbar_end
1380    pub fn desk_taskbar_end(&mut self) {
1381        self.focus_pane_end();
1382        self.box_end();
1383    }
1384
1385    /// desk_taskbar(id, &mut st, flags): the buttons of desk `id`'s windows (DTB_*), in the
1386    /// order of desk_begin's list; without a size they take the room the row leaves.  Their
1387    /// changes apply at once; desk_end reports them.
1388    pub fn desk_taskbar(&mut self, id: u32, st: &mut DeskState, flags: u32) {
1389        let l = &self.wp.lay;
1390        if l.next_w == 0 && l.next_h == 0 && l.next_rect_set == 0 {
1391            // the rest of the row, as high as the row
1392            let h = self.dw_dip(self.box_rect()[3]).max(1);
1393            self.next_size(SIZE_FILL, h);
1394        }
1395        self.box_begin(pid(id, P_TASK, 0), BOX_H | BOX_FIXED | BOX_CLIP, 0);
1396        let area = self.box_rect();
1397        let mut rt = self.dw_take(id);
1398        let sh = self.style.shell();
1399        let wins: Vec<(u32, String, u32, u32)> = rt
1400            .infos
1401            .iter()
1402            .filter(|i| i.flags & DWF_NOTASK == 0 && st.contains(i.key))
1403            .map(|i| (i.key, i.title.clone(), i.icon, i.flags))
1404            .collect();
1405        // the widths: natural, between TASK_MIN and TASK_MAX; what does not fit goes into the
1406        // "Weitere Fenster" menu (the active window stays in the row)
1407        let icons = flags & DTB_ICONS != 0;
1408        let gap = self.dp(TASK_GAP);
1409        let mut ws: Vec<i32> = Vec::with_capacity(wins.len());
1410        for (_, t, ic, _) in &wins {
1411            let mut tsp = Sp { label: Some(t), icon: *ic, flags, ..Sp::default() };
1412            let (w, _) = sh.taskbar_button_size(self, &mut tsp);
1413            ws.push(if icons { w } else { w.clamp(self.dp(TASK_MIN), self.dp(TASK_MAX)) });
1414        }
1415        let total: i32 = ws.iter().sum::<i32>() + gap * (ws.len() as i32 - 1).max(0);
1416        let mut shown: Vec<usize> = (0..wins.len()).collect();
1417        let mut over: Vec<usize> = Vec::new();
1418        if total > area[2] && !wins.is_empty() {
1419            // shrink to the minimum first
1420            let minw = if icons { ws.iter().copied().max().unwrap_or(0) } else { self.dp(TASK_MIN) };
1421            let n = wins.len() as i32;
1422            if minw * n + gap * (n - 1) <= area[2] {
1423                let each = ((area[2] - gap * (n - 1)) / n).max(minw);
1424                for w in ws.iter_mut() {
1425                    *w = (*w).min(each);
1426                }
1427            } else {
1428                let ow = self.dp(OVER_W);
1429                let k = ((area[2] - ow + gap) / (minw + gap)).max(0) as usize;
1430                for w in ws.iter_mut() {
1431                    *w = minw;
1432                }
1433                over = shown.split_off(k.min(shown.len()));
1434                if let Some(p) = over.iter().position(|&i| wins[i].0 == st.active) {
1435                    let a = over.remove(p);
1436                    if let Some(last) = shown.pop() {
1437                        over.insert(0, last);
1438                    }
1439                    shown.push(a);
1440                }
1441            }
1442        }
1443        // the buttons are one Tab stop (the arrows move between them)
1444        self.focus_group_begin(pid(id, P_TASK, 1));
1445        let mut x = area[0];
1446        for &k in &shown {
1447            let (key, ref title, icon, wf) = wins[k];
1448            let s = st.state(key);
1449            let w = ws[k];
1450            let r = [x, area[1], w, area[3]];
1451            x += w + gap;
1452            let bid = pid(id, P_TBTN, key);
1453            let ev = self.hit_rect(bid, r[0], r[1], r[2], r[3], HB_FOCUS);
1454            let state = self.wp.ctx.last_state;
1455            let on = st.active == key && s & DW_MIN == 0;
1456            let mut bsp = Sp { ev, label: Some(title), icon, value: on as i32, value2: (s & DW_MIN != 0) as i32, flags, ..Sp::default() };
1457            bsp.place(r);
1458            sh.taskbar_button(self, &bsp);
1459            if self.focus_ring(ev) {
1460                let stl = self.style;
1461                stl.focus_ring(self, r, 4);
1462            }
1463            self.wp.ctx.last_rect = r;
1464            self.wp.ctx.last_state = state | if on { SS_CHECKED as u32 } else { 0 };
1465            let name = named(title, s & DW_MIN);
1466            self.sem(K_BUTTON, bid, Some(&name), s as i32);
1467            if ev & EV_ACT != 0 {
1468                if on {
1469                    if wf & DWF_NOMIN == 0 {
1470                        st.minimize(key);
1471                        rt.evs.push(DeskEv::State(key));
1472                    }
1473                } else {
1474                    if s & DW_MIN != 0 {
1475                        rt.evs.push(DeskEv::State(key));
1476                    }
1477                    st.activate(key);
1478                    rt.focus_kb = self.wp.ctx.focus_visible;
1479                }
1480                self.repass = true;
1481            }
1482            // its menu at the button (a right click: at the pointer)
1483            let menu = pid(id, P_TMENU, key);
1484            if ev & EV_RCLICK != 0 {
1485                self.popup_open(menu);
1486                let kb = self.kb_ctx_rect().is_some();
1487                if kb {
1488                    self.pp_flag(menu, super::popup::PPF_FOCUS);
1489                }
1490                rt.tmenu_point = if kb { 0 } else { menu };
1491            }
1492            self.wp.ctx.last_rect = r;
1493            let fl = if rt.tmenu_point == menu { FL_POINT } else { FL_ABOVE };
1494            if self.flyout_begin(menu, fl) {
1495                let rows: [(&'static str, bool); 4] = [
1496                    (tr("Wiederherstellen"), s & (DW_MAX | DW_MIN | DW_LEFT | DW_RIGHT) != 0),
1497                    (tr("Minimieren"), wf & DWF_NOMIN == 0 && s & DW_MIN == 0),
1498                    (tr("Maximieren"), wf & (DWF_NOMAX | DWF_FIXED) == 0 && s & DW_MAX == 0),
1499                    (tr("Schließen"), wf & DWF_NOCLOSE == 0),
1500                ];
1501                for (j, (label, ok)) in rows.into_iter().enumerate() {
1502                    if j == 3 {
1503                        self.menu_sep();
1504                    }
1505                    if !ok {
1506                        self.next_flags(ST_DISABLED);
1507                    }
1508                    if self.menu_item(pid(id, P_TITEM + j as u32, key), label) & EV_ACT != 0 && ok {
1509                        match j {
1510                            0 => st.restore(key),
1511                            1 => st.minimize(key),
1512                            2 => st.maximize(key),
1513                            _ => {}
1514                        }
1515                        rt.evs.push(if j == 3 { DeskEv::Close(key) } else { DeskEv::State(key) });
1516                        self.repass = true;
1517                    }
1518                }
1519            } else if rt.tmenu_point == menu {
1520                rt.tmenu_point = 0;
1521            }
1522            self.flyout_end();
1523        }
1524        self.focus_group_end();
1525        // the windows that do not fit
1526        if !over.is_empty() {
1527            let ow = self.dp(OVER_W);
1528            let r = [area[0] + area[2] - ow, area[1], ow, area[3]];
1529            let oid = pid(id, P_OVER, 0);
1530            self.next_rect_px(r[0], r[1], r[2], r[3]);
1531            self.next_icon(IC_MORE);
1532            self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1533            if self.button(oid, tr("Weitere Fenster")) & EV_ACT != 0 {
1534                self.popup_open(oid);
1535                if self.wp.ctx.focus_visible {
1536                    self.pp_flag(oid, super::popup::PPF_FOCUS);
1537                }
1538            }
1539            if self.flyout_begin(oid, FL_ABOVE | FL_ALIGN_END) {
1540                for &k in &over {
1541                    let (key, ref title, icon, _) = wins[k];
1542                    let s = st.state(key);
1543                    if icon != 0 {
1544                        self.next_icon(icon);
1545                    }
1546                    if self.menu_item(pid(id, P_OITEM, key), &named(title, s & DW_MIN)) & EV_ACT != 0 {
1547                        if s & DW_MIN != 0 {
1548                            rt.evs.push(DeskEv::State(key));
1549                        }
1550                        st.activate(key);
1551                        rt.focus_kb = self.wp.ctx.focus_visible;
1552                        self.repass = true;
1553                    }
1554                }
1555            }
1556            self.flyout_end();
1557        }
1558        // the keyboard in the taskbar counts as inside the area (Alt+F6 ...), also when the
1559        // taskbar comes after desk_end
1560        let focus = self.wp.ctx.focus;
1561        if focus != 0 && (wins.iter().any(|w| pid(id, P_TBTN, w.0) == focus) || focus == pid(id, P_OVER, 0)) {
1562            rt.focus_here = true;
1563            rt.focus_in = true;
1564        }
1565        self.dw_put(rt);
1566        self.box_end();
1567    }
1568}