1use crate::cluster_layout::{ClusterCycleDirection, ClusterWorkspacePlacement};
2use crate::field::NodeId;
3use crate::tiling::Rect;
4
5pub fn stacking_visible_limit(max_visible: usize) -> usize {
6 if max_visible == 0 {
7 usize::MAX
8 } else {
9 max_visible
10 }
11}
12
13pub fn layout_stacking_workspace(
14 bounds: Rect,
15 members: &[NodeId],
16 frame_pad: f32,
17) -> Vec<ClusterWorkspacePlacement> {
18 if members.is_empty() {
19 return Vec::new();
20 }
21
22 const STACK_CARD_WIDTH_FRAC: f32 = 0.74;
23 const STACK_CARD_HEIGHT_FRAC: f32 = 0.84;
24 const STACK_PEEK_X_FRAC: f32 = 0.04;
25 const STACK_SCALE_STEP: f32 = 0.035;
26 const STACK_MIN_SCALE: f32 = 0.84;
27
28 let frame_pad = frame_pad.max(0.0);
29 let base_frame_w = (bounds.w * STACK_CARD_WIDTH_FRAC).clamp(1.0, bounds.w.max(1.0));
30 let base_frame_h = (bounds.h * STACK_CARD_HEIGHT_FRAC).clamp(1.0, bounds.h.max(1.0));
31 let center_x = bounds.x + bounds.w * 0.5;
32 let center_y = bounds.y + bounds.h * 0.5;
33 let active_frame_x = center_x - base_frame_w * 0.5;
34 let active_frame_y = center_y - base_frame_h * 0.5;
35 let peek_x = (bounds.w * STACK_PEEK_X_FRAC).max(24.0) + frame_pad * 2.0;
36
37 let mut layered = Vec::with_capacity(members.len());
38 for (index, node_id) in members.iter().copied().enumerate() {
39 let scale = (1.0 - index as f32 * STACK_SCALE_STEP).clamp(STACK_MIN_SCALE, 1.0);
40 let frame_w = (base_frame_w * scale).clamp(1.0, bounds.w.max(1.0));
41 let frame_h = (base_frame_h * scale).clamp(1.0, bounds.h.max(1.0));
42 let width = (frame_w - frame_pad * 2.0).clamp(1.0, bounds.w.max(1.0));
43 let height = (frame_h - frame_pad * 2.0).clamp(1.0, bounds.h.max(1.0));
44 let offset_x = peek_x * index as f32;
45 let rect = Rect {
46 x: active_frame_x + offset_x + frame_pad,
47 y: active_frame_y + frame_pad,
48 w: width,
49 h: height,
50 };
51 layered.push((index, node_id, rect));
52 }
53
54 layered.sort_by_key(|(index, _, _)| std::cmp::Reverse(*index));
55 layered
56 .into_iter()
57 .enumerate()
58 .map(|(depth, (_, node_id, rect))| ClusterWorkspacePlacement {
59 node_id,
60 rect,
61 depth,
62 })
63 .collect()
64}
65
66pub fn cycle_stacking_members(
67 members: &mut Vec<NodeId>,
68 direction: ClusterCycleDirection,
69) -> Option<NodeId> {
70 if members.is_empty() {
71 return None;
72 }
73
74 match direction {
75 ClusterCycleDirection::Prev => members.rotate_right(1),
76 ClusterCycleDirection::Next => members.rotate_left(1),
77 }
78 members.first().copied()
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 fn ids(n: u64) -> Vec<NodeId> {
86 (0..n).map(NodeId::new).collect()
87 }
88
89 #[test]
90 fn zero_max_visible_means_unlimited_for_stacking() {
91 assert_eq!(stacking_visible_limit(0), usize::MAX);
92 }
93
94 #[test]
95 fn stacking_front_card_is_centered_and_front_most() {
96 let members = ids(4);
97 let result = layout_stacking_workspace(
98 Rect {
99 x: 0.0,
100 y: 0.0,
101 w: 1000.0,
102 h: 600.0,
103 },
104 &members,
105 12.0,
106 );
107
108 let active = result
109 .iter()
110 .find(|placement| placement.node_id == members[0])
111 .expect("active placement");
112
113 assert_eq!(active.depth, result.len() - 1);
114 assert!((active.rect.x - 12.0 + (active.rect.w + 24.0) * 0.5 - 500.0).abs() <= 0.5);
115 assert!((active.rect.y - 12.0 + (active.rect.h + 24.0) * 0.5 - 300.0).abs() <= 0.5);
116 }
117
118 #[test]
119 fn stacking_visible_cards_step_outward_in_one_direction() {
120 let members = ids(5);
121 let result = layout_stacking_workspace(
122 Rect {
123 x: 0.0,
124 y: 0.0,
125 w: 1000.0,
126 h: 600.0,
127 },
128 &members,
129 12.0,
130 );
131
132 let active = result
133 .iter()
134 .find(|placement| placement.node_id == members[0])
135 .expect("active placement");
136 let second = result
137 .iter()
138 .find(|placement| placement.node_id == members[1])
139 .expect("second placement");
140 let third = result
141 .iter()
142 .find(|placement| placement.node_id == members[2])
143 .expect("third placement");
144
145 assert!(second.rect.x > active.rect.x);
146 assert!((second.rect.y - active.rect.y).abs() <= 0.5);
147 assert!(third.rect.x > second.rect.x);
148 assert!((third.rect.y - second.rect.y).abs() <= 0.5);
149 assert!(second.rect.w < active.rect.w);
150 assert!(third.rect.w < second.rect.w);
151 assert!(second.rect.h < active.rect.h);
152 assert!(third.rect.h < second.rect.h);
153 assert!(second.rect.x - active.rect.x > 24.0);
154 }
155
156 #[test]
157 fn cycling_stacking_members_rotates_order() {
158 let mut members = ids(4);
159
160 assert_eq!(
161 cycle_stacking_members(&mut members, ClusterCycleDirection::Next),
162 Some(NodeId::new(1))
163 );
164 assert_eq!(
165 members,
166 vec![
167 NodeId::new(1),
168 NodeId::new(2),
169 NodeId::new(3),
170 NodeId::new(0)
171 ]
172 );
173 }
174}