1pub 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
23pub 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, 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#[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 pub fn new() -> Self {
149 LayoutNode {
150 inner: Rc::new(RefCell::new(LayoutNodeInner::new(None, 0))),
151 }
152 }
153
154 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 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 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 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 pub fn add_child(&self, child: Self) {
189 self.inner.borrow_mut().children.push(child);
190 }
191
192 pub fn set_label(&self, label: Option<impl ToString>) {
194 self.inner.borrow_mut().label = label.map(|label| label.to_string());
195 }
196
197 pub fn set_traversal_index(&self, index: u32) {
200 self.inner.borrow_mut().traversal_index = index;
201 }
202
203 pub fn set_children(&self, children: impl IntoIterator<Item = Self>) {
205 self.inner.borrow_mut().children = children.into_iter().collect();
206 }
207
208 pub fn set_dir(&self, dir: LayoutDir) {
210 self.inner.borrow_mut().style.layout_dir = dir;
211 }
212
213 pub fn set_size_proportion(&self, proportion: f32) {
215 self.inner.borrow_mut().style.size_proportion = proportion;
216 }
217
218 pub fn set_gaps(&self, gaps: impl Into<Gaps>) {
220 self.inner.borrow_mut().style.gaps = gaps.into();
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
226pub enum LayoutDir {
227 Row,
229 Column,
231}
232
233#[derive(Debug, Clone, Copy, Default, PartialEq)]
235pub struct Gaps {
236 pub left: f32,
238 pub right: f32,
240 pub top: f32,
242 pub bottom: f32,
244}
245
246impl Gaps {
247 pub fn new() -> Self {
249 Default::default()
250 }
251
252 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#[derive(Clone, Debug)]
363pub struct LayoutArgs {
364 pub output: OutputHandle,
366 pub window_count: u32,
368 pub tags: Vec<TagHandle>,
370}
371
372pub trait LayoutGenerator {
374 fn layout(&self, window_count: u32) -> LayoutNode;
376}
377
378#[derive(Debug, Clone)]
380pub struct LayoutRequester {
381 sender: UnboundedSender<LayoutRequest>,
382}
383
384impl LayoutRequester {
385 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 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}