1pub 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
23pub struct LayoutResponse {
25 pub root_node: LayoutNode,
27 pub tree_id: u32,
32}
33
34pub 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#[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 pub fn new() -> Self {
160 LayoutNode {
161 inner: Rc::new(RefCell::new(LayoutNodeInner::new(None, 0))),
162 }
163 }
164
165 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 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 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 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 pub fn add_child(&self, child: Self) {
200 self.inner.borrow_mut().children.push(child);
201 }
202
203 pub fn set_label(&self, label: Option<impl ToString>) {
205 self.inner.borrow_mut().label = label.map(|label| label.to_string());
206 }
207
208 pub fn set_traversal_index(&self, index: u32) {
211 self.inner.borrow_mut().traversal_index = index;
212 }
213
214 pub fn set_children(&self, children: impl IntoIterator<Item = Self>) {
216 self.inner.borrow_mut().children = children.into_iter().collect();
217 }
218
219 pub fn set_dir(&self, dir: LayoutDir) {
221 self.inner.borrow_mut().style.layout_dir = dir;
222 }
223
224 pub fn set_size_proportion(&self, proportion: f32) {
226 self.inner.borrow_mut().style.size_proportion = proportion;
227 }
228
229 pub fn set_gaps(&self, gaps: impl Into<Gaps>) {
231 self.inner.borrow_mut().style.gaps = gaps.into();
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237pub enum LayoutDir {
238 Row,
240 Column,
242}
243
244#[derive(Debug, Clone, Copy, Default, PartialEq)]
246pub struct Gaps {
247 pub left: f32,
249 pub right: f32,
251 pub top: f32,
253 pub bottom: f32,
255}
256
257impl Gaps {
258 pub fn new() -> Self {
260 Default::default()
261 }
262
263 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#[derive(Clone, Debug)]
374pub struct LayoutArgs {
375 pub output: OutputHandle,
377 pub window_count: u32,
379 pub tags: Vec<TagHandle>,
381}
382
383pub trait LayoutGenerator {
385 fn layout(&self, window_count: u32) -> LayoutNode;
387}
388
389#[derive(Debug, Clone)]
391pub struct LayoutRequester {
392 sender: UnboundedSender<LayoutRequest>,
393}
394
395impl LayoutRequester {
396 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 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}