Skip to main content

ui/
dock.rs

1// dock -- a docking area: the app's panels at the sides of a document area (left, right,
2// bottom) in tab groups with splitters, dragged to other places with drop markers, floating in
3// tool windows and docked again, hidden, collapsed to a rail; the layout is the app's DockState
4//
5//   dock_begin(id, &mut st, panels)   the area takes the next place (without next_size it fills
6//                                     the box); panels describe every panel each frame
7//                                     (DockPanel: id != 0, title, icon, DP_* flags, first place)
8//   dock_next(&mut st) -> Some(id)    the next place to fill: DOCK_CENTER (the document area)
9//                                     first, then the shown panel of every group, the floating
10//                                     windows last; emit its content (a vertical box that clips)
11//                                     and call it again until None
12//   dock_end(&mut st) -> changed      true when the user changed the layout (save it then)
13//   DockState                         the layout, kept by the app: save() -> text, load(text),
14//                                     reset, show / hide / toggle / focus / activate, float /
15//                                     dock_to, float_group / dock_group_to (a whole group),
16//                                     collapse / collapsed, size / set_size, groups /
17//                                     floats / hidden_panels, side / visible / front / shown
18//
19// Places: a side holds tab groups (left / right: one above the other, bottom: side by side)
20// with splitters between them and towards the document area; a collapsed side is a rail of
21// icon buttons.  Left and right are the start and the end side: the layout is logical, so a
22// mirrored (RTL) window shows the left side at the right like everything else.
23// A press on a tab (or on the empty part of a tab strip: the whole group) can drag only after it
24// was held for DOCK_HOLD_MS: then the tab shows it (DOCK_TAB_HOLD, the move cursor; a timer gives
25// the pass while the pointer rests), and the drag starts when the pointer moves past the usual
26// threshold from where it is at that moment.  Movement during the hold cancels nothing and never
27// counts (a slip or a tremor starts nothing), a release without a drag is a click if it is over
28// the tab.  Clicks, double clicks, the menu and the splitters need no hold.
29// A drag shows drop markers at the three edges of the area (a new group on that side) and on the
30// group under the pointer (as a tab, before, after it); a release over a tab strip inserts it
31// there; anywhere else -- also outside the window -- the panel floats in a tool window at the
32// pointer (WF_TOOL, window ids id + 1 .. id + DOCK_FLOAT_MAX).  Tabs of a floating window drag the
33// same way onto the main window's markers (the pointer stays with the floating window); its pin
34// button or a double click docks it where it came from, its close button hides its panels.  Esc
35// cancels a hold and a drag (the release is no click then).
36// Keyboard: the tab strip of a group is one focus stop (Left / Right / Home / End switch tabs,
37// the menu key or Shift+F10 open the group's menu with every move), Ctrl+PgDn / Ctrl+PgUp switch
38// tabs while the focus is anywhere in the group, DockState::focus(id) brings a panel forward
39// and gives its tab strip the focus (the app binds it to its commands).
40// Structural changes (clicks, drops, menu rows) are applied at dock_end, followed by another
41// pass; splitters change the sizes at once.  Widget ids are parts of the area id (pid).
42
43use alloc::string::String;
44use alloc::vec::Vec;
45
46use super::consts::*;
47use super::keys::*;
48use super::layout::{dp_at, px2dip_at};
49use super::win::Shared;
50use super::{Sp, Ui};
51
52/// the sides of a dock area (DockPanel::at, DockState::side); left / right are the start / end
53/// side (a mirrored window shows them the other way round)
54pub const DOCK_LEFT: u8 = 0;
55pub const DOCK_RIGHT: u8 = 1;
56pub const DOCK_BOTTOM: u8 = 2;
57/// a floating tool window
58pub const DOCK_FLOAT: u8 = 3;
59/// dock_next: the document area in the middle
60pub const DOCK_CENTER: u32 = 0;
61/// DockPanel flags: no hide button or menu row (the app shows and hides it)
62pub const DP_NOCLOSE: u32 = 1;
63/// never floats (a drop outside the markers does nothing)
64pub const DP_NOFLOAT: u32 = 2;
65/// starts hidden (until DockState::show / focus); later the layout remembers it
66pub const DP_HIDDEN: u32 = 4;
67/// its group moves as a whole: when it is the shown tab, the group menu's "Links / Rechts /
68/// Unten andocken" and "Lösen" and a double click on the strip's empty part take every tab of
69/// the group (panels that belong together, such as an app's developer tools;
70/// DockState::dock_group_to / float_group)
71pub const DP_GROUP: u32 = 8;
72/// floating windows of one dock area at most (fixed: each is a window, WIN_MAX)
73pub const DOCK_FLOAT_MAX: usize = 6;
74/// drop marker kinds (StyleShell::dock_marker, sp.value): the part of the place it fills
75pub const DOCK_MK_LEFT: i32 = 0;
76pub const DOCK_MK_RIGHT: i32 = 1;
77pub const DOCK_MK_TOP: i32 = 2;
78pub const DOCK_MK_BOTTOM: i32 = 3;
79pub const DOCK_MK_CENTER: i32 = 4;
80/// a press on a tab or on the empty part of a tab strip can drag only after this many ms with
81/// the button down, so that a slip of the hand during a click neither moves nor floats a panel;
82/// clicks, double clicks (they float), the menu and the splitters need no hold
83pub const DOCK_HOLD_MS: u32 = 300;
84/// StyleShell::dock_tab, sp.flags: the press was held long enough, the tab can be moved now (a
85/// press on the strip's empty part: every tab of the group, they would all move)
86pub const DOCK_TAB_HOLD: u32 = 1;
87
88const UIT_HOLD: u32 = 0x7FFE_0050; // timer: the hold is over (a resting pointer gets no other pass)
89
90// metrics (DIP)
91const HDR: i32 = 32; // tab strip of a group, unless the style's SM_DOCK_HDR says otherwise
92const BTN: i32 = 28; // its buttons (SM_DOCK_BTN)
93const GRIP: i32 = 6; // splitters
94const RAIL: i32 = 36; // a collapsed side
95const SIDE_MIN: i32 = 120;
96const CENTER_MIN: i32 = 160;
97const GROUP_MIN: i32 = 64;
98const MARK: i32 = 36; // a drop marker
99const SIDE_DEF: [i32; 3] = [260, 280, 200];
100const GROUP_DEF: i32 = 200;
101const FLOAT_W: (i32, i32) = (200, 480); // the client size a floated group gets (clamped)
102const FLOAT_H: (i32, i32) = (160, 480);
103const TAG_USER: u32 = 0x100; // tags of the groups the user made (below: DockPanel groups)
104const SIZE_MAX: i32 = 8192;
105
106// id parts (pid)
107const P_AREA: u32 = 1;
108const P_CENTER: u32 = 2;
109const P_CONTENT: u32 = 3;
110const P_TAB: u32 = 4;
111const P_STRIP: u32 = 5;
112const P_MENU: u32 = 6;
113const P_BTN1: u32 = 7;
114const P_BTN2: u32 = 8;
115const P_GRIP: u32 = 9;
116const P_GGRIP: u32 = 10;
117const P_RAIL: u32 = 11;
118const P_MARK: u32 = 12;
119const P_MITEM: u32 = 16; // .. + 8
120
121// a widget id of dock area `dock`: part `part` of the panel / group `a`
122fn pid(dock: u32, part: u32, a: u32) -> u32 {
123    let mut h = dock.wrapping_mul(0x9E37_79B1) ^ part.wrapping_mul(0x85EB_CA6B) ^ a.wrapping_mul(0xC2B2_AE35);
124    h ^= h >> 15;
125    h = h.wrapping_mul(0x2C1B_3C6D);
126    h ^= h >> 12;
127    h | 0x8000_0000
128}
129
130// the key of a group: side and tag (floating windows: DOCK_FLOAT and the slot)
131fn gkey(side: u8, tag: u32) -> u32 {
132    (side as u32) << 24 | (tag & 0x00FF_FFFF)
133}
134
135fn inside(r: [i32; 4], p: [i32; 2]) -> bool {
136    p[0] >= r[0] && p[0] < r[0] + r[2] && p[1] >= r[1] && p[1] < r[1] + r[3]
137}
138
139/// a panel as the app describes it to dock_begin every frame
140#[derive(Clone, Copy, Debug)]
141pub struct DockPanel<'a> {
142    /// != 0 and stable (the saved layout refers to it)
143    pub id: u32,
144    pub title: &'a str,
145    /// IC_* of its tab and of the rail of a collapsed side (0 = none)
146    pub icon: u32,
147    /// DP_*
148    pub flags: u32,
149    /// where it goes the first time: DOCK_LEFT / DOCK_RIGHT / DOCK_BOTTOM / DOCK_FLOAT
150    pub side: u8,
151    /// panels of one side with the same group number start as tabs of one group; the groups
152    /// in the order of their numbers
153    pub group: u8,
154}
155
156impl<'a> DockPanel<'a> {
157    /// a panel that starts in group 0 of the left side
158    pub const fn new(id: u32, title: &'a str, icon: u32) -> DockPanel<'a> {
159        DockPanel { id, title, icon, flags: 0, side: DOCK_LEFT, group: 0 }
160    }
161    /// its first place: side (DOCK_*) and group number
162    pub const fn at(mut self, side: u8, group: u8) -> DockPanel<'a> {
163        self.side = side;
164        self.group = group;
165        self
166    }
167    /// add DP_* flags
168    pub const fn flags(mut self, f: u32) -> DockPanel<'a> {
169        self.flags |= f;
170        self
171    }
172}
173
174// ------------------------------------------------------------------ the layout (DockState)
175
176#[derive(Clone, Default, Debug, PartialEq, Eq)]
177struct Group {
178    /// unique on its side (DockPanel::group for the first groups, from TAG_USER for new ones)
179    tag: u32,
180    /// share of the side's length (DIP when it was last set; 0 = GROUP_DEF)
181    size: i32,
182    /// the shown tab
183    active: u32,
184    panels: Vec<u32>,
185}
186
187#[derive(Clone, Default, Debug, PartialEq, Eq)]
188struct Side {
189    /// width (left / right) or height (bottom) in DIP, 0 = the default
190    size: i32,
191    collapsed: bool,
192    groups: Vec<Group>,
193}
194
195#[derive(Clone, Default, Debug, PartialEq, Eq)]
196struct Float {
197    slot: u8,
198    /// x, y: outer top left in DIP from the main window's client origin (physical); client
199    /// width and height (DIP) -- a WinRect
200    rect: [i32; 4],
201    /// where the pin button docks it
202    home: u8,
203    home_tag: u32,
204    group: Group,
205}
206
207// a hidden panel and where it comes back (also: where a panel was before it moved)
208#[derive(Clone, Default, Debug, PartialEq, Eq)]
209struct Hidden {
210    panel: u32,
211    /// DOCK_* (DOCK_FLOAT: a floating window at rect)
212    side: u8,
213    tag: u32,
214    pos: u32,
215    rect: [i32; 4],
216    home: u8,
217    home_tag: u32,
218    /// the tab order of its group when it left, the group's panels hidden before it included
219    /// (a panel comes back between the neighbours it had, whichever of them are back already;
220    /// empty: pos counts)
221    order: Vec<u32>,
222}
223
224// the visible tabs of a group in their order, with the panels of an order of before (a hidden
225// one's) put between them where that order had them
226fn merge_order(visible: &[u32], before: &[u32]) -> Vec<u32> {
227    let mut out: Vec<u32> = visible.to_vec();
228    for (i, &m) in before.iter().enumerate() {
229        if out.contains(&m) {
230            continue;
231        }
232        // after the nearest panel before it there that is placed already, else at the front
233        let at = before[..i].iter().rev().find_map(|q| out.iter().position(|x| x == q)).map_or(0, |k| k + 1);
234        out.insert(at, m);
235    }
236    out
237}
238
239#[derive(Clone, Copy, PartialEq, Eq, Debug)]
240enum Loc {
241    None,
242    Side(usize, usize, usize),
243    Float(usize, usize),
244    Hidden(usize),
245}
246
247/// the layout of a dock area: kept by the app and passed to dock_begin / dock_next /
248/// dock_end.  save() / load() turn it into a few lines of text: `dock 1`, then `side S SIZE
249/// COLLAPSED`, `group S TAG SIZE ACTIVE PANEL...`, `float SLOT X Y W H HOME HOMETAG ACTIVE
250/// PANEL...`, `hidden PANEL S TAG POS X Y W H HOME HOMETAG [ORDER...]` (ORDER: its group's tab
251/// order when it left; unknown lines are skipped).
252#[derive(Clone, Default, Debug, PartialEq, Eq)]
253pub struct DockState {
254    sides: [Side; 3],
255    floats: Vec<Float>,
256    hidden: Vec<Hidden>,
257    /// a pending DockState::focus (not saved)
258    focus_req: u32,
259}
260
261impl DockState {
262    /// the empty layout: every panel goes to its DockPanel place in the first frame
263    pub fn new() -> DockState {
264        DockState::default()
265    }
266
267    fn loc(&self, id: u32) -> Loc {
268        for (s, sd) in self.sides.iter().enumerate() {
269            for (g, gr) in sd.groups.iter().enumerate() {
270                if let Some(t) = gr.panels.iter().position(|&p| p == id) {
271                    return Loc::Side(s, g, t);
272                }
273            }
274        }
275        for (f, fl) in self.floats.iter().enumerate() {
276            if let Some(t) = fl.group.panels.iter().position(|&p| p == id) {
277                return Loc::Float(f, t);
278            }
279        }
280        match self.hidden.iter().position(|h| h.panel == id) {
281            Some(h) => Loc::Hidden(h),
282            None => Loc::None,
283        }
284    }
285
286    // take panel id out of its place -> where it was (None: the layout does not know it); an
287    // emptied group or floating window goes away
288    fn take(&mut self, id: u32) -> Option<Hidden> {
289        fn unlink(g: &mut Group, t: usize, id: u32) {
290            g.panels.remove(t);
291            if g.active == id {
292                let n = g.panels.len();
293                g.active = if n == 0 { 0 } else { g.panels[t.min(n - 1)] };
294            }
295        }
296        match self.loc(id) {
297            Loc::Side(s, g, t) => {
298                let tag = self.sides[s].groups[g].tag;
299                // the group's order with the panels hidden from it before (the latest one knows it)
300                let earlier = self.hidden.iter().rev().find(|h| h.side == s as u8 && h.tag == tag && !h.order.is_empty());
301                let order = merge_order(&self.sides[s].groups[g].panels, earlier.map_or(&[][..], |h| &h.order));
302                let gr = &mut self.sides[s].groups[g];
303                unlink(gr, t, id);
304                if gr.panels.is_empty() {
305                    self.sides[s].groups.remove(g);
306                }
307                Some(Hidden { panel: id, side: s as u8, tag, pos: t as u32, rect: [0; 4], home: s as u8, home_tag: tag, order })
308            }
309            Loc::Float(f, t) => {
310                let fl = &mut self.floats[f];
311                unlink(&mut fl.group, t, id);
312                let h = Hidden { panel: id, side: DOCK_FLOAT, tag: 0, pos: t as u32, rect: fl.rect, home: fl.home, home_tag: fl.home_tag, order: Vec::new() };
313                if fl.group.panels.is_empty() {
314                    self.floats.remove(f);
315                }
316                Some(h)
317            }
318            Loc::Hidden(h) => Some(self.hidden.remove(h)),
319            Loc::None => None,
320        }
321    }
322
323    // a tag no group of side s has (the user's groups: from TAG_USER)
324    fn new_tag(&self, s: usize) -> u32 {
325        self.sides[s].groups.iter().map(|g| g.tag + 1).max().unwrap_or(0).max(TAG_USER)
326    }
327
328    // ids as tabs of the group (s, tag) from pos on (the group is made at the end when there is
329    // none); `active` is shown, the side expands
330    fn put_tabs(&mut self, s: usize, tag: u32, pos: usize, ids: &[u32], active: u32) {
331        let sd = &mut self.sides[s];
332        let g = match sd.groups.iter().position(|g| g.tag == tag) {
333            Some(g) => g,
334            None => {
335                sd.groups.push(Group { tag, ..Group::default() });
336                sd.groups.len() - 1
337            }
338        };
339        let gr = &mut sd.groups[g];
340        let pos = pos.min(gr.panels.len());
341        for (k, &p) in ids.iter().enumerate() {
342            gr.panels.insert(pos + k, p);
343        }
344        gr.active = active;
345        sd.collapsed = false;
346    }
347
348    // ids as a new group of side s: before / after the group with tag `at` (it gives half of
349    // its share), else at the end
350    fn put_group(&mut self, s: usize, at: Option<(u32, bool)>, ids: &[u32], active: u32) {
351        let tag = self.new_tag(s);
352        let sd = &mut self.sides[s];
353        let mut size = 0;
354        let mut i = sd.groups.len();
355        if let Some((t, after)) = at {
356            if let Some(g) = sd.groups.iter().position(|g| g.tag == t) {
357                let half = (if sd.groups[g].size > 0 { sd.groups[g].size } else { GROUP_DEF } / 2).max(1);
358                sd.groups[g].size = half;
359                size = half;
360                i = g + after as usize;
361            }
362        }
363        sd.groups.insert(i, Group { tag, size, active, panels: ids.to_vec() });
364        sd.collapsed = false;
365    }
366
367    // ids in a new floating window -> false when every slot is taken
368    fn put_float(&mut self, rect: [i32; 4], home: u8, home_tag: u32, ids: &[u32], active: u32) -> bool {
369        let Some(slot) = (0..DOCK_FLOAT_MAX as u8).find(|k| !self.floats.iter().any(|f| f.slot == *k)) else {
370            return false;
371        };
372        let group = Group { tag: 0, size: 0, active, panels: ids.to_vec() };
373        self.floats.push(Float { slot, rect, home: home.min(DOCK_BOTTOM), home_tag, group });
374        true
375    }
376
377    // where panel id goes back into group (s, tag): before the first tab of the group that came
378    // after it in order, else after the last one that came before it (None: order says nothing)
379    fn order_pos(&self, s: usize, tag: u32, order: &[u32], id: u32) -> Option<usize> {
380        let gr = self.sides[s].groups.iter().find(|g| g.tag == tag)?;
381        let at = order.iter().position(|&p| p == id)?;
382        let pos = |q: &u32| gr.panels.iter().position(|p| p == q);
383        order[at + 1..].iter().find_map(pos).or_else(|| order[..at].iter().rev().find_map(pos).map(|k| k + 1))
384    }
385
386    // a place taken with take: the panel goes back there (between the neighbours it had)
387    fn restore(&mut self, h: Hidden) {
388        let id = h.panel;
389        if h.side < DOCK_FLOAT {
390            let pos = self.order_pos(h.side as usize, h.tag, &h.order, id).unwrap_or(h.pos as usize);
391            self.put_tabs(h.side as usize, h.tag, pos, &[id], id);
392        } else if !self.put_float(h.rect, h.home, h.home_tag, &[id], id) {
393            self.put_tabs(h.home.min(DOCK_BOTTOM) as usize, h.home_tag, usize::MAX, &[id], id);
394        }
395    }
396
397    fn cascade(&self) -> [i32; 4] {
398        let k = self.floats.len() as i32;
399        [40 + 32 * k, 80 + 32 * k, 280, 320]
400    }
401
402    // panels the layout does not know yet go to their DockPanel places
403    fn adopt(&mut self, panels: &[DockPanel]) {
404        for p in panels {
405            if p.id == 0 || self.loc(p.id) != Loc::None {
406                continue;
407            }
408            let tag = p.group as u32;
409            let float = p.side >= DOCK_FLOAT;
410            let home = if float { DOCK_RIGHT } else { p.side };
411            let rect = self.cascade();
412            if p.flags & DP_HIDDEN != 0 {
413                let side = if float { DOCK_FLOAT } else { home };
414                self.hidden.push(Hidden { panel: p.id, side, tag, pos: u32::MAX, rect, home, home_tag: tag, order: Vec::new() });
415                continue;
416            }
417            if float && self.put_float(rect, home, tag, &[p.id], p.id) {
418                continue;
419            }
420            let sd = &mut self.sides[home as usize];
421            match sd.groups.iter().position(|g| g.tag == tag) {
422                Some(g) => {
423                    let gr = &mut sd.groups[g];
424                    gr.panels.push(p.id);
425                    if gr.active == 0 {
426                        gr.active = p.id;
427                    }
428                }
429                None => {
430                    let i = sd.groups.iter().position(|g| g.tag > tag).unwrap_or(sd.groups.len());
431                    sd.groups.insert(i, Group { tag, size: 0, active: p.id, panels: alloc::vec![p.id] });
432                }
433            }
434        }
435    }
436
437    /// forget the layout: every panel goes to its DockPanel place in the next frame
438    pub fn reset(&mut self) {
439        *self = DockState::default();
440    }
441
442    /// show a hidden panel where it was, as the shown tab of its group
443    pub fn show(&mut self, id: u32) {
444        if let Loc::Hidden(_) = self.loc(id) {
445            if let Some(h) = self.take(id) {
446                self.restore(h);
447            }
448        }
449        self.activate(id);
450    }
451
452    /// hide a panel (show brings it back to the same place)
453    pub fn hide(&mut self, id: u32) {
454        if matches!(self.loc(id), Loc::Side(..) | Loc::Float(..)) {
455            if let Some(h) = self.take(id) {
456                self.hidden.push(h);
457            }
458        }
459    }
460
461    /// show a hidden panel, hide a visible one
462    pub fn toggle(&mut self, id: u32) {
463        if self.visible(id) {
464            self.hide(id);
465        } else {
466            self.show(id);
467        }
468    }
469
470    /// show the panel as the shown tab (a collapsed side expands) and give its tab strip the
471    /// keyboard focus in the next pass (a floating window comes to the front)
472    pub fn focus(&mut self, id: u32) {
473        self.show(id);
474        if self.visible(id) {
475            self.focus_req = id;
476        }
477    }
478
479    /// make the panel the shown tab of its group (a collapsed side expands)
480    pub fn activate(&mut self, id: u32) {
481        match self.loc(id) {
482            Loc::Side(s, g, _) => {
483                self.sides[s].groups[g].active = id;
484                self.sides[s].collapsed = false;
485            }
486            Loc::Float(f, _) => self.floats[f].group.active = id,
487            _ => {}
488        }
489    }
490
491    /// float the panel in a tool window: rect = x, y (DIP from the main window's client
492    /// origin), client width, height; None: cascaded from the top left -> false when every
493    /// floating window of the area is in use
494    pub fn float(&mut self, id: u32, rect: Option<[i32; 4]>) -> bool {
495        if let Loc::Float(f, _) = self.loc(id) {
496            if self.floats[f].group.panels.len() == 1 {
497                if let Some(r) = rect {
498                    self.floats[f].rect = r;
499                }
500                return true;
501            }
502        }
503        let r = rect.unwrap_or_else(|| self.cascade());
504        let Some(h) = self.take(id) else { return false };
505        let (home, home_tag) = if h.side < DOCK_FLOAT { (h.side, h.tag) } else { (h.home, h.home_tag) };
506        if self.put_float(r, home, home_tag, &[id], id) {
507            return true;
508        }
509        self.restore(h);
510        false
511    }
512
513    /// dock the panel to a side, as a new group at its end
514    pub fn dock_to(&mut self, id: u32, side: u8) {
515        if self.take(id).is_some() {
516            self.put_group(side.min(DOCK_BOTTOM) as usize, None, &[id], id);
517        }
518    }
519
520    // the panels of the group or floating window of panel id and its shown tab, taken out of
521    // the layout (None: hidden or unknown; a group already on side `keep` stays)
522    fn take_group(&mut self, id: u32, keep: usize) -> Option<(Vec<u32>, u32)> {
523        match self.loc(id) {
524            Loc::Side(s, g, _) if s != keep => {
525                let gr = self.sides[s].groups.remove(g);
526                Some((gr.panels, gr.active))
527            }
528            Loc::Float(f, _) => {
529                let fl = self.floats.remove(f);
530                Some((fl.group.panels, fl.group.active))
531            }
532            _ => None,
533        }
534    }
535
536    /// dock the whole group of the panel to a side, as a new group at its end: every tab in its
537    /// order, the shown one stays shown (a floating window's group too; a group already on
538    /// that side stays where it is)
539    pub fn dock_group_to(&mut self, id: u32, side: u8) {
540        let s = side.min(DOCK_BOTTOM) as usize;
541        if let Some((panels, active)) = self.take_group(id, s) {
542            self.put_group(s, None, &panels, active);
543        }
544    }
545
546    /// float the whole group of the panel in a tool window (rect as for float; its pin button
547    /// docks it back on its side) -> false when every floating window of the area is in use or
548    /// the panel is hidden; a group that floats already only moves to rect
549    pub fn float_group(&mut self, id: u32, rect: Option<[i32; 4]>) -> bool {
550        if let Loc::Float(f, _) = self.loc(id) {
551            if let Some(r) = rect {
552                self.floats[f].rect = r;
553            }
554            return true;
555        }
556        let Loc::Side(s, g, _) = self.loc(id) else { return false };
557        if self.floats.len() >= DOCK_FLOAT_MAX {
558            return false;
559        }
560        let r = rect.unwrap_or_else(|| self.cascade());
561        let gr = self.sides[s].groups.remove(g);
562        self.put_float(r, s as u8, gr.tag, &gr.panels, gr.active)
563    }
564
565    // every panel of floating window `slot` back to where it came from
566    fn dock_home(&mut self, slot: u8) {
567        let Some(f) = self.floats.iter().position(|f| f.slot == slot) else { return };
568        let fl = self.floats.remove(f);
569        let s = fl.home.min(DOCK_BOTTOM) as usize;
570        self.put_tabs(s, fl.home_tag, usize::MAX, &fl.group.panels, fl.group.active);
571    }
572
573    // every panel of floating window `slot` hidden (its close button)
574    fn hide_float(&mut self, slot: u8) {
575        if let Some(f) = self.floats.iter().position(|f| f.slot == slot) {
576            for p in self.floats[f].group.panels.clone() {
577                self.hide(p);
578            }
579        }
580    }
581
582    /// collapse a side to a rail of icon buttons, or expand it
583    pub fn collapse(&mut self, side: u8, on: bool) {
584        if let Some(s) = self.sides.get_mut(side as usize) {
585            s.collapsed = on;
586        }
587    }
588
589    /// the side is collapsed
590    pub fn collapsed(&self, side: u8) -> bool {
591        self.sides.get(side as usize).is_some_and(|s| s.collapsed)
592    }
593
594    /// the width (left / right) or the height (bottom) of a side in DIP
595    pub fn size(&self, side: u8) -> i32 {
596        let s = side.min(DOCK_BOTTOM) as usize;
597        if self.sides[s].size > 0 { self.sides[s].size } else { SIDE_DEF[s] }
598    }
599
600    /// set the width / height of a side (DIP)
601    pub fn set_size(&mut self, side: u8, dip: i32) {
602        if let Some(s) = self.sides.get_mut(side as usize) {
603            s.size = dip.clamp(SIDE_MIN, SIZE_MAX);
604        }
605    }
606
607    /// the tab groups of a side from start to end, each with its panels in tab order
608    pub fn groups(&self, side: u8) -> Vec<Vec<u32>> {
609        self.sides.get(side as usize).map_or(Vec::new(), |s| s.groups.iter().map(|g| g.panels.clone()).collect())
610    }
611
612    /// the panels of every floating window
613    pub fn floats(&self) -> Vec<Vec<u32>> {
614        self.floats.iter().map(|f| f.group.panels.clone()).collect()
615    }
616
617    /// the hidden panels
618    pub fn hidden_panels(&self) -> Vec<u32> {
619        self.hidden.iter().map(|h| h.panel).collect()
620    }
621
622    /// where the panel is: DOCK_LEFT / DOCK_RIGHT / DOCK_BOTTOM / DOCK_FLOAT (None: hidden or
623    /// unknown)
624    pub fn side(&self, id: u32) -> Option<u8> {
625        match self.loc(id) {
626            Loc::Side(s, _, _) => Some(s as u8),
627            Loc::Float(..) => Some(DOCK_FLOAT),
628            _ => None,
629        }
630    }
631
632    /// docked or floating (not hidden)
633    pub fn visible(&self, id: u32) -> bool {
634        self.side(id).is_some()
635    }
636
637    /// the shown tab of its group (also on a collapsed side)
638    pub fn front(&self, id: u32) -> bool {
639        match self.loc(id) {
640            Loc::Side(s, g, _) => self.sides[s].groups[g].active == id,
641            Loc::Float(f, _) => self.floats[f].group.active == id,
642            _ => false,
643        }
644    }
645
646    /// its content is on the screen: the front tab, on a side that is not collapsed (or
647    /// floating)
648    pub fn shown(&self, id: u32) -> bool {
649        self.front(id) && !matches!(self.loc(id), Loc::Side(s, _, _) if self.sides[s].collapsed)
650    }
651
652    /// the layout as text (see DockState)
653    pub fn save(&self) -> String {
654        use core::fmt::Write;
655        let mut o = String::from("dock 1\n");
656        for (s, sd) in self.sides.iter().enumerate() {
657            let _ = writeln!(o, "side {} {} {}", s, sd.size, sd.collapsed as u8);
658        }
659        let list = |o: &mut String, ids: &[u32]| {
660            for p in ids {
661                let _ = write!(o, " {}", p);
662            }
663            o.push('\n');
664        };
665        for (s, sd) in self.sides.iter().enumerate() {
666            for g in &sd.groups {
667                let _ = write!(o, "group {} {} {} {}", s, g.tag, g.size, g.active);
668                list(&mut o, &g.panels);
669            }
670        }
671        for f in &self.floats {
672            let r = f.rect;
673            let _ = write!(o, "float {} {} {} {} {} {} {} {}", f.slot, r[0], r[1], r[2], r[3], f.home, f.home_tag, f.group.active);
674            list(&mut o, &f.group.panels);
675        }
676        for h in &self.hidden {
677            let r = h.rect;
678            let _ = write!(o, "hidden {} {} {} {} {} {} {} {} {} {}", h.panel, h.side, h.tag, h.pos, r[0], r[1], r[2], r[3], h.home, h.home_tag);
679            list(&mut o, &h.order);
680        }
681        o
682    }
683
684    /// a layout saved with save -> None when the text is no dock layout (version 1)
685    pub fn load(text: &str) -> Option<DockState> {
686        let mut lines = text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#'));
687        let mut w = lines.next()?.split_whitespace();
688        if w.next()? != "dock" || w.next()? != "1" {
689            return None;
690        }
691        let mut st = DockState::default();
692        let mut seen: Vec<u32> = Vec::new();
693        for l in lines {
694            let mut w = l.split_whitespace();
695            let kind = w.next().unwrap_or("");
696            let v: Vec<i64> = w.map(|x| x.parse::<i64>().unwrap_or(i64::MIN)).collect();
697            if v.contains(&i64::MIN) {
698                continue;
699            }
700            let num = |i: usize| v.get(i).copied().unwrap_or(0);
701            let int = |i: usize| num(i).clamp(-(SIZE_MAX as i64) * 4, SIZE_MAX as i64 * 4) as i32;
702            let id = |i: usize| num(i).clamp(0, u32::MAX as i64) as u32;
703            // the panels of v[i..j] that no earlier line had
704            let mut fresh = |i: usize, j: usize| -> Vec<u32> {
705                let mut out = Vec::new();
706                for k in i..j.min(v.len()) {
707                    let p = id(k);
708                    if p != 0 && !seen.contains(&p) {
709                        seen.push(p);
710                        out.push(p);
711                    }
712                }
713                out
714            };
715            match kind {
716                "side" if v.len() >= 3 && (0..3).contains(&num(0)) => {
717                    let sd = &mut st.sides[num(0) as usize];
718                    sd.size = int(1).clamp(0, SIZE_MAX);
719                    sd.collapsed = num(2) != 0;
720                }
721                "group" if v.len() >= 5 && (0..3).contains(&num(0)) => {
722                    let s = num(0) as usize;
723                    let mut tag = id(1) & 0x00FF_FFFF;
724                    let panels = fresh(4, v.len());
725                    if panels.is_empty() {
726                        continue;
727                    }
728                    if st.sides[s].groups.iter().any(|g| g.tag == tag) {
729                        tag = st.new_tag(s);
730                    }
731                    let active = if panels.contains(&id(3)) { id(3) } else { panels[0] };
732                    st.sides[s].groups.push(Group { tag, size: int(2).clamp(0, SIZE_MAX), active, panels });
733                }
734                "float" if v.len() >= 9 => {
735                    let panels = fresh(8, v.len());
736                    if panels.is_empty() || st.floats.len() >= DOCK_FLOAT_MAX {
737                        continue;
738                    }
739                    let mut slot = num(0).clamp(0, DOCK_FLOAT_MAX as i64 - 1) as u8;
740                    if st.floats.iter().any(|f| f.slot == slot) {
741                        slot = (0..DOCK_FLOAT_MAX as u8).find(|k| !st.floats.iter().any(|f| f.slot == *k)).unwrap_or(0);
742                    }
743                    let rect = [int(1), int(2), int(3).clamp(1, SIZE_MAX), int(4).clamp(1, SIZE_MAX)];
744                    let active = if panels.contains(&id(7)) { id(7) } else { panels[0] };
745                    let home = num(5).clamp(0, 2) as u8;
746                    st.floats.push(Float { slot, rect, home, home_tag: id(6) & 0x00FF_FFFF, group: Group { tag: 0, size: 0, active, panels } });
747                }
748                "hidden" if v.len() >= 10 => {
749                    let Some(&panel) = fresh(0, 1).first() else { continue };
750                    let rect = [int(4), int(5), int(6).clamp(1, SIZE_MAX), int(7).clamp(1, SIZE_MAX)];
751                    st.hidden.push(Hidden {
752                        panel,
753                        side: num(1).clamp(0, 3) as u8,
754                        tag: id(2) & 0x00FF_FFFF,
755                        pos: id(3),
756                        rect,
757                        home: num(8).clamp(0, 2) as u8,
758                        home_tag: id(9) & 0x00FF_FFFF,
759                        order: (10..v.len()).map(id).filter(|&p| p != 0).collect(),
760                    });
761                }
762                _ => {}
763            }
764        }
765        Some(st)
766    }
767}
768
769// ------------------------------------------------------------------ the runtime part
770
771// the frame's description of a panel
772struct PInfo {
773    id: u32,
774    title: String,
775    icon: u32,
776    flags: u32,
777}
778
779// a docked group as laid out in this pass (main window px, logical)
780#[derive(Clone, Default)]
781struct Geo {
782    side: u8,
783    tag: u32,
784    rect: [i32; 4],
785    hdr: [i32; 4],
786    body: [i32; 4],
787    /// the panels the app describes this frame, in tab order, and their tabs
788    panels: Vec<u32>,
789    tabs: Vec<[i32; 4]>,
790    active: u32,
791}
792
793#[derive(Clone, Copy, PartialEq, Eq, Debug)]
794enum Target {
795    None,
796    /// a new group at the end of a side
797    Side(u8),
798    /// tabs of group (side, tag) at an index (usize::MAX: the end)
799    Tab(u8, u32, usize),
800    /// a new group before (false) / after (true) group (side, tag)
801    Split(u8, u32, bool),
802    /// a floating window at the pointer
803    Float,
804}
805
806struct Mark {
807    kind: i32,
808    rect: [i32; 4],
809    to: Target,
810    label: &'static str,
811}
812
813// a panel (or a group) being dragged
814#[derive(Clone)]
815struct Drag {
816    panels: Vec<u32>,
817    active: u32,
818    /// the group it comes from: (side, tag), a floating window (DOCK_FLOAT, slot)
819    src: (u8, u32),
820    /// the window and the widget that hold the pointer
821    win: u32,
822    wid: u32,
823    /// the pointer's offset from the start corner of the source's tab strip (DIP)
824    grab: [i32; 2],
825    /// the client size a floating window of it gets (DIP)
826    size: [i32; 2],
827    /// the pointer in the main window (logical px)
828    at: [i32; 2],
829    seen: u32,
830}
831
832enum Act {
833    Drop(Drag),
834    Float(u32, [i32; 4]),
835    /// (DP_GROUP) the whole group of the panel
836    FloatGroup(u32, [i32; 4]),
837    Home(u8),
838    ToSide(u32, u8),
839    GroupToSide(u32, u8),
840    Hide(u32),
841    Collapse(u8, bool),
842    Activate(u32),
843}
844
845#[derive(Clone, Copy)]
846enum Slot {
847    Center,
848    Group(usize),
849    Float(usize),
850}
851
852// the library's side of a floating window: its open flag and placement
853#[derive(Default)]
854struct WinSlot {
855    open: Shared<i32>,
856    rect: Shared<WinRect>,
857    applied: [i32; 4],
858    used: bool,
859}
860
861/// one dock area's state between the calls (and the passes)
862#[derive(Default)]
863pub(crate) struct Rt {
864    id: u32,
865    panels: Vec<PInfo>,
866    area: [i32; 4],
867    center: [i32; 4],
868    /// per side: 0 absent, 1 shown, 2 collapsed (a rail); its rectangle and its splitter
869    on: [u8; 3],
870    side: [[i32; 4]; 3],
871    grip: [[i32; 4]; 3],
872    groups: Vec<Geo>,
873    slots: Vec<Slot>,
874    next: usize,
875    /// what dock_next opened: 0 nothing, 1 a box (center / group), 2 a floating window
876    open: u8,
877    /// the open group / floating window (gkey) and its strip, the hit count before its content
878    open_key: u32,
879    open_panels: Vec<u32>,
880    open_active: u32,
881    hits0: usize,
882    drag: Option<Drag>,
883    /// the window of a press held on a tab or a strip in the last pass (0: none or the main one)
884    held_win: u32,
885    /// the press (window, widget, press_ms) whose hold is over, the pointer at that moment
886    hold_at: Option<((u32, u32, u32), [i32; 2])>,
887    acts: Vec<Act>,
888    wins: Vec<WinSlot>,
889    /// groups (gkey) that had the keyboard focus in the last pass / this pass
890    focus_in: Vec<u32>,
891    focus_now: Vec<u32>,
892    /// the menu opened by a right click (anchored to the pointer)
893    menu_point: u32,
894    changed: bool,
895}
896
897impl Rt {
898    fn info(&self, id: u32) -> Option<&PInfo> {
899        self.panels.iter().find(|p| p.id == id)
900    }
901    fn present(&self, id: u32) -> bool {
902        self.panels.iter().any(|p| p.id == id)
903    }
904    fn title(&self, id: u32) -> String {
905        self.info(id).map_or(String::new(), |p| p.title.clone())
906    }
907    fn flags(&self, id: u32) -> u32 {
908        self.info(id).map_or(0, |p| p.flags)
909    }
910    // the group's panels the app describes, and the one shown
911    fn shown(&self, g: &Group) -> (Vec<u32>, u32) {
912        let v: Vec<u32> = g.panels.iter().copied().filter(|&p| self.present(p)).collect();
913        let a = if v.contains(&g.active) { g.active } else { v.first().copied().unwrap_or(0) };
914        (v, a)
915    }
916}
917
918/// the dock areas of the app (Mods)
919#[derive(Default)]
920pub(crate) struct S {
921    docks: Vec<Rt>,
922    /// the areas between dock_begin and dock_end
923    cur: Vec<u32>,
924}
925
926// a library text (German source; i18n::tr)
927fn tr(s: &'static str) -> &'static str {
928    super::i18n::tr(s)
929}
930
931impl Ui {
932    fn dk_take(&mut self, id: u32) -> Rt {
933        let d = &mut self.m.dock.docks;
934        match d.iter().position(|r| r.id == id) {
935            Some(i) => d.swap_remove(i),
936            None => Rt { id, ..Rt::default() },
937        }
938    }
939
940    fn dk_put(&mut self, rt: Rt) {
941        self.m.dock.docks.push(rt);
942    }
943
944    // a side's name as the user sees it (mirrored windows show left at the right)
945    fn dk_side_label(&self, s: u8) -> &'static str {
946        let s = if self.rtl && s < DOCK_BOTTOM { s ^ 1 } else { s };
947        match s {
948            DOCK_LEFT => tr("Links andocken"),
949            DOCK_RIGHT => tr("Rechts andocken"),
950            _ => tr("Unten andocken"),
951        }
952    }
953
954    // the style's SM_DOCK_* value in px; def (DIP) when the style gives none
955    fn dk_metric(&self, id: u32, def: i32) -> i32 {
956        let v = self.style.metric(id);
957        self.dp(if v > 0 { v } else { def })
958    }
959
960    // vk with exactly these modifiers pressed this frame (consumed; a text box keeps its keys)
961    fn dk_key(&mut self, vk: u32, mods: u32) -> bool {
962        let m3 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
963        let k = self.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & m3 == mods);
964        match self.text_key_filter(k) {
965            Some(i) => {
966                self.wp.ctx.keyq[i] |= 0x8000_0000;
967                true
968            }
969            None => false,
970        }
971    }
972
973    // a point of the live window (logical px) in the main window (logical px)
974    fn dk_main_point(&self, x: i32, y: i32) -> [i32; 2] {
975        let win = self.uiw_cur;
976        if win == 0 {
977            return [x, y];
978        }
979        let px = if self.rtl { self.wp.rt.fb.w - 1 - x } else { x };
980        let dpi = self.wp.rt.dpi;
981        let (dx, dy) = self.uiw_desk_pos(win);
982        let (mx, my) = (dx + px2dip_at(px, dpi), dy + px2dip_at(y, dpi));
983        let mdpi = self.win_dpi(0);
984        let (mpx, mpy) = (dp_at(mx, mdpi), dp_at(my, mdpi));
985        let w0 = self.win_fb(0).w;
986        [if self.rtl { w0 - 1 - mpx } else { mpx }, mpy]
987    }
988
989    // a logical rectangle of the main window as a floating window's WinRect values (DIP,
990    // physical), moved by (dx, dy) DIP and clamped to the floating sizes
991    fn dk_float_near(&self, r: [i32; 4], dx: i32, dy: i32) -> [i32; 4] {
992        let p = self.phys_rect(r);
993        let w = self.px2dip(r[2]).clamp(FLOAT_W.0, FLOAT_W.1);
994        let h = self.px2dip(r[3]).clamp(FLOAT_H.0, FLOAT_H.1);
995        [self.px2dip(p[0]) + dx, self.px2dip(p[1]) + dy, w, h]
996    }
997
998    // ------------------------------------------------------------------ app API
999
1000    /// dock_begin(id, &mut st, panels): open the dock area (always pair with dock_end; fill it
1001    /// with dock_next).  It takes the next place of the box (the whole rest when no size was
1002    /// set); panels: every panel the app has, each frame (titles and icons may change).
1003    #[cfg_attr(jfgui_dev, track_caller)]
1004    pub fn dock_begin(&mut self, id: u32, st: &mut DockState, panels: &[DockPanel]) {
1005        let _src = self.src_scope();
1006        let mut rt = self.dk_take(id);
1007        let cur = &mut self.m.dock.cur;
1008        cur.retain(|&c| c != id); // (a dock_end the app left out)
1009        cur.push(id);
1010        rt.panels = panels.iter().filter(|p| p.id != 0).map(|p| PInfo { id: p.id, title: String::from(p.title), icon: p.icon, flags: p.flags }).collect();
1011        st.adopt(panels);
1012        for k in 0..rt.wins.len() {
1013            let w = &mut rt.wins[k];
1014            if w.used && w.open.get() == 0 {
1015                // the user closed the floating window: its panels are hidden
1016                w.used = false;
1017                w.open.set(1);
1018                st.hide_float(k as u8);
1019                rt.changed = true;
1020            }
1021            if !st.floats.iter().any(|f| f.slot as usize == k) {
1022                w.applied = [i32::MIN; 4]; // a later window in this slot gets its own placement
1023            }
1024        }
1025        // a drag whose widget went away
1026        if rt.drag.as_ref().is_some_and(|d| self.frame.wrapping_sub(d.seen) > 2) {
1027            rt.drag = None;
1028        }
1029        // a press held in a floating window (a hold or a drag): Esc comes here while no widget
1030        // there has the focus
1031        let held = core::mem::take(&mut rt.held_win);
1032        if held != 0 && self.key(VK_ESCAPE) {
1033            rt.drag = None;
1034            if let Some(p) = self.uiw_parts_mut(held) {
1035                p.ctx.active = 0; // the release is no click
1036            }
1037            self.repass = true;
1038        }
1039        let l = &self.wp.lay;
1040        if l.next_w == 0 && l.next_h == 0 && l.next_rect_set == 0 {
1041            self.next_size(SIZE_FILL, SIZE_FILL);
1042        }
1043        self.box_begin(pid(id, P_AREA, 0), BOX_V | BOX_FIXED, 0);
1044        rt.area = self.box_rect();
1045        self.dk_layout(&mut rt, st);
1046        self.dk_emit(&mut rt, st);
1047        // what dock_next yields: the document area, the groups, the floating windows
1048        rt.slots.clear();
1049        rt.slots.push(Slot::Center);
1050        for g in 0..rt.groups.len() {
1051            rt.slots.push(Slot::Group(g));
1052        }
1053        for f in 0..st.floats.len() {
1054            if st.floats[f].group.panels.iter().any(|&p| rt.present(p)) {
1055                rt.slots.push(Slot::Float(f));
1056            }
1057        }
1058        rt.next = 0;
1059        rt.open = 0;
1060        self.dk_put(rt);
1061    }
1062
1063    /// dock_next(&mut st) -> the next place to fill (None: done): DOCK_CENTER, then the id of
1064    /// a shown panel; its content goes into a vertical box (it clips) until the next call
1065    #[cfg_attr(jfgui_dev, track_caller)]
1066    pub fn dock_next(&mut self, st: &mut DockState) -> Option<u32> {
1067        let _src = self.src_scope();
1068        let id = *self.m.dock.cur.last()?;
1069        let mut rt = self.dk_take(id);
1070        self.dk_close(&mut rt);
1071        let mut out = None;
1072        while out.is_none() && rt.next < rt.slots.len() {
1073            let s = rt.slots[rt.next];
1074            rt.next += 1;
1075            match s {
1076                Slot::Center => {
1077                    let c = rt.center;
1078                    self.next_rect_px(c[0], c[1], c[2], c[3]);
1079                    self.box_begin(pid(id, P_CENTER, 0), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1080                    self.focus_pane_begin(pid(id, P_CENTER, 0)); // F6 (kbnav.rs)
1081                    rt.open = 1;
1082                    rt.open_key = u32::MAX;
1083                    out = Some(DOCK_CENTER);
1084                }
1085                Slot::Group(g) => {
1086                    let geo = rt.groups[g].clone();
1087                    let b = geo.body;
1088                    self.next_rect_px(b[0], b[1], b[2], b[3]);
1089                    self.box_begin(pid(id, P_CONTENT, geo.active), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1090                    self.focus_pane_begin(pid(id, P_STRIP, gkey(geo.side, geo.tag))); // with its tab strip
1091                    rt.open = 1;
1092                    rt.open_key = gkey(geo.side, geo.tag);
1093                    rt.open_panels = geo.panels.clone();
1094                    rt.open_active = geo.active;
1095                    rt.hits0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1096                    out = Some(geo.active);
1097                }
1098                Slot::Float(f) => out = self.dk_float_open(&mut rt, st, f),
1099            }
1100        }
1101        self.dk_put(rt);
1102        out
1103    }
1104
1105    /// dock_end(&mut st) -> the user changed the layout in this pass (store st.save() then)
1106    #[cfg_attr(jfgui_dev, track_caller)]
1107    pub fn dock_end(&mut self, st: &mut DockState) -> bool {
1108        let _src = self.src_scope();
1109        let Some(id) = self.m.dock.cur.pop() else { return false };
1110        let mut rt = self.dk_take(id);
1111        self.dk_close(&mut rt);
1112        self.dk_overlay(&rt, st);
1113        let acts = core::mem::take(&mut rt.acts);
1114        for a in acts {
1115            self.dk_apply(&mut rt, st, a);
1116        }
1117        self.box_end(); // the area
1118        rt.focus_in = core::mem::take(&mut rt.focus_now);
1119        let changed = core::mem::take(&mut rt.changed);
1120        self.dk_put(rt);
1121        changed
1122    }
1123
1124    // ------------------------------------------------------------------ layout
1125
1126    // the sides, their groups and the document area of this pass
1127    fn dk_layout(&mut self, rt: &mut Rt, st: &DockState) {
1128        let a = rt.area;
1129        let g = self.dp(GRIP);
1130        let rail = self.dp(RAIL);
1131        for s in 0..3 {
1132            let any = st.sides[s].groups.iter().any(|gr| gr.panels.iter().any(|&p| rt.present(p)));
1133            rt.on[s] = if !any { 0 } else if st.sides[s].collapsed { 2 } else { 1 };
1134        }
1135        let want = |s: usize| self.dp(st.size(s as u8));
1136        let gw = |s: usize| if rt.on[s] == 1 { g } else { 0 };
1137        // left and right: what the document area leaves them
1138        let mut w = [0i32; 3];
1139        let fixed: i32 = (0..2).filter(|&s| rt.on[s] == 2).map(|_| rail).sum();
1140        let room = (a[2] - gw(0) - gw(1) - fixed - self.dp(CENTER_MIN)).max(0);
1141        for s in 0..2 {
1142            w[s] = match rt.on[s] {
1143                1 => want(s),
1144                2 => rail,
1145                _ => 0,
1146            };
1147        }
1148        let (e0, e1) = (if rt.on[0] == 1 { w[0] } else { 0 }, if rt.on[1] == 1 { w[1] } else { 0 });
1149        if e0 + e1 > room {
1150            if e0 > 0 && e1 > 0 {
1151                w[0] = (e0 as i64 * room as i64 / (e0 + e1) as i64) as i32;
1152                w[1] = room - w[0];
1153            } else if e0 > 0 {
1154                w[0] = room;
1155            } else {
1156                w[1] = room;
1157            }
1158        }
1159        w[2] = match rt.on[2] {
1160            1 => want(2).min((a[3] - g - self.dp(CENTER_MIN) / 2).max(0)),
1161            2 => rail,
1162            _ => 0,
1163        };
1164        let (x0, y0, x1, y1) = (a[0], a[1], a[0] + a[2], a[1] + a[3]);
1165        rt.side[0] = [x0, y0, w[0], a[3]];
1166        rt.grip[0] = [x0 + w[0], y0, gw(0), a[3]];
1167        rt.side[1] = [x1 - w[1], y0, w[1], a[3]];
1168        rt.grip[1] = [x1 - w[1] - gw(1), y0, gw(1), a[3]];
1169        let cx0 = x0 + w[0] + gw(0);
1170        let cw = (x1 - w[1] - gw(1) - cx0).max(0);
1171        rt.side[2] = [cx0, y1 - w[2], cw, w[2]];
1172        rt.grip[2] = [cx0, y1 - w[2] - gw(2), cw, gw(2)];
1173        rt.center = [cx0, y0, cw, (y1 - w[2] - gw(2) - y0).max(0)];
1174        // the groups of the shown sides along them
1175        rt.groups.clear();
1176        let hh = self.dk_metric(SM_DOCK_HDR, HDR);
1177        for s in 0..3 {
1178            if rt.on[s] != 1 {
1179                continue;
1180            }
1181            let list: Vec<(&Group, Vec<u32>, u32)> = st.sides[s]
1182                .groups
1183                .iter()
1184                .filter_map(|gr| {
1185                    let (v, act) = rt.shown(gr);
1186                    if v.is_empty() { None } else { Some((gr, v, act)) }
1187                })
1188                .collect();
1189            let n = list.len() as i32;
1190            let r = rt.side[s];
1191            let ax = if s == DOCK_BOTTOM as usize { 0 } else { 1 };
1192            let avail = (r[2 + ax] - g * (n - 1)).max(0);
1193            let sum: i64 = list.iter().map(|(gr, _, _)| if gr.size > 0 { gr.size } else { GROUP_DEF } as i64).sum();
1194            let mut pos = r[ax];
1195            let mut used = 0;
1196            for (k, (gr, v, act)) in list.iter().enumerate() {
1197                let share = if gr.size > 0 { gr.size } else { GROUP_DEF } as i64;
1198                let len = if k as i32 == n - 1 { avail - used } else { (avail as i64 * share / sum.max(1)) as i32 };
1199                used += len;
1200                let mut rc = r;
1201                rc[ax] = pos;
1202                rc[2 + ax] = len;
1203                pos += len + g;
1204                let h = hh.min(rc[3]);
1205                rt.groups.push(Geo {
1206                    side: s as u8,
1207                    tag: gr.tag,
1208                    rect: rc,
1209                    hdr: [rc[0], rc[1], rc[2], h],
1210                    body: [rc[0], rc[1] + h, rc[2], rc[3] - h],
1211                    panels: v.clone(),
1212                    tabs: Vec::new(),
1213                    active: *act,
1214                });
1215            }
1216        }
1217    }
1218
1219    // ------------------------------------------------------------------ the parts
1220
1221    // splitters, rails, the groups' tab strips
1222    fn dk_emit(&mut self, rt: &mut Rt, st: &mut DockState) {
1223        let id = rt.id;
1224        let sh = self.style.shell();
1225        let g = self.dp(GRIP);
1226        // splitters between the sides and the document area
1227        for s in 0..3u8 {
1228            if rt.on[s as usize] != 1 {
1229                continue;
1230            }
1231            let r = rt.grip[s as usize];
1232            let horiz = s == DOCK_BOTTOM;
1233            let cur = if horiz { CUR_SIZE_NS } else { CUR_SIZE_WE };
1234            let ev = self.hit_rect(pid(id, P_GRIP, s as u32), r[0], r[1], r[2], r[3], HB_SPLITTER | cur << HB_CURSOR_SHIFT);
1235            // the keyboard (F8 reaches the grip, kbnav.rs): the grip moves with the arrows
1236            let d = self.kb_grip(ev, !horiz);
1237            if d != 0 {
1238                let v = match s {
1239                    DOCK_LEFT => st.size(s).saturating_add(d),
1240                    _ => st.size(s).saturating_sub(d),
1241                }
1242                .clamp(SIDE_MIN, SIZE_MAX);
1243                if v != st.size(s) {
1244                    st.sides[s as usize].size = v;
1245                    rt.changed = true;
1246                    self.repass = true;
1247                }
1248            }
1249            if ev & EV_HELD != 0 {
1250                let (mx, my) = self.mouse();
1251                let a = rt.area;
1252                let px = match s {
1253                    DOCK_LEFT => mx - a[0],
1254                    DOCK_RIGHT => a[0] + a[2] - mx,
1255                    _ => a[1] + a[3] - my,
1256                } - (g >> 1);
1257                let v = self.px2dip(px).clamp(SIDE_MIN, SIZE_MAX);
1258                if v != st.size(s) {
1259                    st.sides[s as usize].size = v;
1260                    rt.changed = true;
1261                    self.repass = true;
1262                }
1263            }
1264            let mut sp = Sp { ev, flags: horiz as u32, ..Sp::default() };
1265            sp.place(r);
1266            sh.split_grip(self, &sp);
1267            if self.focus_ring(ev) {
1268                let stl = self.style;
1269                stl.focus_ring(self, r, 0);
1270            }
1271            // the record: the side's size (DIP)
1272            let size = st.size(s);
1273            let rec = self.sem(K_SLIDER, pid(id, P_GRIP, s as u32), Some(tr("Teiler")), size);
1274            rec.min = SIDE_MIN;
1275            rec.max = SIZE_MAX;
1276        }
1277        // collapsed sides: a rail with a button per panel
1278        for s in 0..3u8 {
1279            if rt.on[s as usize] != 2 {
1280                continue;
1281            }
1282            let r = rt.side[s as usize];
1283            let mut sp = Sp { flags: s as u32, ..Sp::default() };
1284            sp.place(r);
1285            sh.dock_rail(self, &sp);
1286            let b = self.dp(32);
1287            let m = (self.dp(RAIL) - b) >> 1;
1288            let mut k = 0;
1289            for gr in &st.sides[s as usize].groups {
1290                for &p in &gr.panels {
1291                    let Some(info) = rt.info(p) else { continue };
1292                    let (title, icon) = (info.title.clone(), info.icon);
1293                    let off = m + k * (b + self.dp(4));
1294                    if s == DOCK_BOTTOM {
1295                        self.next_rect_px(r[0] + off, r[1] + m, b, b);
1296                    } else {
1297                        self.next_rect_px(r[0] + m, r[1] + off, b, b);
1298                    }
1299                    self.next_icon(if icon != 0 { icon } else { IC_DOCUMENT });
1300                    self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
1301                    if self.button(pid(id, P_RAIL, p), &title) & EV_ACT != 0 {
1302                        rt.acts.push(Act::Activate(p));
1303                    }
1304                    k += 1;
1305                }
1306            }
1307        }
1308        // the groups (each tab strip in its group's pane: F6)
1309        for gi in 0..rt.groups.len() {
1310            let geo = rt.groups[gi].clone();
1311            self.focus_pane_begin(pid(id, P_STRIP, gkey(geo.side, geo.tag)));
1312            let tabs = self.dk_header(rt, st, geo.side, geo.tag, geo.rect, geo.hdr, &geo.panels, geo.active);
1313            self.focus_pane_end();
1314            rt.groups[gi].tabs = tabs;
1315        }
1316        // splitters between the groups of a side
1317        for gi in 1..rt.groups.len() {
1318            let (a, b) = (rt.groups[gi - 1].clone(), rt.groups[gi].clone());
1319            if a.side != b.side {
1320                continue;
1321            }
1322            let ax = if a.side == DOCK_BOTTOM { 0 } else { 1 };
1323            let mut r = a.rect;
1324            r[ax] = a.rect[ax] + a.rect[2 + ax];
1325            r[2 + ax] = g;
1326            let cur = if ax == 1 { CUR_SIZE_NS } else { CUR_SIZE_WE };
1327            let ev = self.hit_rect(pid(id, P_GGRIP, gkey(a.side, a.tag)), r[0], r[1], r[2], r[3], HB_SPLITTER | cur << HB_CURSOR_SHIFT);
1328            // the first group's new length (px): the keyboard (F8, arrows) or the pointer
1329            let kd = self.kb_grip(ev, ax == 0).clamp(-100_000, 100_000);
1330            let want = if kd != 0 {
1331                Some(a.rect[2 + ax] + self.dp(kd))
1332            } else if ev & EV_HELD != 0 {
1333                let (mx, my) = self.mouse();
1334                let m = if ax == 1 { my } else { mx };
1335                Some(m - a.rect[ax] - (g >> 1))
1336            } else {
1337                None
1338            };
1339            if let Some(w) = want {
1340                let pair = a.rect[2 + ax] + g + b.rect[2 + ax];
1341                let min = self.dp(GROUP_MIN);
1342                if pair - g >= 2 * min {
1343                    let na = w.clamp(min, pair - g - min);
1344                    if na != a.rect[2 + ax] {
1345                        // every group of the side keeps its size in DIP, these two share theirs anew
1346                        let side = a.side as usize;
1347                        for geo in rt.groups.iter().filter(|x| x.side == a.side) {
1348                            let len = if geo.tag == a.tag {
1349                                na
1350                            } else if geo.tag == b.tag {
1351                                pair - g - na
1352                            } else {
1353                                geo.rect[2 + ax]
1354                            };
1355                            if let Some(gr) = st.sides[side].groups.iter_mut().find(|x| x.tag == geo.tag) {
1356                                gr.size = px2dip_at(len, self.wp.rt.dpi).max(1);
1357                            }
1358                        }
1359                        rt.changed = true;
1360                        self.repass = true;
1361                    }
1362                }
1363            }
1364            let mut sp = Sp { ev, flags: (ax == 1) as u32, ..Sp::default() };
1365            sp.place(r);
1366            sh.split_grip(self, &sp);
1367            if self.focus_ring(ev) {
1368                let stl = self.style;
1369                stl.focus_ring(self, r, 0);
1370            }
1371            // the record: the first group's length of the two (DIP)
1372            let len = self.px2dip(a.rect[2 + ax]);
1373            let pair = self.px2dip(a.rect[2 + ax] + g + b.rect[2 + ax]);
1374            let rec = self.sem(K_SLIDER, pid(id, P_GGRIP, gkey(a.side, a.tag)), Some(tr("Teiler")), len);
1375            rec.max = pair;
1376        }
1377    }
1378
1379    // the tab strip of a group (side, tag) or of a floating window (DOCK_FLOAT, slot): the
1380    // surface, the tabs, the buttons and the menu -> the tabs' rectangles
1381    #[allow(clippy::too_many_arguments)]
1382    fn dk_header(&mut self, rt: &mut Rt, st: &mut DockState, side: u8, tag: u32, rect: [i32; 4], hdr: [i32; 4], panels: &[u32], active: u32) -> Vec<[i32; 4]> {
1383        let id = rt.id;
1384        let key = gkey(side, tag);
1385        let float = side == DOCK_FLOAT;
1386        let sh = self.style.shell();
1387        let focus_in = rt.focus_in.contains(&key);
1388        let mut sp = Sp { value: focus_in as i32, ..Sp::default() };
1389        sp.place(rect);
1390        sh.dock_group(self, &sp);
1391        sp.place(hdr);
1392        sh.dock_header(self, &sp);
1393        // the buttons at the end (a narrow group keeps only the menu), the tabs before them
1394        let bw = self.dk_metric(SM_DOCK_BTN, BTN).min(hdr[3]);
1395        let pad = self.dp(4);
1396        let nb = if hdr[2] - pad * 2 - bw * 3 >= self.dp(72) { 3 } else { 1 };
1397        let tx = hdr[0] + pad;
1398        let tw = (hdr[2] - pad * 2 - bw * nb).max(0);
1399        let mut ws: Vec<i32> = Vec::with_capacity(panels.len());
1400        for &p in panels {
1401            let (title, icon) = rt.info(p).map_or((String::new(), 0), |i| (i.title.clone(), i.icon));
1402            let mut tsp = Sp { label: Some(&title), icon, ..Sp::default() };
1403            ws.push(sh.dock_tab_size(self, &mut tsp).0);
1404        }
1405        let total: i32 = ws.iter().sum();
1406        if total > tw && total > 0 {
1407            let min = self.dp(24);
1408            for w in ws.iter_mut() {
1409                *w = ((*w as i64 * tw as i64 / total as i64) as i32).max(min);
1410            }
1411        }
1412        let mut tabs = Vec::with_capacity(panels.len());
1413        let mut x = tx;
1414        for &w in &ws {
1415            tabs.push([x, hdr[1], w, hdr[3]]);
1416            x += w;
1417        }
1418        let size = if float {
1419            st.floats.iter().find(|f| f.slot as u32 == tag).map_or([280, 320], |f| [f.rect[2], f.rect[3]])
1420        } else {
1421            [self.px2dip(rect[2]).clamp(FLOAT_W.0, FLOAT_W.1), self.px2dip(rect[3]).clamp(FLOAT_H.0, FLOAT_H.1)]
1422        };
1423        let menu = pid(id, P_MENU, key);
1424        let strip = pid(id, P_STRIP, key);
1425        // the strip: one focus stop; its empty part drags the whole group
1426        let sev = self.hit_rect(strip, tx, hdr[1], tw, hdr[3], HB_FOCUS);
1427        let whole = rt.flags(active) & DP_GROUP != 0; // the group moves as a whole
1428        if !self.dk_drag(rt, sev, strip, (side, tag), panels, active, hdr, size) && sev & EV_DOUBLE != 0 {
1429            let r = self.dk_float_near(rect, 24, 24);
1430            rt.acts.push(if float { Act::Home(tag as u8) } else if whole { Act::FloatGroup(active, r) } else { Act::Float(active, r) });
1431        }
1432        let strip_hold = self.dk_held(sev);
1433        if st.focus_req != 0 && st.focus_req == active {
1434            st.focus_req = 0;
1435            self.focus(strip);
1436        }
1437        let n = panels.len();
1438        let at = panels.iter().position(|&p| p == active).unwrap_or(0);
1439        if sev & EV_FOCUS != 0 && n > 0 {
1440            let mut k = at;
1441            if self.key(VK_LEFT) {
1442                k = k.saturating_sub(1);
1443            }
1444            if self.key(VK_RIGHT) {
1445                k = (k + 1).min(n - 1);
1446            }
1447            if self.key(VK_HOME) {
1448                k = 0;
1449            }
1450            if self.key(VK_END) {
1451                k = n - 1;
1452            }
1453            if k != at {
1454                rt.acts.push(Act::Activate(panels[k]));
1455                self.wp.ctx.focus_visible = true;
1456            }
1457            if self.key(VK_APPS) || self.dk_key(VK_F10, MOD_SHIFT) {
1458                self.popup_open(menu);
1459                self.pp_flag(menu, super::popup::PPF_FOCUS);
1460                self.wp.ctx.focus_visible = true;
1461                rt.menu_point = 0;
1462            }
1463        }
1464        // the tabs
1465        let strip_focus = sev & EV_FOCUS != 0;
1466        for (i, &p) in panels.iter().enumerate() {
1467            let r = tabs[i];
1468            let tid = pid(id, P_TAB, p);
1469            let ev = self.hit_rect(tid, r[0], r[1], r[2], r[3], 0);
1470            let state = self.wp.ctx.last_state;
1471            if !self.dk_drag(rt, ev, tid, (side, tag), &[p], p, hdr, size) {
1472                if ev & EV_DOUBLE != 0 && rt.flags(p) & DP_NOFLOAT == 0 {
1473                    rt.acts.push(if float { Act::Home(tag as u8) } else { Act::Float(p, self.dk_float_near(rect, 24, 24)) });
1474                } else if ev & EV_ACT != 0 {
1475                    rt.acts.push(Act::Activate(p));
1476                    self.wp.ctx.focus = strip;
1477                    self.wp.ctx.focus_visible = false;
1478                }
1479                if ev & EV_RCLICK != 0 {
1480                    rt.acts.push(Act::Activate(p));
1481                    self.popup_open(menu);
1482                    rt.menu_point = menu;
1483                }
1484            }
1485            let (title, icon) = rt.info(p).map_or((String::new(), 0), |i| (i.title.clone(), i.icon));
1486            let flags = if strip_hold || self.dk_held(ev) { DOCK_TAB_HOLD } else { 0 };
1487            let mut tsp = Sp { ev, flags, label: Some(&title), icon, value: (p == active) as i32, value2: focus_in as i32, ..Sp::default() };
1488            tsp.place(r);
1489            sh.dock_tab(self, &tsp);
1490            let mut s = state;
1491            if p == active {
1492                s |= SS_SELECTED as u32;
1493                if strip_focus {
1494                    s |= SS_FOCUSED as u32;
1495                    if self.wp.ctx.focus_visible {
1496                        let stl = self.style;
1497                        stl.focus_ring(self, r, 4);
1498                    }
1499                }
1500            }
1501            self.wp.ctx.last_rect = r;
1502            self.wp.ctx.last_state = s;
1503            self.sem(K_TAB, tid, Some(&title), i as i32);
1504        }
1505        // the buttons: menu, collapse (a floating window: dock), hide
1506        let title = rt.title(active);
1507        let flags = rt.flags(active);
1508        let by = hdr[1] + ((hdr[3] - bw) >> 1);
1509        let bx = hdr[0] + hdr[2] - pad - bw * nb;
1510        let name = |s: &str| alloc::format!("{}: {}", s, title);
1511        self.next_rect_px(bx, by, bw, bw);
1512        self.next_icon(IC_MORE);
1513        self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1514        if self.button(menu, &name(tr("Weitere Optionen"))) & EV_ACT != 0 {
1515            self.popup_open(menu);
1516            if self.wp.ctx.focus_visible {
1517                self.pp_flag(menu, super::popup::PPF_FOCUS);
1518            }
1519            rt.menu_point = 0;
1520        }
1521        let fl = if rt.menu_point == menu { FL_POINT } else { FL_ALIGN_END };
1522        if self.flyout_begin(menu, fl) {
1523            let row = |ui: &mut Ui, k: u32, label: &str, on: bool| -> bool {
1524                if !on {
1525                    return false;
1526                }
1527                ui.menu_item(pid(id, P_MITEM + k, key), label) & EV_ACT != 0
1528            };
1529            if float {
1530                if row(self, 0, tr("Andocken"), true) {
1531                    rt.acts.push(Act::Home(tag as u8));
1532                }
1533            } else if row(self, 0, tr("Lösen"), flags & DP_NOFLOAT == 0) {
1534                let r = self.dk_float_near(rect, 24, 24);
1535                rt.acts.push(if whole { Act::FloatGroup(active, r) } else { Act::Float(active, r) });
1536            }
1537            for s in 0..3u8 {
1538                let l = self.dk_side_label(s);
1539                if row(self, 1 + s as u32, l, s != side) {
1540                    rt.acts.push(if whole { Act::GroupToSide(active, s) } else { Act::ToSide(active, s) });
1541                }
1542            }
1543            self.menu_sep();
1544            if row(self, 4, tr("Einklappen"), !float) {
1545                rt.acts.push(Act::Collapse(side, true));
1546            }
1547            if row(self, 5, tr("Ausblenden"), flags & DP_NOCLOSE == 0) {
1548                rt.acts.push(Act::Hide(active));
1549            }
1550        } else if rt.menu_point == menu {
1551            rt.menu_point = 0;
1552        }
1553        self.flyout_end();
1554        if nb == 1 {
1555            return tabs;
1556        }
1557        self.next_rect_px(bx + bw, by, bw, bw);
1558        if float {
1559            self.next_icon(IC_PIN);
1560            self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1561            if self.button(pid(id, P_BTN1, key), &name(tr("Andocken"))) & EV_ACT != 0 {
1562                rt.acts.push(Act::Home(tag as u8));
1563            }
1564        } else {
1565            self.next_icon(match side {
1566                DOCK_LEFT => IC_CHEV_LEFT,
1567                DOCK_RIGHT => IC_CHEV_RIGHT,
1568                _ => IC_CHEV_DOWN,
1569            });
1570            self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1571            if self.button(pid(id, P_BTN1, key), &name(tr("Einklappen"))) & EV_ACT != 0 {
1572                rt.acts.push(Act::Collapse(side, true));
1573            }
1574        }
1575        self.next_rect_px(bx + bw * 2, by, bw, bw);
1576        self.next_icon(IC_CANCEL);
1577        self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT | if flags & DP_NOCLOSE != 0 { ST_DISABLED } else { 0 });
1578        if self.button(pid(id, P_BTN2, key), &name(tr("Ausblenden"))) & EV_ACT != 0 {
1579            rt.acts.push(Act::Hide(active));
1580        }
1581        tabs
1582    }
1583
1584    // a press on a tab or a strip (its widget's events ev) was held long enough to drag
1585    fn dk_held(&self, ev: u32) -> bool {
1586        ev & EV_HELD != 0 && self.now_ms.wrapping_sub(self.wp.ctx.press_ms) >= DOCK_HOLD_MS
1587    }
1588
1589    // the drag of a tab or a strip (widget wid, its events ev): held for DOCK_HOLD_MS, starts
1590    // past the threshold from where the pointer was then, follows the pointer, drops at the
1591    // release -> true when the events belonged to the drag
1592    #[allow(clippy::too_many_arguments)]
1593    fn dk_drag(&mut self, rt: &mut Rt, ev: u32, wid: u32, src: (u8, u32), panels: &[u32], active: u32, hdr: [i32; 4], size: [i32; 2]) -> bool {
1594        let win = self.uiw_cur;
1595        let frame = self.frame;
1596        if ev & EV_HELD != 0 {
1597            rt.held_win = win;
1598        }
1599        if let Some(d) = rt.drag.as_mut() {
1600            if d.wid != wid || d.win != win {
1601                return false;
1602            }
1603            let (mx, my) = self.mouse();
1604            let at = self.dk_main_point(mx, my);
1605            d.seen = frame;
1606            if ev & EV_HELD != 0 {
1607                if self.key(VK_ESCAPE) {
1608                    rt.drag = None;
1609                    self.wp.ctx.active = 0; // the release is no click
1610                    self.repass = true;
1611                } else if d.at != at {
1612                    d.at = at;
1613                    self.repass = true;
1614                }
1615                return true;
1616            }
1617            // released: dropped where the pointer is (dock_end finds the place)
1618            if let Some(mut d) = rt.drag.take() {
1619                d.at = at;
1620                rt.acts.push(Act::Drop(d));
1621            }
1622            return true;
1623        }
1624        let held = self.now_ms.wrapping_sub(self.wp.ctx.press_ms);
1625        if ev & EV_HELD == 0 {
1626            if ev & EV_COMMIT != 0 && held >= DOCK_HOLD_MS {
1627                self.repass = true; // released: the next pass gives the pointer its cursor back
1628            }
1629            return false;
1630        }
1631        if panels.is_empty() || self.dragging() {
1632            return false;
1633        }
1634        if self.key(VK_ESCAPE) {
1635            self.wp.ctx.active = 0; // the release is no click
1636            self.repass = true;
1637            return true;
1638        }
1639        if held >= DOCK_HOLD_MS || ev & EV_DRAG != 0 {
1640            self.wp.ctx.last_click_id = 0; // a hold or a slip is no first click of a double click
1641        }
1642        if held < DOCK_HOLD_MS {
1643            self.timer(UIT_HOLD, DOCK_HOLD_MS - held);
1644            return false;
1645        }
1646        plat::set_cursor(CUR_MOVE); // (the active widget keeps it until the release)
1647        // behave's threshold, but from the pointer at the end of the hold (EV_DRAG counts from
1648        // the press): what it did during the hold starts nothing; (-1, -1) is outside the
1649        // window (E_LEAVE), no place to measure from or to
1650        let press = (win, wid, self.wp.ctx.press_ms);
1651        let cur = [self.wp.ctx.mx, self.wp.ctx.my];
1652        if cur == [-1, -1] {
1653            return false;
1654        }
1655        let from = rt.hold_at.filter(|h| h.0 == press).map_or(cur, |h| h.1);
1656        rt.hold_at = Some((press, from));
1657        let lim = self.dp(4);
1658        if cur[0].wrapping_sub(from[0]).wrapping_abs() <= lim && cur[1].wrapping_sub(from[1]).wrapping_abs() <= lim {
1659            return false;
1660        }
1661        let (mx, my) = self.mouse();
1662        let at = self.dk_main_point(mx, my);
1663        let (px, py) = (self.wp.ctx.press_x, self.wp.ctx.press_y);
1664        let grab = [self.px2dip(px - hdr[0]), self.px2dip(py - hdr[1])];
1665        rt.drag = Some(Drag { panels: panels.to_vec(), active, src, win, wid, grab, size, at, seen: frame });
1666        self.repass = true;
1667        true
1668    }
1669
1670    // a floating window: placement, window_begin, its tab strip, the content box -> the panel
1671    fn dk_float_open(&mut self, rt: &mut Rt, st: &mut DockState, f: usize) -> Option<u32> {
1672        let fl = st.floats[f].clone();
1673        let (panels, active) = rt.shown(&fl.group);
1674        if panels.is_empty() {
1675            return None;
1676        }
1677        let k = fl.slot as usize;
1678        while rt.wins.len() <= k {
1679            rt.wins.push(WinSlot::default());
1680        }
1681        {
1682            // the layout's placement, or what the user did to the window
1683            let w = &mut rt.wins[k];
1684            let cur = w.rect.get();
1685            let c = [cur.x, cur.y, cur.w, cur.h];
1686            if w.applied != fl.rect {
1687                let r = fl.rect;
1688                w.rect.set(WinRect::new(r[0], r[1], r[2], r[3], WRF_POS));
1689                w.applied = fl.rect;
1690            } else if c != w.applied && cur.flags & WRF_POS != 0 {
1691                st.floats[f].rect = c;
1692                w.applied = c;
1693                rt.changed = true;
1694            }
1695            w.open.set(1);
1696        }
1697        let wid = rt.id.wrapping_add(1 + k as u32);
1698        let title = rt.title(active);
1699        let (open, rect) = (rt.wins[k].open.clone(), rt.wins[k].rect.clone());
1700        if !self.window_begin(wid, &title, Some(&open), WF_TOOL | WF_RESIZABLE, Some(&rect)) {
1701            self.window_end();
1702            return None;
1703        }
1704        rt.wins[k].used = true;
1705        let r = self.box_rect();
1706        let hh = self.dk_metric(SM_DOCK_HDR, HDR).min(r[3]);
1707        let hdr = [r[0], r[1], r[2], hh];
1708        self.dk_header(rt, st, DOCK_FLOAT, k as u32, r, hdr, &panels, active);
1709        self.next_rect_px(r[0], r[1] + hh, r[2], r[3] - hh);
1710        self.box_begin(pid(rt.id, P_CONTENT, active), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1711        rt.open = 2;
1712        rt.open_key = gkey(DOCK_FLOAT, k as u32);
1713        rt.open_panels = panels;
1714        rt.open_active = active;
1715        rt.hits0 = 0;
1716        Some(active)
1717    }
1718
1719    // close what dock_next opened: Ctrl+PgDn / Ctrl+PgUp while the focus is in the group
1720    fn dk_close(&mut self, rt: &mut Rt) {
1721        let open = core::mem::take(&mut rt.open);
1722        if open == 0 {
1723            return;
1724        }
1725        if open == 1 {
1726            self.focus_pane_end();
1727        }
1728        self.box_end();
1729        let key = rt.open_key;
1730        if key != u32::MAX {
1731            let strip = pid(rt.id, P_STRIP, key);
1732            let focus = self.wp.ctx.focus;
1733            let c = &self.wp.ctx;
1734            let hits = &c.hits[c.hit_cur];
1735            let from = rt.hits0.min(hits.len());
1736            let here = focus != 0 && (focus == strip || hits[from..].iter().any(|h| h.id == focus));
1737            if here {
1738                rt.focus_now.push(key);
1739                let n = rt.open_panels.len();
1740                let at = rt.open_panels.iter().position(|&p| p == rt.open_active).unwrap_or(0);
1741                let mut k = at;
1742                if n > 1 && self.dk_key(VK_NEXT, MOD_CTRL) {
1743                    k = (at + 1) % n;
1744                }
1745                if n > 1 && self.dk_key(VK_PRIOR, MOD_CTRL) {
1746                    k = (at + n - 1) % n;
1747                }
1748                if k != at {
1749                    rt.acts.push(Act::Activate(rt.open_panels[k]));
1750                    self.focus(strip);
1751                }
1752            }
1753        }
1754        if open == 2 {
1755            self.window_end();
1756        }
1757    }
1758
1759    // ------------------------------------------------------------------ drop markers
1760
1761    // the markers of a drag: on the group under the pointer, at the area's edges
1762    fn dk_marks(&self, rt: &Rt, d: &Drag) -> Vec<Mark> {
1763        let mut v = Vec::new();
1764        let a = rt.area;
1765        let at = d.at;
1766        if !inside(a, at) {
1767            return v;
1768        }
1769        let m = self.dp(MARK);
1770        let gap = self.dp(4);
1771        if let Some(g) = rt.groups.iter().find(|g| inside(g.rect, at)) {
1772            if !self.dk_whole(d, g) {
1773                let (cx, cy) = (g.body[0] + g.body[2] / 2, g.body[1] + g.body[3] / 2);
1774                let c = [cx - m / 2, cy - m / 2, m, m];
1775                v.push(Mark { kind: DOCK_MK_CENTER, rect: c, to: Target::Tab(g.side, g.tag, usize::MAX), label: "Als Register" });
1776                if g.side == DOCK_BOTTOM {
1777                    v.push(Mark { kind: DOCK_MK_LEFT, rect: [c[0] - m - gap, c[1], m, m], to: Target::Split(g.side, g.tag, false), label: "Davor" });
1778                    v.push(Mark { kind: DOCK_MK_RIGHT, rect: [c[0] + m + gap, c[1], m, m], to: Target::Split(g.side, g.tag, true), label: "Dahinter" });
1779                } else {
1780                    v.push(Mark { kind: DOCK_MK_TOP, rect: [c[0], c[1] - m - gap, m, m], to: Target::Split(g.side, g.tag, false), label: "Darüber" });
1781                    v.push(Mark { kind: DOCK_MK_BOTTOM, rect: [c[0], c[1] + m + gap, m, m], to: Target::Split(g.side, g.tag, true), label: "Darunter" });
1782                }
1783            }
1784        }
1785        let e = self.dp(12);
1786        let my = a[1] + (a[3] - m) / 2;
1787        v.push(Mark { kind: DOCK_MK_LEFT, rect: [a[0] + e, my, m, m], to: Target::Side(DOCK_LEFT), label: "Links andocken" });
1788        v.push(Mark { kind: DOCK_MK_RIGHT, rect: [a[0] + a[2] - e - m, my, m, m], to: Target::Side(DOCK_RIGHT), label: "Rechts andocken" });
1789        let c = rt.center;
1790        v.push(Mark { kind: DOCK_MK_BOTTOM, rect: [c[0] + (c[2] - m) / 2, a[1] + a[3] - e - m, m, m], to: Target::Side(DOCK_BOTTOM), label: "Unten andocken" });
1791        v
1792    }
1793
1794    // the drag carries every panel of group g (it would land where it is)
1795    fn dk_whole(&self, d: &Drag, g: &Geo) -> bool {
1796        d.src == (g.side, g.tag) && g.panels.iter().all(|p| d.panels.contains(p))
1797    }
1798
1799    // where a release at d.at puts the dragged panels
1800    fn dk_target(&self, rt: &Rt, d: &Drag) -> Target {
1801        let at = d.at;
1802        if let Some(mk) = self.dk_marks(rt, d).into_iter().find(|mk| inside(mk.rect, at)) {
1803            return mk.to;
1804        }
1805        for g in &rt.groups {
1806            if inside(g.hdr, at) {
1807                if self.dk_whole(d, g) {
1808                    return Target::None;
1809                }
1810                let i = g.tabs.iter().filter(|t| t[0] + t[2] / 2 < at[0]).count();
1811                return Target::Tab(g.side, g.tag, i);
1812            }
1813        }
1814        if d.panels.iter().any(|&p| rt.flags(p) & DP_NOFLOAT != 0) {
1815            return Target::None;
1816        }
1817        Target::Float
1818    }
1819
1820    // the place a target shows while the pointer is over it (main window px)
1821    fn dk_preview(&self, rt: &Rt, st: &DockState, d: &Drag, to: Target) -> Option<[i32; 4]> {
1822        let half = |r: [i32; 4], side: u8, end: bool| {
1823            let ax = if side == DOCK_BOTTOM { 0 } else { 1 };
1824            let mut q = r;
1825            q[2 + ax] = r[2 + ax] / 2;
1826            if end {
1827                q[ax] = r[ax] + r[2 + ax] - q[2 + ax];
1828            }
1829            q
1830        };
1831        let group = |s: u8, t: u32| rt.groups.iter().find(|g| g.side == s && g.tag == t);
1832        match to {
1833            Target::None => None,
1834            Target::Tab(s, t, _) => group(s, t).map(|g| g.rect),
1835            Target::Split(s, t, after) => group(s, t).map(|g| half(g.rect, s, after)),
1836            Target::Side(s) => {
1837                if rt.on[s as usize] == 1 {
1838                    return rt.groups.iter().rev().find(|g| g.side == s).map(|g| half(g.rect, s, true));
1839                }
1840                let a = rt.area;
1841                let c = rt.center;
1842                let n = self.dp(st.size(s));
1843                Some(match s {
1844                    DOCK_LEFT => [a[0], a[1], n.min(a[2] / 2), a[3]],
1845                    DOCK_RIGHT => [a[0] + a[2] - n.min(a[2] / 2), a[1], n.min(a[2] / 2), a[3]],
1846                    _ => [c[0], a[1] + a[3] - n.min(a[3] / 2), c[2], n.min(a[3] / 2)],
1847                })
1848            }
1849            Target::Float => {
1850                let fb = &self.wp.rt.fb;
1851                if !inside([0, 0, fb.w, fb.h], d.at) {
1852                    return None;
1853                }
1854                Some([d.at[0] - self.dp(d.grab[0]), d.at[1] - self.dp(d.grab[1]), self.dp(d.size[0]), self.dp(d.size[1])])
1855            }
1856        }
1857    }
1858
1859    // (dock_end, the main window) the drag's preview and markers over everything
1860    fn dk_overlay(&mut self, rt: &Rt, st: &DockState) {
1861        let Some(d) = rt.drag.clone() else { return };
1862        let to = self.dk_target(rt, &d);
1863        let marks = self.dk_marks(rt, &d);
1864        let prev = self.layer_set(L_POPUP);
1865        let (lr, ls) = (self.wp.ctx.last_rect, self.wp.ctx.last_state);
1866        let sh = self.style.shell();
1867        if let Some(r) = self.dk_preview(rt, st, &d, to) {
1868            let mut sp = Sp { value: (to == Target::Float) as i32, ..Sp::default() };
1869            sp.place(r);
1870            sh.dock_preview(self, &sp);
1871        }
1872        for (k, mk) in marks.iter().enumerate() {
1873            let hover = inside(mk.rect, d.at);
1874            let mut sp = Sp { value: mk.kind, ev: if hover { EV_HOVER } else { 0 }, ..Sp::default() };
1875            sp.place(mk.rect);
1876            sh.dock_marker(self, &sp);
1877            let label = match mk.to {
1878                Target::Side(s) => self.dk_side_label(s),
1879                _ => tr(mk.label),
1880            };
1881            self.wp.ctx.last_rect = mk.rect;
1882            self.wp.ctx.last_state = if hover { SS_HOVER as u32 } else { 0 };
1883            self.sem(K_BUTTON, pid(rt.id, P_MARK, k as u32), Some(label), 0);
1884        }
1885        self.wp.ctx.last_rect = lr;
1886        self.wp.ctx.last_state = ls;
1887        self.layer_set(prev);
1888    }
1889
1890    // ------------------------------------------------------------------ changes
1891
1892    fn dk_apply(&mut self, rt: &mut Rt, st: &mut DockState, a: Act) {
1893        match a {
1894            Act::Activate(p) => st.activate(p),
1895            Act::Hide(p) => st.hide(p),
1896            Act::Collapse(s, on) => st.collapse(s, on),
1897            Act::Home(slot) => st.dock_home(slot),
1898            Act::ToSide(p, s) => st.dock_to(p, s),
1899            Act::GroupToSide(p, s) => st.dock_group_to(p, s),
1900            Act::Float(p, r) => {
1901                st.float(p, Some(r));
1902            }
1903            Act::FloatGroup(p, r) => {
1904                st.float_group(p, Some(r));
1905            }
1906            Act::Drop(d) => {
1907                let to = self.dk_target(rt, &d);
1908                self.dk_drop(st, d, to);
1909            }
1910        }
1911        rt.changed = true;
1912        self.repass = true;
1913    }
1914
1915    // the dragged panels to their new place
1916    fn dk_drop(&mut self, st: &mut DockState, d: Drag, to: Target) {
1917        let panels: Vec<u32> = d.panels.iter().copied().filter(|&p| st.visible(p)).collect();
1918        if panels.is_empty() {
1919            return;
1920        }
1921        let active = if panels.contains(&d.active) { d.active } else { panels[0] };
1922        match to {
1923            Target::None => {}
1924            Target::Side(s) => {
1925                for &p in &panels {
1926                    st.take(p);
1927                }
1928                st.put_group(s as usize, None, &panels, active);
1929            }
1930            Target::Tab(s, tag, i) => {
1931                // the index among the tabs that stay
1932                let mut i = i;
1933                if let Some(g) = st.sides[s as usize].groups.iter().find(|g| g.tag == tag) {
1934                    let end = i.min(g.panels.len());
1935                    i = end - g.panels[..end].iter().filter(|p| panels.contains(p)).count();
1936                }
1937                for &p in &panels {
1938                    st.take(p);
1939                }
1940                st.put_tabs(s as usize, tag, i, &panels, active);
1941            }
1942            Target::Split(s, tag, after) => {
1943                for &p in &panels {
1944                    st.take(p);
1945                }
1946                let there = st.sides[s as usize].groups.iter().any(|g| g.tag == tag);
1947                st.put_group(s as usize, if there { Some((tag, after)) } else { None }, &panels, active);
1948            }
1949            Target::Float => {
1950                // the window's top left: the strip under the pointer as it was grabbed
1951                let px = if self.rtl { self.wp.rt.fb.w - 1 - d.at[0] } else { d.at[0] };
1952                let (x, y) = (self.px2dip(px), self.px2dip(d.at[1]));
1953                let (w, h) = (d.size[0], d.size[1]);
1954                let gx = if self.rtl { w - d.grab[0] } else { d.grab[0] };
1955                let tbh = self.style.metric(SM_WIN_TBH_TOOL);
1956                let r = [x - gx, y - d.grab[1] - tbh, w, h];
1957                // a whole floating window moves
1958                if d.src.0 == DOCK_FLOAT {
1959                    if let Some(f) = st.floats.iter_mut().find(|f| f.slot as u32 == d.src.1) {
1960                        if f.group.panels.iter().all(|p| panels.contains(p)) {
1961                            f.rect[0] = r[0];
1962                            f.rect[1] = r[1];
1963                            return;
1964                        }
1965                    }
1966                }
1967                let homes: Vec<Hidden> = panels.iter().filter_map(|&p| st.take(p)).collect();
1968                let (home, home_tag) = match homes.first() {
1969                    Some(h) if h.side < DOCK_FLOAT => (h.side, h.tag),
1970                    Some(h) => (h.home, h.home_tag),
1971                    None => (DOCK_RIGHT, 0),
1972                };
1973                if !st.put_float(r, home, home_tag, &panels, active) {
1974                    for h in homes {
1975                        st.restore(h);
1976                    }
1977                }
1978            }
1979        }
1980    }
1981}