cursive_tabs/
panel.rs

1use crossbeam::channel::{unbounded, Sender};
2use cursive::direction::{Absolute, Direction};
3use cursive::event::{AnyCb, Event, EventResult, Key};
4use cursive::view::{CannotFocus, Selector, View, ViewNotFound};
5use cursive::views::NamedView;
6use cursive::{Printer, Vec2};
7use log::debug;
8use num::clamp;
9
10use crate::error;
11use crate::Bar;
12use crate::TabBar;
13use crate::TabView;
14
15#[derive(Clone, Copy, Debug)]
16pub enum Align {
17    Start,
18    Center,
19    End,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Placement {
24    VerticalLeft,
25    VerticalRight,
26    HorizontalTop,
27    HorizontalBottom,
28}
29
30impl Align {
31    pub fn get_offset(self, content: usize, container: usize) -> usize {
32        if container < content {
33            0
34        } else {
35            match self {
36                Align::Start => 0,
37                Align::Center => (container - content) / 2,
38                Align::End => container - content,
39            }
40        }
41    }
42}
43
44/// The `TabPanel` is an ease of use wrapper around a `TabView` and its `TabBar`.
45/// Additionally the TabBar in the Panel can be horizontally aligned, by default it is set to be left aligned.
46///
47/// # Example
48/// ```
49/// use cursive_tabs::{Align, TabPanel};
50/// use cursive::views::TextView;
51/// use cursive::view::Nameable;
52///
53/// let mut tabs = TabPanel::new()
54///       .with_tab(TextView::new("First").with_name("First"))
55///       .with_tab(TextView::new("Second").with_name("Second"))
56///       .with_bar_alignment(Align::Center);
57/// ```
58///
59/// A TabView is also usable separately, so if you prefer the tabs without the TabBar and Panel around have a look at `TabView`.
60pub struct TabPanel {
61    bar: TabBar,
62    bar_size: Vec2,
63    tab_size: Vec2,
64    tx: Sender<String>,
65    tabs: TabView,
66    bar_focused: bool,
67    bar_align: Align,
68    bar_placement: Placement,
69}
70
71impl Default for TabPanel {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl TabPanel {
78    /// Returns a new instance of a TabPanel.
79    /// Alignment is set by default to left, to change this use `set_bar_alignment` to change to any other `HAlign` provided by `cursive`.
80    pub fn new() -> Self {
81        let mut tabs = TabView::new();
82        let (tx, rx) = unbounded();
83        let (active_tx, active_rx) = unbounded();
84        tabs.set_bar_rx(rx);
85        tabs.set_active_key_tx(active_tx);
86        Self {
87            bar: TabBar::new(active_rx)
88                .with_placement(Placement::HorizontalTop)
89                .with_alignment(Align::Start),
90            bar_size: Vec2::new(1, 1),
91            tab_size: Vec2::new(1, 1),
92            tabs,
93            tx,
94            bar_focused: true,
95            bar_align: Align::Start,
96            bar_placement: Placement::HorizontalTop,
97        }
98    }
99
100    /// Returns the current active tab of the `TabView`.
101    /// Note: Calls `active_tab` on the enclosed `TabView`.
102    pub fn active_tab(&self) -> Option<&str> {
103        self.tabs.active_tab()
104    }
105
106    /// Returns a reference to the underlying view.
107    pub fn active_view(&self) -> Option<&dyn View> {
108        self.tabs.active_view()
109    }
110
111    /// Returns a mutable reference to the underlying view.
112    pub fn active_view_mut(&mut self) -> Option<&mut dyn View> {
113        self.tabs.active_view_mut()
114    }
115
116    pub fn views(&self) -> Vec<&dyn View> {
117        self.tabs.views()
118    }
119
120    pub fn views_mut(&mut self) -> Vec<&mut dyn View> {
121        self.tabs.views_mut()
122    }
123
124    /// Non-consuming variant to set the active tab in the `TabView`.
125    /// Note: Calls `set_active_tab` on the enclosed `TabView`.
126    pub fn set_active_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
127        self.tabs.set_active_tab(id)
128    }
129
130    /// Consuming & Chainable variant to set the active tab in the `TabView`.
131    ///  Note: Calls `set_active_tab` on the enclosed `TabView`.
132    ///
133    pub fn with_active_tab(mut self, id: &str) -> Result<Self, Self> {
134        match self.tabs.set_active_tab(id) {
135            Ok(_) => Ok(self),
136            Err(_) => Err(self),
137        }
138    }
139
140    /// Non-consuming variant to add new tabs to the `TabView`.
141    /// Note: Calls `add_tab` on the enclosed `TabView`.
142    pub fn add_tab<T: View>(&mut self, view: NamedView<T>) {
143        let id = view.name();
144        self.bar.add_button(self.tx.clone(), id);
145        self.tabs.add_tab(view);
146    }
147
148    /// Consuming & Chainable variant to add a new tab.
149    /// Note: Calls `add_tab` on the enclosed `TabView`.
150    pub fn with_tab<T: View>(mut self, view: NamedView<T>) -> Self {
151        let id = view.name();
152        self.bar.add_button(self.tx.clone(), id);
153        self.tabs.add_tab(view);
154        self
155    }
156
157    /// Swaps the given tab keys.
158    /// If at least one of them cannot be found then no operation is performed
159    pub fn swap_tabs(&mut self, fst: &str, snd: &str) {
160        self.tabs.swap_tabs(fst, snd);
161        self.bar.swap_button(fst, snd);
162    }
163
164    /// Non-consuming variant to add new tabs to the `TabView` at a certain position.
165    /// It is fail-safe, if the postion is greater than the amount of tabs, it is appended to the end.
166    /// Note: Calls `add_tab_at` on the enclosed `TabView`.
167    pub fn add_tab_at<T: View>(&mut self, view: NamedView<T>, pos: usize) {
168        let id = view.name();
169        self.bar.add_button_at(self.tx.clone(), id, pos);
170        self.tabs.add_tab_at(view, pos);
171    }
172
173    /// Consuming & Chainable variant to add a new tab at a certain position.
174    /// It is fail-safe, if the postion is greater than the amount of tabs, it is appended to the end.
175    /// Note: Calls `add_tab_at` on the enclosed `TabView`.
176    pub fn with_tab_at<T: View>(mut self, view: NamedView<T>, pos: usize) -> Self {
177        let id = view.name();
178        self.bar.add_button_at(self.tx.clone(), id, pos);
179        self.tabs.add_tab_at(view, pos);
180        self
181    }
182
183    /// Remove a tab of the enclosed `TabView`.
184    pub fn remove_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
185        self.bar.remove_button(id);
186        self.tabs.remove_tab(id)
187    }
188
189    /// Proceeds to the next view in order of addition.
190    pub fn next(&mut self) {
191        self.tabs.next()
192    }
193
194    /// Go back to the previous view in order of addition.
195    pub fn prev(&mut self) {
196        self.tabs.prev()
197    }
198
199    /// Consumable & Chainable variant to set the bar alignment.
200    pub fn with_bar_alignment(mut self, align: Align) -> Self {
201        self.set_bar_alignment(align);
202
203        self
204    }
205
206    /// Non-consuming variant to set the bar alignment.
207    pub fn set_bar_alignment(&mut self, align: Align) {
208        self.bar_align = align;
209        self.bar.set_alignment(align);
210    }
211
212    pub fn with_bar_placement(mut self, placement: Placement) -> Self {
213        self.set_bar_placement(placement);
214        self
215    }
216
217    pub fn set_bar_placement(&mut self, placement: Placement) {
218        self.bar_placement = placement;
219        self.bar.set_placement(placement);
220    }
221
222    /// Returns the current order of tabs as an Vector with the keys of the views.
223    pub fn tab_order(&self) -> Vec<String> {
224        self.tabs.tab_order()
225    }
226
227    // Print lines corresponding to the current placement
228    fn draw_outer_panel(&self, printer: &Printer) {
229        match self.bar_placement {
230            Placement::HorizontalTop => {
231                // Side bars
232                printer.print_vline((0, 0), printer.size.y, "│");
233                printer.print_vline((printer.size.x - 1, 0), printer.size.y, "│");
234                // Bottom line
235                printer.print_hline((0, printer.size.y - 1), printer.size.x, "─");
236
237                printer.print((0, self.bar_size.y - 1), "┌");
238                printer.print((printer.size.x - 1, self.bar_size.y - 1), "┐");
239                printer.print((0, printer.size.y - 1), "└");
240                printer.print((printer.size.x - 1, printer.size.y - 1), "┘");
241            }
242            Placement::HorizontalBottom => {
243                // Side bars
244                printer.print_vline((0, 0), printer.size.y, "│");
245                printer.print_vline((printer.size.x - 1, 0), printer.size.y, "│");
246                // Top line
247                let lowest = clamp(printer.size.y - self.bar_size.y, 0, printer.size.y - 1);
248                printer.print_hline((0, 0), printer.size.x, "─");
249                printer.print((0, 0), "┌");
250                printer.print((printer.size.x - 1, 0), "┐");
251                printer.print((0, lowest), "└");
252                printer.print((printer.size.x - 1, lowest), "┘");
253            }
254            Placement::VerticalLeft => {
255                // Side bar
256                printer.print_vline((printer.size.x - 1, 0), printer.size.y, "│");
257                // Top lines
258                printer.print_hline((self.bar_size.x - 1, 0), printer.size.x, "─");
259                printer.print_hline(
260                    (self.bar_size.x - 1, printer.size.y - 1),
261                    printer.size.x,
262                    "─",
263                );
264                printer.print((self.bar_size.x - 1, 0), "┌");
265                printer.print((printer.size.x - 1, 0), "┐");
266                printer.print((self.bar_size.x - 1, printer.size.y - 1), "└");
267                printer.print((printer.size.x - 1, printer.size.y - 1), "┘");
268            }
269            Placement::VerticalRight => {
270                // Side bar
271                printer.print_vline((0, 0), printer.size.y, "│");
272                // Top lines
273                printer.print_hline((0, 0), printer.size.x, "─");
274                // Line draws too far here, needs to be overwritten with blanks
275                printer.print_hline((0, printer.size.y - 1), printer.size.x, "─");
276
277                let right = clamp(printer.size.x - self.bar_size.x, 0, printer.size.x - 1);
278                printer.print((0, 0), "┌");
279                printer.print((right, 0), "┐");
280                printer.print_hline((right + 1, 0), printer.size.x, " ");
281                printer.print((0, printer.size.y - 1), "└");
282                printer.print((right, printer.size.y - 1), "┘");
283                printer.print_hline((right + 1, printer.size.y - 1), printer.size.x, " ");
284            }
285        }
286    }
287
288    fn on_event_focused(&mut self, evt: Event) -> EventResult {
289        match self.bar.on_event(evt.relativized(match self.bar_placement {
290            Placement::HorizontalTop | Placement::VerticalLeft => Vec2::new(0, 0),
291            Placement::HorizontalBottom => self.tab_size.keep_y() + Vec2::new(0, 1),
292            Placement::VerticalRight => self.tab_size.keep_x() + Vec2::new(1, 0),
293        })) {
294            EventResult::Consumed(cb) => EventResult::Consumed(cb),
295            EventResult::Ignored => match evt {
296                Event::Key(Key::Down) if self.bar_placement == Placement::HorizontalTop => {
297                    if let Ok(result) = self.tabs.take_focus(Direction::up()) {
298                        self.bar_focused = false;
299                        result.and(EventResult::consumed())
300                    } else {
301                        EventResult::Ignored
302                    }
303                }
304                Event::Key(Key::Up) if self.bar_placement == Placement::HorizontalBottom => {
305                    if let Ok(result) = self.tabs.take_focus(Direction::down()) {
306                        self.bar_focused = false;
307                        result.and(EventResult::consumed())
308                    } else {
309                        EventResult::Ignored
310                    }
311                }
312                Event::Key(Key::Left) if self.bar_placement == Placement::VerticalRight => {
313                    if let Ok(result) = self.tabs.take_focus(Direction::right()) {
314                        self.bar_focused = false;
315                        result.and(EventResult::consumed())
316                    } else {
317                        EventResult::Ignored
318                    }
319                }
320                Event::Key(Key::Right) if self.bar_placement == Placement::VerticalLeft => {
321                    if let Ok(result) = self.tabs.take_focus(Direction::left()) {
322                        self.bar_focused = false;
323                        result.and(EventResult::consumed())
324                    } else {
325                        EventResult::Ignored
326                    }
327                }
328                _ => EventResult::Ignored,
329            },
330        }
331    }
332
333    fn on_event_unfocused(&mut self, evt: Event) -> EventResult {
334        match self
335            .tabs
336            .on_event(evt.relativized(match self.bar_placement {
337                Placement::HorizontalTop => Vec2::new(1, self.bar_size.y),
338                Placement::VerticalLeft => Vec2::new(self.bar_size.x, 1),
339                Placement::HorizontalBottom | Placement::VerticalRight => Vec2::new(1, 1),
340            })) {
341            EventResult::Consumed(cb) => EventResult::Consumed(cb),
342            EventResult::Ignored => match evt {
343                Event::Key(Key::Up) if self.bar_placement == Placement::HorizontalTop => {
344                    self.bar_focused = true;
345                    EventResult::Consumed(None)
346                }
347                Event::Key(Key::Down) if self.bar_placement == Placement::HorizontalBottom => {
348                    self.bar_focused = true;
349                    EventResult::Consumed(None)
350                }
351                Event::Key(Key::Left) if self.bar_placement == Placement::VerticalLeft => {
352                    self.bar_focused = true;
353                    EventResult::Consumed(None)
354                }
355                Event::Key(Key::Right) if self.bar_placement == Placement::VerticalRight => {
356                    self.bar_focused = true;
357                    EventResult::Consumed(None)
358                }
359                _ => EventResult::Ignored,
360            },
361        }
362    }
363
364    fn check_focus_grab(&mut self, event: &Event) -> EventResult {
365        if let Event::Mouse {
366            offset,
367            position,
368            event,
369        } = *event
370        {
371            debug!(
372                "mouse event: offset: {:?} , position: {:?}",
373                offset, position
374            );
375            if !event.grabs_focus() {
376                return EventResult::Ignored;
377            }
378
379            match self.bar_placement {
380                Placement::VerticalRight | Placement::HorizontalBottom => {
381                    if position > offset && self.tab_size.fits(position - offset) {
382                        if let Ok(res) = self.tabs.take_focus(Direction::none()) {
383                            self.bar_focused = false;
384                            return res;
385                        }
386                    } else {
387                        self.bar_focused = true;
388                    }
389                }
390                Placement::HorizontalTop | Placement::VerticalLeft => {
391                    // Here we want conceptually position >= offset, which is what Vec2::fits does.
392                    // (The actual >= means strictly > or strictly equal, which is not _quite_ what we want in 2D.)
393                    if position.fits(offset)
394                        && (self.bar_size - Vec2::new(1, 1)).fits(position - offset)
395                    {
396                        self.bar_focused = true;
397                    } else if let Ok(res) = self.tabs.take_focus(Direction::none()) {
398                        self.bar_focused = false;
399                        return res;
400                    }
401                }
402            }
403        }
404        EventResult::Ignored
405    }
406}
407
408impl View for TabPanel {
409    fn draw(&self, printer: &Printer) {
410        self.draw_outer_panel(printer);
411        let printer_bar = printer
412            .offset(match self.bar_placement {
413                Placement::HorizontalTop => (1, 0),
414                Placement::HorizontalBottom => (
415                    1,
416                    clamp(printer.size.y - self.bar_size.y, 0, printer.size.y - 1),
417                ),
418                Placement::VerticalLeft => (0, 1),
419                Placement::VerticalRight => (
420                    clamp(printer.size.x - self.bar_size.x, 0, printer.size.x - 1),
421                    1,
422                ),
423            })
424            .cropped(match self.bar_placement {
425                Placement::HorizontalTop | Placement::HorizontalBottom => {
426                    (printer.size.x - 2, self.bar_size.y)
427                }
428                Placement::VerticalRight | Placement::VerticalLeft => {
429                    (self.bar_size.x, printer.size.y - 2)
430                }
431            })
432            .focused(self.bar_focused);
433        let printer_tab = printer
434            .offset(match self.bar_placement {
435                Placement::VerticalLeft => (self.bar_size.x, 1),
436                Placement::VerticalRight => (1, 1),
437                Placement::HorizontalBottom => (1, 1),
438                Placement::HorizontalTop => (1, self.bar_size.y),
439            })
440            // Inner area
441            .cropped(match self.bar_placement {
442                Placement::VerticalLeft | Placement::VerticalRight => {
443                    (printer.size.x - self.bar_size.x - 1, printer.size.y - 2)
444                }
445                Placement::HorizontalBottom | Placement::HorizontalTop => {
446                    (printer.size.x - 2, printer.size.y - self.bar_size.y - 1)
447                }
448            })
449            .focused(!self.bar_focused);
450        self.bar.draw(&printer_bar);
451        self.tabs.draw(&printer_tab);
452    }
453
454    fn layout(&mut self, vec: Vec2) {
455        self.bar.layout(match self.bar_placement {
456            Placement::VerticalRight | Placement::VerticalLeft => {
457                Vec2::new(self.bar_size.x, vec.y - 2)
458            }
459            Placement::HorizontalBottom | Placement::HorizontalTop => {
460                Vec2::new(vec.x - 2, self.bar_size.y)
461            }
462        });
463        self.tabs.layout(match self.bar_placement {
464            Placement::VerticalRight | Placement::VerticalLeft => {
465                self.tab_size = Vec2::new(vec.x - self.bar_size.x - 1, vec.y - 2);
466                self.tab_size
467            }
468            Placement::HorizontalBottom | Placement::HorizontalTop => {
469                self.tab_size = Vec2::new(vec.x - 2, vec.y - self.bar_size.y - 1);
470                self.tab_size
471            }
472        });
473    }
474
475    fn needs_relayout(&self) -> bool {
476        self.bar.needs_relayout() || self.tabs.needs_relayout()
477    }
478
479    fn required_size(&mut self, cst: Vec2) -> Vec2 {
480        let tab_size = self.tabs.required_size(cst);
481        self.bar_size = self.bar.required_size(cst);
482        match self.bar_placement {
483            Placement::HorizontalTop | Placement::HorizontalBottom => self
484                .bar_size
485                .stack_vertical(&tab_size)
486                .stack_vertical(&Vec2::new(tab_size.x + 2, 1)),
487            Placement::VerticalLeft | Placement::VerticalRight => self
488                .bar_size
489                .stack_horizontal(&tab_size)
490                .stack_vertical(&Vec2::new(1, tab_size.y + 2)),
491        }
492    }
493
494    fn on_event(&mut self, evt: Event) -> EventResult {
495        let result = self.check_focus_grab(&evt);
496
497        result.and(if self.bar_focused {
498            self.on_event_focused(evt)
499        } else {
500            self.on_event_unfocused(evt)
501        })
502    }
503
504    fn take_focus(&mut self, d: Direction) -> Result<EventResult, CannotFocus> {
505        let tabs_take_focus = |panel: &mut TabPanel, d: Direction| {
506            let result = panel.tabs.take_focus(d);
507
508            if result.is_ok() {
509                panel.bar_focused = false;
510            } else {
511                panel.bar_focused = true;
512            }
513
514            result
515        };
516
517        let mut result = Ok(EventResult::consumed());
518
519        match self.bar_placement {
520            Placement::HorizontalBottom => match d {
521                Direction::Abs(Absolute::Up) => {
522                    result = tabs_take_focus(self, d);
523                }
524                Direction::Abs(Absolute::Left) | Direction::Abs(Absolute::Right) => {
525                    if !self.bar_focused {
526                        result = tabs_take_focus(self, d);
527                    }
528                }
529                Direction::Abs(Absolute::Down) => {
530                    self.bar_focused = true;
531                }
532                _ => (),
533            },
534            Placement::HorizontalTop => match d {
535                Direction::Abs(Absolute::Down) => {
536                    result = tabs_take_focus(self, d);
537                }
538                Direction::Abs(Absolute::Left) | Direction::Abs(Absolute::Right) => {
539                    if !self.bar_focused {
540                        result = tabs_take_focus(self, d);
541                    }
542                }
543                Direction::Abs(Absolute::Up) => {
544                    self.bar_focused = true;
545                }
546                _ => (),
547            },
548            Placement::VerticalLeft => match d {
549                Direction::Abs(Absolute::Right) => {
550                    result = tabs_take_focus(self, d);
551                }
552                Direction::Abs(Absolute::Up) | Direction::Abs(Absolute::Down) => {
553                    if !self.bar_focused {
554                        result = tabs_take_focus(self, d);
555                    }
556                }
557                Direction::Abs(Absolute::Left) => self.bar_focused = true,
558                _ => {}
559            },
560            Placement::VerticalRight => match d {
561                Direction::Abs(Absolute::Left) => {
562                    result = tabs_take_focus(self, d);
563                }
564                Direction::Abs(Absolute::Up) | Direction::Abs(Absolute::Down) => {
565                    if !self.bar_focused {
566                        result = tabs_take_focus(self, d)
567                    }
568                }
569                Direction::Abs(Absolute::Right) => self.bar_focused = true,
570                _ => {}
571            },
572        }
573
574        return Ok(result.unwrap_or(EventResult::Ignored));
575    }
576
577    fn focus_view(&mut self, slt: &Selector) -> Result<EventResult, ViewNotFound> {
578        self.tabs.focus_view(slt)
579    }
580
581    fn call_on_any<'a>(&mut self, slt: &Selector, cb: AnyCb<'a>) {
582        self.bar.call_on_any(slt, cb);
583        self.tabs.call_on_any(slt, cb);
584    }
585}