Skip to main content

halley_core/
tiling.rs

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}