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