cursive_extras/views/tabs/
mod.rs

1use std::slice::{Iter, IterMut};
2use cursive_core::{
3    Cursive, Printer,
4    Vec2, Rect,
5    event::Callback,
6    align::HAlign,
7    theme::ColorStyle,
8    utils::markup::StyledString,
9    views::BoxedView
10};
11use unicode_width::UnicodeWidthStr;
12use crate::SpannedStrExt;
13
14mod container;
15mod dialog;
16mod layer;
17
18pub use self::{
19    container::TabContainer,
20    dialog::TabDialog,
21    layer::TabLayer
22};
23
24/// Iterator over the views and their IDs in a tabbed view
25pub type TabIter<'a> = Iter<'a, (usize, BoxedView)>;
26
27/// Mutable iterator over the views and their IDs in a tabbed view
28pub type TabIterMut<'a> = IterMut<'a, (usize, BoxedView)>;
29
30// what part of the tab view is being focused?
31#[derive(Copy, Clone, PartialEq, Eq)]
32enum TabFocus {
33    TitleBar(usize),
34    Content,
35    Buttons(usize)
36}
37
38// what should the tab button do?
39enum ButtonAction {
40    Close, // close current tab
41    Next, // next tab
42    Prev, // previous tab
43    CallBack(Callback) // custom action
44}
45
46impl<F: Fn(&mut Cursive) + Send + Sync + 'static> From<F> for ButtonAction {
47    fn from(func: F) -> Self { ButtonAction::CallBack(Callback::from_fn(func)) }
48}
49
50// a button in the tab dialog title bar
51#[derive(Clone)]
52struct TitleButton {
53    label: String,
54    rect: Rect,
55    b_type: TitleButtonType
56}
57
58impl TitleButton {
59    fn new(label: &str, id: usize) -> TitleButton {
60        TitleButton {
61            label: format!(" {label} "),
62            rect: Rect::from_size((1, 0), (label.width(), 1)),
63            b_type: TitleButtonType::Tab(id)
64        }
65    }
66
67    // creates an overflow button
68    fn overflow() -> TitleButton {
69        TitleButton {
70            label: String::from("..."),
71            rect: Rect::from_size((1, 0), (3, 1)),
72            b_type: TitleButtonType::Overflow
73        }
74    }
75
76    fn set_label(&mut self, text: &str) {
77        self.label = format!(" {text} ");
78        self.rect = Rect::from_size((1, 0), (text.width(), 1));
79    }
80
81    fn width(&self) -> usize { self.label.width() + 1 }
82    fn has_mouse_pos(&self, pos: Vec2) -> bool { self.rect.contains(pos) }
83    fn offset(&mut self, offset: usize) { self.rect.offset((offset, 0)); }
84
85    fn draw(&self, printer: &Printer, selected: bool, focused: bool, first: bool, last: bool) {
86        let loc = self.rect.top_left() - (1, 0);
87        let fc = if first && focused { "┨" }
88        else if first { "┤" }
89        else if focused { "┃" }
90        else { "│" };
91
92        let lc = if last && focused { "┠" }
93        else if last { "├" }
94        else if focused { "┃" }
95        else { "│" };
96
97        printer.with_style(ColorStyle::primary(), |printer| {
98            printer.print(loc, fc);
99            printer.print(loc + (self.label.width() + 1, 0), lc);
100        });
101
102        let title_style =
103            if selected{ ColorStyle::highlight() }
104            else if focused { ColorStyle::title_primary() }
105            else { ColorStyle::title_secondary() };
106
107        printer.print_styled(loc + (1, 0), StyledString::styled(&self.label, title_style).as_spanned_str());
108    }
109}
110
111// the type of title bar button
112#[derive(Copy, Clone, PartialEq, Eq)]
113enum TitleButtonType {
114    // button with associated view
115    Tab(usize),
116
117    // overflow menu
118    Overflow
119}
120
121// tab view button
122struct ViewButton {
123    text: String,
124    action: ButtonAction,
125    rect: Rect
126}
127
128impl ViewButton {
129    fn new(text: &str, action: ButtonAction) -> ViewButton {
130        ViewButton {
131            text: text.to_string(),
132            action,
133            rect: Rect::from_size((0, 0), (text.width() + 2, 1))
134        }
135    }
136
137    fn from_fn<F: Fn(&mut Cursive) + Send + Sync + 'static>(text: &str, func: F) -> ViewButton {
138        Self::new(text, ButtonAction::from(func))
139    }
140
141    fn draw(&self, printer: &Printer, selected: bool) {
142        let style = if selected { ColorStyle::highlight() }
143        else { ColorStyle::primary() };
144        let text = StyledString::styled(format!("<{}>", self.text), style);
145        printer.print_styled(self.rect.top_left(), text.as_spanned_str());
146    }
147
148    fn width(&self) -> usize { self.text.chars().count() + 3 }
149    fn has_mouse_pos(&self, pos: Vec2) -> bool  { self.rect.contains(pos) }
150}
151
152// align the title bar of a tabbed view
153fn align_title_buttons(shown_buttons: &mut Vec<TitleButton>, buttons: &[TitleButton], align: HAlign, width: usize, dialog: bool) {
154    if !buttons.is_empty() {
155        shown_buttons.clear();
156        let mut buttons_len = 0;
157        for button in buttons.iter() {
158            shown_buttons.push(button.clone());
159            buttons_len += button.width();
160        }
161
162        if buttons_len > width {
163            shown_buttons.clear();
164            buttons_len = 0;
165            for (i, button) in buttons.iter().enumerate() {
166                if i >= 3 {
167                    shown_buttons.push(TitleButton::overflow());
168                    buttons_len += 4;
169                    break;
170                }
171                else {
172                    shown_buttons.push(button.clone());
173                    buttons_len += button.width();
174                }
175            }
176        }
177
178        let offset = match align {
179            HAlign::Left => if dialog { 1 } else { 0 },
180            HAlign::Center => (width - buttons_len) / 2 ,
181            HAlign::Right => width - buttons_len - if dialog { 0 } else { 1 }
182        };
183
184        let mut cur_len = 0;
185        for button in shown_buttons.iter_mut() {
186            button.offset(offset + cur_len);
187            cur_len += button.width();
188        }
189    }
190}
191
192// align the buttons of a tabbed view
193fn align_buttons(buttons: &mut [ViewButton], align: HAlign, size: Vec2, dialog: bool) {
194    if !buttons.is_empty() {
195        // the length of all the buttons
196        let mut buttons_len = 0;
197        for button in buttons.iter() {
198            buttons_len += button.width();
199        }
200        let offset = match align {
201            HAlign::Left => 1,
202            HAlign::Center => (size.x - buttons_len) / 2 ,
203            HAlign::Right => size.x + if dialog { 0 } else { 1 } - buttons_len
204        };
205
206        let mut cur_len = 0;
207        for button in buttons.iter_mut() {
208            let loc = (offset + cur_len, size.y - if dialog { 2 } else { 1 });
209            let rsize = button.rect.size();
210            button.rect = Rect::from_size(loc, rsize);
211            cur_len += button.width();
212        }
213    }
214}
OSZAR »