1use crate::field::NodeId;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct Rect {
5 pub x: f32,
6 pub y: f32,
7 pub w: f32,
8 pub h: f32,
9}
10
11impl Rect {
12 pub fn right(&self) -> f32 {
13 self.x + self.w
14 }
15
16 pub fn bottom(&self) -> f32 {
17 self.y + self.h
18 }
19
20 pub fn inset(&self, pad: f32) -> Rect {
21 Rect {
22 x: self.x + pad,
23 y: self.y + pad,
24 w: (self.w - 2.0 * pad).max(0.0),
25 h: (self.h - 2.0 * pad).max(0.0),
26 }
27 }
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum TileRole {
32 Master,
33 Stack,
34}
35
36#[derive(Clone, Debug, PartialEq)]
37pub struct Tile {
38 pub id: NodeId,
39 pub role: TileRole,
40 pub rect: Rect,
41}
42
43#[derive(Clone, Debug, PartialEq)]
44pub struct MasterStackLayout {
45 pub tiles: Vec<Tile>,
46}
47
48pub fn layout_master_stack(container: Rect, members: &[NodeId]) -> MasterStackLayout {
49 if members.is_empty() {
50 return MasterStackLayout { tiles: Vec::new() };
51 }
52
53 if members.len() == 1 {
54 return MasterStackLayout {
55 tiles: vec![Tile {
56 id: members[0],
57 role: TileRole::Master,
58 rect: container,
59 }],
60 };
61 }
62
63 let master_w = (container.w * 0.6).clamp(0.0, container.w);
64 let stack_w = (container.w - master_w).max(0.0);
65 let master_rect = Rect {
66 x: container.x,
67 y: container.y,
68 w: master_w,
69 h: container.h,
70 };
71 let stack_rect = Rect {
72 x: container.x + master_w,
73 y: container.y,
74 w: stack_w,
75 h: container.h,
76 };
77
78 let mut tiles = Vec::with_capacity(members.len());
79 tiles.push(Tile {
80 id: members[0],
81 role: TileRole::Master,
82 rect: master_rect,
83 });
84
85 let stack_members = &members[1..];
86 let stack_len = stack_members.len() as f32;
87 let mut next_y = stack_rect.y;
88
89 for (index, member) in stack_members.iter().enumerate().rev() {
90 let height = if index == 0 {
91 stack_rect.bottom() - next_y
92 } else {
93 stack_rect.h / stack_len
94 }
95 .max(0.0);
96
97 tiles.push(Tile {
98 id: *member,
99 role: TileRole::Stack,
100 rect: Rect {
101 x: stack_rect.x,
102 y: next_y,
103 w: stack_rect.w,
104 h: height,
105 },
106 });
107 next_y += height;
108 }
109
110 tiles.sort_by_key(|tile| {
111 members
112 .iter()
113 .position(|member| *member == tile.id)
114 .unwrap_or(usize::MAX)
115 });
116
117 MasterStackLayout { tiles }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn ids(n: u64) -> Vec<NodeId> {
125 (0..n).map(NodeId::new).collect()
126 }
127
128 #[test]
129 fn empty_members_produces_no_tiles() {
130 let layout = layout_master_stack(
131 Rect {
132 x: 0.0,
133 y: 0.0,
134 w: 1000.0,
135 h: 600.0,
136 },
137 &[],
138 );
139
140 assert!(layout.tiles.is_empty());
141 }
142
143 #[test]
144 fn single_member_fills_container_as_master() {
145 let members = ids(1);
146 let container = Rect {
147 x: 0.0,
148 y: 0.0,
149 w: 1000.0,
150 h: 600.0,
151 };
152
153 let layout = layout_master_stack(container, &members);
154
155 assert_eq!(layout.tiles.len(), 1);
156 assert_eq!(layout.tiles[0].id, members[0]);
157 assert_eq!(layout.tiles[0].role, TileRole::Master);
158 assert_eq!(layout.tiles[0].rect, container);
159 }
160
161 #[test]
162 fn master_stays_on_left_and_stack_on_right() {
163 let members = ids(3);
164 let layout = layout_master_stack(
165 Rect {
166 x: 0.0,
167 y: 0.0,
168 w: 1000.0,
169 h: 600.0,
170 },
171 &members,
172 );
173
174 assert_eq!(layout.tiles[0].role, TileRole::Master);
175 assert_eq!(layout.tiles[0].id, members[0]);
176 assert!(layout.tiles[0].rect.x < layout.tiles[1].rect.x);
177 assert!(layout.tiles[0].rect.x < layout.tiles[2].rect.x);
178 }
179
180 #[test]
181 fn stack_members_are_laid_out_bottom_up() {
182 let members = ids(4);
183 let layout = layout_master_stack(
184 Rect {
185 x: 0.0,
186 y: 0.0,
187 w: 1000.0,
188 h: 600.0,
189 },
190 &members,
191 );
192
193 let second = &layout.tiles[1];
194 let third = &layout.tiles[2];
195 let fourth = &layout.tiles[3];
196
197 assert_eq!(second.id, members[1]);
198 assert_eq!(third.id, members[2]);
199 assert_eq!(fourth.id, members[3]);
200 assert!(second.rect.y > third.rect.y);
201 assert!(third.rect.y > fourth.rect.y);
202 assert_eq!(fourth.rect.y, 0.0);
203 }
204
205 #[test]
206 fn every_member_gets_geometry_without_overflow() {
207 let members = ids(7);
208 let layout = layout_master_stack(
209 Rect {
210 x: 50.0,
211 y: 20.0,
212 w: 1400.0,
213 h: 900.0,
214 },
215 &members,
216 );
217
218 assert_eq!(layout.tiles.len(), members.len());
219 assert!(
220 layout
221 .tiles
222 .iter()
223 .all(|tile| tile.rect.w >= 0.0 && tile.rect.h >= 0.0)
224 );
225 assert!(layout.tiles.iter().all(|tile| members.contains(&tile.id)));
226 }
227}