cursive_extras/views/tabs/
dialog.rs

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