1use crate::{config::Config, utils::helpers::cycle_vec};
2use leftwm_layouts::Layout;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::LayoutMode;
7
8#[derive(Serialize, Deserialize, Debug, Clone)]
10pub struct LayoutManager {
11 mode: LayoutMode,
13
14 available_layouts: Vec<Layout>,
18
19 available_layouts_per_ws: HashMap<usize, Vec<Layout>>,
24
25 layouts: HashMap<usize, Vec<Layout>>,
28}
29
30impl LayoutManager {
31 pub fn new(config: &impl Config) -> Self {
33 let mut available_layouts: Vec<Layout> = Vec::new();
34
35 tracing::trace!("Looking for layouts named: {:?}", config.layouts());
36 for name in config.layouts() {
37 if let Some(def) = config
38 .layout_definitions()
39 .iter()
40 .find(|def| def.name == name)
41 {
42 available_layouts.push(def.clone());
43 } else {
44 tracing::warn!("There is no Layout with the name {:?}", name);
45 }
46 }
47
48 let mut available_layouts_per_ws: HashMap<usize, Vec<Layout>> = HashMap::new();
49
50 for (i, ws) in config.workspaces().unwrap_or_default().iter().enumerate() {
51 if let Some(ws_layout_names) = &ws.layouts {
52 let wsid = i + 1;
53 for ws_layout_name in ws_layout_names {
54 if let Some(layout) = config
55 .layout_definitions()
56 .iter()
57 .find(|layout| layout.name == *ws_layout_name)
58 {
59 available_layouts_per_ws
60 .entry(wsid)
61 .and_modify(|layouts| layouts.push(layout.clone()))
62 .or_insert_with(|| vec![layout.clone()]);
63 } else {
64 tracing::warn!(
65 "There is no Layout with the name {:?}, but was configured on workspace {:?}",
66 ws_layout_name,
67 wsid
68 );
69 }
70 }
71 }
72 if let Some(default_layout) = &ws.default_layout {
73 let wsid = i + 1;
74 if let Some(layout) = config
75 .layout_definitions()
76 .iter()
77 .find(|layout| layout.name == *default_layout)
78 {
79 available_layouts_per_ws
81 .entry(wsid)
82 .and_modify(|layouts| {
83 if !layouts.iter().any(|l| l.name == layout.name) {
84 layouts.push(layout.clone());
85 }
86 })
87 .or_insert_with(|| vec![layout.clone()]);
88 } else {
89 tracing::warn!(
90 "There is no Layout with the name {:?}, but was configured as default on workspace {:?}",
91 default_layout,
92 wsid
93 );
94 }
95 }
96 }
97
98 if available_layouts.is_empty() {
99 tracing::warn!(
100 "No Layouts were loaded from config - defaulting to a single default Layout"
101 );
102 available_layouts.push(Layout::default());
103 }
104
105 tracing::trace!("The general available layouts are: {:?}", available_layouts);
106 tracing::trace!(
107 "The workspace specific available layouts are: {:?}",
108 available_layouts_per_ws
109 );
110
111 let mut layout_manager = Self {
112 mode: config.layout_mode(),
113 available_layouts,
114 available_layouts_per_ws,
115 layouts: HashMap::new(),
116 };
117
118 for (i, ws) in config.workspaces().unwrap_or_default().iter().enumerate() {
120 if let Some(default_layout) = &ws.default_layout {
121 let wsid = i + 1;
122 layout_manager.set_layout(wsid, wsid, default_layout);
123 }
124 }
125
126 layout_manager
127 }
128
129 pub fn restore(&mut self, old: &LayoutManager) {
130 if self.mode != old.mode {
131 tracing::debug!("The LayoutMode has changed, layouts will not be restored");
132 return;
133 }
134 if self.available_layouts != old.available_layouts {
137 tracing::debug!("The available Layouts have changed, layouts will not be restored");
138 return;
139 }
140 if self.available_layouts_per_ws != old.available_layouts_per_ws {
141 tracing::debug!(
142 "The available Layouts per Workspace have changed, layouts will not be restored"
143 );
144 return;
145 }
146 self.layouts.clone_from(&old.layouts);
147 }
148
149 fn id(&self, wsid: usize, tagid: usize) -> usize {
151 match self.mode {
152 LayoutMode::Tag => tagid,
153 LayoutMode::Workspace => wsid,
154 }
155 }
156
157 fn layouts(&mut self, wsid: usize, tagid: usize) -> &Vec<Layout> {
163 self.layouts_mut(wsid, tagid)
164 }
165
166 fn layouts_mut(&mut self, wsid: usize, tagid: usize) -> &mut Vec<Layout> {
172 let id = self.id(wsid, tagid);
173 self.layouts.entry(id).or_insert_with(|| match &self.mode {
174 LayoutMode::Tag => self.available_layouts.clone(),
175 LayoutMode::Workspace => self
176 .available_layouts_per_ws
177 .get(&wsid)
178 .unwrap_or(&self.available_layouts)
179 .clone(),
180 })
181 }
182
183 pub fn layout_maybe(&self, wsid: usize, tagid: usize) -> Option<&Layout> {
190 let id = self.id(wsid, tagid);
191 self.layouts.get(&id).and_then(|vec| vec.first())
192 }
193
194 pub fn layout(&mut self, wsid: usize, tagid: usize) -> &Layout {
200 let layouts = self.layouts(wsid, tagid);
201 assert!(
202 !layouts.is_empty(),
203 "there should be always at least one layout, because LeftWM must fallback to a default if empty"
204 );
205 layouts.first().unwrap()
206 }
207
208 pub fn layout_mut(&mut self, wsid: usize, tagid: usize) -> &mut Layout {
214 let layouts = self.layouts_mut(wsid, tagid);
215 assert!(
216 !layouts.is_empty(),
217 "there should be always at least one layout, because LeftWM must fallback to a default if empty"
218 );
219 layouts.first_mut().unwrap()
220 }
221
222 pub fn cycle_next_layout(&mut self, wsid: usize, tagid: usize) {
223 cycle_vec(self.layouts_mut(wsid, tagid), -1);
224 }
225
226 pub fn cycle_previous_layout(&mut self, wsid: usize, tagid: usize) {
227 cycle_vec(self.layouts_mut(wsid, tagid), 1);
228 }
229
230 pub fn set_layout(&mut self, wsid: usize, tagid: usize, name: &str) {
231 let i = self
232 .layouts(wsid, tagid)
233 .iter()
234 .enumerate()
235 .find(|(_, layout)| layout.name == name)
236 .map(|(i, _)| i);
237
238 match i {
239 Some(index) => cycle_vec(self.layouts_mut(wsid, tagid), -(index as i32)),
240 None => None,
241 };
242 }
243
244 }
246
247#[cfg(test)]
248mod tests {
249 use leftwm_layouts::layouts::Layouts;
250
251 use crate::{
252 config::tests::TestConfig,
253 layouts::{self, EVEN_VERTICAL, MONOCLE},
254 };
255
256 use super::LayoutManager;
257
258 fn layout_manager() -> LayoutManager {
259 let config = TestConfig {
260 layouts: vec![
261 layouts::MONOCLE.to_string(),
262 layouts::EVEN_VERTICAL.to_string(),
263 layouts::MAIN_AND_HORIZONTAL_STACK.to_string(),
264 ],
265 layout_definitions: Layouts::default().layouts,
266 workspaces: Some(vec![
267 crate::config::Workspace {
268 layouts: Some(vec![
269 layouts::CENTER_MAIN.to_string(),
270 layouts::CENTER_MAIN_BALANCED.to_string(),
271 layouts::MAIN_AND_DECK.to_string(),
272 ]),
273 ..Default::default()
274 },
275 crate::config::Workspace {
276 ..Default::default()
277 },
278 crate::config::Workspace {
279 layouts: Some(vec![]),
280 ..Default::default()
281 },
282 crate::config::Workspace {
284 default_layout: Some(layouts::MAIN_AND_HORIZONTAL_STACK.to_string()),
285 ..Default::default()
286 },
287 crate::config::Workspace {
289 layouts: Some(vec![
290 layouts::CENTER_MAIN.to_string(),
291 layouts::CENTER_MAIN_BALANCED.to_string(),
292 layouts::MAIN_AND_VERT_STACK.to_string(),
293 ]),
294 default_layout: Some(layouts::MAIN_AND_VERT_STACK.to_string()),
295 ..Default::default()
296 },
297 crate::config::Workspace {
299 default_layout: Some(layouts::FIBONACCI.to_string()),
300 ..Default::default()
301 },
302 crate::config::Workspace {
304 layouts: Some(vec![]),
305 default_layout: Some(layouts::EVEN_VERTICAL.to_string()),
306 ..Default::default()
307 },
308 crate::config::Workspace {
310 layouts: Some(vec![
311 layouts::CENTER_MAIN_BALANCED.to_string(),
312 layouts::MAIN_AND_DECK.to_string(),
313 ]),
314 default_layout: Some(layouts::CENTER_MAIN.to_string()),
315 ..Default::default()
316 },
317 ]),
318 ..Default::default()
319 };
320
321 LayoutManager::new(&config)
322 }
323
324 #[test]
325 fn layouts_should_fallback_to_the_global_list() {
326 let layout_manager = layout_manager();
327 assert_eq!(1, layout_manager.id(1, 2));
328 }
329
330 #[test]
331 fn monocle_layout_only_has_single_windows() {
332 let mut layout_manager = layout_manager();
333 layout_manager.set_layout(2, 1, MONOCLE);
334 assert_eq!(MONOCLE, &layout_manager.layout(2, 1).name);
335 layout_manager.set_layout(2, 1, EVEN_VERTICAL);
336 assert_eq!(EVEN_VERTICAL, &layout_manager.layout(2, 1).name);
337 }
338
339 #[test]
340 fn default_layouts_should_be_set() {
341 let mut layout_manager = layout_manager();
342 assert_eq!(
343 layouts::MAIN_AND_HORIZONTAL_STACK,
344 &layout_manager.layout(4, 1).name
345 );
346 assert_eq!(
347 layouts::MAIN_AND_VERT_STACK,
348 &layout_manager.layout(5, 1).name
349 );
350 assert_eq!(layouts::FIBONACCI, &layout_manager.layout(6, 1).name);
351 assert_eq!(layouts::EVEN_VERTICAL, &layout_manager.layout(7, 1).name);
352 assert_eq!(layouts::CENTER_MAIN, &layout_manager.layout(8, 1).name);
353 }
354}