cursive_extras/views/tabs/
layer.rs

1use crossbeam_channel::{bounded, Receiver, Sender};
2use cursive_core::{
3    Cursive, Printer, Vec2, View,
4    align::HAlign,
5    traits::{Scrollable, With, Resizable},
6    direction::{Absolute, Direction},
7    event::{
8        AnyCb, Event,
9        EventResult, Key,
10        MouseButton, MouseEvent
11    },
12    view::{
13        CannotFocus,
14        Position,
15        Selector,
16        ViewNotFound
17    },
18    views::{
19        BoxedView,
20        LayerPosition,
21        SelectView,
22        Dialog,
23        OnEventView
24    }
25};
26use rust_utils::chainable;
27use crate::c_focus;
28use super::{
29    TitleButtonType,
30    TabContainer,
31    TabFocus,
32    TabIter,
33    TabIterMut,
34    ViewButton,
35    ButtonAction,
36    TitleButton
37};
38
39/// A view similar to `TabDialog` but it fills up all the available space
40///
41/// Best used with `Cursive::add_fullscreen_layer()`
42pub struct TabLayer {
43    // the underlying tab view
44    tabs: TabContainer,
45
46    // buttons on the title bar
47    shown_title_buttons: Vec<TitleButton>,
48
49    // all the title bar buttons
50    title_buttons: Vec<TitleButton>,
51
52    // alignment of the title bar
53    title_align: HAlign,
54
55    // dialog buttons
56    buttons: Vec<ViewButton>,
57
58    // button alignment
59    button_align: HAlign,
60
61    // what is being focused?
62    focus: TabFocus,
63
64    // channel to communicate with tab overflow menu
65    id_snd: Sender<usize>,
66    id_rcv: Receiver<usize>
67}
68
69impl TabLayer {
70    /// Create a new `TabLayer`
71    #[must_use]
72    pub fn new() -> TabLayer {
73        let (id_snd, id_rcv) = bounded(1);
74        TabLayer {
75            tabs: TabContainer::new(),
76            title_buttons: vec![],
77            shown_title_buttons: vec![],
78            title_align: HAlign::Center,
79            buttons: vec![],
80            button_align: HAlign::Left,
81            focus: TabFocus::Content,
82            id_snd,
83            id_rcv
84        }
85    }
86
87    /// Add a new button to the view
88    #[chainable]
89    pub fn add_button<F: Fn(&mut Cursive) + Send + Sync + 'static>(&mut self, text: &str, func: F) {
90        let new_button = ViewButton::from_fn(text, func);
91        if self.has_next_prev_buttons() {
92            let last_idx = self.buttons.len() - 2;
93            self.buttons.insert(last_idx, new_button);
94        }
95        else { self.buttons.push(new_button) }
96    }
97
98    /// Add a button to close the view
99    #[chainable]
100    pub fn add_dismiss_button(&mut self, text: &str) {
101        let new_button = ViewButton::from_fn(text, |r| {
102            if r.screen().len() <= 1 { r.quit(); }
103            else { r.pop_layer(); }
104        });
105        if self.has_next_prev_buttons() {
106            let last_idx = self.buttons.len() - 2;
107            self.buttons.insert(last_idx, new_button);
108        }
109        else { self.buttons.push(new_button) }
110    }
111
112    /// Add buttons to cycle through the tabs
113    #[chainable]
114    pub fn add_next_prev_buttons(&mut self, show: bool) {
115        if show {
116            self.buttons.insert(0, ViewButton::new("Previous", ButtonAction::Prev));
117            self.buttons.push(ViewButton::new("Next", ButtonAction::Next));
118        }
119        else if self.has_next_prev_buttons() {
120            self.buttons.remove(0);
121            self.buttons.pop();
122        }
123    }
124
125    /// Add a button to remove the current tab
126    #[chainable]
127    pub fn add_close_button(&mut self, show: bool) {
128        if show {
129            let new_button = ViewButton::new("Close", ButtonAction::Close);
130            if self.has_next_prev_buttons() {
131                let last_idx = self.buttons.len() - 2;
132                self.buttons.insert(last_idx, new_button);
133            }
134            else { self.buttons.push(new_button) }
135        }
136        else {
137            self.buttons.retain(|b| matches!(b.action, ButtonAction::Close));
138        }
139    }
140
141    /// Set the alignment of the buttons
142    #[chainable]
143    pub fn set_button_align(&mut self, align: HAlign) { self.button_align = align; }
144
145    /// Set the alignment of the buttons
146    #[chainable]
147    pub fn set_title_align(&mut self, align: HAlign) { self.title_align = align; }
148
149    /// Remove all the buttons
150    pub fn clear_buttons(&mut self) { self.buttons.clear(); }
151
152    /// The numerical ID of the current view
153    pub fn cur_id(&self) -> Option<usize> { self.tabs.cur_id() }
154
155    /// The current view being shown (the 'focused' view)
156    pub fn cur_view(&self) -> Option<&BoxedView> { self.tabs.cur_view() }
157
158    /// Mutable version of `cur_view()`
159    pub fn cur_view_mut(&mut self) -> Option<&mut BoxedView> { self.tabs.cur_view_mut() }
160
161
162    /// The current tab title
163    ///
164    /// Returns an empty string if the tab dialog is empty
165    pub fn cur_title(&self) -> &str {
166        if let Some(id) = self.cur_id() {
167            for button in &self.title_buttons {
168                if let TitleButtonType::Tab(tab_id) = button.b_type {
169                    if tab_id == id {
170                        return &button.label;
171                    }
172                }
173            }
174            ""
175        }
176        else { "" }
177    }
178    /// Iterate through all the views in this `TabLayer`
179    pub fn views(&self) -> TabIter { self.tabs.views() }
180
181    /// Mutable version of `views()`
182    pub fn views_mut(&mut self) -> TabIterMut { self.tabs.views_mut() }
183
184    /// Set the current tab by ID
185    pub fn set_cur_tab(&mut self, id: usize) { self.tabs.set_cur_tab(id); }
186
187    /// Set the title of a specific tab id
188    pub fn set_title(&mut self, id: usize, new_title: &str) {
189        for button in &mut self.title_buttons {
190            let upd_title = if let TitleButtonType::Tab(tab_id) = button.b_type { tab_id == id }
191            else { false };
192            if upd_title { button.set_label(new_title) }
193        }
194    }
195
196    /// Add a new view tab and returns the ID of the new tab
197    pub fn add_tab<V: View>(&mut self, title: &str, view: V) -> usize {
198        let new_id = self.tabs.add_tab(view);
199        self.set_cur_tab(new_id);
200        self.title_buttons.push(TitleButton::new(title, new_id));
201        new_id
202    }
203
204    /// Add a new view tab
205    ///
206    /// Chainable version
207    #[must_use]
208    pub fn tab<V: View>(mut self, title: &str, view: V) -> TabLayer {
209        self.add_tab(title, view);
210        self.set_cur_tab(0);
211        self
212    }
213
214    /// Delete tab by ID
215    pub fn remove_tab(&mut self, id: usize) {
216        self.tabs.remove_tab(id);
217        self.title_buttons.retain(|t_bt| {
218            if let TitleButtonType::Tab(old_id) = t_bt.b_type {
219                old_id != id
220            }
221            else { true }
222        });
223    }
224
225    /// Return all the tab IDs
226    pub fn ids(&self) -> Vec<usize> { self.tabs.ids() }
227
228    // do we have the next and previous buttons?
229    fn has_next_prev_buttons(&self) -> bool {
230        matches!(self.buttons.last(), Some(ViewButton { action: ButtonAction::Next, ..}))
231    }
232
233    // open the overflow menu
234    fn open_overflow(&self) -> EventResult {
235        let id_snd = self.id_snd.clone();
236
237        let mut tabs_menu = SelectView::new()
238            .on_submit(move |root, id| {
239                id_snd.send(*id).unwrap();
240                root.pop_layer();
241            });
242
243        for button in &self.title_buttons {
244            if let TitleButtonType::Tab(tab_id) = button.b_type {
245                tabs_menu.add_item(&button.label, tab_id);
246            }
247        }
248
249        // get overflow button location
250        // there should be exactly 4 items if there is an overflow button
251        let ofs = self.shown_title_buttons[3].rect.top_left() + (0, 1);
252
253        EventResult::with_cb_once(move |root| {
254            // if the user has a custom set FPS, don't mess with it
255            // otherwise set the FPS to 30
256            if root.fps().is_none() { root.set_fps(30); }
257
258            let current_offset = root
259                .screen()
260                .layer_offset(LayerPosition::FromFront(0))
261                .unwrap_or_else(Vec2::zero);
262            let offset = ofs.signed() - current_offset;
263
264            root.screen_mut().add_layer_at(
265                Position::parent(offset),
266                c_focus!(
267                    Dialog::around(tabs_menu.scrollable())
268                        .wrap_with(OnEventView::new)
269                        .on_event(Event::Key(Key::Esc), |r| { r.pop_layer(); })
270                        .max_height(20)
271                )
272            )
273        })
274    }
275
276    // execute the action of a selected button
277    fn exec_button_action(&mut self, selected: usize) -> EventResult {
278        match &self.buttons[selected].action {
279            ButtonAction::Close => {
280                let tab_num = self.cur_id().unwrap();
281                if self.tabs.views().count() > 1 {
282                    self.remove_tab(tab_num);
283                }
284                return EventResult::consumed();
285            }
286
287            ButtonAction::Next => self.tabs.next(),
288            ButtonAction::Prev => self.tabs.prev(),
289            ButtonAction::CallBack(cb) => return EventResult::Consumed(Some(cb.clone()))
290        }
291        EventResult::consumed()
292    }
293
294    // button event handler
295    fn button_event(&mut self, event: &Event) -> EventResult {
296        let num_title_buttons = self.shown_title_buttons.len();
297        let num_buttons = self.buttons.len();
298
299        match event {
300            Event::Key(Key::Enter) => {
301                match self.focus {
302                    TabFocus::TitleBar(selected) => {
303                        let button = &self.shown_title_buttons[selected];
304                        return match button.b_type {
305                            TitleButtonType::Tab(id) => {
306                                self.set_cur_tab(id);
307                                EventResult::consumed()
308                            }
309
310                            TitleButtonType::Overflow => self.open_overflow()
311                        }
312                    }
313
314                    TabFocus::Buttons(selected) => return self.exec_button_action(selected),
315                    TabFocus::Content => { }
316                }
317            }
318
319            Event::Key(Key::Down) => {
320                if matches!(self.focus, TabFocus::TitleBar(_)) {
321                    self.focus = TabFocus::Content;
322                    if let Ok(result) = self.tabs.take_focus(Direction::none()) {
323                        return result.and(EventResult::consumed());
324                    }
325                }
326            }
327
328            Event::Key(Key::Left) => {
329                match self.focus {
330                    TabFocus::TitleBar(ref mut selected) => {
331                        if *selected == 0 { *selected = num_title_buttons; }
332                        *selected -= 1;
333                        return EventResult::consumed();
334                    }
335
336                    TabFocus::Buttons(ref mut selected) => {
337                        if *selected == 0 { *selected = num_buttons; }
338                        *selected -= 1;
339                        return EventResult::consumed();
340                    }
341
342                    TabFocus::Content => { }
343                }
344            }
345
346            Event::Key(Key::Right) => {
347                match self.focus {
348                    TabFocus::TitleBar(ref mut selected) => {
349                        *selected += 1;
350                        if *selected >= num_title_buttons { *selected = 0; }
351                        return EventResult::consumed();
352                    }
353
354                    TabFocus::Buttons(ref mut selected) => {
355                        *selected += 1;
356                        if *selected >= num_buttons { *selected = 0; }
357                        return EventResult::consumed();
358                    }
359
360                    TabFocus::Content => { }
361                }
362            }
363
364            Event::Key(Key::End) => {
365                match self.focus {
366                    TabFocus::TitleBar(ref mut selected) => {
367                        *selected = num_title_buttons - 1;
368                        return EventResult::consumed();
369                    }
370
371                    TabFocus::Buttons(ref mut selected) => {
372                        *selected = num_buttons - 1;
373                        return EventResult::consumed();
374                    }
375
376                    TabFocus::Content => { }
377                }
378            }
379
380            Event::Key(Key::Home) => {
381                match self.focus {
382                    TabFocus::TitleBar(ref mut selected) | TabFocus::Buttons(ref mut selected) => {
383                        *selected = 0;
384                        return EventResult::consumed();
385                    }
386
387                    TabFocus::Content => { }
388                }
389            }
390
391            Event::Key(Key::Tab) => {
392                match self.focus {
393                    TabFocus::TitleBar(_) => self.focus = TabFocus::Content,
394                    TabFocus::Content => self.focus = TabFocus::Buttons(0),
395                    TabFocus::Buttons(ref mut selected) => {
396                        if *selected >= self.buttons.len() - 1 { return EventResult::Ignored }
397                        *selected += 1;
398                    }
399                }
400                return EventResult::consumed();
401            }
402
403            Event::Refresh => {
404                if let Ok(id) = self.id_rcv.try_recv() {
405                    self.set_cur_tab(id);
406                    self.tabs.set_as_first(id);
407                    return EventResult::consumed();
408                }
409            }
410
411            Event::Key(Key::Up) => {
412                if matches!(self.focus, TabFocus::Buttons(_)) {
413                    self.focus = TabFocus::Content;
414                    return EventResult::consumed();
415                }
416            }
417
418            Event::FocusLost => self.focus = TabFocus::Content,
419            _ => { }
420        }
421        EventResult::Ignored
422    }
423
424    // event handler for the currently shown view
425    fn view_event(&mut self, event: &Event) -> EventResult {
426        match self.tabs.on_event(event.relativized((1, 1))) {
427            EventResult::Ignored => {
428                match event {
429                    Event::Key(Key::Up) => {
430                        self.focus = TabFocus::TitleBar(0);
431                        return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
432                    }
433
434                    Event::Key(Key::Down) => {
435                        if !self.buttons.is_empty() {
436                            self.focus = TabFocus::Buttons(0);
437                            return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
438                        }
439                    }
440
441                    Event::Key(Key::Tab) => {
442                        if matches!(self.focus, TabFocus::Content) {
443                            self.focus = TabFocus::Buttons(0);
444                            return EventResult::consumed();
445                        }
446                    }
447
448                    Event::FocusLost => {
449                        self.focus = TabFocus::Content;
450                        return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
451                    }
452
453                    _ => { }
454                }
455                EventResult::Ignored
456            }
457
458            res => res
459        }
460    }
461
462    // mouse event handler
463    fn mouse_event(&mut self, event: &Event) -> Option<EventResult> {
464        if let Event::Mouse {
465            offset, position,
466            event: MouseEvent::Release(button),
467        } = event {
468            let pos = position.checked_sub(offset)?;
469
470            for (i, t_button) in self.shown_title_buttons.iter().enumerate() {
471                if t_button.has_mouse_pos(pos) {
472                    return if *button == MouseButton::Left {
473                        let res = match t_button.b_type {
474                            TitleButtonType::Tab(id) => {
475                                self.set_cur_tab(id);
476                                EventResult::consumed()
477                            }
478
479                            TitleButtonType::Overflow => self.open_overflow()
480                        };
481                        self.focus = TabFocus::TitleBar(i);
482                        Some(res)
483                    }
484                    else {
485                        self.focus = TabFocus::TitleBar(i);
486                        Some(EventResult::consumed())
487                    }
488                }
489            }
490
491            for (i, b_button) in self.buttons.iter().enumerate() {
492                if b_button.has_mouse_pos(pos) {
493                    return if *button == MouseButton::Left {
494                        self.focus = TabFocus::Buttons(i);
495                        Some(self.exec_button_action(i))
496                    }
497                    else {
498                        self.focus = TabFocus::Buttons(i);
499                        Some(EventResult::consumed())
500                    }
501                }
502            }
503            self.focus = TabFocus::Content;
504        }
505        None
506    }
507}
508
509impl View for TabLayer {
510    fn draw(&self, printer: &Printer) {
511        // draw the title bar buttons
512        if !self.title_buttons.is_empty() {
513            let mut f_index: Option<usize> = None;
514            for (i, button) in self.shown_title_buttons.iter().enumerate() {
515                let selected = if let TabFocus::TitleBar(sel) = self.focus { i == sel && printer.focused }
516                else { false };
517                let focused = if let TitleButtonType::Tab(id) = button.b_type {
518                    if let Some(cur_id) = self.cur_id() {
519                        cur_id == id
520                    }
521                    else { false }
522                }
523                else { false };
524                // skip the focused title button
525                if focused { f_index = Some(i); }
526                else {
527                    button.draw(printer, selected, false, false, false);
528                }
529            }
530
531            // now draw the focused button so it is on top
532            if let Some(index) = f_index {
533                let selected = if let TabFocus::TitleBar(sel) = self.focus { index == sel && printer.focused }
534                else { false };
535                self.shown_title_buttons[index]
536                    .draw(printer, selected, true, false, false);
537            }
538        }
539        printer.print_hline((0, 1), printer.size.x, "─");
540
541        let y = if self.buttons.is_empty() { printer.size.y + 1 }
542        else { printer.size.y };
543        let tabs_printer = printer
544            .cropped((printer.size.x, y))
545            .offset((0, 2))
546            .focused(self.focus == TabFocus::Content);
547
548        self.tabs.draw(&tabs_printer);
549
550        // draw the buttons of there are any
551        if !self.buttons.is_empty() {
552            for (i, button) in self.buttons.iter().enumerate() {
553                let selected = if let TabFocus::Buttons(sel) = self.focus { i == sel && printer.focused }
554                else { false };
555                button.draw(printer, selected);
556            }
557        }
558    }
559
560    fn layout(&mut self, size: Vec2) {
561        // align the title bar buttons
562        super::align_title_buttons(&mut self.shown_title_buttons, &self.title_buttons, self.title_align, size.x, false);
563
564        // align the buttons
565        super::align_buttons(&mut self.buttons, self.button_align, size, false);
566
567        // set the size for the current view
568        self.tabs.layout(size - (0, 3));
569    }
570
571    fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
572        let focus_tab = |dialog: &mut Self, d: Direction| {
573            let result = dialog.tabs.take_focus(d);
574            let focus = if result.is_err() { TabFocus::TitleBar(0) }
575            else { TabFocus::Content };
576            dialog.focus = focus;
577            result
578        };
579
580        let mut result: Result<EventResult, CannotFocus> = Ok(EventResult::consumed());
581        match source {
582            Direction::Abs(Absolute::Down) => result = focus_tab(self, source),
583
584            Direction::Abs(Absolute::Left) | Direction::Abs(Absolute::Right) => {
585                if !matches!(self.focus, TabFocus::TitleBar(_)) {
586                    result = focus_tab(self, source);
587                }
588            }
589
590            Direction::Abs(Absolute::Up) => self.focus = TabFocus::TitleBar(0),
591            _ => { }
592        }
593
594        Ok(result.unwrap_or(EventResult::Ignored))
595    }
596
597    fn on_event(&mut self, event: Event) -> EventResult {
598        self.mouse_event(&event).unwrap_or(
599            if self.focus == TabFocus::Content { self.view_event(&event) }
600            else { self.button_event(&event) }
601        )
602    }
603
604    fn required_size(&mut self, constraint: Vec2) -> Vec2 { constraint }
605    fn needs_relayout(&self) -> bool { self.tabs.needs_relayout() }
606    fn focus_view(&mut self, sel: &Selector) -> Result<EventResult, ViewNotFound> { self.tabs.focus_view(sel) }
607    fn call_on_any(&mut self, sel: &Selector, cb: AnyCb) { self.tabs.call_on_any(sel, cb); }
608}
609
610impl Default for TabLayer {
611    fn default() -> TabLayer { TabLayer::new() }
612}