pinnacle_api/
layout.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Layout management.
6//!
7//! Read the [wiki page](https://pinnacle-comp.github.io/pinnacle/configuration/layout.html)
8//! for more information.
9
10pub mod generators;
11
12use std::{cell::RefCell, collections::HashMap, rc::Rc};
13
14use pinnacle_api_defs::pinnacle::layout::{
15    self,
16    v1::{LayoutRequest, TraversalOverrides, layout_request},
17};
18use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
19use tokio_stream::StreamExt;
20
21use crate::{BlockOnTokio, client::Client, output::OutputHandle, tag::TagHandle};
22
23/// A response to a layout request containing a layout tree.
24pub struct LayoutResponse {
25    /// The root node of the layout tree.
26    pub root_node: LayoutNode,
27    /// An identifier for the layout tree.
28    ///
29    /// Trees that are considered "the same", like trees for a certain tag and layout,
30    /// should have the same identifier to allow Pinnacle to remember tile sizing.
31    pub tree_id: u32,
32}
33
34/// Manages layout requests from the compositor.
35///
36/// You must call this function to begin handling incoming layout requests.
37/// Whenever a layout request comes in, `on_layout` will be called with the arguments of the
38/// layout. The closure must then return a [`LayoutResponse`] containing the root of a layout tree through a [`LayoutNode`], along with a unique identifier.
39///
40/// This returns a [`LayoutRequester`] that allows you to force the compositor to emit a
41/// layout request.
42///
43/// See the module level documentation for more information on how to generate layouts.
44pub fn manage(
45    mut on_layout: impl FnMut(LayoutArgs) -> LayoutResponse + Send + 'static,
46) -> LayoutRequester {
47    let (from_client, to_server) = unbounded_channel::<LayoutRequest>();
48    let to_server_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(to_server);
49    let mut from_server = Client::layout()
50        .layout(to_server_stream)
51        .block_on_tokio()
52        .unwrap()
53        .into_inner();
54
55    let from_client_clone = from_client.clone();
56
57    let requester = LayoutRequester {
58        sender: from_client_clone,
59    };
60
61    let fut = async move {
62        while let Some(Ok(response)) = from_server.next().await {
63            let args = LayoutArgs {
64                output: OutputHandle {
65                    name: response.output_name.clone(),
66                },
67                window_count: response.window_count,
68                tags: response
69                    .tag_ids
70                    .into_iter()
71                    .map(|id| TagHandle { id })
72                    .collect(),
73            };
74            let tree_response = on_layout(args);
75            from_client
76                .send(LayoutRequest {
77                    request: Some(layout_request::Request::TreeResponse(
78                        layout_request::TreeResponse {
79                            tree_id: tree_response.tree_id,
80                            request_id: response.request_id,
81                            output_name: response.output_name,
82                            root_node: Some(tree_response.root_node.into()),
83                        },
84                    )),
85                })
86                .unwrap();
87        }
88    };
89
90    tokio::spawn(fut);
91    requester
92}
93
94/// A single node of a layout tree.
95///
96/// [`LayoutNode`]s allow you to hierarchically represent layouts in a tree structure.
97/// They have the following properties:
98/// - A layout direction, set with [`set_dir`][Self::set_dir]: This determines the direction
99///   that children layout nodes are laid out in.
100/// - A size proportion, set with [`set_size_proportion`][Self::set_size_proportion]: This
101///   determines the proportion of space a layout node takes up in relation to its siblings.
102/// - Gaps, set with [`set_gaps`][Self::set_gaps]: This determines the gaps surrounding a
103///   layout node.
104/// - A traversal index, set with [`set_traversal_index`][Self::set_traversal_index]: This
105///   determines the order that the layout tree is traversed in when assigning layout node
106///   geometries to windows.
107/// - Traversal overrides, set with [`set_traversal_overrides`][Self::set_traversal_overrides]:
108///   This provides a way to provide per-window overrides to tree traversal. This is used to
109///   enable otherwise impossible window insertion strategies. For example, the
110///   [`Corner`][self::generators::Corner] layout generator overrides traversal to allow
111///   windows to be inserted into the vertical and horizontal stacks in an alternating fashion.
112/// - An optional label, set with [`set_label`][Self::set_label]: This gives the compositor a hint
113///   when diffing layout trees, allowing it to, for example, decide whether to move a node or
114///   delete it and insert a new one.
115#[derive(Debug, Clone)]
116pub struct LayoutNode {
117    inner: Rc<RefCell<LayoutNodeInner>>,
118}
119
120impl PartialEq for LayoutNode {
121    fn eq(&self, other: &Self) -> bool {
122        Rc::ptr_eq(&self.inner, &other.inner)
123    }
124}
125
126#[derive(Debug, Clone)]
127struct LayoutNodeInner {
128    label: Option<String>,
129    traversal_index: u32,
130    traversal_overrides: HashMap<u32, Vec<u32>>,
131    style: Style,
132    children: Vec<LayoutNode>,
133}
134
135impl LayoutNodeInner {
136    fn new(label: Option<String>, traversal_index: u32) -> Self {
137        LayoutNodeInner {
138            label,
139            traversal_index,
140            traversal_overrides: Default::default(),
141            style: Style {
142                layout_dir: LayoutDir::Row,
143                gaps: Gaps::default(),
144                size_proportion: 1.0,
145            },
146            children: Vec::new(),
147        }
148    }
149}
150
151impl Default for LayoutNode {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157impl LayoutNode {
158    /// Creates a new layout node.
159    pub fn new() -> Self {
160        LayoutNode {
161            inner: Rc::new(RefCell::new(LayoutNodeInner::new(None, 0))),
162        }
163    }
164
165    /// Creates a new layout node with the given label.
166    pub fn new_with_label(label: impl ToString) -> Self {
167        LayoutNode {
168            inner: Rc::new(RefCell::new(LayoutNodeInner::new(
169                Some(label.to_string()),
170                0,
171            ))),
172        }
173    }
174
175    /// Creates a new layout node with the given traversal index.
176    pub fn new_with_traversal_index(index: u32) -> Self {
177        LayoutNode {
178            inner: Rc::new(RefCell::new(LayoutNodeInner::new(None, index))),
179        }
180    }
181
182    /// Creates a new layout node with the given label and traversal index.
183    pub fn new_with_label_and_index(label: impl ToString, index: u32) -> Self {
184        LayoutNode {
185            inner: Rc::new(RefCell::new(LayoutNodeInner::new(
186                Some(label.to_string()),
187                index,
188            ))),
189        }
190    }
191
192    /// Sets this node's traversal overrides, allowing it to change how windows are assigned
193    /// geometries.
194    pub fn set_traversal_overrides(&self, overrides: impl IntoIterator<Item = (u32, Vec<u32>)>) {
195        self.inner.borrow_mut().traversal_overrides = overrides.into_iter().collect();
196    }
197
198    /// Adds a child layout node to this node.
199    pub fn add_child(&self, child: Self) {
200        self.inner.borrow_mut().children.push(child);
201    }
202
203    /// Sets this node's label.
204    pub fn set_label(&self, label: Option<impl ToString>) {
205        self.inner.borrow_mut().label = label.map(|label| label.to_string());
206    }
207
208    /// Sets this node's traversal index, changing how the compositor traverses the tree when
209    /// assigning geometries to windows.
210    pub fn set_traversal_index(&self, index: u32) {
211        self.inner.borrow_mut().traversal_index = index;
212    }
213
214    /// Sets this node's children.
215    pub fn set_children(&self, children: impl IntoIterator<Item = Self>) {
216        self.inner.borrow_mut().children = children.into_iter().collect();
217    }
218
219    /// Sets this node's [`LayoutDir`].
220    pub fn set_dir(&self, dir: LayoutDir) {
221        self.inner.borrow_mut().style.layout_dir = dir;
222    }
223
224    /// Sets this node's size proportion in relation to its siblings.
225    pub fn set_size_proportion(&self, proportion: f32) {
226        self.inner.borrow_mut().style.size_proportion = proportion;
227    }
228
229    /// Sets the gaps this node places around its children.
230    pub fn set_gaps(&self, gaps: impl Into<Gaps>) {
231        self.inner.borrow_mut().style.gaps = gaps.into();
232    }
233}
234
235/// A layout direction.
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237pub enum LayoutDir {
238    /// Lays out nodes in a row.
239    Row,
240    /// Lays out nodes in a column.
241    Column,
242}
243
244/// Gaps around a layout node.
245#[derive(Debug, Clone, Copy, Default, PartialEq)]
246pub struct Gaps {
247    /// How many pixels should be inset from the left.
248    pub left: f32,
249    /// How many pixels should be inset from the right.
250    pub right: f32,
251    /// How many pixels should be inset from the top.
252    pub top: f32,
253    /// How many pixels should be inset from the bottom.
254    pub bottom: f32,
255}
256
257impl Gaps {
258    /// Creates a gap of 0 pixels on all sides.
259    pub fn new() -> Self {
260        Default::default()
261    }
262
263    /// Creates a gap with a uniform number of pixels on all sides.
264    pub fn uniform(gaps: f32) -> Self {
265        gaps.into()
266    }
267}
268
269impl From<f32> for Gaps {
270    fn from(value: f32) -> Self {
271        Self {
272            left: value,
273            right: value,
274            top: value,
275            bottom: value,
276        }
277    }
278}
279
280impl From<u32> for Gaps {
281    fn from(value: u32) -> Self {
282        let value = value as f32;
283        Self {
284            left: value,
285            right: value,
286            top: value,
287            bottom: value,
288        }
289    }
290}
291
292impl From<u16> for Gaps {
293    fn from(value: u16) -> Self {
294        let value = value as f32;
295        Self {
296            left: value,
297            right: value,
298            top: value,
299            bottom: value,
300        }
301    }
302}
303
304impl From<u8> for Gaps {
305    fn from(value: u8) -> Self {
306        let value = value as f32;
307        Self {
308            left: value,
309            right: value,
310            top: value,
311            bottom: value,
312        }
313    }
314}
315
316#[derive(Debug, Clone)]
317struct Style {
318    layout_dir: LayoutDir,
319    gaps: Gaps,
320    size_proportion: f32,
321}
322
323impl From<LayoutNode> for layout::v1::LayoutNode {
324    fn from(value: LayoutNode) -> Self {
325        fn api_node_from_layout_node(node: LayoutNode) -> layout::v1::LayoutNode {
326            let style = node.inner.borrow().style.clone();
327
328            layout::v1::LayoutNode {
329                label: node.inner.borrow().label.clone(),
330                traversal_overrides: node
331                    .inner
332                    .borrow()
333                    .traversal_overrides
334                    .iter()
335                    .map(|(idx, overrides)| {
336                        (
337                            *idx,
338                            TraversalOverrides {
339                                overrides: overrides.clone(),
340                            },
341                        )
342                    })
343                    .collect(),
344                traversal_index: node.inner.borrow().traversal_index,
345                style: Some(layout::v1::NodeStyle {
346                    flex_dir: match node.inner.borrow().style.layout_dir {
347                        LayoutDir::Row => layout::v1::FlexDir::Row,
348                        LayoutDir::Column => layout::v1::FlexDir::Column,
349                    }
350                    .into(),
351                    size_proportion: node.inner.borrow().style.size_proportion,
352                    gaps: Some(layout::v1::Gaps {
353                        left: style.gaps.left,
354                        right: style.gaps.right,
355                        top: style.gaps.top,
356                        bottom: style.gaps.bottom,
357                    }),
358                }),
359                children: node
360                    .inner
361                    .borrow()
362                    .children
363                    .iter()
364                    .map(|node| api_node_from_layout_node(node.clone()))
365                    .collect(),
366            }
367        }
368        api_node_from_layout_node(value)
369    }
370}
371
372/// Arguments from an incoming layout request.
373#[derive(Clone, Debug)]
374pub struct LayoutArgs {
375    /// The output that is being laid out.
376    pub output: OutputHandle,
377    /// The number of windows being laid out.
378    pub window_count: u32,
379    /// The *focused* tags on the output.
380    pub tags: Vec<TagHandle>,
381}
382
383/// Types that can generate layouts by computing a tree of [`LayoutNode`]s.
384pub trait LayoutGenerator {
385    /// Generates a tree of [`LayoutNode`]s.
386    fn layout(&self, window_count: u32) -> LayoutNode;
387}
388
389/// A struct that can request layouts.
390#[derive(Debug, Clone)]
391pub struct LayoutRequester {
392    sender: UnboundedSender<LayoutRequest>,
393}
394
395impl LayoutRequester {
396    /// Requests a layout from the compositor.
397    ///
398    /// This uses the focused output for the request.
399    /// If you want to layout a specific output, see [`LayoutRequester::request_layout_on_output`].
400    pub fn request_layout(&self) {
401        let Some(output_name) = crate::output::get_focused().map(|op| op.name) else {
402            return;
403        };
404        self.sender
405            .send(LayoutRequest {
406                request: Some(layout_request::Request::ForceLayout(
407                    layout_request::ForceLayout { output_name },
408                )),
409            })
410            .unwrap();
411    }
412
413    /// Requests a layout from the compositor for the given output.
414    pub fn request_layout_on_output(&self, output: &OutputHandle) {
415        self.sender
416            .send(LayoutRequest {
417                request: Some(layout_request::Request::ForceLayout(
418                    layout_request::ForceLayout {
419                        output_name: output.name.clone(),
420                    },
421                )),
422            })
423            .unwrap();
424    }
425}