cursive_extras/views/tabs/
dialog.rs1use 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
45pub struct TabDialog {
48 tabs: TabContainer,
50
51 shown_title_buttons: Vec<TitleButton>,
53
54 title_buttons: Vec<TitleButton>,
56
57 title_align: HAlign,
59
60 buttons: Vec<ViewButton>,
62
63 button_align: HAlign,
65
66 focus: TabFocus,
68
69 id_snd: Sender<usize>,
71 id_rcv: Receiver<usize>
72}
73
74impl TabDialog {
75 #[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 #[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 #[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 #[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 #[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 #[chainable]
148 pub fn set_button_align(&mut self, align: HAlign) { self.button_align = align; }
149
150 #[chainable]
152 pub fn set_title_align(&mut self, align: HAlign) { self.title_align = align; }
153
154 pub fn clear_buttons(&mut self) { self.buttons.clear(); }
156
157 pub fn cur_id(&self) -> Option<usize> { self.tabs.cur_id() }
159
160 pub fn cur_view(&self) -> Option<&BoxedView> { self.tabs.cur_view() }
162
163 pub fn cur_view_mut(&mut self) -> Option<&mut BoxedView> { self.tabs.cur_view_mut() }
165
166 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 pub fn views(&self) -> TabIter { self.tabs.views() }
185
186 pub fn views_mut(&mut self) -> TabIterMut { self.tabs.views_mut() }
188
189 pub fn set_cur_tab(&mut self, id: usize) { self.tabs.set_cur_tab(id); }
191
192 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 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 #[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 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 pub fn ids(&self) -> Vec<usize> { self.tabs.ids() }
232
233 fn has_next_prev_buttons(&self) -> bool {
235 matches!(self.buttons.last(), Some(ViewButton { action: ButtonAction::Next, ..}))
236 }
237
238 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 let ofs = self.shown_title_buttons[3].rect.top_left() + (0, 1);
257
258 EventResult::with_cb_once(move |root| {
259 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 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 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 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 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 printer.print_box((0, 0), printer.size, false);
518
519 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 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 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 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 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 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 super::align_buttons(&mut self.buttons, self.button_align, size, true);
576
577 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}