Skip to main content

halley_core/
cluster_layout.rs

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}