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