cursive_extras/views/tabs/
mod.rs1use 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
24pub type TabIter<'a> = Iter<'a, (usize, BoxedView)>;
26
27pub type TabIterMut<'a> = IterMut<'a, (usize, BoxedView)>;
29
30#[derive(Copy, Clone, PartialEq, Eq)]
32enum TabFocus {
33 TitleBar(usize),
34 Content,
35 Buttons(usize)
36}
37
38enum ButtonAction {
40 Close, Next, Prev, CallBack(Callback) }
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#[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 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#[derive(Copy, Clone, PartialEq, Eq)]
113enum TitleButtonType {
114 Tab(usize),
116
117 Overflow
119}
120
121struct 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
152fn 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
192fn align_buttons(buttons: &mut [ViewButton], align: HAlign, size: Vec2, dialog: bool) {
194 if !buttons.is_empty() {
195 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}