penrose/core/layout/
transformers.rs

1use crate::{
2    builtin::layout::{messages::UnwrapTransformer, Monocle},
3    core::layout::{messages::Message, Layout},
4    pure::{geometry::Rect, Stack},
5    Xid,
6};
7use std::mem::swap;
8
9/// A wrapper round another [Layout] that is able to intercept and modify both the positions being
10/// returned by the inner layout and messages being sent to it.
11pub trait LayoutTransformer: Send + Sync + Clone + Sized + 'static {
12    /// The same as [Layout::name] but for [LayoutTransformer] itself.
13    fn transformed_name(&self) -> String;
14
15    /// Provide a mutable reference to the [Layout] wrapped by this transformer.
16    fn inner_mut(&mut self) -> &mut Box<dyn Layout>;
17
18    /// Replace the currently wrapped [Layout] with a new one.
19    fn swap_inner(&mut self, mut new: Box<dyn Layout>) -> Box<dyn Layout> {
20        swap(self.inner_mut(), &mut new);
21
22        new
23    }
24
25    /// Remove the inner [Layout] from this [LayoutTransformer].
26    ///
27    /// The default implementation of this method simply swaps the built-in [Monocle]
28    /// layout for the inner layout using [LayoutTransformer::swap_inner]. If a better
29    /// implementation is possible then it should be implemented but this default will
30    /// give the correct behaviour when [LayoutTransformer::passthrough_message] receives
31    /// an [UnwrapTransformer] message.
32    fn unwrap(&mut self) -> Box<dyn Layout> {
33        self.swap_inner(Box::new(Monocle))
34    }
35
36    /// Modify the initial [Rect] that will be passed to the inner [Layout].
37    ///
38    /// The default implementation of this method leaves the initial Rect unchanged.
39    fn transform_initial(&self, r: Rect) -> Rect {
40        r
41    }
42
43    /// Optionally modify any of the positions returned by the inner [Layout] before they are
44    /// applied by the window manager. The dimensions of the screen being layed out are avaiable
45    /// as `r`.
46    ///
47    /// The default implementation of this method leaves the positions returned by the inner layout
48    /// unchanged.
49    fn transform_positions(&mut self, _r: Rect, positions: Vec<(Xid, Rect)>) -> Vec<(Xid, Rect)> {
50        positions
51    }
52
53    /// Apply the [LayoutTransformer] to its wrapped inner [Layout].
54    #[allow(clippy::type_complexity)]
55    fn run_transform<F>(&mut self, f: F, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>)
56    where
57        F: FnOnce(Rect, &mut Box<dyn Layout>) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>),
58    {
59        let r = self.transform_initial(r);
60        let (new, positions) = (f)(r, self.inner_mut());
61        let transformed = self.transform_positions(r, positions);
62
63        if let Some(l) = new {
64            self.swap_inner(l);
65        }
66
67        (None, transformed)
68    }
69
70    /// Pass a message on to the wrapped inner [Layout].
71    ///
72    /// The default implementation of this method will return `Some(inner_layout)` if it
73    /// receives an [UnwrapTransformer] [Message] using the `unwrap` method of this trait.
74    fn passthrough_message(&mut self, m: &Message) -> Option<Box<dyn Layout>> {
75        if let Some(new) = self.inner_mut().handle_message(m) {
76            self.swap_inner(new);
77        }
78
79        None
80    }
81}
82
83impl<LT> Layout for LT
84where
85    LT: LayoutTransformer,
86{
87    fn name(&self) -> String {
88        self.transformed_name()
89    }
90
91    fn boxed_clone(&self) -> Box<dyn Layout> {
92        Box::new(self.clone())
93    }
94
95    fn layout_workspace(
96        &mut self,
97        tag: &str,
98        stack: &Option<Stack<Xid>>,
99        r: Rect,
100    ) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
101        self.run_transform(|r, inner| inner.layout_workspace(tag, stack, r), r)
102    }
103
104    fn layout(&mut self, s: &Stack<Xid>, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
105        self.run_transform(|r, inner| inner.layout(s, r), r)
106    }
107
108    fn layout_empty(&mut self, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
109        self.run_transform(|r, inner| inner.layout_empty(r), r)
110    }
111
112    fn handle_message(&mut self, m: &Message) -> Option<Box<dyn Layout>> {
113        if let Some(&UnwrapTransformer) = m.downcast_ref() {
114            return Some(self.unwrap());
115        }
116
117        self.passthrough_message(m)
118    }
119}
120
121/// Quickly define a [LayoutTransformer] from a single element tuple struct and a
122/// transformation function: `fn(Rect, Vec<(Xid, Rect)>) -> Vec<(Xid, Rect)>`.
123///
124/// The struct must have a single field which is a `Box<dyn Layout>`.
125///
126/// # Example
127/// ```no_run
128/// # use penrose::{core::layout::Layout, pure::geometry::Rect, simple_transformer, Xid};
129/// fn my_transformation_function(r: Rect, positions: Vec<(Xid, Rect)>) -> Vec<(Xid, Rect)> {
130///     // transformation implementation goes here
131///     positions
132/// }
133///
134/// simple_transformer!(MyTransformer, my_transformation_function, "MyTransform");
135/// ```
136#[macro_export]
137macro_rules! simple_transformer {
138    (
139        $(#[$struct_docs:meta])*
140        $t:ident,
141        $f:ident,
142        $prefix:expr
143    ) => {
144        $(#[$struct_docs])*
145        #[derive(Debug, Clone)]
146        pub struct $t(Box<dyn $crate::core::layout::Layout>);
147
148        impl $t {
149            /// Wrap an existing layout with this transformer
150            pub fn wrap(
151                layout: Box<dyn $crate::core::layout::Layout>,
152            ) -> Box<dyn $crate::core::layout::Layout> {
153                Box::new(Self(layout))
154            }
155        }
156
157        impl $crate::core::layout::LayoutTransformer for $t {
158            fn transformed_name(&self) -> String {
159                format!("{}<{}>", $prefix, self.0.name())
160            }
161
162            fn inner_mut(&mut self) -> &mut Box<dyn $crate::core::layout::Layout> {
163                &mut self.0
164            }
165
166            fn transform_positions(
167                &mut self,
168                r: $crate::pure::geometry::Rect,
169                positions: Vec<($crate::core::Xid, $crate::pure::geometry::Rect)>,
170            ) -> Vec<($crate::core::Xid, $crate::pure::geometry::Rect)> {
171                $f(r, positions)
172            }
173        }
174    };
175}