1use alloc::string::String;
44use alloc::vec::Vec;
45
46use super::consts::*;
47use super::keys::*;
48use super::layout::{dp_at, px2dip_at};
49use super::win::Shared;
50use super::{Sp, Ui};
51
52pub const DOCK_LEFT: u8 = 0;
55pub const DOCK_RIGHT: u8 = 1;
56pub const DOCK_BOTTOM: u8 = 2;
57pub const DOCK_FLOAT: u8 = 3;
59pub const DOCK_CENTER: u32 = 0;
61pub const DP_NOCLOSE: u32 = 1;
63pub const DP_NOFLOAT: u32 = 2;
65pub const DP_HIDDEN: u32 = 4;
67pub const DP_GROUP: u32 = 8;
72pub const DOCK_FLOAT_MAX: usize = 6;
74pub const DOCK_MK_LEFT: i32 = 0;
76pub const DOCK_MK_RIGHT: i32 = 1;
77pub const DOCK_MK_TOP: i32 = 2;
78pub const DOCK_MK_BOTTOM: i32 = 3;
79pub const DOCK_MK_CENTER: i32 = 4;
80pub const DOCK_HOLD_MS: u32 = 300;
84pub const DOCK_TAB_HOLD: u32 = 1;
87
88const 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;
96const CENTER_MIN: i32 = 160;
97const GROUP_MIN: i32 = 64;
98const MARK: i32 = 36; const SIDE_DEF: [i32; 3] = [260, 280, 200];
100const GROUP_DEF: i32 = 200;
101const FLOAT_W: (i32, i32) = (200, 480); const FLOAT_H: (i32, i32) = (160, 480);
103const TAG_USER: u32 = 0x100; const SIZE_MAX: i32 = 8192;
105
106const P_AREA: u32 = 1;
108const P_CENTER: u32 = 2;
109const P_CONTENT: u32 = 3;
110const P_TAB: u32 = 4;
111const P_STRIP: u32 = 5;
112const P_MENU: u32 = 6;
113const P_BTN1: u32 = 7;
114const P_BTN2: u32 = 8;
115const P_GRIP: u32 = 9;
116const P_GGRIP: u32 = 10;
117const P_RAIL: u32 = 11;
118const P_MARK: u32 = 12;
119const P_MITEM: u32 = 16; fn pid(dock: u32, part: u32, a: u32) -> u32 {
123 let mut h = dock.wrapping_mul(0x9E37_79B1) ^ part.wrapping_mul(0x85EB_CA6B) ^ a.wrapping_mul(0xC2B2_AE35);
124 h ^= h >> 15;
125 h = h.wrapping_mul(0x2C1B_3C6D);
126 h ^= h >> 12;
127 h | 0x8000_0000
128}
129
130fn gkey(side: u8, tag: u32) -> u32 {
132 (side as u32) << 24 | (tag & 0x00FF_FFFF)
133}
134
135fn inside(r: [i32; 4], p: [i32; 2]) -> bool {
136 p[0] >= r[0] && p[0] < r[0] + r[2] && p[1] >= r[1] && p[1] < r[1] + r[3]
137}
138
139#[derive(Clone, Copy, Debug)]
141pub struct DockPanel<'a> {
142 pub id: u32,
144 pub title: &'a str,
145 pub icon: u32,
147 pub flags: u32,
149 pub side: u8,
151 pub group: u8,
154}
155
156impl<'a> DockPanel<'a> {
157 pub const fn new(id: u32, title: &'a str, icon: u32) -> DockPanel<'a> {
159 DockPanel { id, title, icon, flags: 0, side: DOCK_LEFT, group: 0 }
160 }
161 pub const fn at(mut self, side: u8, group: u8) -> DockPanel<'a> {
163 self.side = side;
164 self.group = group;
165 self
166 }
167 pub const fn flags(mut self, f: u32) -> DockPanel<'a> {
169 self.flags |= f;
170 self
171 }
172}
173
174#[derive(Clone, Default, Debug, PartialEq, Eq)]
177struct Group {
178 tag: u32,
180 size: i32,
182 active: u32,
184 panels: Vec<u32>,
185}
186
187#[derive(Clone, Default, Debug, PartialEq, Eq)]
188struct Side {
189 size: i32,
191 collapsed: bool,
192 groups: Vec<Group>,
193}
194
195#[derive(Clone, Default, Debug, PartialEq, Eq)]
196struct Float {
197 slot: u8,
198 rect: [i32; 4],
201 home: u8,
203 home_tag: u32,
204 group: Group,
205}
206
207#[derive(Clone, Default, Debug, PartialEq, Eq)]
209struct Hidden {
210 panel: u32,
211 side: u8,
213 tag: u32,
214 pos: u32,
215 rect: [i32; 4],
216 home: u8,
217 home_tag: u32,
218 order: Vec<u32>,
222}
223
224fn merge_order(visible: &[u32], before: &[u32]) -> Vec<u32> {
227 let mut out: Vec<u32> = visible.to_vec();
228 for (i, &m) in before.iter().enumerate() {
229 if out.contains(&m) {
230 continue;
231 }
232 let at = before[..i].iter().rev().find_map(|q| out.iter().position(|x| x == q)).map_or(0, |k| k + 1);
234 out.insert(at, m);
235 }
236 out
237}
238
239#[derive(Clone, Copy, PartialEq, Eq, Debug)]
240enum Loc {
241 None,
242 Side(usize, usize, usize),
243 Float(usize, usize),
244 Hidden(usize),
245}
246
247#[derive(Clone, Default, Debug, PartialEq, Eq)]
253pub struct DockState {
254 sides: [Side; 3],
255 floats: Vec<Float>,
256 hidden: Vec<Hidden>,
257 focus_req: u32,
259}
260
261impl DockState {
262 pub fn new() -> DockState {
264 DockState::default()
265 }
266
267 fn loc(&self, id: u32) -> Loc {
268 for (s, sd) in self.sides.iter().enumerate() {
269 for (g, gr) in sd.groups.iter().enumerate() {
270 if let Some(t) = gr.panels.iter().position(|&p| p == id) {
271 return Loc::Side(s, g, t);
272 }
273 }
274 }
275 for (f, fl) in self.floats.iter().enumerate() {
276 if let Some(t) = fl.group.panels.iter().position(|&p| p == id) {
277 return Loc::Float(f, t);
278 }
279 }
280 match self.hidden.iter().position(|h| h.panel == id) {
281 Some(h) => Loc::Hidden(h),
282 None => Loc::None,
283 }
284 }
285
286 fn take(&mut self, id: u32) -> Option<Hidden> {
289 fn unlink(g: &mut Group, t: usize, id: u32) {
290 g.panels.remove(t);
291 if g.active == id {
292 let n = g.panels.len();
293 g.active = if n == 0 { 0 } else { g.panels[t.min(n - 1)] };
294 }
295 }
296 match self.loc(id) {
297 Loc::Side(s, g, t) => {
298 let tag = self.sides[s].groups[g].tag;
299 let earlier = self.hidden.iter().rev().find(|h| h.side == s as u8 && h.tag == tag && !h.order.is_empty());
301 let order = merge_order(&self.sides[s].groups[g].panels, earlier.map_or(&[][..], |h| &h.order));
302 let gr = &mut self.sides[s].groups[g];
303 unlink(gr, t, id);
304 if gr.panels.is_empty() {
305 self.sides[s].groups.remove(g);
306 }
307 Some(Hidden { panel: id, side: s as u8, tag, pos: t as u32, rect: [0; 4], home: s as u8, home_tag: tag, order })
308 }
309 Loc::Float(f, t) => {
310 let fl = &mut self.floats[f];
311 unlink(&mut fl.group, t, id);
312 let h = Hidden { panel: id, side: DOCK_FLOAT, tag: 0, pos: t as u32, rect: fl.rect, home: fl.home, home_tag: fl.home_tag, order: Vec::new() };
313 if fl.group.panels.is_empty() {
314 self.floats.remove(f);
315 }
316 Some(h)
317 }
318 Loc::Hidden(h) => Some(self.hidden.remove(h)),
319 Loc::None => None,
320 }
321 }
322
323 fn new_tag(&self, s: usize) -> u32 {
325 self.sides[s].groups.iter().map(|g| g.tag + 1).max().unwrap_or(0).max(TAG_USER)
326 }
327
328 fn put_tabs(&mut self, s: usize, tag: u32, pos: usize, ids: &[u32], active: u32) {
331 let sd = &mut self.sides[s];
332 let g = match sd.groups.iter().position(|g| g.tag == tag) {
333 Some(g) => g,
334 None => {
335 sd.groups.push(Group { tag, ..Group::default() });
336 sd.groups.len() - 1
337 }
338 };
339 let gr = &mut sd.groups[g];
340 let pos = pos.min(gr.panels.len());
341 for (k, &p) in ids.iter().enumerate() {
342 gr.panels.insert(pos + k, p);
343 }
344 gr.active = active;
345 sd.collapsed = false;
346 }
347
348 fn put_group(&mut self, s: usize, at: Option<(u32, bool)>, ids: &[u32], active: u32) {
351 let tag = self.new_tag(s);
352 let sd = &mut self.sides[s];
353 let mut size = 0;
354 let mut i = sd.groups.len();
355 if let Some((t, after)) = at {
356 if let Some(g) = sd.groups.iter().position(|g| g.tag == t) {
357 let half = (if sd.groups[g].size > 0 { sd.groups[g].size } else { GROUP_DEF } / 2).max(1);
358 sd.groups[g].size = half;
359 size = half;
360 i = g + after as usize;
361 }
362 }
363 sd.groups.insert(i, Group { tag, size, active, panels: ids.to_vec() });
364 sd.collapsed = false;
365 }
366
367 fn put_float(&mut self, rect: [i32; 4], home: u8, home_tag: u32, ids: &[u32], active: u32) -> bool {
369 let Some(slot) = (0..DOCK_FLOAT_MAX as u8).find(|k| !self.floats.iter().any(|f| f.slot == *k)) else {
370 return false;
371 };
372 let group = Group { tag: 0, size: 0, active, panels: ids.to_vec() };
373 self.floats.push(Float { slot, rect, home: home.min(DOCK_BOTTOM), home_tag, group });
374 true
375 }
376
377 fn order_pos(&self, s: usize, tag: u32, order: &[u32], id: u32) -> Option<usize> {
380 let gr = self.sides[s].groups.iter().find(|g| g.tag == tag)?;
381 let at = order.iter().position(|&p| p == id)?;
382 let pos = |q: &u32| gr.panels.iter().position(|p| p == q);
383 order[at + 1..].iter().find_map(pos).or_else(|| order[..at].iter().rev().find_map(pos).map(|k| k + 1))
384 }
385
386 fn restore(&mut self, h: Hidden) {
388 let id = h.panel;
389 if h.side < DOCK_FLOAT {
390 let pos = self.order_pos(h.side as usize, h.tag, &h.order, id).unwrap_or(h.pos as usize);
391 self.put_tabs(h.side as usize, h.tag, pos, &[id], id);
392 } else if !self.put_float(h.rect, h.home, h.home_tag, &[id], id) {
393 self.put_tabs(h.home.min(DOCK_BOTTOM) as usize, h.home_tag, usize::MAX, &[id], id);
394 }
395 }
396
397 fn cascade(&self) -> [i32; 4] {
398 let k = self.floats.len() as i32;
399 [40 + 32 * k, 80 + 32 * k, 280, 320]
400 }
401
402 fn adopt(&mut self, panels: &[DockPanel]) {
404 for p in panels {
405 if p.id == 0 || self.loc(p.id) != Loc::None {
406 continue;
407 }
408 let tag = p.group as u32;
409 let float = p.side >= DOCK_FLOAT;
410 let home = if float { DOCK_RIGHT } else { p.side };
411 let rect = self.cascade();
412 if p.flags & DP_HIDDEN != 0 {
413 let side = if float { DOCK_FLOAT } else { home };
414 self.hidden.push(Hidden { panel: p.id, side, tag, pos: u32::MAX, rect, home, home_tag: tag, order: Vec::new() });
415 continue;
416 }
417 if float && self.put_float(rect, home, tag, &[p.id], p.id) {
418 continue;
419 }
420 let sd = &mut self.sides[home as usize];
421 match sd.groups.iter().position(|g| g.tag == tag) {
422 Some(g) => {
423 let gr = &mut sd.groups[g];
424 gr.panels.push(p.id);
425 if gr.active == 0 {
426 gr.active = p.id;
427 }
428 }
429 None => {
430 let i = sd.groups.iter().position(|g| g.tag > tag).unwrap_or(sd.groups.len());
431 sd.groups.insert(i, Group { tag, size: 0, active: p.id, panels: alloc::vec![p.id] });
432 }
433 }
434 }
435 }
436
437 pub fn reset(&mut self) {
439 *self = DockState::default();
440 }
441
442 pub fn show(&mut self, id: u32) {
444 if let Loc::Hidden(_) = self.loc(id) {
445 if let Some(h) = self.take(id) {
446 self.restore(h);
447 }
448 }
449 self.activate(id);
450 }
451
452 pub fn hide(&mut self, id: u32) {
454 if matches!(self.loc(id), Loc::Side(..) | Loc::Float(..)) {
455 if let Some(h) = self.take(id) {
456 self.hidden.push(h);
457 }
458 }
459 }
460
461 pub fn toggle(&mut self, id: u32) {
463 if self.visible(id) {
464 self.hide(id);
465 } else {
466 self.show(id);
467 }
468 }
469
470 pub fn focus(&mut self, id: u32) {
473 self.show(id);
474 if self.visible(id) {
475 self.focus_req = id;
476 }
477 }
478
479 pub fn activate(&mut self, id: u32) {
481 match self.loc(id) {
482 Loc::Side(s, g, _) => {
483 self.sides[s].groups[g].active = id;
484 self.sides[s].collapsed = false;
485 }
486 Loc::Float(f, _) => self.floats[f].group.active = id,
487 _ => {}
488 }
489 }
490
491 pub fn float(&mut self, id: u32, rect: Option<[i32; 4]>) -> bool {
495 if let Loc::Float(f, _) = self.loc(id) {
496 if self.floats[f].group.panels.len() == 1 {
497 if let Some(r) = rect {
498 self.floats[f].rect = r;
499 }
500 return true;
501 }
502 }
503 let r = rect.unwrap_or_else(|| self.cascade());
504 let Some(h) = self.take(id) else { return false };
505 let (home, home_tag) = if h.side < DOCK_FLOAT { (h.side, h.tag) } else { (h.home, h.home_tag) };
506 if self.put_float(r, home, home_tag, &[id], id) {
507 return true;
508 }
509 self.restore(h);
510 false
511 }
512
513 pub fn dock_to(&mut self, id: u32, side: u8) {
515 if self.take(id).is_some() {
516 self.put_group(side.min(DOCK_BOTTOM) as usize, None, &[id], id);
517 }
518 }
519
520 fn take_group(&mut self, id: u32, keep: usize) -> Option<(Vec<u32>, u32)> {
523 match self.loc(id) {
524 Loc::Side(s, g, _) if s != keep => {
525 let gr = self.sides[s].groups.remove(g);
526 Some((gr.panels, gr.active))
527 }
528 Loc::Float(f, _) => {
529 let fl = self.floats.remove(f);
530 Some((fl.group.panels, fl.group.active))
531 }
532 _ => None,
533 }
534 }
535
536 pub fn dock_group_to(&mut self, id: u32, side: u8) {
540 let s = side.min(DOCK_BOTTOM) as usize;
541 if let Some((panels, active)) = self.take_group(id, s) {
542 self.put_group(s, None, &panels, active);
543 }
544 }
545
546 pub fn float_group(&mut self, id: u32, rect: Option<[i32; 4]>) -> bool {
550 if let Loc::Float(f, _) = self.loc(id) {
551 if let Some(r) = rect {
552 self.floats[f].rect = r;
553 }
554 return true;
555 }
556 let Loc::Side(s, g, _) = self.loc(id) else { return false };
557 if self.floats.len() >= DOCK_FLOAT_MAX {
558 return false;
559 }
560 let r = rect.unwrap_or_else(|| self.cascade());
561 let gr = self.sides[s].groups.remove(g);
562 self.put_float(r, s as u8, gr.tag, &gr.panels, gr.active)
563 }
564
565 fn dock_home(&mut self, slot: u8) {
567 let Some(f) = self.floats.iter().position(|f| f.slot == slot) else { return };
568 let fl = self.floats.remove(f);
569 let s = fl.home.min(DOCK_BOTTOM) as usize;
570 self.put_tabs(s, fl.home_tag, usize::MAX, &fl.group.panels, fl.group.active);
571 }
572
573 fn hide_float(&mut self, slot: u8) {
575 if let Some(f) = self.floats.iter().position(|f| f.slot == slot) {
576 for p in self.floats[f].group.panels.clone() {
577 self.hide(p);
578 }
579 }
580 }
581
582 pub fn collapse(&mut self, side: u8, on: bool) {
584 if let Some(s) = self.sides.get_mut(side as usize) {
585 s.collapsed = on;
586 }
587 }
588
589 pub fn collapsed(&self, side: u8) -> bool {
591 self.sides.get(side as usize).is_some_and(|s| s.collapsed)
592 }
593
594 pub fn size(&self, side: u8) -> i32 {
596 let s = side.min(DOCK_BOTTOM) as usize;
597 if self.sides[s].size > 0 { self.sides[s].size } else { SIDE_DEF[s] }
598 }
599
600 pub fn set_size(&mut self, side: u8, dip: i32) {
602 if let Some(s) = self.sides.get_mut(side as usize) {
603 s.size = dip.clamp(SIDE_MIN, SIZE_MAX);
604 }
605 }
606
607 pub fn groups(&self, side: u8) -> Vec<Vec<u32>> {
609 self.sides.get(side as usize).map_or(Vec::new(), |s| s.groups.iter().map(|g| g.panels.clone()).collect())
610 }
611
612 pub fn floats(&self) -> Vec<Vec<u32>> {
614 self.floats.iter().map(|f| f.group.panels.clone()).collect()
615 }
616
617 pub fn hidden_panels(&self) -> Vec<u32> {
619 self.hidden.iter().map(|h| h.panel).collect()
620 }
621
622 pub fn side(&self, id: u32) -> Option<u8> {
625 match self.loc(id) {
626 Loc::Side(s, _, _) => Some(s as u8),
627 Loc::Float(..) => Some(DOCK_FLOAT),
628 _ => None,
629 }
630 }
631
632 pub fn visible(&self, id: u32) -> bool {
634 self.side(id).is_some()
635 }
636
637 pub fn front(&self, id: u32) -> bool {
639 match self.loc(id) {
640 Loc::Side(s, g, _) => self.sides[s].groups[g].active == id,
641 Loc::Float(f, _) => self.floats[f].group.active == id,
642 _ => false,
643 }
644 }
645
646 pub fn shown(&self, id: u32) -> bool {
649 self.front(id) && !matches!(self.loc(id), Loc::Side(s, _, _) if self.sides[s].collapsed)
650 }
651
652 pub fn save(&self) -> String {
654 use core::fmt::Write;
655 let mut o = String::from("dock 1\n");
656 for (s, sd) in self.sides.iter().enumerate() {
657 let _ = writeln!(o, "side {} {} {}", s, sd.size, sd.collapsed as u8);
658 }
659 let list = |o: &mut String, ids: &[u32]| {
660 for p in ids {
661 let _ = write!(o, " {}", p);
662 }
663 o.push('\n');
664 };
665 for (s, sd) in self.sides.iter().enumerate() {
666 for g in &sd.groups {
667 let _ = write!(o, "group {} {} {} {}", s, g.tag, g.size, g.active);
668 list(&mut o, &g.panels);
669 }
670 }
671 for f in &self.floats {
672 let r = f.rect;
673 let _ = write!(o, "float {} {} {} {} {} {} {} {}", f.slot, r[0], r[1], r[2], r[3], f.home, f.home_tag, f.group.active);
674 list(&mut o, &f.group.panels);
675 }
676 for h in &self.hidden {
677 let r = h.rect;
678 let _ = write!(o, "hidden {} {} {} {} {} {} {} {} {} {}", h.panel, h.side, h.tag, h.pos, r[0], r[1], r[2], r[3], h.home, h.home_tag);
679 list(&mut o, &h.order);
680 }
681 o
682 }
683
684 pub fn load(text: &str) -> Option<DockState> {
686 let mut lines = text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#'));
687 let mut w = lines.next()?.split_whitespace();
688 if w.next()? != "dock" || w.next()? != "1" {
689 return None;
690 }
691 let mut st = DockState::default();
692 let mut seen: Vec<u32> = Vec::new();
693 for l in lines {
694 let mut w = l.split_whitespace();
695 let kind = w.next().unwrap_or("");
696 let v: Vec<i64> = w.map(|x| x.parse::<i64>().unwrap_or(i64::MIN)).collect();
697 if v.contains(&i64::MIN) {
698 continue;
699 }
700 let num = |i: usize| v.get(i).copied().unwrap_or(0);
701 let int = |i: usize| num(i).clamp(-(SIZE_MAX as i64) * 4, SIZE_MAX as i64 * 4) as i32;
702 let id = |i: usize| num(i).clamp(0, u32::MAX as i64) as u32;
703 let mut fresh = |i: usize, j: usize| -> Vec<u32> {
705 let mut out = Vec::new();
706 for k in i..j.min(v.len()) {
707 let p = id(k);
708 if p != 0 && !seen.contains(&p) {
709 seen.push(p);
710 out.push(p);
711 }
712 }
713 out
714 };
715 match kind {
716 "side" if v.len() >= 3 && (0..3).contains(&num(0)) => {
717 let sd = &mut st.sides[num(0) as usize];
718 sd.size = int(1).clamp(0, SIZE_MAX);
719 sd.collapsed = num(2) != 0;
720 }
721 "group" if v.len() >= 5 && (0..3).contains(&num(0)) => {
722 let s = num(0) as usize;
723 let mut tag = id(1) & 0x00FF_FFFF;
724 let panels = fresh(4, v.len());
725 if panels.is_empty() {
726 continue;
727 }
728 if st.sides[s].groups.iter().any(|g| g.tag == tag) {
729 tag = st.new_tag(s);
730 }
731 let active = if panels.contains(&id(3)) { id(3) } else { panels[0] };
732 st.sides[s].groups.push(Group { tag, size: int(2).clamp(0, SIZE_MAX), active, panels });
733 }
734 "float" if v.len() >= 9 => {
735 let panels = fresh(8, v.len());
736 if panels.is_empty() || st.floats.len() >= DOCK_FLOAT_MAX {
737 continue;
738 }
739 let mut slot = num(0).clamp(0, DOCK_FLOAT_MAX as i64 - 1) as u8;
740 if st.floats.iter().any(|f| f.slot == slot) {
741 slot = (0..DOCK_FLOAT_MAX as u8).find(|k| !st.floats.iter().any(|f| f.slot == *k)).unwrap_or(0);
742 }
743 let rect = [int(1), int(2), int(3).clamp(1, SIZE_MAX), int(4).clamp(1, SIZE_MAX)];
744 let active = if panels.contains(&id(7)) { id(7) } else { panels[0] };
745 let home = num(5).clamp(0, 2) as u8;
746 st.floats.push(Float { slot, rect, home, home_tag: id(6) & 0x00FF_FFFF, group: Group { tag: 0, size: 0, active, panels } });
747 }
748 "hidden" if v.len() >= 10 => {
749 let Some(&panel) = fresh(0, 1).first() else { continue };
750 let rect = [int(4), int(5), int(6).clamp(1, SIZE_MAX), int(7).clamp(1, SIZE_MAX)];
751 st.hidden.push(Hidden {
752 panel,
753 side: num(1).clamp(0, 3) as u8,
754 tag: id(2) & 0x00FF_FFFF,
755 pos: id(3),
756 rect,
757 home: num(8).clamp(0, 2) as u8,
758 home_tag: id(9) & 0x00FF_FFFF,
759 order: (10..v.len()).map(id).filter(|&p| p != 0).collect(),
760 });
761 }
762 _ => {}
763 }
764 }
765 Some(st)
766 }
767}
768
769struct PInfo {
773 id: u32,
774 title: String,
775 icon: u32,
776 flags: u32,
777}
778
779#[derive(Clone, Default)]
781struct Geo {
782 side: u8,
783 tag: u32,
784 rect: [i32; 4],
785 hdr: [i32; 4],
786 body: [i32; 4],
787 panels: Vec<u32>,
789 tabs: Vec<[i32; 4]>,
790 active: u32,
791}
792
793#[derive(Clone, Copy, PartialEq, Eq, Debug)]
794enum Target {
795 None,
796 Side(u8),
798 Tab(u8, u32, usize),
800 Split(u8, u32, bool),
802 Float,
804}
805
806struct Mark {
807 kind: i32,
808 rect: [i32; 4],
809 to: Target,
810 label: &'static str,
811}
812
813#[derive(Clone)]
815struct Drag {
816 panels: Vec<u32>,
817 active: u32,
818 src: (u8, u32),
820 win: u32,
822 wid: u32,
823 grab: [i32; 2],
825 size: [i32; 2],
827 at: [i32; 2],
829 seen: u32,
830}
831
832enum Act {
833 Drop(Drag),
834 Float(u32, [i32; 4]),
835 FloatGroup(u32, [i32; 4]),
837 Home(u8),
838 ToSide(u32, u8),
839 GroupToSide(u32, u8),
840 Hide(u32),
841 Collapse(u8, bool),
842 Activate(u32),
843}
844
845#[derive(Clone, Copy)]
846enum Slot {
847 Center,
848 Group(usize),
849 Float(usize),
850}
851
852#[derive(Default)]
854struct WinSlot {
855 open: Shared<i32>,
856 rect: Shared<WinRect>,
857 applied: [i32; 4],
858 used: bool,
859}
860
861#[derive(Default)]
863pub(crate) struct Rt {
864 id: u32,
865 panels: Vec<PInfo>,
866 area: [i32; 4],
867 center: [i32; 4],
868 on: [u8; 3],
870 side: [[i32; 4]; 3],
871 grip: [[i32; 4]; 3],
872 groups: Vec<Geo>,
873 slots: Vec<Slot>,
874 next: usize,
875 open: u8,
877 open_key: u32,
879 open_panels: Vec<u32>,
880 open_active: u32,
881 hits0: usize,
882 drag: Option<Drag>,
883 held_win: u32,
885 hold_at: Option<((u32, u32, u32), [i32; 2])>,
887 acts: Vec<Act>,
888 wins: Vec<WinSlot>,
889 focus_in: Vec<u32>,
891 focus_now: Vec<u32>,
892 menu_point: u32,
894 changed: bool,
895}
896
897impl Rt {
898 fn info(&self, id: u32) -> Option<&PInfo> {
899 self.panels.iter().find(|p| p.id == id)
900 }
901 fn present(&self, id: u32) -> bool {
902 self.panels.iter().any(|p| p.id == id)
903 }
904 fn title(&self, id: u32) -> String {
905 self.info(id).map_or(String::new(), |p| p.title.clone())
906 }
907 fn flags(&self, id: u32) -> u32 {
908 self.info(id).map_or(0, |p| p.flags)
909 }
910 fn shown(&self, g: &Group) -> (Vec<u32>, u32) {
912 let v: Vec<u32> = g.panels.iter().copied().filter(|&p| self.present(p)).collect();
913 let a = if v.contains(&g.active) { g.active } else { v.first().copied().unwrap_or(0) };
914 (v, a)
915 }
916}
917
918#[derive(Default)]
920pub(crate) struct S {
921 docks: Vec<Rt>,
922 cur: Vec<u32>,
924}
925
926fn tr(s: &'static str) -> &'static str {
928 super::i18n::tr(s)
929}
930
931impl Ui {
932 fn dk_take(&mut self, id: u32) -> Rt {
933 let d = &mut self.m.dock.docks;
934 match d.iter().position(|r| r.id == id) {
935 Some(i) => d.swap_remove(i),
936 None => Rt { id, ..Rt::default() },
937 }
938 }
939
940 fn dk_put(&mut self, rt: Rt) {
941 self.m.dock.docks.push(rt);
942 }
943
944 fn dk_side_label(&self, s: u8) -> &'static str {
946 let s = if self.rtl && s < DOCK_BOTTOM { s ^ 1 } else { s };
947 match s {
948 DOCK_LEFT => tr("Links andocken"),
949 DOCK_RIGHT => tr("Rechts andocken"),
950 _ => tr("Unten andocken"),
951 }
952 }
953
954 fn dk_metric(&self, id: u32, def: i32) -> i32 {
956 let v = self.style.metric(id);
957 self.dp(if v > 0 { v } else { def })
958 }
959
960 fn dk_key(&mut self, vk: u32, mods: u32) -> bool {
962 let m3 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
963 let k = self.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & m3 == mods);
964 match self.text_key_filter(k) {
965 Some(i) => {
966 self.wp.ctx.keyq[i] |= 0x8000_0000;
967 true
968 }
969 None => false,
970 }
971 }
972
973 fn dk_main_point(&self, x: i32, y: i32) -> [i32; 2] {
975 let win = self.uiw_cur;
976 if win == 0 {
977 return [x, y];
978 }
979 let px = if self.rtl { self.wp.rt.fb.w - 1 - x } else { x };
980 let dpi = self.wp.rt.dpi;
981 let (dx, dy) = self.uiw_desk_pos(win);
982 let (mx, my) = (dx + px2dip_at(px, dpi), dy + px2dip_at(y, dpi));
983 let mdpi = self.win_dpi(0);
984 let (mpx, mpy) = (dp_at(mx, mdpi), dp_at(my, mdpi));
985 let w0 = self.win_fb(0).w;
986 [if self.rtl { w0 - 1 - mpx } else { mpx }, mpy]
987 }
988
989 fn dk_float_near(&self, r: [i32; 4], dx: i32, dy: i32) -> [i32; 4] {
992 let p = self.phys_rect(r);
993 let w = self.px2dip(r[2]).clamp(FLOAT_W.0, FLOAT_W.1);
994 let h = self.px2dip(r[3]).clamp(FLOAT_H.0, FLOAT_H.1);
995 [self.px2dip(p[0]) + dx, self.px2dip(p[1]) + dy, w, h]
996 }
997
998 #[cfg_attr(jfgui_dev, track_caller)]
1004 pub fn dock_begin(&mut self, id: u32, st: &mut DockState, panels: &[DockPanel]) {
1005 let _src = self.src_scope();
1006 let mut rt = self.dk_take(id);
1007 let cur = &mut self.m.dock.cur;
1008 cur.retain(|&c| c != id); cur.push(id);
1010 rt.panels = panels.iter().filter(|p| p.id != 0).map(|p| PInfo { id: p.id, title: String::from(p.title), icon: p.icon, flags: p.flags }).collect();
1011 st.adopt(panels);
1012 for k in 0..rt.wins.len() {
1013 let w = &mut rt.wins[k];
1014 if w.used && w.open.get() == 0 {
1015 w.used = false;
1017 w.open.set(1);
1018 st.hide_float(k as u8);
1019 rt.changed = true;
1020 }
1021 if !st.floats.iter().any(|f| f.slot as usize == k) {
1022 w.applied = [i32::MIN; 4]; }
1024 }
1025 if rt.drag.as_ref().is_some_and(|d| self.frame.wrapping_sub(d.seen) > 2) {
1027 rt.drag = None;
1028 }
1029 let held = core::mem::take(&mut rt.held_win);
1032 if held != 0 && self.key(VK_ESCAPE) {
1033 rt.drag = None;
1034 if let Some(p) = self.uiw_parts_mut(held) {
1035 p.ctx.active = 0; }
1037 self.repass = true;
1038 }
1039 let l = &self.wp.lay;
1040 if l.next_w == 0 && l.next_h == 0 && l.next_rect_set == 0 {
1041 self.next_size(SIZE_FILL, SIZE_FILL);
1042 }
1043 self.box_begin(pid(id, P_AREA, 0), BOX_V | BOX_FIXED, 0);
1044 rt.area = self.box_rect();
1045 self.dk_layout(&mut rt, st);
1046 self.dk_emit(&mut rt, st);
1047 rt.slots.clear();
1049 rt.slots.push(Slot::Center);
1050 for g in 0..rt.groups.len() {
1051 rt.slots.push(Slot::Group(g));
1052 }
1053 for f in 0..st.floats.len() {
1054 if st.floats[f].group.panels.iter().any(|&p| rt.present(p)) {
1055 rt.slots.push(Slot::Float(f));
1056 }
1057 }
1058 rt.next = 0;
1059 rt.open = 0;
1060 self.dk_put(rt);
1061 }
1062
1063 #[cfg_attr(jfgui_dev, track_caller)]
1066 pub fn dock_next(&mut self, st: &mut DockState) -> Option<u32> {
1067 let _src = self.src_scope();
1068 let id = *self.m.dock.cur.last()?;
1069 let mut rt = self.dk_take(id);
1070 self.dk_close(&mut rt);
1071 let mut out = None;
1072 while out.is_none() && rt.next < rt.slots.len() {
1073 let s = rt.slots[rt.next];
1074 rt.next += 1;
1075 match s {
1076 Slot::Center => {
1077 let c = rt.center;
1078 self.next_rect_px(c[0], c[1], c[2], c[3]);
1079 self.box_begin(pid(id, P_CENTER, 0), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1080 self.focus_pane_begin(pid(id, P_CENTER, 0)); rt.open = 1;
1082 rt.open_key = u32::MAX;
1083 out = Some(DOCK_CENTER);
1084 }
1085 Slot::Group(g) => {
1086 let geo = rt.groups[g].clone();
1087 let b = geo.body;
1088 self.next_rect_px(b[0], b[1], b[2], b[3]);
1089 self.box_begin(pid(id, P_CONTENT, geo.active), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1090 self.focus_pane_begin(pid(id, P_STRIP, gkey(geo.side, geo.tag))); rt.open = 1;
1092 rt.open_key = gkey(geo.side, geo.tag);
1093 rt.open_panels = geo.panels.clone();
1094 rt.open_active = geo.active;
1095 rt.hits0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1096 out = Some(geo.active);
1097 }
1098 Slot::Float(f) => out = self.dk_float_open(&mut rt, st, f),
1099 }
1100 }
1101 self.dk_put(rt);
1102 out
1103 }
1104
1105 #[cfg_attr(jfgui_dev, track_caller)]
1107 pub fn dock_end(&mut self, st: &mut DockState) -> bool {
1108 let _src = self.src_scope();
1109 let Some(id) = self.m.dock.cur.pop() else { return false };
1110 let mut rt = self.dk_take(id);
1111 self.dk_close(&mut rt);
1112 self.dk_overlay(&rt, st);
1113 let acts = core::mem::take(&mut rt.acts);
1114 for a in acts {
1115 self.dk_apply(&mut rt, st, a);
1116 }
1117 self.box_end(); rt.focus_in = core::mem::take(&mut rt.focus_now);
1119 let changed = core::mem::take(&mut rt.changed);
1120 self.dk_put(rt);
1121 changed
1122 }
1123
1124 fn dk_layout(&mut self, rt: &mut Rt, st: &DockState) {
1128 let a = rt.area;
1129 let g = self.dp(GRIP);
1130 let rail = self.dp(RAIL);
1131 for s in 0..3 {
1132 let any = st.sides[s].groups.iter().any(|gr| gr.panels.iter().any(|&p| rt.present(p)));
1133 rt.on[s] = if !any { 0 } else if st.sides[s].collapsed { 2 } else { 1 };
1134 }
1135 let want = |s: usize| self.dp(st.size(s as u8));
1136 let gw = |s: usize| if rt.on[s] == 1 { g } else { 0 };
1137 let mut w = [0i32; 3];
1139 let fixed: i32 = (0..2).filter(|&s| rt.on[s] == 2).map(|_| rail).sum();
1140 let room = (a[2] - gw(0) - gw(1) - fixed - self.dp(CENTER_MIN)).max(0);
1141 for s in 0..2 {
1142 w[s] = match rt.on[s] {
1143 1 => want(s),
1144 2 => rail,
1145 _ => 0,
1146 };
1147 }
1148 let (e0, e1) = (if rt.on[0] == 1 { w[0] } else { 0 }, if rt.on[1] == 1 { w[1] } else { 0 });
1149 if e0 + e1 > room {
1150 if e0 > 0 && e1 > 0 {
1151 w[0] = (e0 as i64 * room as i64 / (e0 + e1) as i64) as i32;
1152 w[1] = room - w[0];
1153 } else if e0 > 0 {
1154 w[0] = room;
1155 } else {
1156 w[1] = room;
1157 }
1158 }
1159 w[2] = match rt.on[2] {
1160 1 => want(2).min((a[3] - g - self.dp(CENTER_MIN) / 2).max(0)),
1161 2 => rail,
1162 _ => 0,
1163 };
1164 let (x0, y0, x1, y1) = (a[0], a[1], a[0] + a[2], a[1] + a[3]);
1165 rt.side[0] = [x0, y0, w[0], a[3]];
1166 rt.grip[0] = [x0 + w[0], y0, gw(0), a[3]];
1167 rt.side[1] = [x1 - w[1], y0, w[1], a[3]];
1168 rt.grip[1] = [x1 - w[1] - gw(1), y0, gw(1), a[3]];
1169 let cx0 = x0 + w[0] + gw(0);
1170 let cw = (x1 - w[1] - gw(1) - cx0).max(0);
1171 rt.side[2] = [cx0, y1 - w[2], cw, w[2]];
1172 rt.grip[2] = [cx0, y1 - w[2] - gw(2), cw, gw(2)];
1173 rt.center = [cx0, y0, cw, (y1 - w[2] - gw(2) - y0).max(0)];
1174 rt.groups.clear();
1176 let hh = self.dk_metric(SM_DOCK_HDR, HDR);
1177 for s in 0..3 {
1178 if rt.on[s] != 1 {
1179 continue;
1180 }
1181 let list: Vec<(&Group, Vec<u32>, u32)> = st.sides[s]
1182 .groups
1183 .iter()
1184 .filter_map(|gr| {
1185 let (v, act) = rt.shown(gr);
1186 if v.is_empty() { None } else { Some((gr, v, act)) }
1187 })
1188 .collect();
1189 let n = list.len() as i32;
1190 let r = rt.side[s];
1191 let ax = if s == DOCK_BOTTOM as usize { 0 } else { 1 };
1192 let avail = (r[2 + ax] - g * (n - 1)).max(0);
1193 let sum: i64 = list.iter().map(|(gr, _, _)| if gr.size > 0 { gr.size } else { GROUP_DEF } as i64).sum();
1194 let mut pos = r[ax];
1195 let mut used = 0;
1196 for (k, (gr, v, act)) in list.iter().enumerate() {
1197 let share = if gr.size > 0 { gr.size } else { GROUP_DEF } as i64;
1198 let len = if k as i32 == n - 1 { avail - used } else { (avail as i64 * share / sum.max(1)) as i32 };
1199 used += len;
1200 let mut rc = r;
1201 rc[ax] = pos;
1202 rc[2 + ax] = len;
1203 pos += len + g;
1204 let h = hh.min(rc[3]);
1205 rt.groups.push(Geo {
1206 side: s as u8,
1207 tag: gr.tag,
1208 rect: rc,
1209 hdr: [rc[0], rc[1], rc[2], h],
1210 body: [rc[0], rc[1] + h, rc[2], rc[3] - h],
1211 panels: v.clone(),
1212 tabs: Vec::new(),
1213 active: *act,
1214 });
1215 }
1216 }
1217 }
1218
1219 fn dk_emit(&mut self, rt: &mut Rt, st: &mut DockState) {
1223 let id = rt.id;
1224 let sh = self.style.shell();
1225 let g = self.dp(GRIP);
1226 for s in 0..3u8 {
1228 if rt.on[s as usize] != 1 {
1229 continue;
1230 }
1231 let r = rt.grip[s as usize];
1232 let horiz = s == DOCK_BOTTOM;
1233 let cur = if horiz { CUR_SIZE_NS } else { CUR_SIZE_WE };
1234 let ev = self.hit_rect(pid(id, P_GRIP, s as u32), r[0], r[1], r[2], r[3], HB_SPLITTER | cur << HB_CURSOR_SHIFT);
1235 let d = self.kb_grip(ev, !horiz);
1237 if d != 0 {
1238 let v = match s {
1239 DOCK_LEFT => st.size(s).saturating_add(d),
1240 _ => st.size(s).saturating_sub(d),
1241 }
1242 .clamp(SIDE_MIN, SIZE_MAX);
1243 if v != st.size(s) {
1244 st.sides[s as usize].size = v;
1245 rt.changed = true;
1246 self.repass = true;
1247 }
1248 }
1249 if ev & EV_HELD != 0 {
1250 let (mx, my) = self.mouse();
1251 let a = rt.area;
1252 let px = match s {
1253 DOCK_LEFT => mx - a[0],
1254 DOCK_RIGHT => a[0] + a[2] - mx,
1255 _ => a[1] + a[3] - my,
1256 } - (g >> 1);
1257 let v = self.px2dip(px).clamp(SIDE_MIN, SIZE_MAX);
1258 if v != st.size(s) {
1259 st.sides[s as usize].size = v;
1260 rt.changed = true;
1261 self.repass = true;
1262 }
1263 }
1264 let mut sp = Sp { ev, flags: horiz as u32, ..Sp::default() };
1265 sp.place(r);
1266 sh.split_grip(self, &sp);
1267 if self.focus_ring(ev) {
1268 let stl = self.style;
1269 stl.focus_ring(self, r, 0);
1270 }
1271 let size = st.size(s);
1273 let rec = self.sem(K_SLIDER, pid(id, P_GRIP, s as u32), Some(tr("Teiler")), size);
1274 rec.min = SIDE_MIN;
1275 rec.max = SIZE_MAX;
1276 }
1277 for s in 0..3u8 {
1279 if rt.on[s as usize] != 2 {
1280 continue;
1281 }
1282 let r = rt.side[s as usize];
1283 let mut sp = Sp { flags: s as u32, ..Sp::default() };
1284 sp.place(r);
1285 sh.dock_rail(self, &sp);
1286 let b = self.dp(32);
1287 let m = (self.dp(RAIL) - b) >> 1;
1288 let mut k = 0;
1289 for gr in &st.sides[s as usize].groups {
1290 for &p in &gr.panels {
1291 let Some(info) = rt.info(p) else { continue };
1292 let (title, icon) = (info.title.clone(), info.icon);
1293 let off = m + k * (b + self.dp(4));
1294 if s == DOCK_BOTTOM {
1295 self.next_rect_px(r[0] + off, r[1] + m, b, b);
1296 } else {
1297 self.next_rect_px(r[0] + m, r[1] + off, b, b);
1298 }
1299 self.next_icon(if icon != 0 { icon } else { IC_DOCUMENT });
1300 self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
1301 if self.button(pid(id, P_RAIL, p), &title) & EV_ACT != 0 {
1302 rt.acts.push(Act::Activate(p));
1303 }
1304 k += 1;
1305 }
1306 }
1307 }
1308 for gi in 0..rt.groups.len() {
1310 let geo = rt.groups[gi].clone();
1311 self.focus_pane_begin(pid(id, P_STRIP, gkey(geo.side, geo.tag)));
1312 let tabs = self.dk_header(rt, st, geo.side, geo.tag, geo.rect, geo.hdr, &geo.panels, geo.active);
1313 self.focus_pane_end();
1314 rt.groups[gi].tabs = tabs;
1315 }
1316 for gi in 1..rt.groups.len() {
1318 let (a, b) = (rt.groups[gi - 1].clone(), rt.groups[gi].clone());
1319 if a.side != b.side {
1320 continue;
1321 }
1322 let ax = if a.side == DOCK_BOTTOM { 0 } else { 1 };
1323 let mut r = a.rect;
1324 r[ax] = a.rect[ax] + a.rect[2 + ax];
1325 r[2 + ax] = g;
1326 let cur = if ax == 1 { CUR_SIZE_NS } else { CUR_SIZE_WE };
1327 let ev = self.hit_rect(pid(id, P_GGRIP, gkey(a.side, a.tag)), r[0], r[1], r[2], r[3], HB_SPLITTER | cur << HB_CURSOR_SHIFT);
1328 let kd = self.kb_grip(ev, ax == 0).clamp(-100_000, 100_000);
1330 let want = if kd != 0 {
1331 Some(a.rect[2 + ax] + self.dp(kd))
1332 } else if ev & EV_HELD != 0 {
1333 let (mx, my) = self.mouse();
1334 let m = if ax == 1 { my } else { mx };
1335 Some(m - a.rect[ax] - (g >> 1))
1336 } else {
1337 None
1338 };
1339 if let Some(w) = want {
1340 let pair = a.rect[2 + ax] + g + b.rect[2 + ax];
1341 let min = self.dp(GROUP_MIN);
1342 if pair - g >= 2 * min {
1343 let na = w.clamp(min, pair - g - min);
1344 if na != a.rect[2 + ax] {
1345 let side = a.side as usize;
1347 for geo in rt.groups.iter().filter(|x| x.side == a.side) {
1348 let len = if geo.tag == a.tag {
1349 na
1350 } else if geo.tag == b.tag {
1351 pair - g - na
1352 } else {
1353 geo.rect[2 + ax]
1354 };
1355 if let Some(gr) = st.sides[side].groups.iter_mut().find(|x| x.tag == geo.tag) {
1356 gr.size = px2dip_at(len, self.wp.rt.dpi).max(1);
1357 }
1358 }
1359 rt.changed = true;
1360 self.repass = true;
1361 }
1362 }
1363 }
1364 let mut sp = Sp { ev, flags: (ax == 1) as u32, ..Sp::default() };
1365 sp.place(r);
1366 sh.split_grip(self, &sp);
1367 if self.focus_ring(ev) {
1368 let stl = self.style;
1369 stl.focus_ring(self, r, 0);
1370 }
1371 let len = self.px2dip(a.rect[2 + ax]);
1373 let pair = self.px2dip(a.rect[2 + ax] + g + b.rect[2 + ax]);
1374 let rec = self.sem(K_SLIDER, pid(id, P_GGRIP, gkey(a.side, a.tag)), Some(tr("Teiler")), len);
1375 rec.max = pair;
1376 }
1377 }
1378
1379 #[allow(clippy::too_many_arguments)]
1382 fn dk_header(&mut self, rt: &mut Rt, st: &mut DockState, side: u8, tag: u32, rect: [i32; 4], hdr: [i32; 4], panels: &[u32], active: u32) -> Vec<[i32; 4]> {
1383 let id = rt.id;
1384 let key = gkey(side, tag);
1385 let float = side == DOCK_FLOAT;
1386 let sh = self.style.shell();
1387 let focus_in = rt.focus_in.contains(&key);
1388 let mut sp = Sp { value: focus_in as i32, ..Sp::default() };
1389 sp.place(rect);
1390 sh.dock_group(self, &sp);
1391 sp.place(hdr);
1392 sh.dock_header(self, &sp);
1393 let bw = self.dk_metric(SM_DOCK_BTN, BTN).min(hdr[3]);
1395 let pad = self.dp(4);
1396 let nb = if hdr[2] - pad * 2 - bw * 3 >= self.dp(72) { 3 } else { 1 };
1397 let tx = hdr[0] + pad;
1398 let tw = (hdr[2] - pad * 2 - bw * nb).max(0);
1399 let mut ws: Vec<i32> = Vec::with_capacity(panels.len());
1400 for &p in panels {
1401 let (title, icon) = rt.info(p).map_or((String::new(), 0), |i| (i.title.clone(), i.icon));
1402 let mut tsp = Sp { label: Some(&title), icon, ..Sp::default() };
1403 ws.push(sh.dock_tab_size(self, &mut tsp).0);
1404 }
1405 let total: i32 = ws.iter().sum();
1406 if total > tw && total > 0 {
1407 let min = self.dp(24);
1408 for w in ws.iter_mut() {
1409 *w = ((*w as i64 * tw as i64 / total as i64) as i32).max(min);
1410 }
1411 }
1412 let mut tabs = Vec::with_capacity(panels.len());
1413 let mut x = tx;
1414 for &w in &ws {
1415 tabs.push([x, hdr[1], w, hdr[3]]);
1416 x += w;
1417 }
1418 let size = if float {
1419 st.floats.iter().find(|f| f.slot as u32 == tag).map_or([280, 320], |f| [f.rect[2], f.rect[3]])
1420 } else {
1421 [self.px2dip(rect[2]).clamp(FLOAT_W.0, FLOAT_W.1), self.px2dip(rect[3]).clamp(FLOAT_H.0, FLOAT_H.1)]
1422 };
1423 let menu = pid(id, P_MENU, key);
1424 let strip = pid(id, P_STRIP, key);
1425 let sev = self.hit_rect(strip, tx, hdr[1], tw, hdr[3], HB_FOCUS);
1427 let whole = rt.flags(active) & DP_GROUP != 0; if !self.dk_drag(rt, sev, strip, (side, tag), panels, active, hdr, size) && sev & EV_DOUBLE != 0 {
1429 let r = self.dk_float_near(rect, 24, 24);
1430 rt.acts.push(if float { Act::Home(tag as u8) } else if whole { Act::FloatGroup(active, r) } else { Act::Float(active, r) });
1431 }
1432 let strip_hold = self.dk_held(sev);
1433 if st.focus_req != 0 && st.focus_req == active {
1434 st.focus_req = 0;
1435 self.focus(strip);
1436 }
1437 let n = panels.len();
1438 let at = panels.iter().position(|&p| p == active).unwrap_or(0);
1439 if sev & EV_FOCUS != 0 && n > 0 {
1440 let mut k = at;
1441 if self.key(VK_LEFT) {
1442 k = k.saturating_sub(1);
1443 }
1444 if self.key(VK_RIGHT) {
1445 k = (k + 1).min(n - 1);
1446 }
1447 if self.key(VK_HOME) {
1448 k = 0;
1449 }
1450 if self.key(VK_END) {
1451 k = n - 1;
1452 }
1453 if k != at {
1454 rt.acts.push(Act::Activate(panels[k]));
1455 self.wp.ctx.focus_visible = true;
1456 }
1457 if self.key(VK_APPS) || self.dk_key(VK_F10, MOD_SHIFT) {
1458 self.popup_open(menu);
1459 self.pp_flag(menu, super::popup::PPF_FOCUS);
1460 self.wp.ctx.focus_visible = true;
1461 rt.menu_point = 0;
1462 }
1463 }
1464 let strip_focus = sev & EV_FOCUS != 0;
1466 for (i, &p) in panels.iter().enumerate() {
1467 let r = tabs[i];
1468 let tid = pid(id, P_TAB, p);
1469 let ev = self.hit_rect(tid, r[0], r[1], r[2], r[3], 0);
1470 let state = self.wp.ctx.last_state;
1471 if !self.dk_drag(rt, ev, tid, (side, tag), &[p], p, hdr, size) {
1472 if ev & EV_DOUBLE != 0 && rt.flags(p) & DP_NOFLOAT == 0 {
1473 rt.acts.push(if float { Act::Home(tag as u8) } else { Act::Float(p, self.dk_float_near(rect, 24, 24)) });
1474 } else if ev & EV_ACT != 0 {
1475 rt.acts.push(Act::Activate(p));
1476 self.wp.ctx.focus = strip;
1477 self.wp.ctx.focus_visible = false;
1478 }
1479 if ev & EV_RCLICK != 0 {
1480 rt.acts.push(Act::Activate(p));
1481 self.popup_open(menu);
1482 rt.menu_point = menu;
1483 }
1484 }
1485 let (title, icon) = rt.info(p).map_or((String::new(), 0), |i| (i.title.clone(), i.icon));
1486 let flags = if strip_hold || self.dk_held(ev) { DOCK_TAB_HOLD } else { 0 };
1487 let mut tsp = Sp { ev, flags, label: Some(&title), icon, value: (p == active) as i32, value2: focus_in as i32, ..Sp::default() };
1488 tsp.place(r);
1489 sh.dock_tab(self, &tsp);
1490 let mut s = state;
1491 if p == active {
1492 s |= SS_SELECTED as u32;
1493 if strip_focus {
1494 s |= SS_FOCUSED as u32;
1495 if self.wp.ctx.focus_visible {
1496 let stl = self.style;
1497 stl.focus_ring(self, r, 4);
1498 }
1499 }
1500 }
1501 self.wp.ctx.last_rect = r;
1502 self.wp.ctx.last_state = s;
1503 self.sem(K_TAB, tid, Some(&title), i as i32);
1504 }
1505 let title = rt.title(active);
1507 let flags = rt.flags(active);
1508 let by = hdr[1] + ((hdr[3] - bw) >> 1);
1509 let bx = hdr[0] + hdr[2] - pad - bw * nb;
1510 let name = |s: &str| alloc::format!("{}: {}", s, title);
1511 self.next_rect_px(bx, by, bw, bw);
1512 self.next_icon(IC_MORE);
1513 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1514 if self.button(menu, &name(tr("Weitere Optionen"))) & EV_ACT != 0 {
1515 self.popup_open(menu);
1516 if self.wp.ctx.focus_visible {
1517 self.pp_flag(menu, super::popup::PPF_FOCUS);
1518 }
1519 rt.menu_point = 0;
1520 }
1521 let fl = if rt.menu_point == menu { FL_POINT } else { FL_ALIGN_END };
1522 if self.flyout_begin(menu, fl) {
1523 let row = |ui: &mut Ui, k: u32, label: &str, on: bool| -> bool {
1524 if !on {
1525 return false;
1526 }
1527 ui.menu_item(pid(id, P_MITEM + k, key), label) & EV_ACT != 0
1528 };
1529 if float {
1530 if row(self, 0, tr("Andocken"), true) {
1531 rt.acts.push(Act::Home(tag as u8));
1532 }
1533 } else if row(self, 0, tr("Lösen"), flags & DP_NOFLOAT == 0) {
1534 let r = self.dk_float_near(rect, 24, 24);
1535 rt.acts.push(if whole { Act::FloatGroup(active, r) } else { Act::Float(active, r) });
1536 }
1537 for s in 0..3u8 {
1538 let l = self.dk_side_label(s);
1539 if row(self, 1 + s as u32, l, s != side) {
1540 rt.acts.push(if whole { Act::GroupToSide(active, s) } else { Act::ToSide(active, s) });
1541 }
1542 }
1543 self.menu_sep();
1544 if row(self, 4, tr("Einklappen"), !float) {
1545 rt.acts.push(Act::Collapse(side, true));
1546 }
1547 if row(self, 5, tr("Ausblenden"), flags & DP_NOCLOSE == 0) {
1548 rt.acts.push(Act::Hide(active));
1549 }
1550 } else if rt.menu_point == menu {
1551 rt.menu_point = 0;
1552 }
1553 self.flyout_end();
1554 if nb == 1 {
1555 return tabs;
1556 }
1557 self.next_rect_px(bx + bw, by, bw, bw);
1558 if float {
1559 self.next_icon(IC_PIN);
1560 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1561 if self.button(pid(id, P_BTN1, key), &name(tr("Andocken"))) & EV_ACT != 0 {
1562 rt.acts.push(Act::Home(tag as u8));
1563 }
1564 } else {
1565 self.next_icon(match side {
1566 DOCK_LEFT => IC_CHEV_LEFT,
1567 DOCK_RIGHT => IC_CHEV_RIGHT,
1568 _ => IC_CHEV_DOWN,
1569 });
1570 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT);
1571 if self.button(pid(id, P_BTN1, key), &name(tr("Einklappen"))) & EV_ACT != 0 {
1572 rt.acts.push(Act::Collapse(side, true));
1573 }
1574 }
1575 self.next_rect_px(bx + bw * 2, by, bw, bw);
1576 self.next_icon(IC_CANCEL);
1577 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | ST_COMPACT | if flags & DP_NOCLOSE != 0 { ST_DISABLED } else { 0 });
1578 if self.button(pid(id, P_BTN2, key), &name(tr("Ausblenden"))) & EV_ACT != 0 {
1579 rt.acts.push(Act::Hide(active));
1580 }
1581 tabs
1582 }
1583
1584 fn dk_held(&self, ev: u32) -> bool {
1586 ev & EV_HELD != 0 && self.now_ms.wrapping_sub(self.wp.ctx.press_ms) >= DOCK_HOLD_MS
1587 }
1588
1589 #[allow(clippy::too_many_arguments)]
1593 fn dk_drag(&mut self, rt: &mut Rt, ev: u32, wid: u32, src: (u8, u32), panels: &[u32], active: u32, hdr: [i32; 4], size: [i32; 2]) -> bool {
1594 let win = self.uiw_cur;
1595 let frame = self.frame;
1596 if ev & EV_HELD != 0 {
1597 rt.held_win = win;
1598 }
1599 if let Some(d) = rt.drag.as_mut() {
1600 if d.wid != wid || d.win != win {
1601 return false;
1602 }
1603 let (mx, my) = self.mouse();
1604 let at = self.dk_main_point(mx, my);
1605 d.seen = frame;
1606 if ev & EV_HELD != 0 {
1607 if self.key(VK_ESCAPE) {
1608 rt.drag = None;
1609 self.wp.ctx.active = 0; self.repass = true;
1611 } else if d.at != at {
1612 d.at = at;
1613 self.repass = true;
1614 }
1615 return true;
1616 }
1617 if let Some(mut d) = rt.drag.take() {
1619 d.at = at;
1620 rt.acts.push(Act::Drop(d));
1621 }
1622 return true;
1623 }
1624 let held = self.now_ms.wrapping_sub(self.wp.ctx.press_ms);
1625 if ev & EV_HELD == 0 {
1626 if ev & EV_COMMIT != 0 && held >= DOCK_HOLD_MS {
1627 self.repass = true; }
1629 return false;
1630 }
1631 if panels.is_empty() || self.dragging() {
1632 return false;
1633 }
1634 if self.key(VK_ESCAPE) {
1635 self.wp.ctx.active = 0; self.repass = true;
1637 return true;
1638 }
1639 if held >= DOCK_HOLD_MS || ev & EV_DRAG != 0 {
1640 self.wp.ctx.last_click_id = 0; }
1642 if held < DOCK_HOLD_MS {
1643 self.timer(UIT_HOLD, DOCK_HOLD_MS - held);
1644 return false;
1645 }
1646 plat::set_cursor(CUR_MOVE); let press = (win, wid, self.wp.ctx.press_ms);
1651 let cur = [self.wp.ctx.mx, self.wp.ctx.my];
1652 if cur == [-1, -1] {
1653 return false;
1654 }
1655 let from = rt.hold_at.filter(|h| h.0 == press).map_or(cur, |h| h.1);
1656 rt.hold_at = Some((press, from));
1657 let lim = self.dp(4);
1658 if cur[0].wrapping_sub(from[0]).wrapping_abs() <= lim && cur[1].wrapping_sub(from[1]).wrapping_abs() <= lim {
1659 return false;
1660 }
1661 let (mx, my) = self.mouse();
1662 let at = self.dk_main_point(mx, my);
1663 let (px, py) = (self.wp.ctx.press_x, self.wp.ctx.press_y);
1664 let grab = [self.px2dip(px - hdr[0]), self.px2dip(py - hdr[1])];
1665 rt.drag = Some(Drag { panels: panels.to_vec(), active, src, win, wid, grab, size, at, seen: frame });
1666 self.repass = true;
1667 true
1668 }
1669
1670 fn dk_float_open(&mut self, rt: &mut Rt, st: &mut DockState, f: usize) -> Option<u32> {
1672 let fl = st.floats[f].clone();
1673 let (panels, active) = rt.shown(&fl.group);
1674 if panels.is_empty() {
1675 return None;
1676 }
1677 let k = fl.slot as usize;
1678 while rt.wins.len() <= k {
1679 rt.wins.push(WinSlot::default());
1680 }
1681 {
1682 let w = &mut rt.wins[k];
1684 let cur = w.rect.get();
1685 let c = [cur.x, cur.y, cur.w, cur.h];
1686 if w.applied != fl.rect {
1687 let r = fl.rect;
1688 w.rect.set(WinRect::new(r[0], r[1], r[2], r[3], WRF_POS));
1689 w.applied = fl.rect;
1690 } else if c != w.applied && cur.flags & WRF_POS != 0 {
1691 st.floats[f].rect = c;
1692 w.applied = c;
1693 rt.changed = true;
1694 }
1695 w.open.set(1);
1696 }
1697 let wid = rt.id.wrapping_add(1 + k as u32);
1698 let title = rt.title(active);
1699 let (open, rect) = (rt.wins[k].open.clone(), rt.wins[k].rect.clone());
1700 if !self.window_begin(wid, &title, Some(&open), WF_TOOL | WF_RESIZABLE, Some(&rect)) {
1701 self.window_end();
1702 return None;
1703 }
1704 rt.wins[k].used = true;
1705 let r = self.box_rect();
1706 let hh = self.dk_metric(SM_DOCK_HDR, HDR).min(r[3]);
1707 let hdr = [r[0], r[1], r[2], hh];
1708 self.dk_header(rt, st, DOCK_FLOAT, k as u32, r, hdr, &panels, active);
1709 self.next_rect_px(r[0], r[1] + hh, r[2], r[3] - hh);
1710 self.box_begin(pid(rt.id, P_CONTENT, active), BOX_V | BOX_CLIP | BOX_FIXED, 0);
1711 rt.open = 2;
1712 rt.open_key = gkey(DOCK_FLOAT, k as u32);
1713 rt.open_panels = panels;
1714 rt.open_active = active;
1715 rt.hits0 = 0;
1716 Some(active)
1717 }
1718
1719 fn dk_close(&mut self, rt: &mut Rt) {
1721 let open = core::mem::take(&mut rt.open);
1722 if open == 0 {
1723 return;
1724 }
1725 if open == 1 {
1726 self.focus_pane_end();
1727 }
1728 self.box_end();
1729 let key = rt.open_key;
1730 if key != u32::MAX {
1731 let strip = pid(rt.id, P_STRIP, key);
1732 let focus = self.wp.ctx.focus;
1733 let c = &self.wp.ctx;
1734 let hits = &c.hits[c.hit_cur];
1735 let from = rt.hits0.min(hits.len());
1736 let here = focus != 0 && (focus == strip || hits[from..].iter().any(|h| h.id == focus));
1737 if here {
1738 rt.focus_now.push(key);
1739 let n = rt.open_panels.len();
1740 let at = rt.open_panels.iter().position(|&p| p == rt.open_active).unwrap_or(0);
1741 let mut k = at;
1742 if n > 1 && self.dk_key(VK_NEXT, MOD_CTRL) {
1743 k = (at + 1) % n;
1744 }
1745 if n > 1 && self.dk_key(VK_PRIOR, MOD_CTRL) {
1746 k = (at + n - 1) % n;
1747 }
1748 if k != at {
1749 rt.acts.push(Act::Activate(rt.open_panels[k]));
1750 self.focus(strip);
1751 }
1752 }
1753 }
1754 if open == 2 {
1755 self.window_end();
1756 }
1757 }
1758
1759 fn dk_marks(&self, rt: &Rt, d: &Drag) -> Vec<Mark> {
1763 let mut v = Vec::new();
1764 let a = rt.area;
1765 let at = d.at;
1766 if !inside(a, at) {
1767 return v;
1768 }
1769 let m = self.dp(MARK);
1770 let gap = self.dp(4);
1771 if let Some(g) = rt.groups.iter().find(|g| inside(g.rect, at)) {
1772 if !self.dk_whole(d, g) {
1773 let (cx, cy) = (g.body[0] + g.body[2] / 2, g.body[1] + g.body[3] / 2);
1774 let c = [cx - m / 2, cy - m / 2, m, m];
1775 v.push(Mark { kind: DOCK_MK_CENTER, rect: c, to: Target::Tab(g.side, g.tag, usize::MAX), label: "Als Register" });
1776 if g.side == DOCK_BOTTOM {
1777 v.push(Mark { kind: DOCK_MK_LEFT, rect: [c[0] - m - gap, c[1], m, m], to: Target::Split(g.side, g.tag, false), label: "Davor" });
1778 v.push(Mark { kind: DOCK_MK_RIGHT, rect: [c[0] + m + gap, c[1], m, m], to: Target::Split(g.side, g.tag, true), label: "Dahinter" });
1779 } else {
1780 v.push(Mark { kind: DOCK_MK_TOP, rect: [c[0], c[1] - m - gap, m, m], to: Target::Split(g.side, g.tag, false), label: "Darüber" });
1781 v.push(Mark { kind: DOCK_MK_BOTTOM, rect: [c[0], c[1] + m + gap, m, m], to: Target::Split(g.side, g.tag, true), label: "Darunter" });
1782 }
1783 }
1784 }
1785 let e = self.dp(12);
1786 let my = a[1] + (a[3] - m) / 2;
1787 v.push(Mark { kind: DOCK_MK_LEFT, rect: [a[0] + e, my, m, m], to: Target::Side(DOCK_LEFT), label: "Links andocken" });
1788 v.push(Mark { kind: DOCK_MK_RIGHT, rect: [a[0] + a[2] - e - m, my, m, m], to: Target::Side(DOCK_RIGHT), label: "Rechts andocken" });
1789 let c = rt.center;
1790 v.push(Mark { kind: DOCK_MK_BOTTOM, rect: [c[0] + (c[2] - m) / 2, a[1] + a[3] - e - m, m, m], to: Target::Side(DOCK_BOTTOM), label: "Unten andocken" });
1791 v
1792 }
1793
1794 fn dk_whole(&self, d: &Drag, g: &Geo) -> bool {
1796 d.src == (g.side, g.tag) && g.panels.iter().all(|p| d.panels.contains(p))
1797 }
1798
1799 fn dk_target(&self, rt: &Rt, d: &Drag) -> Target {
1801 let at = d.at;
1802 if let Some(mk) = self.dk_marks(rt, d).into_iter().find(|mk| inside(mk.rect, at)) {
1803 return mk.to;
1804 }
1805 for g in &rt.groups {
1806 if inside(g.hdr, at) {
1807 if self.dk_whole(d, g) {
1808 return Target::None;
1809 }
1810 let i = g.tabs.iter().filter(|t| t[0] + t[2] / 2 < at[0]).count();
1811 return Target::Tab(g.side, g.tag, i);
1812 }
1813 }
1814 if d.panels.iter().any(|&p| rt.flags(p) & DP_NOFLOAT != 0) {
1815 return Target::None;
1816 }
1817 Target::Float
1818 }
1819
1820 fn dk_preview(&self, rt: &Rt, st: &DockState, d: &Drag, to: Target) -> Option<[i32; 4]> {
1822 let half = |r: [i32; 4], side: u8, end: bool| {
1823 let ax = if side == DOCK_BOTTOM { 0 } else { 1 };
1824 let mut q = r;
1825 q[2 + ax] = r[2 + ax] / 2;
1826 if end {
1827 q[ax] = r[ax] + r[2 + ax] - q[2 + ax];
1828 }
1829 q
1830 };
1831 let group = |s: u8, t: u32| rt.groups.iter().find(|g| g.side == s && g.tag == t);
1832 match to {
1833 Target::None => None,
1834 Target::Tab(s, t, _) => group(s, t).map(|g| g.rect),
1835 Target::Split(s, t, after) => group(s, t).map(|g| half(g.rect, s, after)),
1836 Target::Side(s) => {
1837 if rt.on[s as usize] == 1 {
1838 return rt.groups.iter().rev().find(|g| g.side == s).map(|g| half(g.rect, s, true));
1839 }
1840 let a = rt.area;
1841 let c = rt.center;
1842 let n = self.dp(st.size(s));
1843 Some(match s {
1844 DOCK_LEFT => [a[0], a[1], n.min(a[2] / 2), a[3]],
1845 DOCK_RIGHT => [a[0] + a[2] - n.min(a[2] / 2), a[1], n.min(a[2] / 2), a[3]],
1846 _ => [c[0], a[1] + a[3] - n.min(a[3] / 2), c[2], n.min(a[3] / 2)],
1847 })
1848 }
1849 Target::Float => {
1850 let fb = &self.wp.rt.fb;
1851 if !inside([0, 0, fb.w, fb.h], d.at) {
1852 return None;
1853 }
1854 Some([d.at[0] - self.dp(d.grab[0]), d.at[1] - self.dp(d.grab[1]), self.dp(d.size[0]), self.dp(d.size[1])])
1855 }
1856 }
1857 }
1858
1859 fn dk_overlay(&mut self, rt: &Rt, st: &DockState) {
1861 let Some(d) = rt.drag.clone() else { return };
1862 let to = self.dk_target(rt, &d);
1863 let marks = self.dk_marks(rt, &d);
1864 let prev = self.layer_set(L_POPUP);
1865 let (lr, ls) = (self.wp.ctx.last_rect, self.wp.ctx.last_state);
1866 let sh = self.style.shell();
1867 if let Some(r) = self.dk_preview(rt, st, &d, to) {
1868 let mut sp = Sp { value: (to == Target::Float) as i32, ..Sp::default() };
1869 sp.place(r);
1870 sh.dock_preview(self, &sp);
1871 }
1872 for (k, mk) in marks.iter().enumerate() {
1873 let hover = inside(mk.rect, d.at);
1874 let mut sp = Sp { value: mk.kind, ev: if hover { EV_HOVER } else { 0 }, ..Sp::default() };
1875 sp.place(mk.rect);
1876 sh.dock_marker(self, &sp);
1877 let label = match mk.to {
1878 Target::Side(s) => self.dk_side_label(s),
1879 _ => tr(mk.label),
1880 };
1881 self.wp.ctx.last_rect = mk.rect;
1882 self.wp.ctx.last_state = if hover { SS_HOVER as u32 } else { 0 };
1883 self.sem(K_BUTTON, pid(rt.id, P_MARK, k as u32), Some(label), 0);
1884 }
1885 self.wp.ctx.last_rect = lr;
1886 self.wp.ctx.last_state = ls;
1887 self.layer_set(prev);
1888 }
1889
1890 fn dk_apply(&mut self, rt: &mut Rt, st: &mut DockState, a: Act) {
1893 match a {
1894 Act::Activate(p) => st.activate(p),
1895 Act::Hide(p) => st.hide(p),
1896 Act::Collapse(s, on) => st.collapse(s, on),
1897 Act::Home(slot) => st.dock_home(slot),
1898 Act::ToSide(p, s) => st.dock_to(p, s),
1899 Act::GroupToSide(p, s) => st.dock_group_to(p, s),
1900 Act::Float(p, r) => {
1901 st.float(p, Some(r));
1902 }
1903 Act::FloatGroup(p, r) => {
1904 st.float_group(p, Some(r));
1905 }
1906 Act::Drop(d) => {
1907 let to = self.dk_target(rt, &d);
1908 self.dk_drop(st, d, to);
1909 }
1910 }
1911 rt.changed = true;
1912 self.repass = true;
1913 }
1914
1915 fn dk_drop(&mut self, st: &mut DockState, d: Drag, to: Target) {
1917 let panels: Vec<u32> = d.panels.iter().copied().filter(|&p| st.visible(p)).collect();
1918 if panels.is_empty() {
1919 return;
1920 }
1921 let active = if panels.contains(&d.active) { d.active } else { panels[0] };
1922 match to {
1923 Target::None => {}
1924 Target::Side(s) => {
1925 for &p in &panels {
1926 st.take(p);
1927 }
1928 st.put_group(s as usize, None, &panels, active);
1929 }
1930 Target::Tab(s, tag, i) => {
1931 let mut i = i;
1933 if let Some(g) = st.sides[s as usize].groups.iter().find(|g| g.tag == tag) {
1934 let end = i.min(g.panels.len());
1935 i = end - g.panels[..end].iter().filter(|p| panels.contains(p)).count();
1936 }
1937 for &p in &panels {
1938 st.take(p);
1939 }
1940 st.put_tabs(s as usize, tag, i, &panels, active);
1941 }
1942 Target::Split(s, tag, after) => {
1943 for &p in &panels {
1944 st.take(p);
1945 }
1946 let there = st.sides[s as usize].groups.iter().any(|g| g.tag == tag);
1947 st.put_group(s as usize, if there { Some((tag, after)) } else { None }, &panels, active);
1948 }
1949 Target::Float => {
1950 let px = if self.rtl { self.wp.rt.fb.w - 1 - d.at[0] } else { d.at[0] };
1952 let (x, y) = (self.px2dip(px), self.px2dip(d.at[1]));
1953 let (w, h) = (d.size[0], d.size[1]);
1954 let gx = if self.rtl { w - d.grab[0] } else { d.grab[0] };
1955 let tbh = self.style.metric(SM_WIN_TBH_TOOL);
1956 let r = [x - gx, y - d.grab[1] - tbh, w, h];
1957 if d.src.0 == DOCK_FLOAT {
1959 if let Some(f) = st.floats.iter_mut().find(|f| f.slot as u32 == d.src.1) {
1960 if f.group.panels.iter().all(|p| panels.contains(p)) {
1961 f.rect[0] = r[0];
1962 f.rect[1] = r[1];
1963 return;
1964 }
1965 }
1966 }
1967 let homes: Vec<Hidden> = panels.iter().filter_map(|&p| st.take(p)).collect();
1968 let (home, home_tag) = match homes.first() {
1969 Some(h) if h.side < DOCK_FLOAT => (h.side, h.tag),
1970 Some(h) => (h.home, h.home_tag),
1971 None => (DOCK_RIGHT, 0),
1972 };
1973 if !st.put_float(r, home, home_tag, &panels, active) {
1974 for h in homes {
1975 st.restore(h);
1976 }
1977 }
1978 }
1979 }
1980 }
1981}