1use crate::field::NodeId;
2use crate::stacking::{layout_stacking_workspace, stacking_visible_limit};
3use crate::tiling::Rect;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum ClusterWorkspaceLayoutKind {
7 Tiling,
8 Stacking,
9}
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ClusterCycleDirection {
13 Prev,
14 Next,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct ClusterWorkspacePlacement {
19 pub node_id: NodeId,
20 pub rect: Rect,
21 pub depth: usize,
22}
23
24#[derive(Clone, Debug, PartialEq)]
25pub struct ClusterWorkspaceLayoutResult {
26 pub kind: ClusterWorkspaceLayoutKind,
27 pub placements: Vec<ClusterWorkspacePlacement>,
28 pub queue_members: Vec<NodeId>,
29}
30
31#[inline]
32pub fn cluster_visible_limit(kind: ClusterWorkspaceLayoutKind, limit_setting: usize) -> usize {
33 match kind {
34 ClusterWorkspaceLayoutKind::Tiling => {
35 if limit_setting == 0 {
36 usize::MAX
37 } else {
38 limit_setting.saturating_add(1)
39 }
40 }
41 ClusterWorkspaceLayoutKind::Stacking => stacking_visible_limit(limit_setting),
42 }
43}
44
45pub fn layout_cluster_workspace(
46 kind: ClusterWorkspaceLayoutKind,
47 bounds: Rect,
48 inner_gap: f32,
49 frame_pad: f32,
50 members: &[NodeId],
51 limit_setting: usize,
52) -> ClusterWorkspaceLayoutResult {
53 let visible_limit = cluster_visible_limit(kind, limit_setting);
54 let visible_len = members.len().min(visible_limit);
55 let visible_members = &members[..visible_len];
56 let queue_members = if matches!(kind, ClusterWorkspaceLayoutKind::Tiling) {
57 members[visible_len..].to_vec()
58 } else {
59 Vec::new()
60 };
61
62 let placements = match kind {
63 ClusterWorkspaceLayoutKind::Tiling => {
64 layout_tiling_workspace(bounds, inner_gap, visible_members)
65 }
66 ClusterWorkspaceLayoutKind::Stacking => {
67 layout_stacking_workspace(bounds, visible_members, frame_pad)
68 }
69 };
70
71 ClusterWorkspaceLayoutResult {
72 kind,
73 placements,
74 queue_members,
75 }
76}
77
78fn layout_tiling_workspace(
79 bounds: Rect,
80 inner_gap: f32,
81 members: &[NodeId],
82) -> Vec<ClusterWorkspacePlacement> {
83 if members.is_empty() {
84 return Vec::new();
85 }
86
87 if members.len() == 1 {
88 return vec![ClusterWorkspacePlacement {
89 node_id: members[0],
90 rect: bounds,
91 depth: 0,
92 }];
93 }
94
95 let inner_gap = inner_gap.max(0.0);
96 let split_w = (bounds.w - inner_gap).max(0.0);
97 let master_w = (split_w * 0.6).clamp(0.0, split_w);
98 let stack_w = (split_w - master_w).max(0.0);
99 let stack_x = bounds.x + master_w + inner_gap;
100 let stack_count = members.len() - 1;
101
102 let mut placements = Vec::with_capacity(members.len());
103 placements.push(ClusterWorkspacePlacement {
104 node_id: members[0],
105 rect: Rect {
106 x: bounds.x,
107 y: bounds.y,
108 w: master_w,
109 h: bounds.h,
110 },
111 depth: 0,
112 });
113
114 if stack_count == 1 {
115 placements.push(ClusterWorkspacePlacement {
116 node_id: members[1],
117 rect: Rect {
118 x: stack_x,
119 y: bounds.y,
120 w: stack_w,
121 h: bounds.h,
122 },
123 depth: 1,
124 });
125 return placements;
126 }
127
128 let total_stack_gap = inner_gap * (stack_count.saturating_sub(1) as f32);
129 let stack_window_h = ((bounds.h - total_stack_gap).max(0.0)) / stack_count as f32;
130 let mut next_y = bounds.y;
131 let bounds_bottom = bounds.y + bounds.h;
132
133 for index in 0..stack_count {
134 let remaining = stack_count - index;
135 let height = if remaining == 1 {
136 (bounds_bottom - next_y).max(0.0)
137 } else {
138 stack_window_h.max(0.0)
139 };
140 placements.push(ClusterWorkspacePlacement {
141 node_id: members[index + 1],
142 rect: Rect {
143 x: stack_x,
144 y: next_y,
145 w: stack_w,
146 h: height,
147 },
148 depth: index + 1,
149 });
150 next_y += height + inner_gap;
151 }
152
153 placements
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn ids(n: u64) -> Vec<NodeId> {
161 (0..n).map(NodeId::new).collect()
162 }
163
164 #[test]
165 fn tiling_limit_keeps_master_plus_stack() {
166 let result = layout_cluster_workspace(
167 ClusterWorkspaceLayoutKind::Tiling,
168 Rect {
169 x: 0.0,
170 y: 0.0,
171 w: 1000.0,
172 h: 700.0,
173 },
174 12.0,
175 0.0,
176 &ids(6),
177 3,
178 );
179
180 assert_eq!(result.placements.len(), 4);
181 assert_eq!(result.queue_members.len(), 2);
182 }
183
184 #[test]
185 fn stacking_limit_counts_total_visible_cards_without_queue() {
186 let result = layout_cluster_workspace(
187 ClusterWorkspaceLayoutKind::Stacking,
188 Rect {
189 x: 0.0,
190 y: 0.0,
191 w: 1000.0,
192 h: 700.0,
193 },
194 0.0,
195 12.0,
196 &ids(6),
197 3,
198 );
199
200 assert_eq!(result.placements.len(), 3);
201 assert!(result.queue_members.is_empty());
202 }
203
204 #[test]
205 fn zero_max_visible_means_unlimited_for_stacking() {
206 let members = ids(5);
207 let result = layout_cluster_workspace(
208 ClusterWorkspaceLayoutKind::Stacking,
209 Rect {
210 x: 0.0,
211 y: 0.0,
212 w: 900.0,
213 h: 600.0,
214 },
215 0.0,
216 12.0,
217 &members,
218 0,
219 );
220
221 assert_eq!(result.placements.len(), members.len());
222 assert!(result.queue_members.is_empty());
223 }
224}