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