Skip to main content

cursive_split_panel/
view.rs

1use super::{actions::*, after::*, split_panel::*, which::*};
2
3use {
4    cursive::{direction::*, event::*, theme::*, view::*, *},
5    std::cmp::*,
6};
7
8const TYPE_NAME: &str = "SplitPanel";
9
10const TOP_LEFT: &str = "┌";
11const BOTTOM_LEFT: &str = "└";
12const TOP_RIGHT: &str = "┐";
13const BOTTOM_RIGHT: &str = "┘";
14const VERTICAL: &str = "│";
15const VERTICAL_THICK: &str = "┃";
16const HORIZONTAL: &str = "─";
17const HORIZONTAL_THICK: &str = "━";
18const TOP_HINGE: &str = "┰";
19const BOTTOM_HINGE: &str = "┸";
20const LEFT_HINGE: &str = "┝";
21const RIGHT_HINGE: &str = "┥";
22
23impl View for SplitPanel {
24    fn type_name(&self) -> &'static str {
25        TYPE_NAME
26    }
27
28    fn focus_view(&mut self, selector: &Selector) -> Result<EventResult, ViewNotFound> {
29        // TODO: we are losing the focus_view callback
30        if self.front.view.focus_view(selector).is_ok() {
31            Ok(self.set_focus(Some(WhichPane::Front)))
32        } else if self.back.view.focus_view(selector).is_ok() {
33            Ok(self.set_focus(Some(WhichPane::Back)))
34        } else {
35            Err(ViewNotFound)
36        }
37    }
38
39    fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
40        match source.relative(self.orientation).unwrap_or(Relative::Front) {
41            Relative::Front => match self.front.view.take_focus(source) {
42                Ok(event_result) => Ok(event_result),
43                Err(_) => self.back.view.take_focus(source),
44            },
45
46            Relative::Back => match self.back.view.take_focus(source) {
47                Ok(event_result) => Ok(event_result),
48                Err(_) => self.front.view.take_focus(source),
49            },
50        }
51    }
52
53    fn needs_relayout(&self) -> bool {
54        self.needs_relayout || self.front.view.needs_relayout() || self.back.view.needs_relayout()
55    }
56
57    fn layout(&mut self, mut size: Vec2) {
58        self.size = Some(size);
59
60        // Extent (not including border)
61        if self.border {
62            size = size - (2, 2);
63        }
64        let extent = *size.get(self.orientation);
65
66        // Divider
67        let divider = self.layout_divider(extent);
68
69        // Front
70        self.front.start = 0;
71        self.front.extent = divider;
72
73        // Back
74        self.back.start = divider + 1;
75        if self.back.start >= extent {
76            // No room for back, so we will effectively disable it
77            self.back.start = 0;
78            self.back.extent = 0;
79        } else {
80            self.back.extent = extent - self.back.start;
81        }
82
83        self.front.layout(size, self.orientation);
84        self.back.layout(size, self.orientation);
85
86        self.needs_relayout = false;
87    }
88
89    fn required_size(&mut self, constraint: Vec2) -> Vec2 {
90        let front = self.front.view.required_size(constraint);
91        let back = self.back.view.required_size(constraint);
92
93        let size = Vec2::from(match self.orientation {
94            Orientation::Horizontal => (front.x + back.x, max(front.y, back.y)),
95            Orientation::Vertical => (max(front.x, back.x), front.y + back.y),
96        });
97
98        if self.border {
99            // Add border and divider
100            size + match self.orientation {
101                Orientation::Horizontal => (3, 2),
102                Orientation::Vertical => (2, 3),
103            }
104        } else {
105            // Add divider
106            size + match self.orientation {
107                Orientation::Horizontal => (1, 0),
108                Orientation::Vertical => (0, 1),
109            }
110        }
111    }
112
113    fn important_area(&self, size: Vec2) -> Rect {
114        match self.focus {
115            Some(WhichPane::Front) => self.front.important_area(size, self.orientation, self.border),
116            Some(WhichPane::Back) => self.back.important_area(size, self.orientation, self.border),
117            None => Rect::from_size((0, 0), size),
118        }
119    }
120
121    fn on_event(&mut self, event: Event) -> EventResult {
122        match if self.movable_divider { self.actions.get(&event) } else { None } {
123            Some(action) => match action {
124                Action::MoveDividerToFront => self.move_divider(-1),
125                Action::MoveDividerToBack => self.move_divider(1),
126            },
127
128            None => match event {
129                Event::Mouse { offset, position, event } => {
130                    if self.moving_divider {
131                        // Moving divider
132
133                        if matches!(event, MouseEvent::Release(_)) {
134                            // End moving divider
135                            self.moving_divider = false;
136                        } else if let Some(position) = position.checked_sub(offset) {
137                            let mut divider = *position.get(self.orientation);
138
139                            if self.border && divider != 0 {
140                                divider -= 1;
141                            }
142
143                            self.set_divider(divider);
144                        }
145
146                        EventResult::consumed()
147                    } else if event.button() == Some(MouseButton::Left)
148                        && self.is_start_moving_divider(offset, position)
149                    {
150                        // Start moving divider
151
152                        self.moving_divider = true;
153                        EventResult::consumed()
154                    } else if let Some((offset, position)) =
155                        self.front.mouse_event(offset, position, self.orientation, self.border, self.size)
156                    {
157                        // In front pane
158
159                        let prior = if event.grabs_focus() {
160                            self.set_focus(Some(WhichPane::Front))
161                        } else {
162                            EventResult::Ignored
163                        };
164
165                        self.front.view.on_event(Event::Mouse { offset, position, event }).after(prior)
166                    } else if let Some((offset, position)) =
167                        self.back.mouse_event(offset, position, self.orientation, self.border, self.size)
168                    {
169                        // In back pane
170
171                        let prior = if event.grabs_focus() {
172                            self.set_focus(Some(WhichPane::Back))
173                        } else {
174                            EventResult::Ignored
175                        };
176
177                        self.back.view.on_event(Event::Mouse { offset, position, event }).after(prior)
178                    } else {
179                        EventResult::Ignored
180                    }
181                }
182
183                event => match self.focus {
184                    Some(WhichPane::Front) => self.front.view.on_event(event),
185                    Some(WhichPane::Back) => self.back.view.on_event(event),
186                    None => {
187                        // We'll default to focusing on the front pane
188                        let prior = self.set_focus(Some(WhichPane::Front));
189                        self.front.view.on_event(event).after(prior)
190                    }
191                },
192            },
193        }
194    }
195
196    fn call_on_any(&mut self, selector: &Selector, callback: AnyCb) {
197        self.front.view.call_on_any(selector, callback);
198        self.back.view.call_on_any(selector, callback);
199    }
200
201    fn draw(&self, printer: &Printer) {
202        let mut _shrinked = None;
203        let printer = if self.draw_lines(printer) {
204            _shrinked = Some(printer.shrinked_centered((2, 2)));
205            &_shrinked.expect("shrinked")
206        } else {
207            printer
208        };
209
210        self.front.draw(printer, self.orientation, self.is_focused(WhichPane::Front));
211        self.back.draw(printer, self.orientation, self.is_focused(WhichPane::Back));
212    }
213}
214
215impl SplitPanel {
216    // Whether a pane is focused.
217    fn is_focused(&self, which: WhichPane) -> bool {
218        self.focus.map(|focus| focus == which).unwrap_or_default()
219    }
220
221    // Set the focused pane (and un-focus the old one).
222    fn set_focus(&mut self, focus: Option<WhichPane>) -> EventResult {
223        let old_focus = self.focus;
224        self.focus = focus;
225        if self.focus != old_focus
226            && let Some(old_focus) = old_focus
227        {
228            return match old_focus {
229                WhichPane::Front => self.front.view.on_event(Event::FocusLost),
230                WhichPane::Back => self.back.view.on_event(Event::FocusLost),
231            };
232        }
233
234        EventResult::consumed()
235    }
236
237    // Whether we've pressed a mouse button on the divider.
238    fn is_start_moving_divider(&self, offset: Vec2, position: Vec2) -> bool {
239        if !self.movable_divider || self.moving_divider {
240            return false;
241        }
242
243        if let Some(mut divider) = self.divider
244            && let Some(size) = self.size
245            && size.x != 0
246            && size.y != 0
247            && let Some(position) = position.checked_sub(offset)
248        {
249            if self.border {
250                divider += 1;
251                let corner = size - (1, 1);
252                match self.orientation {
253                    Orientation::Horizontal => position.y > 0 && position.y < corner.y && position.x == divider,
254                    Orientation::Vertical => position.x > 0 && position.x < corner.x && position.y == divider,
255                }
256            } else {
257                match self.orientation {
258                    Orientation::Horizontal => position.x == divider,
259                    Orientation::Vertical => position.y == divider,
260                }
261            }
262        } else {
263            false
264        }
265    }
266
267    // Move the divider (if allowed).
268    fn move_divider(&mut self, delta: isize) -> EventResult {
269        if !self.movable_divider {
270            return EventResult::Ignored;
271        }
272
273        match self.divider {
274            Some(divider) => {
275                if delta > 0 {
276                    self.set_divider(divider + delta as usize);
277                } else if let Some(divider) = divider.checked_add_signed(delta) {
278                    self.set_divider(divider);
279                }
280            }
281
282            None => self.set_divider(0),
283        }
284
285        EventResult::consumed()
286    }
287
288    // Layout the divider.
289    fn layout_divider(&mut self, extent: usize) -> usize {
290        match self.divider {
291            Some(divider) => {
292                if divider >= extent {
293                    if extent == 0 {
294                        // There won't immediately be room for front and back
295                        self.set_divider(0);
296                    } else {
297                        // There won't immediately be room for back
298                        self.set_divider(extent - 1);
299                    }
300                }
301            }
302
303            // Default to middle
304            None => self.set_divider(extent / 2),
305        }
306
307        self.divider.expect("divider")
308    }
309
310    // Draw border and divider.
311    //
312    // Returns true if a border was drawn.
313    fn draw_lines(&self, printer: &Printer) -> bool {
314        let Some(mut divider) = self.divider else {
315            // This would only happen if we try to draw before layout
316            return false;
317        };
318
319        if printer.size.x == 0 || printer.size.y == 0 {
320            return false;
321        }
322
323        let corner = printer.size - (1, 1);
324
325        if self.border {
326            divider += 1;
327
328            // Border corners
329            printer.print((0, 0), TOP_LEFT);
330            printer.print((corner.x, 0), TOP_RIGHT);
331            printer.print((0, corner.y), BOTTOM_LEFT);
332            printer.print(corner, BOTTOM_RIGHT);
333
334            let mut _divider_printer = None;
335            let divider_printer = if self.moving_divider {
336                let mut divider_printer = printer.clone();
337                divider_printer.set_effect(Effect::Reverse);
338                _divider_printer = Some(divider_printer);
339                &_divider_printer.expect("divider_printer")
340            } else {
341                printer
342            };
343
344            match self.orientation {
345                Orientation::Horizontal => {
346                    // Hinges
347                    printer.print((divider, 0), TOP_HINGE);
348                    printer.print((divider, corner.y), BOTTOM_HINGE);
349
350                    if let Some(length) = divider.checked_sub(1) {
351                        // Top border
352                        printer.print_hline((1, 0), length, HORIZONTAL);
353                        // Bottom border
354                        printer.print_hline((1, corner.y), length, HORIZONTAL);
355                    }
356
357                    if let Some(length) = corner.x.checked_sub(divider + 1) {
358                        // Top border
359                        printer.print_hline((divider + 1, 0), length, HORIZONTAL);
360                        // Bottom border
361                        printer.print_hline((divider + 1, corner.y), length, HORIZONTAL);
362                    }
363
364                    if let Some(length) = corner.y.checked_sub(1) {
365                        // Left border
366                        printer.print_vline((0, 1), length, VERTICAL);
367                        // Divider
368                        divider_printer.print_vline((divider, 1), length, VERTICAL_THICK);
369                        // Right border
370                        printer.print_vline((corner.x, 1), length, VERTICAL);
371                    }
372                }
373
374                Orientation::Vertical => {
375                    // Hinges
376                    printer.print((0, divider), LEFT_HINGE);
377                    printer.print((corner.x, divider), RIGHT_HINGE);
378
379                    if let Some(length) = divider.checked_sub(1) {
380                        // Left border
381                        printer.print_vline((0, 1), length, VERTICAL);
382                        // Right border
383                        printer.print_vline((corner.x, 1), length, VERTICAL);
384                    }
385
386                    if let Some(length) = corner.y.checked_sub(divider + 1) {
387                        // Left border
388                        printer.print_vline((0, divider + 1), length, VERTICAL);
389                        // Right border
390                        printer.print_vline((corner.x, divider + 1), length, VERTICAL);
391                    }
392
393                    if let Some(length) = corner.x.checked_sub(1) {
394                        // Top border
395                        printer.print_hline((1, 0), length, HORIZONTAL);
396                        // Divider
397                        divider_printer.print_hline((1, divider), length, HORIZONTAL_THICK);
398                        // Bottom border
399                        printer.print_hline((1, corner.y), length, HORIZONTAL);
400                    }
401                }
402            }
403
404            true
405        } else {
406            match self.orientation {
407                Orientation::Horizontal => printer.print_vline((divider, 0), corner.y, VERTICAL),
408                Orientation::Vertical => printer.print_vline((divider, 0), corner.y, VERTICAL),
409            }
410
411            false
412        }
413    }
414}