pinnacle_api/layout/
generators.rs

1//! Various builtin generators.
2
3use std::collections::HashMap;
4
5use crate::{tag::TagHandle, util::Axis};
6
7use super::{Gaps, LayoutDir, LayoutGenerator, LayoutNode};
8
9/// A [`LayoutGenerator`] that lays out windows in a line.
10#[derive(Debug, Clone, PartialEq)]
11pub struct Line {
12    /// The gaps between the outer container and this layout.
13    pub outer_gaps: Gaps,
14    /// The gaps between windows within this layout.
15    pub inner_gaps: Gaps,
16    /// THe direction the windows should be laid out.
17    pub direction: LayoutDir,
18    /// Whether or not windows are inserted backwards.
19    pub reversed: bool,
20}
21
22impl LayoutGenerator for Line {
23    fn layout(&self, window_count: u32) -> LayoutNode {
24        let root = LayoutNode::new_with_label("builtin.line");
25        root.set_gaps(self.outer_gaps);
26        root.set_dir(self.direction);
27
28        if window_count == 0 {
29            return root;
30        }
31
32        let children = match self.reversed {
33            false => (0..window_count)
34                .map(|idx| {
35                    let node = LayoutNode::new_with_traversal_index(idx);
36                    node.set_gaps(self.inner_gaps);
37                    node
38                })
39                .collect::<Vec<_>>(),
40            true => (0..window_count)
41                .rev()
42                .map(|idx| {
43                    let node = LayoutNode::new_with_traversal_index(idx);
44                    node.set_gaps(self.inner_gaps);
45                    node
46                })
47                .collect(),
48        };
49
50        root.set_children(children);
51
52        root
53    }
54}
55
56/// Which side the master area will be.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum MasterSide {
59    /// The master area will be on the left.
60    Left,
61    /// The master area will be on the right.
62    Right,
63    /// The master area will be at the top.
64    Top,
65    /// The master area will be at the bottom.
66    Bottom,
67}
68
69/// A [`LayoutGenerator`] that has one master area to one side and a stack of windows
70/// next to it.
71#[derive(Clone, Copy, Debug, PartialEq)]
72pub struct MasterStack {
73    /// The gaps between the outer container and this layout.
74    pub outer_gaps: Gaps,
75    /// The gaps between windows within this layout.
76    pub inner_gaps: Gaps,
77    /// The proportion of the output the master area will take up.
78    ///
79    /// This will be clamped between 0.1 and 0.9.
80    pub master_factor: f32,
81    /// Which side the master area will be.
82    pub master_side: MasterSide,
83    /// How many windows will be in the master area.
84    pub master_count: u32,
85    /// Reverses the direction of window insertion i.e. new windows
86    /// are inserted at the top of the master stack instead of at the
87    /// bottom of the side stack.
88    pub reversed: bool,
89}
90
91impl Default for MasterStack {
92    fn default() -> Self {
93        Self {
94            outer_gaps: Gaps::from(4.0),
95            inner_gaps: Gaps::from(4.0),
96            master_factor: 0.5,
97            master_side: MasterSide::Left,
98            master_count: 1,
99            reversed: false,
100        }
101    }
102}
103
104impl LayoutGenerator for MasterStack {
105    fn layout(&self, window_count: u32) -> LayoutNode {
106        let root = LayoutNode::new_with_label("builtin.master_stack");
107        root.set_gaps(self.outer_gaps);
108        root.set_dir(match self.master_side {
109            MasterSide::Left | MasterSide::Right => LayoutDir::Row,
110            MasterSide::Top | MasterSide::Bottom => LayoutDir::Column,
111        });
112
113        if window_count == 0 {
114            return root;
115        }
116
117        let master_factor = self.master_factor.clamp(0.1, 0.9);
118
119        let (master_tv_idx, stack_tv_idx) = match self.reversed {
120            true => (1, 0),
121            false => (0, 1),
122        };
123
124        let master_count = u32::min(self.master_count, window_count);
125
126        let line = Line {
127            outer_gaps: 0.0.into(),
128            inner_gaps: self.inner_gaps,
129            direction: match self.master_side {
130                MasterSide::Left | MasterSide::Right => LayoutDir::Column,
131                MasterSide::Top | MasterSide::Bottom => LayoutDir::Row,
132            },
133            reversed: self.reversed,
134        };
135
136        let master_side = line.layout(master_count);
137        master_side.set_label(Some("builtin.master_stack.master_side"));
138        master_side.set_traversal_index(master_tv_idx);
139        master_side.set_size_proportion(master_factor * 10.0);
140
141        if window_count <= self.master_count {
142            root.add_child(master_side);
143            return root;
144        }
145
146        let stack_count = window_count - u32::min(self.master_count, window_count);
147        let stack_side = line.layout(stack_count);
148        stack_side.set_label(Some("builtin.master_stack.stack_side"));
149        stack_side.set_traversal_index(stack_tv_idx);
150        stack_side.set_size_proportion((1.0 - master_factor) * 10.0);
151
152        match self.master_side {
153            MasterSide::Left | MasterSide::Top => {
154                root.set_children([master_side, stack_side]);
155            }
156            MasterSide::Right | MasterSide::Bottom => {
157                root.set_children([stack_side, master_side]);
158            }
159        }
160
161        root
162    }
163}
164
165/// A [`LayoutGenerator`] that lays out windows in a shrinking fashion
166/// towards the bottom right corner.
167#[derive(Clone, Debug, PartialEq)]
168pub struct Dwindle {
169    /// The gaps between the outer container and this layout.
170    pub outer_gaps: Gaps,
171    /// The gaps between windows within this layout.
172    pub inner_gaps: Gaps,
173}
174
175impl Default for Dwindle {
176    fn default() -> Self {
177        Self {
178            inner_gaps: 4.0.into(),
179            outer_gaps: 4.0.into(),
180        }
181    }
182}
183
184impl LayoutGenerator for Dwindle {
185    fn layout(&self, win_count: u32) -> LayoutNode {
186        let root = LayoutNode::new_with_label("builtin.dwindle");
187        root.set_gaps(self.outer_gaps);
188
189        if win_count == 0 {
190            return root;
191        }
192
193        if win_count == 1 {
194            let child = LayoutNode::new();
195            child.set_gaps(self.inner_gaps);
196            root.add_child(child);
197            return root;
198        }
199
200        let mut current_node = root.clone();
201
202        for i in 0..win_count - 1 {
203            if current_node != root {
204                current_node.set_gaps(0.0);
205            }
206
207            let child1 = LayoutNode::new_with_traversal_index(0);
208            child1.set_dir(match i % 2 == 0 {
209                true => LayoutDir::Column,
210                false => LayoutDir::Row,
211            });
212            child1.set_gaps(self.inner_gaps);
213            child1.set_label(Some(format!("builtin.dwindle.split.{i}.0")));
214            current_node.add_child(child1);
215
216            let child2 = LayoutNode::new_with_traversal_index(1);
217            child2.set_dir(match i % 2 == 0 {
218                true => LayoutDir::Column,
219                false => LayoutDir::Row,
220            });
221            child2.set_gaps(self.inner_gaps);
222            child2.set_label(Some(format!("builtin.dwindle.split.{i}.1")));
223            current_node.add_child(child2.clone());
224
225            current_node = child2;
226        }
227
228        root
229    }
230}
231
232/// A [`LayoutGenerator`] that lays out windows in a spiral.
233///
234/// This is similar to the [`Dwindle`] layout but in a spiral instead of
235/// towards the bottom right corner.
236#[derive(Clone, Debug, PartialEq)]
237pub struct Spiral {
238    /// The gaps between the outer container and this layout.
239    pub outer_gaps: Gaps,
240    /// The gaps between windows within this layout.
241    pub inner_gaps: Gaps,
242}
243
244impl Default for Spiral {
245    fn default() -> Self {
246        Self {
247            inner_gaps: 4.0.into(),
248            outer_gaps: 4.0.into(),
249        }
250    }
251}
252
253impl LayoutGenerator for Spiral {
254    fn layout(&self, win_count: u32) -> LayoutNode {
255        let root = LayoutNode::new_with_label("builtin.spiral");
256        root.set_gaps(self.outer_gaps);
257
258        if win_count == 0 {
259            return root;
260        }
261
262        if win_count == 1 {
263            let child = LayoutNode::new();
264            child.set_gaps(self.inner_gaps);
265            root.add_child(child);
266            return root;
267        }
268
269        let mut current_node = root.clone();
270
271        for i in 0..win_count - 1 {
272            if current_node != root {
273                current_node.set_gaps(0.0);
274            }
275
276            let child1 = LayoutNode::new();
277            child1.set_dir(match i % 2 == 0 {
278                true => LayoutDir::Column,
279                false => LayoutDir::Row,
280            });
281            child1.set_gaps(self.inner_gaps);
282            child1.set_label(Some(format!("builtin.spiral.split.{i}.0")));
283            current_node.add_child(child1.clone());
284
285            let child2 = LayoutNode::new_with_traversal_index(1);
286            child2.set_dir(match i % 2 == 0 {
287                true => LayoutDir::Column,
288                false => LayoutDir::Row,
289            });
290            child2.set_gaps(self.inner_gaps);
291            child2.set_label(Some(format!("builtin.spiral.split.{i}.1")));
292            current_node.add_child(child2.clone());
293
294            current_node = match i % 4 {
295                0 | 1 => {
296                    child1.set_traversal_index(0);
297                    child2.set_traversal_index(1);
298                    child2
299                }
300                2 | 3 => {
301                    child1.set_traversal_index(1);
302                    child2.set_traversal_index(0);
303                    child1
304                }
305                _ => unreachable!(),
306            };
307        }
308
309        root
310    }
311}
312
313/// Which corner the corner window will in.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
315pub enum CornerLocation {
316    /// The corner window will be in the top left.
317    TopLeft,
318    /// The corner window will be in the top right.
319    TopRight,
320    /// The corner window will be in the bottom left.
321    BottomLeft,
322    /// The corner window will be in the bottom right.
323    BottomRight,
324}
325
326/// A [`LayoutGenerator`] that has one main corner window and a
327/// horizontal and vertical stack flanking it on the other two sides.
328#[derive(Debug, Clone, Copy, PartialEq)]
329pub struct Corner {
330    /// The gaps between the outer container and this layout.
331    pub outer_gaps: Gaps,
332    /// The gaps between windows within this layout.
333    pub inner_gaps: Gaps,
334    /// The proportion of the output that the width of the window takes up.
335    pub corner_width_factor: f32,
336    /// The proportion of the output that the height of the window takes up.
337    pub corner_height_factor: f32,
338    /// The location of the corner window.
339    pub corner_loc: CornerLocation,
340}
341
342impl Default for Corner {
343    fn default() -> Self {
344        Self {
345            inner_gaps: 4.0.into(),
346            outer_gaps: 4.0.into(),
347            corner_width_factor: 0.5,
348            corner_height_factor: 0.5,
349            corner_loc: CornerLocation::TopLeft,
350        }
351    }
352}
353
354impl LayoutGenerator for Corner {
355    fn layout(&self, win_count: u32) -> LayoutNode {
356        let root = LayoutNode::new_with_label("builtin.corner");
357        root.set_gaps(self.outer_gaps);
358
359        if win_count == 0 {
360            return root;
361        }
362
363        if win_count == 1 {
364            let child = LayoutNode::new();
365            child.set_gaps(self.inner_gaps);
366            root.add_child(child);
367            return root;
368        }
369
370        let corner_width_factor = self.corner_width_factor.clamp(0.1, 0.9);
371        let corner_height_factor = self.corner_height_factor.clamp(0.1, 0.9);
372
373        let corner_and_horiz_stack_node =
374            LayoutNode::new_with_label_and_index("builtin.corner.corner_and_stack", 0);
375        corner_and_horiz_stack_node.set_dir(LayoutDir::Column);
376        corner_and_horiz_stack_node.set_size_proportion(corner_width_factor * 10.0);
377
378        let vert_count = (win_count - 1).div_ceil(2);
379        let horiz_count = (win_count - 1) / 2;
380
381        let vert_stack = Line {
382            outer_gaps: 0.0.into(),
383            inner_gaps: self.inner_gaps,
384            direction: LayoutDir::Column,
385            reversed: false,
386        };
387
388        let vert_stack_node = vert_stack.layout(vert_count);
389        vert_stack_node.set_size_proportion((1.0 - corner_width_factor) * 10.0);
390        vert_stack_node.set_traversal_index(1);
391
392        root.set_children(match self.corner_loc {
393            CornerLocation::TopLeft | CornerLocation::BottomLeft => {
394                [corner_and_horiz_stack_node.clone(), vert_stack_node.clone()]
395            }
396            CornerLocation::TopRight | CornerLocation::BottomRight => {
397                [vert_stack_node.clone(), corner_and_horiz_stack_node.clone()]
398            }
399        });
400
401        if horiz_count == 0 {
402            corner_and_horiz_stack_node.set_gaps(self.inner_gaps);
403            return root;
404        }
405
406        let corner_node = LayoutNode::new_with_traversal_index(0);
407        corner_node.set_size_proportion(corner_height_factor * 10.0);
408        corner_node.set_gaps(self.inner_gaps);
409
410        let horiz_stack = Line {
411            outer_gaps: 0.0.into(),
412            inner_gaps: self.inner_gaps,
413            direction: LayoutDir::Row,
414            reversed: false,
415        };
416
417        let horiz_stack_node = horiz_stack.layout(horiz_count);
418        horiz_stack_node.set_size_proportion((1.0 - corner_height_factor) * 10.0);
419        horiz_stack_node.set_traversal_index(1);
420
421        corner_and_horiz_stack_node.set_children(match self.corner_loc {
422            CornerLocation::TopLeft | CornerLocation::TopRight => {
423                [corner_node, horiz_stack_node.clone()]
424            }
425            CornerLocation::BottomLeft | CornerLocation::BottomRight => {
426                [horiz_stack_node.clone(), corner_node]
427            }
428        });
429
430        let traversal_overrides = (0..win_count).map(|idx| (idx, vec![(idx % 2 == 1) as u32]));
431
432        root.set_traversal_overrides(traversal_overrides);
433
434        root
435    }
436}
437
438/// A [`LayoutGenerator`] that attempts to layout windows such that
439/// they are the same size.
440#[derive(Copy, Clone, Debug, PartialEq)]
441pub struct Fair {
442    /// The gaps between the outer container and this layout.
443    pub outer_gaps: Gaps,
444    /// The gaps between windows within this layout.
445    pub inner_gaps: Gaps,
446    /// Which axis the lines of windows will run.
447    pub axis: Axis,
448}
449
450impl Default for Fair {
451    fn default() -> Self {
452        Self {
453            inner_gaps: 4.0.into(),
454            outer_gaps: 4.0.into(),
455            axis: Axis::Vertical,
456        }
457    }
458}
459
460impl LayoutGenerator for Fair {
461    fn layout(&self, win_count: u32) -> LayoutNode {
462        let root = LayoutNode::new_with_label("builtin.fair");
463        root.set_gaps(self.outer_gaps);
464
465        if win_count == 0 {
466            return root;
467        }
468
469        if win_count == 1 {
470            let child = LayoutNode::new();
471            child.set_gaps(self.inner_gaps);
472            child.set_label(Some("builtin.fair.line.0"));
473            root.add_child(child);
474            return root;
475        }
476
477        if win_count == 2 {
478            let child = LayoutNode::new();
479            child.set_gaps(self.inner_gaps);
480            child.set_label(Some("builtin.fair.line.0"));
481            root.add_child(child);
482            let child2 = LayoutNode::new();
483            child2.set_gaps(self.inner_gaps);
484            child2.set_label(Some("builtin.fair.line.1"));
485            root.add_child(child2);
486            return root;
487        }
488
489        let line_count = (win_count as f32).sqrt().round() as u32;
490
491        let mut wins_per_line = Vec::new();
492
493        let max_per_line = if win_count > line_count * line_count {
494            line_count + 1
495        } else {
496            line_count
497        };
498
499        for i in 1..=win_count {
500            let index = (i as f32 / max_per_line as f32).ceil() as usize - 1;
501            if wins_per_line.get(index).is_none() {
502                wins_per_line.push(0);
503            }
504            wins_per_line[index] += 1;
505        }
506
507        let line = Line {
508            outer_gaps: 0.0.into(),
509            inner_gaps: self.inner_gaps,
510            direction: match self.axis {
511                Axis::Horizontal => LayoutDir::Row,
512                Axis::Vertical => LayoutDir::Column,
513            },
514            reversed: false,
515        };
516
517        let lines = wins_per_line.into_iter().enumerate().map(|(i, win_ct)| {
518            let node = line.layout(win_ct);
519            node.set_label(Some(format!("builtin.fair.line.{i}")));
520            node
521        });
522
523        root.set_children(lines);
524
525        root.set_dir(match self.axis {
526            Axis::Horizontal => LayoutDir::Column,
527            Axis::Vertical => LayoutDir::Row,
528        });
529
530        root
531    }
532}
533
534/// A [`LayoutGenerator`] that floats windows.
535///
536/// This works by simply returning an empty layout tree.<br>
537/// Note: the windows are not truly floating, see [`WindowHandle::spilled`] for details.
538///
539/// [`WindowHandle::spilled`]: crate::window::WindowHandle::spilled
540#[derive(Copy, Clone, Debug, Default, PartialEq)]
541pub struct Floating {}
542
543impl LayoutGenerator for Floating {
544    fn layout(&self, _win_count: u32) -> LayoutNode {
545        LayoutNode::new_with_label("builtin.floating")
546    }
547}
548
549/// A [`LayoutGenerator`] that keeps track of layouts per tag and provides
550/// methods to cycle between them.
551pub struct Cycle<T> {
552    /// The layouts this generator will cycle between.
553    pub layouts: Vec<T>,
554    tag_indices: HashMap<u32, usize>,
555    current_tag: Option<TagHandle>,
556}
557
558impl<T: LayoutGenerator + ?Sized> LayoutGenerator for Box<T> {
559    fn layout(&self, window_count: u32) -> LayoutNode {
560        (**self).layout(window_count)
561    }
562}
563
564impl<T: LayoutGenerator + ?Sized> LayoutGenerator for std::sync::Arc<T> {
565    fn layout(&self, window_count: u32) -> LayoutNode {
566        (**self).layout(window_count)
567    }
568}
569
570impl<T: LayoutGenerator + ?Sized> LayoutGenerator for std::rc::Rc<T> {
571    fn layout(&self, window_count: u32) -> LayoutNode {
572        (**self).layout(window_count)
573    }
574}
575
576impl<T> Cycle<T> {
577    /// Creates a new [`Cycle`] from the given [`LayoutGenerator`]s.
578    ///
579    /// # Examples
580    ///
581    /// ```
582    /// # use pinnacle_api::layout::generators::Cycle;
583    /// # use pinnacle_api::layout::generators::MasterStack;
584    /// # use pinnacle_api::layout::generators::Dwindle;
585    /// # use pinnacle_api::layout::generators::Corner;
586    /// # use pinnacle_api::layout::LayoutGenerator;
587    /// let cycler = Cycle::new([
588    ///     Box::<MasterStack>::default() as Box<dyn LayoutGenerator + Send>,
589    ///     Box::<Dwindle>::default() as _,
590    ///     Box::<Corner>::default() as _,
591    /// ]);
592    /// ```
593    pub fn new(layouts: impl IntoIterator<Item = T>) -> Self {
594        Self {
595            layouts: layouts.into_iter().collect(),
596            tag_indices: HashMap::default(),
597            current_tag: None,
598        }
599    }
600
601    /// Cycles the layout forward on the given tag.
602    pub fn cycle_layout_forward(&mut self, tag: &TagHandle) {
603        let index = self.tag_indices.entry(tag.id).or_default();
604        *index += 1;
605        if *index >= self.layouts.len() {
606            *index = 0;
607        }
608    }
609
610    /// Cycles the layout backward on the given tag.
611    pub fn cycle_layout_backward(&mut self, tag: &TagHandle) {
612        let index = self.tag_indices.entry(tag.id).or_default();
613        if let Some(i) = index.checked_sub(1) {
614            *index = i;
615        } else {
616            *index = self.layouts.len().saturating_sub(1);
617        }
618    }
619
620    /// Retrieves the current layout.
621    ///
622    /// Returns `None` if no layouts were given.
623    pub fn current_layout(&self, tag: &TagHandle) -> Option<&T> {
624        self.layouts
625            .get(self.tag_indices.get(&tag.id).copied().unwrap_or_default())
626    }
627
628    /// Sets the current tag to choose a layout for.
629    pub fn set_current_tag(&mut self, tag: TagHandle) {
630        self.current_tag = Some(tag);
631    }
632
633    /// Gets a (most-likely) unique identifier for the current layout tree.
634    /// This is guaranteed to be greater than zero.
635    pub fn current_tree_id(&self) -> u32 {
636        let tag_id = self
637            .current_tag
638            .as_ref()
639            .map(|tag| tag.id)
640            .unwrap_or_default();
641        let layout_id = self.tag_indices.get(&tag_id).copied().unwrap_or_default();
642
643        ((tag_id & u16::MAX as u32) | ((layout_id as u32 & u16::MAX as u32) << 16)) + 1
644    }
645}
646
647impl<T: LayoutGenerator> LayoutGenerator for Cycle<T> {
648    fn layout(&self, window_count: u32) -> LayoutNode {
649        let Some(current_tag) = self.current_tag.as_ref() else {
650            return LayoutNode::new();
651        };
652        let Some(current_layout) = self.current_layout(current_tag) else {
653            return LayoutNode::new();
654        };
655        current_layout.layout(window_count)
656    }
657}