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