| 1 | use crate::{Insets, Layout, Output, OutputId, Rect, TagId, Tree, WindowId, translate}; |
| 2 | |
| 3 | #[derive(Clone, Debug)] |
| 4 | pub(crate) struct Parked { |
| 5 | tree: Tree<WindowId>, |
| 6 | viewed: Vec<TagId>, |
| 7 | floating: Vec<WindowId>, |
| 8 | } |
| 9 | |
| 10 | impl Layout { |
| 11 | /// Adds an output. One that was unplugged before gets back its tree, |
| 12 | /// views and the windows that had to leave it. |
| 13 | pub fn add_output(&mut self, name: &str, area: Rect) -> OutputId { |
| 14 | let id = self.outputs.insert(Output { |
| 15 | name: name.into(), |
| 16 | area, |
| 17 | reserved: Insets::default(), |
| 18 | tree: Tree::default(), |
| 19 | viewed: Vec::new(), |
| 20 | floating: Vec::new(), |
| 21 | }); |
| 22 | if let Some(parked) = self.parked.remove(name) { |
| 23 | self.restore(id, parked); |
| 24 | } |
| 25 | self.active.get_or_insert(id); |
| 26 | let detached: Vec<WindowId> = self |
| 27 | .windows |
| 28 | .iter() |
| 29 | .filter(|(_, w)| w.output.is_none()) |
| 30 | .map(|(id, _)| id) |
| 31 | .collect(); |
| 32 | for w in detached { |
| 33 | self.windows[w].output = Some(id); |
| 34 | self.place(w); |
| 35 | } |
| 36 | id |
| 37 | } |
| 38 | |
| 39 | fn restore(&mut self, id: OutputId, parked: Parked) { |
| 40 | let name = self.outputs[id].name.clone(); |
| 41 | let ours = |l: &Self, w: WindowId| { |
| 42 | l.windows |
| 43 | .get(w) |
| 44 | .is_some_and(|x| x.evicted_from.as_deref() == Some(&name) && x.scratchpad.is_none()) |
| 45 | }; |
| 46 | let mut tree = parked.tree; |
| 47 | tree.retain(|w| ours(self, w) && !self.windows[w].floating); |
| 48 | let floating: Vec<WindowId> = parked |
| 49 | .floating |
| 50 | .into_iter() |
| 51 | .filter(|&w| ours(self, w) && self.windows[w].floating) |
| 52 | .collect(); |
| 53 | for w in tree |
| 54 | .windows() |
| 55 | .chain(floating.iter().copied()) |
| 56 | .collect::<Vec<_>>() |
| 57 | { |
| 58 | self.unplace(w); |
| 59 | let win = &mut self.windows[w]; |
| 60 | if let (Some(from), Some(rect)) = (win.output, win.float_rect) { |
| 61 | win.float_rect = Some(translate( |
| 62 | rect, |
| 63 | self.outputs[from].usable(), |
| 64 | self.outputs[id].usable(), |
| 65 | )); |
| 66 | } |
| 67 | win.output = Some(id); |
| 68 | win.evicted_from = None; |
| 69 | } |
| 70 | let out = &mut self.outputs[id]; |
| 71 | out.tree = tree; |
| 72 | out.floating = floating; |
| 73 | out.viewed = parked |
| 74 | .viewed |
| 75 | .into_iter() |
| 76 | .filter(|&t| self.tags.contains_key(t)) |
| 77 | .collect(); |
| 78 | } |
| 79 | |
| 80 | /// Removes an output. Its windows move to an output viewing their tag, |
| 81 | /// or the active one, and remember where they came from. |
| 82 | pub fn remove_output(&mut self, o: OutputId) { |
| 83 | let Some(out) = self.outputs.remove(o) else { |
| 84 | return; |
| 85 | }; |
| 86 | let left_usable = out.usable(); |
| 87 | self.parked.insert( |
| 88 | out.name.clone(), |
| 89 | Parked { |
| 90 | tree: out.tree, |
| 91 | viewed: out.viewed, |
| 92 | floating: out.floating, |
| 93 | }, |
| 94 | ); |
| 95 | if self.active == Some(o) { |
| 96 | self.active = self.outputs.keys().next(); |
| 97 | } |
| 98 | for sp in &mut self.scratchpads { |
| 99 | if sp.output == Some(o) { |
| 100 | sp.output = self.active; |
| 101 | } |
| 102 | } |
| 103 | let leaving: Vec<WindowId> = self |
| 104 | .windows |
| 105 | .iter() |
| 106 | .filter(|(_, w)| w.output == Some(o)) |
| 107 | .map(|(id, _)| id) |
| 108 | .collect(); |
| 109 | for w in leaving { |
| 110 | let tag = self.windows[w].tag; |
| 111 | let target = self |
| 112 | .outputs |
| 113 | .keys() |
| 114 | .find(|&t| self.is_viewed(t, tag)) |
| 115 | .or(self.active); |
| 116 | let win = &mut self.windows[w]; |
| 117 | if let (Some(t), Some(rect)) = (target, win.float_rect) { |
| 118 | win.float_rect = Some(translate(rect, left_usable, self.outputs[t].usable())); |
| 119 | } |
| 120 | win.output = target; |
| 121 | if win.scratchpad.is_some() { |
| 122 | continue; |
| 123 | } |
| 124 | win.evicted_from.get_or_insert_with(|| out.name.clone()); |
| 125 | if target.is_some() { |
| 126 | self.place(w); |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | pub fn set_output_area(&mut self, o: OutputId, area: Rect) { |
| 132 | let Some(out) = self.outputs.get_mut(o) else { |
| 133 | return; |
| 134 | }; |
| 135 | let before = out.usable(); |
| 136 | out.area = area; |
| 137 | self.rescale_floats(o, before); |
| 138 | } |
| 139 | |
| 140 | /// Returns whether anything changed. |
| 141 | pub fn set_reserved(&mut self, o: OutputId, reserved: Insets) -> bool { |
| 142 | let Some(out) = self.outputs.get_mut(o) else { |
| 143 | return false; |
| 144 | }; |
| 145 | if out.reserved == reserved { |
| 146 | return false; |
| 147 | } |
| 148 | out.reserved = reserved; |
| 149 | let bounds = out.usable(); |
| 150 | for win in self.windows.values_mut().filter(|w| w.output == Some(o)) { |
| 151 | if let Some(rect) = win.float_rect { |
| 152 | win.float_rect = Some(crate::clamp_float(rect, bounds)); |
| 153 | } |
| 154 | } |
| 155 | true |
| 156 | } |
| 157 | |
| 158 | /// Carries floating windows along when the usable area of o changes. |
| 159 | fn rescale_floats(&mut self, o: OutputId, before: Rect) { |
| 160 | let after = self.outputs[o].usable(); |
| 161 | if after == before { |
| 162 | return; |
| 163 | } |
| 164 | for win in self.windows.values_mut().filter(|w| w.output == Some(o)) { |
| 165 | if let Some(rect) = win.float_rect { |
| 166 | win.float_rect = Some(translate(rect, before, after)); |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 | } |