leftwm_layouts/
lib.rs

1use std::cmp;
2use std::vec;
3
4use geometry::Rect;
5use geometry::Split;
6use layouts::three_column;
7use layouts::two_column;
8pub use layouts::Layout;
9use layouts::Main;
10use layouts::SecondStack;
11
12pub mod geometry;
13pub mod layouts;
14
15pub fn apply(definition: &Layout, window_count: usize, container: &Rect) -> Vec<Rect> {
16    if window_count == 0 {
17        return vec![];
18    }
19
20    let mut rects = match (&definition.columns.main, &definition.columns.second_stack) {
21        (None, _) => stack(container, window_count, definition.columns.stack.split),
22        (Some(main), None) => main_stack(container, window_count, definition, main),
23        (Some(main), Some(alternate_stack)) => {
24            stack_main_stack(container, window_count, definition, main, alternate_stack)
25        }
26    };
27
28    // flip the whole layout
29    geometry::flip(&mut rects, definition.flip, container);
30
31    // rotate the whole layout
32    geometry::rotate(&mut rects, definition.rotate, container);
33
34    rects
35}
36
37fn stack(container: &Rect, window_count: usize, split: Option<Split>) -> Vec<Rect> {
38    geometry::split(container, window_count, split)
39}
40
41fn main_stack(
42    container: &Rect,
43    window_count: usize,
44    definition: &Layout,
45    main: &Main,
46) -> Vec<Rect> {
47    let (mut main_tile, mut stack_tile) = two_column(
48        window_count,
49        container,
50        main.count,
51        main.size,
52        definition.reserve,
53    );
54
55    // root rotation
56    match (main_tile, stack_tile) {
57        (None, None) => {}
58        (None, Some(b)) => {
59            let mut v = vec![b];
60            geometry::rotate(&mut v, definition.columns.rotate, container);
61            geometry::flip(&mut v, definition.columns.flip, container);
62            stack_tile = Some(v[0]);
63        }
64        (Some(a), None) => {
65            let mut v = vec![a];
66            geometry::rotate(&mut v, definition.columns.rotate, container);
67            geometry::flip(&mut v, definition.columns.flip, container);
68            main_tile = Some(v[0]);
69        }
70        (Some(a), Some(b)) => {
71            let mut v = vec![a, b];
72            geometry::rotate(&mut v, definition.columns.rotate, container);
73            geometry::flip(&mut v, definition.columns.flip, container);
74            main_tile = Some(v[0]);
75            stack_tile = Some(v[1]);
76        }
77    }
78
79    //geometry::flip(container, &mut rects, definition.flip);
80
81    let mut main_tiles = vec![];
82    if let Some(tile) = main_tile {
83        main_tiles.append(&mut geometry::split(
84            &tile,
85            usize::min(main.count, window_count),
86            main.split,
87        ));
88        geometry::rotate(&mut main_tiles, main.rotate, container);
89        geometry::flip(&mut main_tiles, main.flip, container);
90    }
91
92    let mut stack_tiles = vec![];
93    if let Some(tile) = stack_tile {
94        stack_tiles.append(&mut geometry::split(
95            &tile,
96            window_count.saturating_sub(main.count),
97            definition.columns.stack.split,
98        ));
99        geometry::rotate(&mut stack_tiles, definition.columns.stack.rotate, container);
100        geometry::flip(&mut stack_tiles, definition.columns.stack.flip, container);
101    }
102
103    let mut all = vec![];
104    all.append(&mut main_tiles);
105    all.append(&mut stack_tiles);
106    all
107}
108
109fn stack_main_stack(
110    container: &Rect,
111    window_count: usize,
112    definition: &Layout,
113    main: &Main,
114    alternate_stack: &SecondStack,
115) -> Vec<Rect> {
116    let main_window_count = cmp::min(main.count, window_count);
117    let stack_window_count = window_count.saturating_sub(main_window_count);
118    let balance_stacks = definition.columns.stack.split.is_some();
119    let (left_window_count, right_window_count) = if balance_stacks {
120        let counts = geometry::remainderless_division(stack_window_count, 2);
121        (counts[0], counts[1])
122    } else {
123        (1, cmp::max(0, stack_window_count.saturating_sub(1)))
124    };
125
126    let (mut left_column, mut main_column, mut right_column) = three_column(
127        window_count,
128        container,
129        main_window_count,
130        main.size,
131        definition.reserve,
132        balance_stacks,
133    );
134
135    // prepare columns to rotate / flip
136    let mut columns = vec![];
137    columns.push(left_column.unwrap_or(Rect::new(0, 0, 0, 0)));
138    columns.push(main_column.unwrap_or(Rect::new(0, 0, 0, 0)));
139    columns.push(right_column.unwrap_or(Rect::new(0, 0, 0, 0)));
140    geometry::rotate(&mut columns, definition.columns.rotate, container);
141    geometry::flip(&mut columns, definition.columns.flip, container);
142
143    // copy rotated/flipped columns into the variables
144    let non_empty = |rect: &&Rect| rect.surface_area() > 0;
145    left_column = columns.first().filter(non_empty).copied();
146    main_column = columns.get(1).filter(non_empty).copied();
147    right_column = columns.get(2).filter(non_empty).copied();
148
149    let mut main_tiles = vec![];
150    if let Some(tile) = main_column {
151        main_tiles.append(&mut geometry::split(&tile, main_window_count, main.split));
152        geometry::rotate(&mut main_tiles, main.rotate, container);
153        geometry::flip(&mut main_tiles, main.flip, container);
154    }
155
156    let mut left_tiles = vec![];
157    if let Some(tile) = left_column {
158        left_tiles.append(&mut geometry::split(
159            &tile,
160            left_window_count,
161            definition.columns.stack.split,
162        ));
163        geometry::rotate(&mut left_tiles, definition.columns.stack.rotate, container);
164        geometry::flip(&mut left_tiles, definition.columns.stack.flip, container);
165    }
166
167    let mut right_tiles = vec![];
168    if let Some(tile) = right_column {
169        right_tiles.append(&mut geometry::split(
170            &tile,
171            right_window_count,
172            Some(alternate_stack.split),
173        ));
174        geometry::rotate(&mut right_tiles, alternate_stack.rotate, container);
175        geometry::flip(&mut right_tiles, alternate_stack.flip, container);
176    }
177
178    let mut tiles = vec![];
179    tiles.append(&mut main_tiles);
180    tiles.append(&mut left_tiles);
181    tiles.append(&mut right_tiles);
182    tiles
183}
184
185#[cfg(test)]
186mod tests {
187    use crate::{
188        apply,
189        geometry::{Rect, Split},
190        layouts::{Columns, Layouts, SecondStack, Stack},
191        Layout,
192    };
193
194    #[test]
195    fn single_column_works_with_offset() {
196        let layout = Layout {
197            columns: Columns {
198                main: None,
199                stack: Stack {
200                    split: Some(Split::Horizontal),
201                    ..Default::default()
202                },
203                ..Default::default()
204            },
205            ..Default::default()
206        };
207        let rect = Rect::new(2560, 1440, 2560, 1440);
208        let rects = apply(&layout, 3, &rect);
209
210        assert_eq!(Rect::new(2560, 1440, 2560, 480), rects[0]);
211        assert_eq!(Rect::new(2560, 1920, 2560, 480), rects[1]);
212        assert_eq!(Rect::new(2560, 2400, 2560, 480), rects[2]);
213    }
214
215    #[test]
216    fn main_stack_works_with_offset() {
217        let layout = Layout::default();
218        let rect = Rect::new(2560, 1440, 2560, 1440);
219        let rects = apply(&layout, 3, &rect);
220
221        assert_eq!(Rect::new(2560, 1440, 1280, 1440), rects[0]);
222        assert_eq!(Rect::new(3840, 1440, 1280, 720), rects[1]);
223        assert_eq!(Rect::new(3840, 2160, 1280, 720), rects[2]);
224    }
225
226    #[test]
227    fn stack_main_stack_works_with_offset() {
228        let layout = Layout {
229            columns: Columns {
230                second_stack: Some(SecondStack::default()),
231                ..Default::default()
232            },
233            ..Default::default()
234        };
235        let rect = Rect::new(2560, 1440, 2560, 1440);
236        let rects = apply(&layout, 3, &rect);
237        assert_eq!(Rect::new(3200, 1440, 1280, 1440), rects[0]);
238        assert_eq!(Rect::new(2560, 1440, 640, 1440), rects[1]);
239        assert_eq!(Rect::new(4480, 1440, 640, 1440), rects[2]);
240    }
241
242    #[test]
243    fn should_never_return_more_rects_than_windows_for_any_layout() {
244        let container = Rect::new(0, 0, 40, 20);
245        let mut layouts = Layouts::default().layouts;
246
247        // this specific layout does not exists in the defaults,
248        // but has lead to the issue tested here in the past when user-defined
249        layouts.push(Layout {
250            name: "MultiMain".to_string(),
251            columns: Columns {
252                main: Some(crate::layouts::Main {
253                    count: 2,
254                    ..Default::default()
255                }),
256                ..Default::default()
257            },
258            ..Default::default()
259        });
260
261        for layout in layouts {
262            for i in 0usize..6 {
263                let rects = apply(&layout, i, &container);
264                assert!(
265                    rects.len() <= i,
266                    "got {}, expected <= {}, layout {}",
267                    rects.len(),
268                    i,
269                    &layout.name
270                );
271            }
272        }
273    }
274}