Skip to main content

rmux_core/window/
zoom.rs

1use super::Window;
2use crate::PaneGeometry;
3
4impl Window {
5    /// Returns whether this window is currently zoomed.
6    #[must_use]
7    pub const fn is_zoomed(&self) -> bool {
8        self.zoomed
9    }
10
11    pub(crate) fn toggle_zoom(&mut self, pane_index: u32) -> bool {
12        if self.pane(pane_index).is_none() {
13            return self.zoomed;
14        }
15
16        if self.zoomed {
17            self.auto_unzoom();
18            return false;
19        }
20
21        if self.panes.len() <= 1 {
22            return false;
23        }
24
25        let selected = self.select_pane(pane_index);
26        debug_assert!(selected, "validated zoom target pane must be selectable");
27        self.zoomed = true;
28        self.apply_zoom_geometry();
29        true
30    }
31
32    pub(crate) fn auto_unzoom(&mut self) -> bool {
33        if !self.zoomed {
34            return false;
35        }
36
37        self.zoomed = false;
38        self.recalculate_geometry();
39        true
40    }
41
42    /// Saves the current zoom state and unzooms. If `restore` is true, the
43    /// following `pop_zoom` will re-zoom to the then-active pane.
44    ///
45    /// Matches tmux `window_push_zoom`.
46    pub(crate) fn push_zoom(&mut self, restore: bool) -> bool {
47        let was_zoomed = self.zoomed;
48        if was_zoomed {
49            self.auto_unzoom();
50        }
51        self.zoom_restore_pending = was_zoomed && restore;
52        was_zoomed
53    }
54
55    /// Restores zoom state that was saved by a previous `push_zoom` call.
56    ///
57    /// Matches tmux `window_pop_zoom`.
58    pub(crate) fn pop_zoom(&mut self) {
59        if self.zoom_restore_pending {
60            self.zoom_restore_pending = false;
61            if self.panes.len() > 1 {
62                self.zoomed = true;
63                self.apply_zoom_geometry();
64            }
65        }
66    }
67
68    pub(crate) fn apply_zoom_geometry(&mut self) {
69        let size = self.size;
70        let active_pane = self.active_pane;
71        if let Some(pane) = self.pane_mut(active_pane) {
72            pane.set_geometry(PaneGeometry::new(0, 0, size.cols, size.rows));
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use rmux_proto::{LayoutName, ResizePaneAdjustment, TerminalSize};
81
82    fn window_with_two_panes() -> Window {
83        let mut window = Window::new(TerminalSize { cols: 80, rows: 24 });
84        window.split_after_position(0);
85        window
86    }
87
88    #[test]
89    fn toggle_zoom_expands_the_target_pane_and_auto_unzoom_restores_layout() {
90        let mut window = window_with_two_panes();
91        let pane_zero = window.pane(0).expect("pane 0 exists").geometry();
92        let pane_one = window.pane(1).expect("pane 1 exists").geometry();
93
94        assert!(window.toggle_zoom(1));
95
96        assert!(window.is_zoomed());
97        assert_eq!(window.active_pane_index(), 1);
98        assert_eq!(
99            window.pane(1).expect("pane 1 exists").geometry(),
100            PaneGeometry::new(0, 0, 80, 24)
101        );
102
103        assert!(window.auto_unzoom());
104        assert!(!window.is_zoomed());
105        assert_eq!(window.pane(0).expect("pane 0 exists").geometry(), pane_zero);
106        assert_eq!(window.pane(1).expect("pane 1 exists").geometry(), pane_one);
107    }
108
109    #[test]
110    fn single_pane_zoom_toggle_is_a_noop() {
111        let mut window = Window::new(TerminalSize { cols: 80, rows: 24 });
112        let geometry = window.pane(0).expect("pane 0 exists").geometry();
113
114        assert!(!window.toggle_zoom(0));
115
116        assert!(!window.is_zoomed());
117        assert_eq!(window.pane(0).expect("pane 0 exists").geometry(), geometry);
118    }
119
120    #[test]
121    fn select_pane_unzooms_before_switching_active_panes() {
122        let mut window = window_with_two_panes();
123        assert!(window.toggle_zoom(1));
124
125        assert!(window.select_pane(0));
126
127        assert!(!window.is_zoomed());
128        assert_eq!(window.active_pane_index(), 0);
129        assert_ne!(
130            window.pane(1).expect("pane 1 exists").geometry(),
131            PaneGeometry::new(0, 0, 80, 24)
132        );
133    }
134
135    #[test]
136    fn absolute_resize_auto_unzooms_before_recalculating_layout() {
137        let mut window = window_with_two_panes();
138        assert!(window.toggle_zoom(1));
139
140        window.resize_main_pane(ResizePaneAdjustment::AbsoluteWidth { columns: 20 });
141
142        assert!(!window.is_zoomed());
143        assert_eq!(
144            window.pane(0).expect("pane 0 exists").geometry(),
145            PaneGeometry::new(0, 0, 20, 24)
146        );
147    }
148
149    #[test]
150    fn set_layout_auto_unzooms_before_recalculating_layout() {
151        let mut window = window_with_two_panes();
152        assert!(window.toggle_zoom(1));
153
154        window.set_layout(LayoutName::MainHorizontal);
155
156        assert!(!window.is_zoomed());
157        assert_eq!(
158            window.pane(0).expect("pane 0 exists").geometry(),
159            PaneGeometry::new(0, 0, 80, 22)
160        );
161        assert_eq!(
162            window.pane(1).expect("pane 1 exists").geometry(),
163            PaneGeometry::new(0, 23, 80, 1)
164        );
165    }
166
167    #[test]
168    fn set_even_layout_auto_unzooms_before_recalculating_layout() {
169        let mut window = window_with_two_panes();
170        assert!(window.toggle_zoom(1));
171
172        window.set_layout(LayoutName::EvenHorizontal);
173
174        assert!(!window.is_zoomed());
175        assert_eq!(
176            window.pane(0).expect("pane 0 exists").geometry(),
177            PaneGeometry::new(0, 0, 39, 24)
178        );
179        assert_eq!(
180            window.pane(1).expect("pane 1 exists").geometry(),
181            PaneGeometry::new(40, 0, 40, 24)
182        );
183    }
184
185    #[test]
186    fn push_zoom_without_restore_leaves_the_window_unzoomed_after_pop() {
187        let mut window = window_with_two_panes();
188        assert!(window.toggle_zoom(1));
189
190        assert!(window.push_zoom(false));
191        assert!(!window.is_zoomed());
192        assert!(!window.zoom_restore_pending);
193
194        window.select_pane(0);
195        window.pop_zoom();
196
197        assert!(!window.is_zoomed());
198        assert_eq!(window.active_pane_index(), 0);
199        assert_eq!(
200            window.pane(0).expect("pane 0 exists").geometry(),
201            PaneGeometry::new(0, 0, 40, 24)
202        );
203    }
204
205    #[test]
206    fn push_zoom_with_restore_rezooms_the_new_active_pane_on_pop() {
207        let mut window = window_with_two_panes();
208        assert!(window.toggle_zoom(1));
209
210        assert!(window.push_zoom(true));
211        assert!(!window.is_zoomed());
212        assert!(window.zoom_restore_pending);
213
214        assert!(window.select_pane(0));
215        window.pop_zoom();
216
217        assert!(window.is_zoomed());
218        assert!(!window.zoom_restore_pending);
219        assert_eq!(window.active_pane_index(), 0);
220        assert_eq!(
221            window.pane(0).expect("pane 0 exists").geometry(),
222            PaneGeometry::new(0, 0, 80, 24)
223        );
224        assert_eq!(
225            window.pane(1).expect("pane 1 exists").geometry(),
226            PaneGeometry::new(41, 0, 39, 24)
227        );
228    }
229}