cursive_tabs/
lib.rs

1//! This crate provides a tabbing view for
2//! [gyscos/cursive](https://github.com/gyscos/cursive) views. It is build to
3//! be as simple as possible.
4//!
5//! The behaviour is oriented to be similar to  [`StackView`](https://docs.rs/cursive/0.13.0/cursive/views/struct.StackView.html) of cursive, but with the advantage of selectively displaying
6//! views without needing to delete foremost one.
7//!
8//! # Example
9//! All you need to do to create a new `TabView` is:
10//! ```
11//! # use cursive::{view::Nameable, views::{TextView, Dialog}};
12//! # use cursive_tabs::TabView;
13//! # let mut siv = cursive::default();
14//! let mut tabs = TabView::new();
15//! # // That is all what is needed to display an empty TabView, but of course
16//! # // you can add your own tabs now and switch them around as you want!
17//! # tabs.add_tab(TextView::new("Our first view!").with_name("First"));
18//! # siv.add_layer(Dialog::around(tabs));
19//! # // When your done setting run cursive
20//! # // siv.run();
21//! ```
22//! You can then use the provided methods to modify the content of the `TabView`
23//! Consuming and non-consuming are both provided.
24//!
25//! # Full Example
26//! ```
27//! use cursive::{view::Nameable, views::{TextView, Dialog}};
28//! use cursive_tabs::TabView;
29//!
30//! let mut siv = cursive::default();
31//! let mut tabs = TabView::new();
32//! // That is all what is needed to display an empty TabView, but of course
33//! // you can add your own tabs now and switch them around as you want!
34//! tabs.add_tab(TextView::new("Our first view!").with_name("First"));
35//! siv.add_layer(Dialog::around(tabs));
36//! // When your done setting run cursive
37//! // siv.run();
38//! ```
39extern crate cursive_core as cursive;
40
41use crossbeam::channel::{Receiver, Sender};
42use cursive::direction::Direction;
43use cursive::event::{AnyCb, Event, EventResult};
44use cursive::view::{CannotFocus, Selector, View, ViewNotFound};
45use cursive::views::NamedView;
46use cursive::{Printer, Rect, Vec2};
47use log::debug;
48use std::collections::HashMap;
49
50mod bar;
51mod error;
52mod panel;
53
54// Reexports
55use bar::{Bar, TabBar};
56pub use panel::{Align, Placement, TabPanel};
57/// Main struct which manages views
58pub struct TabView {
59    current_id: Option<String>,
60    // Version 0.6 changes this to only contain NamedViews, in the map this remains the same type though
61    // as NamedViews cannot be sized properly due to their enclosed view trait object
62    map: HashMap<String, Box<dyn View>>,
63    key_order: Vec<String>,
64    bar_rx: Option<Receiver<String>>,
65    active_key_tx: Option<Sender<String>>,
66    invalidated: bool,
67}
68
69impl Default for TabView {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl TabView {
76    /// Returns a new TabView
77    /// # Example
78    /// ```
79    /// # use cursive::{view::Nameable, views::{TextView, Dialog}};
80    /// # use cursive_tabs::TabView;
81    /// #  let mut siv = cursive::default();
82    /// let mut tabs = TabView::new();
83    /// #  // That is all what is needed to display an empty TabView, but of course
84    /// #  // you can add your own tabs now and switch them around as you want!
85    /// #  tabs.add_tab(TextView::new("Our first view!").with_name("First"));
86    /// #  siv.add_layer(Dialog::around(tabs));
87    /// #  // When your done setting run cursive
88    /// #  // siv.run();
89    /// ```
90    pub fn new() -> Self {
91        Self {
92            current_id: None,
93            map: HashMap::new(),
94            key_order: Vec::new(),
95            bar_rx: None,
96            active_key_tx: None,
97            invalidated: true,
98        }
99    }
100
101    /// Returns the currently active tab Id.
102    pub fn active_tab(&self) -> Option<&str> {
103        self.current_id.as_deref()
104    }
105
106    /// Returns a reference to the underlying view.
107    pub fn active_view(&self) -> Option<&dyn View> {
108        self.active_tab()
109            .and_then(|k| self.map.get(k).map(|v| &**v))
110    }
111
112    /// Returns a mutable reference to the underlying view.
113    pub fn active_view_mut(&mut self) -> Option<&mut dyn View> {
114        if let Some(k) = self.current_id.as_ref() {
115            self.map.get_mut(k).map(|v| &mut **v)
116        } else {
117            None
118        }
119    }
120
121    pub fn views(&self) -> Vec<&dyn View> {
122        self.map.values().map(|v| &**v).collect()
123    }
124
125    // Mutable references to all mutable views.
126    pub fn views_mut(&mut self) -> Vec<&mut dyn View> {
127        self.map.values_mut().map(|v| &mut **v).collect()
128    }
129
130    /// Set the currently active (visible) tab.
131    /// If the tab id is not known, an error is returned and no action is performed.
132    pub fn set_active_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
133        if self.map.contains_key(id) {
134            if let Some(sender) = &self.active_key_tx {
135                match sender.send(id.to_owned()) {
136                    Ok(_) => {}
137                    Err(e) => debug!(
138                        "error occured while trying to send new active key to sender: {}",
139                        e
140                    ),
141                }
142            }
143            self.current_id = Some(id.to_owned());
144            self.invalidated = true;
145            Ok(())
146        } else {
147            Err(error::IdNotFound { id: id.to_owned() })
148        }
149    }
150
151    /// Set the currently active (visible) tab.
152    /// If the tab id is not known, an error is returned and no action is performed.
153    ///
154    /// This is the consumable variant.
155    pub fn with_active_tab(mut self, id: &str) -> Result<Self, Self> {
156        match self.set_active_tab(id) {
157            Ok(_) => Ok(self),
158            Err(_) => Err(self),
159        }
160    }
161
162    /// Add a new tab to the tab view.
163    /// The new tab will be set active and will be the visible tab for this tab view.
164    pub fn add_tab<T: View>(&mut self, view: NamedView<T>) {
165        let id = view.name().to_owned();
166        self.map.insert(id.clone(), Box::new(view));
167        self.key_order.push(id.clone());
168        self.current_id = Some(id);
169    }
170
171    /// Add a new tab to the tab view.
172    /// The new tab will be set active and will be the visible tab for this tab view.
173    ///
174    /// This is the consumable variant.
175    pub fn with_tab<T: View>(mut self, view: NamedView<T>) -> Self {
176        self.add_tab(view);
177        self
178    }
179
180    /// Add a new tab at a given position.
181    /// The new tab will be set active and will be the visible tab for this tab view.
182    ///
183    /// This is designed to not fail, if the given position is greater than the number of current tabs, it simply will be appended.
184    pub fn add_tab_at<T: View>(&mut self, view: NamedView<T>, pos: usize) {
185        let id = view.name().to_owned();
186        self.map.insert(id.clone(), Box::new(view));
187        if let Some(sender) = &self.active_key_tx {
188            match sender.send(id.clone()) {
189                Ok(_) => {}
190                Err(send_err) => debug!(
191                    "Could not send new key to receiver in TabBar, has it been dropped? {}",
192                    send_err
193                ),
194            }
195        }
196        self.current_id = Some(id.clone());
197        if self.key_order.len() > pos {
198            self.key_order.insert(pos, id)
199        } else {
200            self.key_order.push(id);
201        }
202    }
203
204    /// Add a new tab at a given position.
205    /// The new tab will be set active and will be the visible tab for this tab view.
206    ///
207    /// It is designed to be fail-safe, if the given position is greater than the number of current tabs, it simply will be appended.
208    ///
209    /// This is the consumable variant.
210    pub fn with_tab_at<T: View>(mut self, view: NamedView<T>, pos: usize) -> Self {
211        self.add_tab_at(view, pos);
212        self
213    }
214
215    /// Swap the tabs position.
216    /// If one of the given key cannot be found, then no operation is performed.
217    pub fn swap_tabs(&mut self, fst: &str, snd: &str) {
218        let mut fst_pos: Option<usize> = None;
219        let mut snd_pos: Option<usize> = None;
220        for (pos, key) in self.tab_order().into_iter().enumerate() {
221            match key {
222                val if val == *fst => fst_pos = Some(pos),
223                val if val == *snd => snd_pos = Some(pos),
224                _ => {}
225            }
226        }
227        if let (Some(fst_pos), Some(snd_pos)) = (fst_pos, snd_pos) {
228            if let Some(cur) = self.current_id.as_ref() {
229                if self.active_key_tx.is_some() && (fst == cur || snd == cur) {
230                    self.active_key_tx
231                        .as_mut()
232                        .unwrap()
233                        .send(cur.to_owned())
234                        .expect("Sending failed.");
235                }
236            }
237            self.key_order.swap(fst_pos, snd_pos);
238        }
239    }
240
241    /// Removes a tab with the given id from the `TabView`.
242    /// If the removed tab is active at the moment, the `TabView` will unfocus it and
243    /// the focus needs to be set manually afterwards, or a new view has to be inserted.
244    pub fn remove_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
245        if self.map.remove(id).is_some() {
246            if let Some(key) = &self.current_id {
247                if key == id {
248                    // Current id no longer valid
249                    self.current_id = None;
250                }
251            }
252            // remove_key experimental
253            self.key_order.retain(|k| k != id);
254            self.invalidated = true;
255            Ok(())
256        } else {
257            Err(error::IdNotFound { id: id.to_owned() })
258        }
259    }
260
261    /// Returns the current order of keys in a vector.
262    /// When you're implementing your own tab bar, be aware that this is the current
263    /// tab bar and is only a copy of the original order, modification will not be
264    /// transferred and future updates in the original not displayed.
265    pub fn tab_order(&self) -> Vec<String> {
266        self.key_order.clone()
267    }
268
269    // Returns the index of the key, length of the vector if the key is not included
270    // This can be done with out sorting
271    fn index_key(cur_key: &str, key_order: &[String]) -> usize {
272        for (idx, key) in key_order.iter().enumerate() {
273            if *key == *cur_key {
274                return idx;
275            }
276        }
277        key_order.len()
278    }
279
280    /// Set the active tab to the next tab in order.
281    pub fn next(&mut self) {
282        if let Some(cur_key) = &self.current_id {
283            let idx = (Self::index_key(&cur_key, &self.key_order) + 1) % self.key_order.len();
284
285            let key = &self.key_order[idx].clone();
286            self.set_active_tab(key)
287                .expect("Key content changed during operation, this should not happen");
288        }
289    }
290
291    /// Set the active tab to the previous tab in order.
292    pub fn prev(&mut self) {
293        if let Some(cur_key) = self.current_id.as_ref().cloned() {
294            let idx_key = Self::index_key(&cur_key, &self.key_order);
295            let idx = (self.key_order.len() + idx_key - 1) % self.key_order.len();
296
297            let key = &self.key_order[idx].clone();
298            self.set_active_tab(key)
299                .expect("Key content changed during operation, this should not happen");
300        }
301    }
302
303    /// Set the receiver for keys to be changed to
304    pub fn set_bar_rx(&mut self, rx: Receiver<String>) {
305        self.bar_rx = Some(rx);
306    }
307
308    /// Set the sender for the key switched to
309    pub fn set_active_key_tx(&mut self, tx: Sender<String>) {
310        self.active_key_tx = Some(tx);
311    }
312}
313
314impl View for TabView {
315    fn draw(&self, printer: &Printer) {
316        if let Some(key) = &self.current_id {
317            if let Some(view) = self.map.get(key) {
318                view.draw(printer);
319            }
320        }
321    }
322
323    fn layout(&mut self, size: Vec2) {
324        self.invalidated = false;
325        if let Some(key) = &self.current_id {
326            if let Some(view) = self.map.get_mut(key) {
327                view.layout(size);
328            }
329        }
330    }
331
332    fn required_size(&mut self, req: Vec2) -> Vec2 {
333        if let Some(rx) = &self.bar_rx {
334            if let Ok(evt) = rx.try_recv() {
335                match self.set_active_tab(&evt) {
336                    Ok(_) => {}
337                    Err(err) => debug!("could not accept tab bar event: {:?}", err),
338                }
339            }
340        }
341        if let Some(key) = &self.current_id {
342            if let Some(view) = self.map.get_mut(key) {
343                view.required_size(req)
344            } else {
345                (1, 1).into()
346            }
347        } else {
348            (1, 1).into()
349        }
350    }
351
352    fn on_event(&mut self, evt: Event) -> EventResult {
353        if let Some(key) = &self.current_id {
354            if let Some(view) = self.map.get_mut(key) {
355                view.on_event(evt)
356            } else {
357                EventResult::Ignored
358            }
359        } else {
360            EventResult::Ignored
361        }
362    }
363
364    fn take_focus(&mut self, src: Direction) -> Result<EventResult, CannotFocus> {
365        if let Some(key) = &self.current_id {
366            if let Some(view) = self.map.get_mut(key) {
367                view.take_focus(src)
368            } else {
369                Err(CannotFocus)
370            }
371        } else {
372            Err(CannotFocus)
373        }
374    }
375
376    fn call_on_any<'a>(&mut self, slt: &Selector, cb: AnyCb<'a>) {
377        for (_, view) in self.map.iter_mut() {
378            view.call_on_any(slt, cb);
379        }
380    }
381
382    fn focus_view(&mut self, slt: &Selector) -> Result<EventResult, ViewNotFound> {
383        if let Some(key) = &self.current_id {
384            if let Some(view) = self.map.get_mut(key) {
385                view.focus_view(slt)
386            } else {
387                Err(ViewNotFound)
388            }
389        } else {
390            Err(ViewNotFound)
391        }
392    }
393
394    fn needs_relayout(&self) -> bool {
395        self.invalidated || {
396            if let Some(key) = &self.current_id {
397                if let Some(view) = self.map.get(key) {
398                    view.needs_relayout()
399                } else {
400                    false
401                }
402            } else {
403                false
404            }
405        }
406    }
407
408    fn important_area(&self, size: Vec2) -> Rect {
409        if let Some(key) = &self.current_id {
410            if let Some(view) = self.map.get(key) {
411                view.important_area(size)
412            } else {
413                Rect::from_point((1, 1))
414            }
415        } else {
416            Rect::from_point((1, 1))
417        }
418    }
419}
420
421#[cfg(test)]
422mod test {
423    use super::TabView;
424    use cursive::{traits::Nameable, views::DummyView};
425
426    #[test]
427    fn smoke() {
428        let _ = TabView::new();
429    }
430
431    #[test]
432    fn insert() {
433        let mut tabs = TabView::new().with_tab(DummyView {}.with_name("0"));
434        tabs.add_tab(DummyView {}.with_name("1"));
435    }
436
437    #[test]
438    fn switch() {
439        let mut tabs = TabView::new();
440        tabs.add_tab(DummyView {}.with_name("0"));
441        tabs.add_tab(DummyView {}.with_name("1"));
442        assert_eq!(tabs.active_tab().expect("Id not correct"), "1");
443        tabs.set_active_tab("0").expect("Id not taken");
444        assert_eq!(tabs.active_tab().expect("Id not correct"), "0");
445    }
446
447    #[test]
448    fn remove() {
449        let mut tabs = TabView::new();
450        tabs.add_tab(DummyView {}.with_name("0"));
451        tabs.add_tab(DummyView {}.with_name("1"));
452        assert_eq!(tabs.remove_tab("1"), Ok(()));
453        assert!(tabs.active_tab().is_none());
454    }
455}