leftwm_layouts/layouts/
layout.rs

1use std::cmp;
2
3use serde::{Deserialize, Serialize};
4
5use crate::geometry::{Flip, Reserve, Rotation, Size, Split};
6
7use super::defaults::{
8    center_main, center_main_balanced, center_main_fluid, dwindle, even_horizontal, even_vertical,
9    fibonacci, grid, main_and_deck, main_and_horizontal_stack, main_and_vert_stack, monocle,
10    right_main_and_vert_stack,
11};
12
13const DEFAULT_MAIN_SIZE_CHANGE_PIXEL: i32 = 50;
14const DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE: i32 = 5;
15
16/// A helper struct that represents a set of layouts and provides
17/// convenience methods
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct Layouts {
20    pub layouts: Vec<Layout>,
21}
22
23impl Eq for Layouts {}
24
25impl Layouts {
26    pub fn get(&self, name: &str) -> Option<&Layout> {
27        self.layouts.iter().find(|&l| l.name.as_str() == name)
28    }
29
30    pub fn get_mut<'a>(&'a mut self, name: &str) -> Option<&'a mut Layout> {
31        self.layouts.iter_mut().find(|l| l.name.as_str() == name)
32    }
33
34    pub fn names(&self) -> Vec<String> {
35        self.layouts.iter().map(|x| x.name.clone()).collect()
36    }
37
38    pub fn len(&self) -> usize {
39        self.layouts.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.layouts.is_empty()
44    }
45
46    pub fn get_index(&self, name: &str) -> Option<usize> {
47        self.layouts.iter().position(|l| l.name.as_str() == name)
48    }
49}
50
51impl Default for Layouts {
52    fn default() -> Self {
53        Self {
54            layouts: vec![
55                even_horizontal(),
56                even_vertical(),
57                monocle(),
58                grid(),
59                main_and_vert_stack(),
60                main_and_horizontal_stack(),
61                right_main_and_vert_stack(),
62                fibonacci(),
63                dwindle(),
64                main_and_deck(),
65                center_main(),
66                center_main_balanced(),
67                center_main_fluid(),
68            ],
69        }
70    }
71}
72
73type LayoutName = String;
74
75/// Describes a layout or pattern in which tiles (windows) will be arranged.
76/// The [`Layout`] allows to describe various types of "fixed" layouts used by a dynamic tiling manager.
77/// Those include layouts like `MainAndStack`, `Fibonacci`, `Dwindle`, `CenterMain`, etc.
78#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
79#[serde(default)]
80pub struct Layout {
81    /// Name and identifier of the layout.
82    /// This is user chosen and no two layouts can have the same name.
83    pub name: LayoutName,
84
85    /// Flips the entire result of tiles as a whole if specified to be anything other than [`Flip::None`]
86    pub flip: Flip,
87
88    /// Rotate the entire result of tiles as a whole, if specified to be anything other than [`Rotation::North`]
89    pub rotate: Rotation,
90
91    /// Defines the layouts behavior if certain "columns" (eg. main, stack, or second-stack) are empty.
92    /// See [`Reserve`] for more information.
93    pub reserve: Reserve,
94
95    /// Configuration concerning the [`Main`], [`Stack`], and [`SecondStack`] columns.
96    /// See [`Columns`] for more information.
97    pub columns: Columns,
98}
99
100impl Layout {
101    /// Returns `true` if the layout must be considered a `Monocle` layout.
102    ///
103    /// The `Monocle` layout is a special layout that always consists
104    /// of 0 or 1 windows. If there is a window, it is shown full screen.
105    pub fn is_monocle(&self) -> bool {
106        self.columns.main.is_none()
107            && self.columns.second_stack.is_none()
108            && self.columns.stack.split.is_none()
109    }
110
111    /// Returns `true` if the layout must be considered a `MainAndDeck` layout.
112    ///
113    /// The `MainAndDeck` layout is a special layout that always consists
114    /// of 0, 1 or 2 windows.
115    pub fn is_main_and_deck(&self) -> bool {
116        match &self.columns.main {
117            Some(main) => {
118                self.columns.second_stack.is_none()
119                    && main.split.is_none()
120                    && self.columns.stack.split.is_none()
121            }
122            None => false,
123        }
124    }
125
126    // Get the size of the [`Main`] column,
127    // may return [`None`] if there is no [`Main`] column.
128    pub fn main_size(&self) -> Option<Size> {
129        self.columns.main.as_ref().map(|m| m.size)
130    }
131
132    // Get the amount of window spaces of the [`Main`] column,
133    // may return [`None`] if there is no [`Main`] column.
134    pub fn main_window_count(&self) -> Option<usize> {
135        self.columns.main.as_ref().map(|m| m.count)
136    }
137
138    /// Set the [`Size`] of the [`Main`] column to a specific value
139    pub fn set_main_size(&mut self, size: Size) {
140        if let Some(main) = self.columns.main.as_mut() {
141            main.size = size;
142        }
143    }
144
145    /// Increase the [`Size`] of the [`Main`] column, but to no
146    /// larger value than what is set in `upper_bound`.
147    ///
148    /// The column is increased by a default amount,
149    /// either [`DEFAULT_MAIN_SIZE_CHANGE_PIXEL`] or
150    /// [`DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE`] depending
151    /// on whether the current [`Size`] is a [`Size::Pixel`] or [`Size::Ratio`].
152    ///
153    /// If the current layout has no [`Main`] column, nothing happens
154    pub fn increase_main_size(&mut self, upper_bound: i32) {
155        if let Some(main) = self.columns.main.as_mut() {
156            match main.size {
157                Size::Pixel(_) => {
158                    self.change_main_size(DEFAULT_MAIN_SIZE_CHANGE_PIXEL, upper_bound);
159                }
160                Size::Ratio(_) => {
161                    self.change_main_size(DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE, upper_bound);
162                }
163            };
164        };
165    }
166
167    /// Decrease the [`Size`] of the [`Main`] column, but to no
168    /// smaller value than zero.
169    ///
170    /// The column is decreased by a default amount,
171    /// either [`DEFAULT_MAIN_SIZE_CHANGE_PIXEL`] or
172    /// [`DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE`] depending
173    /// on whether the current [`Size`] is a [`Size::Pixel`] or [`Size::Ratio`].
174    ///
175    /// If the current layout has no [`Main`] column, nothing happens
176    pub fn decrease_main_size(&mut self) {
177        if let Some(main) = self.columns.main.as_mut() {
178            // note: upper bound doesn't matter when we're decreasing,
179            // so just set it to i32::MAX
180            match main.size {
181                Size::Pixel(_) => self.change_main_size(-DEFAULT_MAIN_SIZE_CHANGE_PIXEL, i32::MAX),
182                Size::Ratio(_) => {
183                    self.change_main_size(-DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE, i32::MAX);
184                }
185            };
186        };
187    }
188
189    /// Change the [`Size`] of the [`Main`] column by a `delta` value.
190    ///
191    /// The `delta` value can be positive or negative and is interpreted
192    /// as either [`Size::Pixel`] or [`Size::Ratio`] based on the current
193    /// [`Size`] of the [`Main`] column.
194    ///
195    /// When the current [`Size`] is a [`Size::Pixel`], the delta is
196    /// interpreted as a pixel value.
197    ///
198    /// ```
199    /// use leftwm_layouts::Layout;
200    /// use leftwm_layouts::geometry::Size;
201    ///
202    /// let mut layout = Layout::default();
203    /// layout.set_main_size(Size::Pixel(200));
204    /// layout.change_main_size(100, 500);
205    /// assert_eq!(Size::Pixel(300), layout.columns.main.unwrap().size);
206    /// ```
207    ///
208    /// When the current [`Size`] is a [`Size::Ratio`], the delta is
209    /// interpreted as a percentage value and converted into a ratio
210    /// (i.e. `5` (percent) => `Size::Ratio(0.05)`).
211    ///
212    /// ```
213    /// use leftwm_layouts::Layout;
214    /// use leftwm_layouts::geometry::Size;
215    ///
216    /// let mut layout = Layout::default();
217    /// layout.set_main_size(Size::Ratio(0.5));
218    /// layout.change_main_size(5, 500);
219    /// assert_eq!(Size::Ratio(0.55), layout.columns.main.unwrap().size);
220    /// ```
221    pub fn change_main_size(&mut self, delta: i32, upper_bound: i32) {
222        if let Some(main) = self.columns.main.as_mut() {
223            main.size = match main.size {
224                Size::Pixel(px) => Size::Pixel(cmp::max(0, cmp::min(upper_bound, px + delta))),
225                Size::Ratio(ratio) => {
226                    Size::Ratio(f32::max(0.0, f32::min(1.0, ratio + (delta as f32 * 0.01))))
227                }
228            }
229        }
230    }
231
232    //pub fn change_main_size_enum(&mut self, amount: Size, upper_bound: i32) {
233    //    if let Some(main) = self.columns.main.as_mut() {
234    //        match (main.size, amount) {
235    //            (Size::Pixel(_), Size::Pixel(px)) => self.change_main_size(px, upper_bound),
236    //            (Size::Pixel(_), Size::Ratio(_)) => todo!(), // ?
237    //            (Size::Ratio(_), Size::Pixel(_)) => todo!(), // ?
238    //            (Size::Ratio(_), Size::Ratio(ratio)) => {
239    //                self.change_main_size((ratio * 100.0).round() as i32, upper_bound)
240    //            }
241    //        }
242    //    };
243    //    amount.into_absolute(upper_bound.unsigned_abs());
244    //}
245
246    // Set the amount of main windows to a specific amount
247    pub fn set_main_window_count(&mut self, count: usize) {
248        if let Some(main) = self.columns.main.as_mut() {
249            main.count = cmp::max(0, count);
250        }
251    }
252
253    // Increase the amount of main windows by 1
254    pub fn increase_main_window_count(&mut self) {
255        if let Some(main) = self.columns.main.as_mut() {
256            main.count = main.count.saturating_add(1);
257        }
258    }
259
260    // Decrease the amount of main windows by 1
261    pub fn decrease_main_window_count(&mut self) {
262        if let Some(main) = self.columns.main.as_mut() {
263            main.count = main.count.saturating_sub(1);
264        }
265    }
266
267    // Rotate the layout as a whole.
268    // Rotates clockwise if `true` and counter-clockwise if `false`.
269    pub fn rotate(&mut self, clockwise: bool) {
270        self.rotate = if clockwise {
271            self.rotate.clockwise()
272        } else {
273            self.rotate.counter_clockwise()
274        }
275    }
276
277    pub fn check(&self) {
278        if self.columns.second_stack.is_some() && self.columns.main.is_none() {
279            // warning -> alternate_stack is ignored -> 1-column
280        }
281    }
282
283    pub fn update_defaults(custom: &Vec<Layout>) -> Vec<Layout> {
284        let mut layouts = Layouts::default().layouts;
285        for custom_layout in custom {
286            layouts.push(custom_layout.clone());
287        }
288        layouts
289    }
290}
291
292impl Default for Layout {
293    fn default() -> Self {
294        Self {
295            name: String::from("Default"),
296            flip: Flip::None,
297            rotate: Rotation::North,
298            reserve: Reserve::None,
299            columns: Columns::default(),
300        }
301    }
302}
303
304/// Describes the columns of a layout. There are only 3 columns which are a fixed part of
305/// `leftwm_layouts`, those are `main`, `stack`, and `second_stack`.
306///
307/// ```txt
308/// +------+------+------+
309/// |      |      |      |
310/// |      |      |      |
311/// |      |      |      |
312/// +------+------+------+
313///  stack   main  second
314///                stack
315/// ```
316///
317/// ## Modifiers
318/// Modifiers like [`Flip`] and [`Rotation`] are applied only to the columns themselves and not their contents.
319///
320/// For example, if you wish for the `Stack` to be on the left side instead of the right side
321/// in a `MainAndStack` layout configuration, the [`Flip`] property could be set to [`Flip::Vertical`],
322/// which results in the columns being flipped, **but not their contents**.
323#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
324#[serde(default)]
325pub struct Columns {
326    /// How the columns should be flipped, does not apply to their contents
327    pub flip: Flip,
328
329    /// How the columns should be rotated, does not apply to their contents
330    pub rotate: Rotation,
331
332    /// Configurations concerning the `main` column.
333    /// This can be set to [`None`], in which case the layout
334    /// will not have a main column. For example, in single-column
335    /// layouts like `EvenVertical`, `Monocle`, etc.
336    /// See [`Main`] for more information.
337    pub main: Option<Main>,
338
339    /// Configurations concerning the `stack` column.
340    /// Other than `main` and `second_stack`, this column is always present.
341    /// See [`Stack`] for more information.
342    pub stack: Stack,
343
344    /// Configurations concerning the `second_stack` column.
345    /// This can be set to [`None`], in which case the layout
346    /// is going to be a two-column layout like `MainAndStack`, `Fibonacci`, etc.
347    ///
348    /// *Note: If this is present but `main` is absent, it is condiered an invalid
349    /// layout configuration. The `second_stack` configuration may be ignored if
350    /// `main` is [`None`]*
351    /// See [`SecondStack`] for more information.
352    pub second_stack: Option<SecondStack>,
353}
354
355impl Default for Columns {
356    fn default() -> Self {
357        Self {
358            flip: Flip::default(),
359            rotate: Rotation::default(),
360            main: Some(Main::default()),
361            stack: Stack::default(),
362            second_stack: None,
363        }
364    }
365}
366
367/// Configurations concerning the `main` column
368#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
369#[serde(default)]
370pub struct Main {
371    /// The default amount of windows to occupy the `main` column (default: `1`)
372    pub count: usize,
373
374    /// The default size of the `main` column (default: `50%`)
375    pub size: Size,
376
377    /// Flip modifier to apply only to the `main` columns' contents
378    pub flip: Flip,
379
380    /// Rotation modifier to apply only to the `main` columns' contents
381    pub rotate: Rotation,
382
383    /// How tiles (windows) inside the `main` column should be split up,
384    /// when there is more than one.
385    ///
386    /// *Note: This can be set to [`None`], in which case the `main` column can't
387    /// contain more than one window (eg. `MainAndDeck`)*
388    pub split: Option<Split>,
389}
390
391impl Default for Main {
392    fn default() -> Self {
393        Self {
394            count: 1,
395            size: Size::Ratio(0.5),
396            flip: Flip::default(),
397            rotate: Rotation::default(),
398            split: Some(Split::Vertical),
399        }
400    }
401}
402
403/// Configurations concerning the `stack` column
404#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
405#[serde(default)]
406pub struct Stack {
407    /// Flip modifier to apply only to the `stack` columns' contents
408    pub flip: Flip,
409
410    /// Rotation modifier to apply only to the `stack` columns' contents
411    pub rotate: Rotation,
412
413    /// How tiles (windows) inside the `stack` column should be split up,
414    /// when there is more than one.
415    ///
416    /// *Note: This can be set to [`None`], in which case the `stack` column can't
417    /// contain more than one window (eg. `Monocle`, `MainAndDeck`)*
418    pub split: Option<Split>,
419}
420
421impl Default for Stack {
422    fn default() -> Self {
423        Self {
424            flip: Flip::default(),
425            rotate: Rotation::default(),
426            split: Some(Split::Horizontal),
427        }
428    }
429}
430
431/// Configurations concerning the `second_stack` column
432#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
433#[serde(default)]
434pub struct SecondStack {
435    /// Flip modifier to apply only to the `second_stack` columns' contents
436    pub flip: Flip,
437
438    /// Rotation modifier to apply only to the `second_stack` columns' contents
439    pub rotate: Rotation,
440
441    /// How tiles (windows) inside the `second_stack` column should be split up,
442    /// when there is more than one.
443    pub split: Split,
444}
445
446impl Default for SecondStack {
447    fn default() -> Self {
448        Self {
449            flip: Flip::default(),
450            rotate: Rotation::default(),
451            split: Split::Horizontal,
452        }
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use crate::{
459        geometry::Size,
460        layouts::{
461            layout::{DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE, DEFAULT_MAIN_SIZE_CHANGE_PIXEL},
462            Layouts,
463        },
464        Layout,
465    };
466
467    #[test]
468    fn monocle_layout_is_monocle() {
469        let layouts = Layouts::default();
470        let layout = layouts.get("Monocle").unwrap();
471        assert!(layout.is_monocle());
472    }
473
474    #[test]
475    fn main_and_deck_layout_is_main_and_deck() {
476        let layouts = Layouts::default();
477        let layout = layouts.get("MainAndDeck").unwrap();
478        assert!(layout.is_main_and_deck());
479    }
480
481    #[test]
482    fn set_main_size_works() {
483        let mut layout = Layout::default();
484        layout.set_main_size(Size::Ratio(0.5));
485        assert_eq!(Some(Size::Ratio(0.5)), layout.main_size());
486    }
487
488    #[test]
489    fn increase_main_size_percentage_works() {
490        let mut layout = Layout::default();
491        layout.set_main_size(Size::Ratio(0.5));
492        layout.increase_main_size(500);
493        assert_eq!(
494            Some(Size::Ratio(
495                0.5 + (DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE as f32 * 0.01)
496            )),
497            layout.main_size()
498        );
499    }
500
501    #[test]
502    fn decrease_main_size_percentage_works() {
503        let mut layout = Layout::default();
504        layout.set_main_size(Size::Ratio(0.5));
505        layout.decrease_main_size();
506        assert_eq!(
507            Some(Size::Ratio(
508                0.5 - (DEFAULT_MAIN_SIZE_CHANGE_PERCENTAGE as f32 * 0.01)
509            )),
510            layout.main_size()
511        );
512    }
513
514    #[test]
515    fn increase_main_size_pixel_works() {
516        let mut layout = Layout::default();
517        layout.set_main_size(Size::Pixel(200));
518        layout.increase_main_size(500);
519        assert_eq!(
520            Some(Size::Pixel(200 + DEFAULT_MAIN_SIZE_CHANGE_PIXEL)),
521            layout.main_size()
522        );
523    }
524
525    #[test]
526    fn decrease_main_size_pixel_works() {
527        let mut layout = Layout::default();
528        layout.set_main_size(Size::Pixel(200));
529        layout.decrease_main_size();
530        assert_eq!(
531            Some(Size::Pixel(200 - DEFAULT_MAIN_SIZE_CHANGE_PIXEL)),
532            layout.main_size()
533        );
534    }
535
536    #[test]
537    fn change_main_size_percentage_negative_works() {
538        let mut layout = Layout::default();
539        layout.set_main_size(Size::Ratio(0.5));
540        layout.change_main_size(-5, 500);
541        assert_eq!(Some(Size::Ratio(0.45)), layout.main_size());
542    }
543
544    #[test]
545    fn change_main_size_percentage_positive_works() {
546        let mut layout = Layout::default();
547        layout.set_main_size(Size::Ratio(0.5));
548        layout.change_main_size(5, 500);
549        assert_eq!(Some(Size::Ratio(0.55)), layout.main_size());
550    }
551
552    #[test]
553    fn change_main_size_pixel_negative_works() {
554        let mut layout = Layout::default();
555        layout.set_main_size(Size::Pixel(200));
556        layout.change_main_size(-5, 500);
557        assert_eq!(Some(Size::Pixel(195)), layout.main_size());
558    }
559
560    #[test]
561    fn change_main_size_pixel_positive_works() {
562        let mut layout = Layout::default();
563        layout.set_main_size(Size::Pixel(200));
564        layout.change_main_size(5, 500);
565        assert_eq!(Some(Size::Pixel(205)), layout.main_size());
566    }
567
568    #[test]
569    fn decrease_main_size_does_not_go_below_zero() {
570        let mut layout = Layout::default();
571        layout.set_main_size(Size::Pixel(200));
572        layout.change_main_size(-200, 500);
573        assert_eq!(Some(Size::Pixel(0)), layout.main_size());
574        layout.change_main_size(-200, 500);
575        assert_eq!(Some(Size::Pixel(0)), layout.main_size());
576    }
577
578    #[test]
579    fn decrease_main_size_does_not_go_above_upper_bound() {
580        let mut layout = Layout::default();
581        layout.set_main_size(Size::Pixel(200));
582        layout.change_main_size(200, 500);
583        assert_eq!(Some(Size::Pixel(400)), layout.main_size());
584        layout.change_main_size(200, 500);
585        assert_eq!(Some(Size::Pixel(500)), layout.main_size());
586    }
587
588    #[test]
589    fn set_main_window_count_works() {
590        let mut layout = Layout::default();
591        layout.set_main_window_count(5);
592        assert_eq!(Some(5), layout.main_window_count());
593    }
594
595    #[test]
596    fn increase_main_window_count_works() {
597        let mut layout = Layout::default();
598        layout.set_main_window_count(5);
599        layout.increase_main_window_count();
600        assert_eq!(Some(6), layout.main_window_count());
601    }
602
603    #[test]
604    fn decrease_main_window_count_works() {
605        let mut layout = Layout::default();
606        layout.set_main_window_count(5);
607        layout.decrease_main_window_count();
608        assert_eq!(Some(4), layout.main_window_count());
609    }
610
611    #[test]
612    fn main_window_count_does_not_go_below_zero() {
613        let mut layout = Layout::default();
614        layout.set_main_window_count(1);
615        layout.decrease_main_window_count();
616        assert_eq!(Some(0), layout.main_window_count());
617        layout.decrease_main_window_count();
618        assert_eq!(Some(0), layout.main_window_count());
619    }
620}