leftwm_layouts/geometry/
split.rs

1use serde::{Deserialize, Serialize};
2
3use super::{divrem, remainderless_division, split, Rect, Rotation};
4
5/// Describes different ways a [`crate::geometry::Rect`] can be split.
6///
7/// *Disclaimer: As it may be confusing - The terms vertical/horizontal are referring to the "splits"
8/// not the orientation of the resulting stack. For example, [`Split::Horizontal`]
9/// splits a rect by **horizontal cuts**, resulting in a "vertically stacked" list of rects.
10/// See the variants' documentation for clarification.*
11#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
12pub enum Split {
13    /// Rectangle is split by `horizontal` cuts.
14    ///
15    /// ```txt
16    /// +--------+      +--------+
17    /// |        |      |        |
18    /// |        |      +--------+
19    /// |        |  =>  |        |
20    /// |        |      +--------+
21    /// |        |      |        |
22    /// +--------+      +--------+
23    /// ```
24    Horizontal,
25
26    /// Rectangle is split by `vertical` cuts.
27    ///
28    /// ```txt
29    /// +--------+      +--+--+--+
30    /// |        |      |  |  |  |
31    /// |        |      |  |  |  |
32    /// |        |  =>  |  |  |  |
33    /// |        |      |  |  |  |
34    /// |        |      |  |  |  |
35    /// +--------+      +--+--+--+
36    /// ```
37    Vertical,
38
39    /// Rectangle is split in a "Grid" pattern while still accounting for
40    /// all of the available space, resulting in some rectangles being larger.
41    ///
42    /// ```txt
43    /// +-------+      +---+---+
44    /// |       |      |   |   |
45    /// |       |      |   |   |
46    /// |       |  =>  |   +---+
47    /// |       |      |   |   |
48    /// |       |      |   |   |
49    /// +-------+      +---+---+
50    /// ```
51    Grid,
52
53    /// Rectangle is split in a "Fibonacci" pattern.
54    ///
55    /// ```txt
56    /// +-------+      +---+---+
57    /// |       |      |   |   |
58    /// |       |      |   |   |
59    /// |       |  =>  |   +-+-+
60    /// |       |      |   |_| |
61    /// |       |      |   | | |
62    /// +-------+      +---+---+
63    /// ```
64    Fibonacci,
65
66    /// Rectangle is split in a "Fibonacci"-like pattern.
67    /// But instead of spiraling into the middle, it spirals into the bottom right.
68    ///
69    /// ```txt
70    /// +-------+      +---+---+
71    /// |       |      |   |   |
72    /// |       |      |   |   |
73    /// |       |  =>  |   +-+-+
74    /// |       |      |   | |_|
75    /// |       |      |   | |||
76    /// +-------+      +---+---+
77    /// ```
78    Dwindle,
79}
80
81pub fn vertical(rect: &Rect, amount: usize) -> Vec<Rect> {
82    let mut from_left = rect.x;
83    remainderless_division(rect.w as usize, amount)
84        .iter()
85        .map(|width| {
86            let rect = Rect::new(from_left, rect.y, *width as u32, rect.h);
87            from_left += *width as i32;
88            rect
89        })
90        .collect()
91}
92
93pub fn horizontal(rect: &Rect, amount: usize) -> Vec<Rect> {
94    let mut from_top = rect.y;
95    remainderless_division(rect.h as usize, amount)
96        .iter()
97        .map(|height| {
98            let rect = Rect::new(rect.x, from_top, rect.w, *height as u32);
99            from_top += *height as i32;
100            rect
101        })
102        .collect()
103}
104
105pub fn grid(rect: &Rect, amount: usize) -> Vec<Rect> {
106    let cols = (amount as f64).sqrt().ceil() as usize;
107    let col_tiles = vertical(rect, cols);
108    // the minimum amount of rows per column
109    let min_rows = (amount as f64 / cols as f64).floor() as usize;
110    // the amount of columns in which there are only the minimum amount of rows
111    let min_row_amount = col_tiles.len() - divrem(amount, cols).1;
112
113    col_tiles
114        .iter()
115        .enumerate()
116        .flat_map(|(i, col_tile)| {
117            let rows = if i < min_row_amount {
118                min_rows
119            } else {
120                min_rows + 1
121            };
122            horizontal(col_tile, rows)
123        })
124        .collect()
125}
126
127pub fn fibonacci(rect: &Rect, amount: usize) -> Vec<Rect> {
128    let tiles: &mut Vec<Rect> = &mut Vec::new();
129    let mut remaining_tile = *rect;
130    let mut direction = Rotation::East;
131    for i in 0..amount {
132        let has_next = i < amount - 1;
133        direction = direction.clockwise();
134        if has_next {
135            let split_axis = match direction {
136                Rotation::North | Rotation::South => Split::Horizontal,
137                Rotation::East | Rotation::West => Split::Vertical,
138            };
139            let backwards = match direction {
140                Rotation::East | Rotation::South => false,
141                Rotation::West | Rotation::North => true,
142            };
143            let splitted_tiles = split(&remaining_tile, 2, Some(split_axis));
144            if backwards {
145                tiles.push(splitted_tiles[1]);
146                remaining_tile = splitted_tiles[0];
147            } else {
148                tiles.push(splitted_tiles[0]);
149                remaining_tile = splitted_tiles[1];
150            }
151        } else {
152            tiles.push(remaining_tile);
153        }
154    }
155    tiles.clone()
156}
157
158pub fn dwindle(rect: &Rect, amount: usize) -> Vec<Rect> {
159    let tiles: &mut Vec<Rect> = &mut Vec::new();
160    let mut remaining_tile = *rect;
161    let mut last_axis = Split::Vertical;
162    for i in 0..amount {
163        let has_next = i < amount - 1;
164        last_axis = if last_axis == Split::Vertical {
165            Split::Horizontal
166        } else {
167            Split::Vertical
168        };
169        if has_next {
170            let splitted_tiles = split(&remaining_tile, 2, Some(last_axis));
171            tiles.push(splitted_tiles[0]);
172            remaining_tile = splitted_tiles[1];
173        } else {
174            tiles.push(remaining_tile);
175        }
176    }
177    tiles.clone()
178}
179
180#[cfg(test)]
181mod tests {
182    use crate::geometry::{
183        split::{dwindle, fibonacci, grid, horizontal, vertical},
184        Rect,
185    };
186
187    const CONTAINER: Rect = Rect {
188        x: 0,
189        y: 0,
190        w: 400,
191        h: 200,
192    };
193
194    #[test]
195    fn split_vertical_two_windows() {
196        let rects = vertical(&CONTAINER, 2);
197        assert_eq!(rects.len(), 2);
198        let expected_first = Rect::new(0, 0, 200, 200);
199        let expected_second = Rect::new(200, 0, 200, 200);
200        assert!(rects[0].eq(&expected_first));
201        assert!(rects[1].eq(&expected_second));
202    }
203
204    #[test]
205    fn split_vertical_three_windows() {
206        let rects = vertical(&CONTAINER, 3);
207        assert_eq!(rects.len(), 3);
208        // first window must be larger because of the remainderless division
209        let expected_first = Rect::new(0, 0, 134, 200);
210        let expected_second = Rect::new(134, 0, 133, 200);
211        let expected_third = Rect::new(267, 0, 133, 200);
212        assert!(rects[0].eq(&expected_first));
213        assert!(rects[1].eq(&expected_second));
214        assert!(rects[2].eq(&expected_third));
215    }
216
217    #[test]
218    fn split_horizontal_two_windows() {
219        let rects = horizontal(&CONTAINER, 2);
220        assert_eq!(rects.len(), 2);
221        let expected_first = Rect::new(0, 0, 400, 100);
222        let expected_second = Rect::new(0, 100, 400, 100);
223        assert!(rects[0].eq(&expected_first));
224        assert!(rects[1].eq(&expected_second));
225    }
226
227    #[test]
228    fn split_horizontal_three_windows() {
229        let rects = horizontal(&CONTAINER, 3);
230        assert_eq!(rects.len(), 3);
231        // first two windows must be taller because of remainderless division
232        let expected_first = Rect::new(0, 0, 400, 67);
233        let expected_second = Rect::new(0, 67, 400, 67);
234        let expected_third = Rect::new(0, 134, 400, 66);
235        assert!(rects[0].eq(&expected_first));
236        assert!(rects[1].eq(&expected_second));
237        assert!(rects[2].eq(&expected_third));
238    }
239
240    #[test]
241    fn split_grid_three_windows() {
242        let rects = grid(&CONTAINER, 3);
243        assert_eq!(rects.len(), 3);
244        let expected_first = Rect::new(0, 0, 200, 200);
245        let expected_second = Rect::new(200, 0, 200, 100);
246        let expected_third = Rect::new(200, 100, 200, 100);
247        assert!(rects[0].eq(&expected_first));
248        assert!(rects[1].eq(&expected_second));
249        assert!(rects[2].eq(&expected_third));
250    }
251
252    #[test]
253    fn split_grid_four_windows() {
254        let rects = grid(&CONTAINER, 4);
255        assert_eq!(rects.len(), 4);
256        let expected_first = Rect::new(0, 0, 200, 100);
257        let expected_second = Rect::new(0, 100, 200, 100);
258        let expected_third = Rect::new(200, 0, 200, 100);
259        let expected_fourth = Rect::new(200, 100, 200, 100);
260        assert!(rects[0].eq(&expected_first));
261        assert!(rects[1].eq(&expected_second));
262        assert!(rects[2].eq(&expected_third));
263        assert!(rects[3].eq(&expected_fourth));
264    }
265
266    #[test]
267    fn split_fibonacci_four_windows() {
268        let rects = fibonacci(&CONTAINER, 4);
269        assert_eq!(rects.len(), 4);
270        let expected_first = Rect::new(0, 0, 400, 100);
271        let expected_second = Rect::new(200, 100, 200, 100);
272        let expected_third = Rect::new(0, 150, 200, 50);
273        let expected_fourth = Rect::new(0, 100, 200, 50);
274        assert!(rects[0].eq(&expected_first));
275        assert!(rects[1].eq(&expected_second));
276        assert!(rects[2].eq(&expected_third));
277        assert!(rects[3].eq(&expected_fourth));
278    }
279
280    #[test]
281    fn split_fibonacci_five_windows() {
282        let rects = fibonacci(&CONTAINER, 5);
283        assert_eq!(rects.len(), 5);
284        let expected_first = Rect::new(0, 0, 400, 100);
285        let expected_second = Rect::new(200, 100, 200, 100);
286        let expected_third = Rect::new(0, 150, 200, 50);
287        let expected_fourth = Rect::new(0, 100, 100, 50);
288        let expected_fifth = Rect::new(100, 100, 100, 50);
289        assert!(rects[0].eq(&expected_first));
290        assert!(rects[1].eq(&expected_second));
291        assert!(rects[2].eq(&expected_third));
292        assert!(rects[3].eq(&expected_fourth));
293        assert!(rects[4].eq(&expected_fifth));
294    }
295
296    #[test]
297    fn split_dwindle_four_windows() {
298        let rects = dwindle(&CONTAINER, 4);
299        assert_eq!(rects.len(), 4);
300        let expected_first = Rect::new(0, 0, 400, 100);
301        let expected_second = Rect::new(0, 100, 200, 100);
302        let expected_third = Rect::new(200, 100, 200, 50);
303        let expected_fourth = Rect::new(200, 150, 200, 50);
304        assert!(rects[0].eq(&expected_first));
305        assert!(rects[1].eq(&expected_second));
306        assert!(rects[2].eq(&expected_third));
307        assert!(rects[3].eq(&expected_fourth));
308    }
309
310    #[test]
311    fn split_dwindle_five_windows() {
312        let rects = dwindle(&CONTAINER, 5);
313        assert_eq!(rects.len(), 5);
314        let expected_first = Rect::new(0, 0, 400, 100);
315        let expected_second = Rect::new(0, 100, 200, 100);
316        let expected_third = Rect::new(200, 100, 200, 50);
317        let expected_fourth = Rect::new(200, 150, 100, 50);
318        let expected_fifth = Rect::new(300, 150, 100, 50);
319        assert!(rects[0].eq(&expected_first));
320        assert!(rects[1].eq(&expected_second));
321        assert!(rects[2].eq(&expected_third));
322        assert!(rects[3].eq(&expected_fourth));
323        assert!(rects[4].eq(&expected_fifth));
324    }
325}