1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
/**! * Layouts are user definable window arangements for a Workspace. * * Layouts are maintained per monitor and allow for indepent management of the two * paramaters (n_main, main_ratio) that are used to modify layout logic. As penrose * makes use of a tagging system as opposed to workspaces, layouts will be passed a * Vec of clients to handle which is determined by the current client and monitor tags. * arrange is only called if there are clients to handle so there is no need to check * that clients.len() > 0. r is the monitor Region defining the size of the monitor * for the layout to position windows. */ use crate::client::Client; use crate::data_types::{Change, Region, ResizeAction, WinId}; use std::fmt; /** * When and how a Layout should be applied. */ #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct LayoutConf { /// If true, this layout function will not be called to produce resize actions pub floating: bool, /// Should gaps be dropped regardless of config pub gapless: bool, /// Should this layout be triggered by window focus as well as add/remove client pub follow_focus: bool, } impl LayoutConf { /// A layout config that only triggers when clients are added / removed and follows user /// defined config options. pub fn default() -> LayoutConf { LayoutConf { floating: false, gapless: false, follow_focus: false, } } } /** * A function that can be used to position Clients on a Workspace. Will be called with the current * client list, the active client ID (if there is one), the size of the screen that the workspace * is shown on and the current values of n_main and ratio for this layout. */ pub type LayoutFunc = fn(&[&Client], Option<WinId>, &Region, u32, f32) -> Vec<ResizeAction>; /** * Responsible for arranging Clients within a Workspace. * * A Layout is primarily a function that will be passed an array of Clients to apply resize actions * to. Only clients that should be tiled for the current monitor will be passed so no checks are * required to see if each client should be handled. The region passed to the layout function * represents the current screen dimensions that can be utilised and gaps/borders will be added to * each client by the WindowManager itself so there is no need to handle that in the layouts * themselves. * Layouts are expected to have a 'main area' that holds the clients with primary focus and any * number of secondary areas for the remaining clients to be tiled. * The user can increase/decrease the size of the main area by setting 'ratio' via key bindings * which should determine the relative size of the main area compared to other cliens. Layouts * maintain their own state for number of clients in the main area and ratio which will be passed * through to the layout function when it is called. */ #[derive(Clone, Copy)] pub struct Layout { /// How this layout should be applied by the WindowManager pub conf: LayoutConf, /// User defined symbol for displaying in the status bar pub symbol: &'static str, max_main: u32, ratio: f32, f: LayoutFunc, } impl fmt::Debug for Layout { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Layout") .field("kind", &self.conf) .field("symbol", &self.symbol) .field("max_main", &self.max_main) .field("ratio", &self.ratio) .field("f", &stringify!(&self.f)) .finish() } } // A no-op floating layout that simply satisfies the type required for Layout fn floating(_: &[&Client], _: Option<WinId>, _: &Region, _: u32, _: f32) -> Vec<ResizeAction> { vec![] } impl Layout { /// Create a new Layout for a specific monitor pub fn new( symbol: &'static str, conf: LayoutConf, f: LayoutFunc, max_main: u32, ratio: f32, ) -> Layout { Layout { symbol, conf, max_main, ratio, f, } } /// A default floating layout that will not attempt to manage windows pub fn floating(symbol: &'static str) -> Layout { Layout { symbol, conf: LayoutConf { floating: true, gapless: false, follow_focus: false, }, f: floating, max_main: 1, ratio: 1.0, } } /// Apply the embedded layout function using the current n_main and ratio pub fn arrange( &self, clients: &[&Client], focused: Option<WinId>, r: &Region, ) -> Vec<ResizeAction> { (self.f)(clients, focused, r, self.max_main, self.ratio) } /// Increase/decrease the number of clients in the main area by 1 pub fn update_max_main(&mut self, change: Change) { match change { Change::More => self.max_main += 1, Change::Less => { if self.max_main > 0 { self.max_main -= 1; } } } } /// Increase/decrease the size of the main area relative to secondary. /// (clamps at 1.0 and 0.0 respectively) pub fn update_main_ratio(&mut self, change: Change, step: f32) { match change { Change::More => self.ratio += step, Change::Less => self.ratio -= step, } if self.ratio < 0.0 { self.ratio = 0.0 } else if self.ratio > 1.0 { self.ratio = 1.0; } } } /* * Utility functions for simplifying writing layouts */ /// number of clients for the main area vs secondary pub fn client_breakdown<T>(clients: &[T], n_main: u32) -> (u32, u32) { let n = clients.len() as u32; if n <= n_main { (n, 0) } else { (n_main, n - n_main) } } /* * Layout functions * * Each of the following is a layout function that can be passed to Layout::new. * No checks are carried out to ensure that clients are tiled correctly (i.e. that * they are non-overlapping) so when adding additional layout functions you are * free to tile them however you wish. Xmonad for example has a 'circle' layout * that deliberately overlaps clients under the main window. */ // ignore paramas and return pairs of window ID and index in the client vec #[cfg(test)] pub(crate) fn mock_layout( clients: &[&Client], _: Option<WinId>, r: &Region, _: u32, _: f32, ) -> Vec<ResizeAction> { clients .iter() .enumerate() .map(|(i, c)| { let (x, y, w, h) = r.values(); let k = i as u32; (c.id(), Region::new(x + k, y + k, w - k, h - k)) }) .collect() } /** * A simple layout that places the main region on the left and tiles remaining * windows in a single column to the right. */ pub fn side_stack( clients: &[&Client], _: Option<WinId>, monitor_region: &Region, max_main: u32, ratio: f32, ) -> Vec<ResizeAction> { let (mx, my, mw, mh) = monitor_region.values(); let (n_main, n_stack) = client_breakdown(&clients, max_main); let h_stack = if n_stack > 0 { mh / n_stack } else { 0 }; let h_main = if n_main > 0 { mh / n_main } else { 0 }; let split = if max_main > 0 { (mw as f32 * ratio) as u32 } else { 0 }; clients .iter() .enumerate() .map(|(n, c)| { let n = n as u32; if n < max_main { let w = if n_stack == 0 { mw } else { split }; (c.id(), Region::new(mx, my + n * h_main, w, h_main)) } else { let sn = n - max_main; // nth stacked client let region = Region::new(mx + split, my + sn * h_stack, mw - split, h_stack); (c.id(), region) } }) .collect() } /** * A simple layout that places the main region at the top of the screen and tiles * remaining windows in a single row underneath. */ pub fn bottom_stack( clients: &[&Client], _: Option<WinId>, monitor_region: &Region, max_main: u32, ratio: f32, ) -> Vec<ResizeAction> { let (mx, my, mw, mh) = monitor_region.values(); let (n_main, n_stack) = client_breakdown(&clients, max_main); let split = if max_main > 0 { (mh as f32 * ratio) as u32 } else { 0 }; let h_main_full = if n_stack > 0 { split } else { mh }; let h_main = if n_main > 0 { h_main_full / n_main } else { 0 }; let w_stack = if n_stack > 0 { mw / n_stack } else { 0 }; clients .iter() .enumerate() .map(|(n, c)| { let n = n as u32; if n < max_main { let region = Region::new(mx, my + n * h_main / n_main, mw, h_main); (c.id(), region) } else { let sn = n - max_main; // nth stacked client let region = Region::new(mx + sn * w_stack, my + split, w_stack, mh - split); (c.id(), region) } }) .collect() } /** * A layout that aims to mimic the feel of having multiple pieces of paper fanned out on a desk, * inspired by http://10gui.com/ * * Without access to the custom hardware required for 10gui, we instead have to rely on the WM * actions we have available: n_main is ignored and instead, the focused client takes up ratio% of * the screen, with the remaining windows being stacked on top of one another to either side. Think * fanning out a hand of cards and then pulling one out and placing it on top of the fan. */ pub fn paper( clients: &[&Client], focused: Option<WinId>, monitor_region: &Region, _: u32, ratio: f32, ) -> Vec<ResizeAction> { let n = clients.len(); if n == 1 { return vec![(clients[0].id(), *monitor_region)]; } let (mx, my, mw, mh) = monitor_region.values(); let min_w = 0.5; // clamp client width at 50% screen size (we're effectively fancy monocle) let cw = (mw as f32 * if ratio > min_w { ratio } else { min_w }) as u32; let step = (mw - cw) / (n - 1) as u32; let fid = focused.unwrap(); // we know we have at least one client now let mut after_focused = false; clients .iter() .enumerate() .map(|(i, c)| { let cid = c.id(); if cid == fid { after_focused = true; (cid, Region::new(mx + i as u32 * step, my, cw, mh)) } else { let mut x = mx + i as u32 * step; if after_focused { x += cw - step }; (cid, Region::new(x, my, step, mh)) } }) .collect() }