1use 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
51pub const DOCK_LEFT: u8 = 0;
54pub const DOCK_RIGHT: u8 = 1;
55pub const DOCK_BOTTOM: u8 = 2;
56pub const DOCK_FLOAT: u8 = 3;
58pub const DOCK_CENTER: u32 = 0;
60pub const DP_NOCLOSE: u32 = 1;
62pub const DP_NOFLOAT: u32 = 2;
64pub const DP_HIDDEN: u32 = 4;
66pub const DOCK_FLOAT_MAX: usize = 6;
68pub 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;
74pub const DOCK_HOLD_MS: u32 = 300;
78pub const DOCK_TAB_HOLD: u32 = 1;
81
82const UIT_HOLD: u32 = 0x7FFE_0050; const HDR: i32 = 32; const BTN: i32 = 28; const GRIP: i32 = 6; const RAIL: i32 = 36; const SIDE_MIN: i32 = 120;
90const CENTER_MIN: i32 = 160;
91const GROUP_MIN: i32 = 64;
92const MARK: i32 = 36; const SIDE_DEF: [i32; 3] = [260, 280, 200];
94const GROUP_DEF: i32 = 200;
95const FLOAT_W: (i32, i32) = (200, 480); const FLOAT_H: (i32, i32) = (160, 480);
97const TAG_USER: u32 = 0x100; const SIZE_MAX: i32 = 8192;
99
100const 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; fn 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
124fn 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#[derive(Clone, Copy, Debug)]
135pub struct DockPanel<'a> {
136 pub id: u32,
138 pub title: &'a str,
139 pub icon: u32,
141 pub flags: u32,
143 pub side: u8,
145 pub group: u8,
148}
149
150impl<'a> DockPanel<'a> {
151 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 pub const fn at(mut self, side: u8, group: u8) -> DockPanel<'a> {
157 self.side = side;
158 self.group = group;
159 self
160 }
161 pub const fn flags(mut self, f: u32) -> DockPanel<'a> {
163 self.flags |= f;
164 self
165 }
166}
167
168#[derive(Clone, Default, Debug, PartialEq, Eq)]
171struct Group {
172 tag: u32,
174 size: i32,
176 active: u32,
178 panels: Vec<u32>,
179}
180
181#[derive(Clone, Default, Debug, PartialEq, Eq)]
182struct Side {
183 size: i32,
185 collapsed: bool,
186 groups: Vec<Group>,
187}
188
189#[derive(Clone, Default, Debug, PartialEq, Eq)]
190struct Float {
191 slot: u8,
192 rect: [i32; 4],
195 home: u8,
197 home_tag: u32,
198 group: Group,
199}
200
201#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
203struct Hidden {
204 panel: u32,
205 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#[derive(Clone, Default, Debug, PartialEq, Eq)]
227pub struct DockState {
228 sides: [Side; 3],
229 floats: Vec<Float>,
230 hidden: Vec<Hidden>,
231 focus_req: u32,
233}
234
235impl DockState {
236 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 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 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 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 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 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 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 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 pub fn reset(&mut self) {
400 *self = DockState::default();
401 }
402
403 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 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 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 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 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 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 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 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 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 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 pub fn collapsed(&self, side: u8) -> bool {
507 self.sides.get(side as usize).is_some_and(|s| s.collapsed)
508 }
509
510 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 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 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 pub fn floats(&self) -> Vec<Vec<u32>> {
530 self.floats.iter().map(|f| f.group.panels.clone()).collect()
531 }
532
533 pub fn hidden_panels(&self) -> Vec<u32> {
535 self.hidden.iter().map(|h| h.panel).collect()
536 }
537
538 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 pub fn visible(&self, id: u32) -> bool {
550 self.side(id).is_some()
551 }
552
553 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 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 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 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 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
683struct PInfo {
687 id: u32,
688 title: String,
689 icon: u32,
690 flags: u32,
691}
692
693#[derive(Clone, Default)]
695struct Geo {
696 side: u8,
697 tag: u32,
698 rect: [i32; 4],
699 hdr: [i32; 4],
700 body: [i32; 4],
701 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 Side(u8),
712 Tab(u8, u32, usize),
714 Split(u8, u32, bool),
716 Float,
718}
719
720struct Mark {
721 kind: i32,
722 rect: [i32; 4],
723 to: Target,
724 label: &'static str,
725}
726
727#[derive(Clone)]
729struct Drag {
730 panels: Vec<u32>,
731 active: u32,
732 src: (u8, u32),
734 win: u32,
736 wid: u32,
737 grab: [i32; 2],
739 size: [i32; 2],
741 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#[derive(Default)]
765struct WinSlot {
766 open: Shared<i32>,
767 rect: Shared<WinRect>,
768 applied: [i32; 4],
769 used: bool,
770}
771
772#[derive(Default)]
774pub(crate) struct Rt {
775 id: u32,
776 panels: Vec<PInfo>,
777 area: [i32; 4],
778 center: [i32; 4],
779 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 open: u8,
788 open_key: u32,
790 open_panels: Vec<u32>,
791 open_active: u32,
792 hits0: usize,
793 drag: Option<Drag>,
794 held_win: u32,
796 hold_at: Option<((u32, u32, u32), [i32; 2])>,
798 acts: Vec<Act>,
799 wins: Vec<WinSlot>,
800 focus_in: Vec<u32>,
802 focus_now: Vec<u32>,
803 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 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#[derive(Default)]
831pub(crate) struct S {
832 docks: Vec<Rt>,
833 cur: Vec<u32>,
835}
836
837fn 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 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 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 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 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 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 #[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); 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 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]; }
935 }
936 if rt.drag.as_ref().is_some_and(|d| self.frame.wrapping_sub(d.seen) > 2) {
938 rt.drag = None;
939 }
940 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; }
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 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 #[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)); 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))); 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 #[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(); 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 #[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; self.repass = true;
1519 } else if d.at != at {
1520 d.at = at;
1521 self.repass = true;
1522 }
1523 return true;
1524 }
1525 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; }
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; 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; }
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); 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 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 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 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 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 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 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 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 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 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 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 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 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 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}