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!("There is no Layout with the name {:?}, but was configured on workspace {:?}", ws_layout_name, wsid);
65 }
66 }
67 }
68 }
69
70 if available_layouts.is_empty() {
71 tracing::warn!(
72 "No Layouts were loaded from config - defaulting to a single default Layout"
73 );
74 available_layouts.push(Layout::default());
75 }
76
77 tracing::trace!("The general available layouts are: {:?}", available_layouts);
78 tracing::trace!(
79 "The workspace specific available layouts are: {:?}",
80 available_layouts_per_ws
81 );
82
83 Self {
84 mode: config.layout_mode(),
85 available_layouts,
86 available_layouts_per_ws,
87 layouts: HashMap::new(),
88 }
89 }
90
91 pub fn restore(&mut self, old: &LayoutManager) {
92 if self.mode != old.mode {
93 tracing::debug!("The LayoutMode has changed, layouts will not be restored");
94 return;
95 }
96 if self.available_layouts != old.available_layouts {
99 tracing::debug!("The available Layouts have changed, layouts will not be restored");
100 return;
101 }
102 if self.available_layouts_per_ws != old.available_layouts_per_ws {
103 tracing::debug!(
104 "The available Layouts per Workspace have changed, layouts will not be restored"
105 );
106 return;
107 }
108 self.layouts.clone_from(&old.layouts);
109 }
110
111 fn id(&self, wsid: usize, tagid: usize) -> usize {
113 match self.mode {
114 LayoutMode::Tag => tagid,
115 LayoutMode::Workspace => wsid,
116 }
117 }
118
119 fn layouts(&mut self, wsid: usize, tagid: usize) -> &Vec<Layout> {
125 self.layouts_mut(wsid, tagid)
126 }
127
128 fn layouts_mut(&mut self, wsid: usize, tagid: usize) -> &mut Vec<Layout> {
134 let id = self.id(wsid, tagid);
135 self.layouts.entry(id).or_insert_with(|| match &self.mode {
136 LayoutMode::Tag => self.available_layouts.clone(),
137 LayoutMode::Workspace => self
138 .available_layouts_per_ws
139 .get(&wsid)
140 .unwrap_or(&self.available_layouts)
141 .clone(),
142 })
143 }
144
145 pub fn layout_maybe(&self, wsid: usize, tagid: usize) -> Option<&Layout> {
152 let id = self.id(wsid, tagid);
153 self.layouts.get(&id).and_then(|vec| vec.first())
154 }
155
156 pub fn layout(&mut self, wsid: usize, tagid: usize) -> &Layout {
162 let layouts = self.layouts(wsid, tagid);
163 assert!(
164 !layouts.is_empty(),
165 "there should be always at least one layout, because LeftWM must fallback to a default if empty"
166 );
167 layouts.first().unwrap()
168 }
169
170 pub fn layout_mut(&mut self, wsid: usize, tagid: usize) -> &mut Layout {
176 let layouts = self.layouts_mut(wsid, tagid);
177 assert!(
178 !layouts.is_empty(),
179 "there should be always at least one layout, because LeftWM must fallback to a default if empty"
180 );
181 layouts.first_mut().unwrap()
182 }
183
184 pub fn cycle_next_layout(&mut self, wsid: usize, tagid: usize) {
185 cycle_vec(self.layouts_mut(wsid, tagid), -1);
186 }
187
188 pub fn cycle_previous_layout(&mut self, wsid: usize, tagid: usize) {
189 cycle_vec(self.layouts_mut(wsid, tagid), 1);
190 }
191
192 pub fn set_layout(&mut self, wsid: usize, tagid: usize, name: &str) {
193 let i = self
194 .layouts(wsid, tagid)
195 .iter()
196 .enumerate()
197 .find(|(_, layout)| layout.name == name)
198 .map(|(i, _)| i);
199
200 match i {
201 Some(index) => cycle_vec(self.layouts_mut(wsid, tagid), -(index as i32)),
202 None => None,
203 };
204 }
205
206 }
208
209#[cfg(test)]
210mod tests {
211 use leftwm_layouts::layouts::Layouts;
212
213 use crate::{
214 config::tests::TestConfig,
215 layouts::{self, EVEN_VERTICAL, MONOCLE},
216 };
217
218 use super::LayoutManager;
219
220 fn layout_manager() -> LayoutManager {
221 let config = TestConfig {
222 layouts: vec![
223 layouts::MONOCLE.to_string(),
224 layouts::EVEN_VERTICAL.to_string(),
225 layouts::MAIN_AND_HORIZONTAL_STACK.to_string(),
226 ],
227 layout_definitions: Layouts::default().layouts,
228 workspaces: Some(vec![
229 crate::config::Workspace {
230 layouts: Some(vec![
231 layouts::CENTER_MAIN.to_string(),
232 layouts::CENTER_MAIN_BALANCED.to_string(),
233 layouts::MAIN_AND_DECK.to_string(),
234 ]),
235 ..Default::default()
236 },
237 crate::config::Workspace {
238 ..Default::default()
239 },
240 crate::config::Workspace {
241 layouts: Some(vec![]),
242 ..Default::default()
243 },
244 ]),
245 ..Default::default()
246 };
247
248 LayoutManager::new(&config)
249 }
250
251 #[test]
252 fn layouts_should_fallback_to_the_global_list() {
253 let layout_manager = layout_manager();
254 assert_eq!(1, layout_manager.id(1, 2));
255 }
256
257 #[test]
258 fn monocle_layout_only_has_single_windows() {
259 let mut layout_manager = layout_manager();
260 layout_manager.set_layout(2, 1, MONOCLE);
261 assert_eq!(MONOCLE, &layout_manager.layout(2, 1).name);
262 layout_manager.set_layout(2, 1, EVEN_VERTICAL);
263 assert_eq!(EVEN_VERTICAL, &layout_manager.layout(2, 1).name);
264 }
265}