cursive_tree_view/
lib.rs

1//! A tree view implementation for [cursive](https://crates.io/crates/cursive).
2#![deny(
3    missing_docs,
4    trivial_casts,
5    trivial_numeric_casts,
6    unsafe_code,
7    unused_import_braces,
8    unused_qualifications
9)]
10
11// Crate Dependencies ---------------------------------------------------------
12extern crate cursive_core as cursive;
13#[macro_use]
14extern crate debug_stub_derive;
15
16// STD Dependencies -----------------------------------------------------------
17use std::cmp;
18use std::fmt::{Debug, Display};
19use std::sync::{Arc, Mutex};
20
21// External Dependencies ------------------------------------------------------
22use cursive::direction::Direction;
23use cursive::event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
24use cursive::theme::ColorStyle;
25use cursive::vec::Vec2;
26use cursive::view::{CannotFocus, View};
27use cursive::{Cursive, Printer};
28use cursive::{Rect, With};
29
30// Internal Dependencies ------------------------------------------------------
31mod tree_list;
32pub use tree_list::Placement;
33use tree_list::TreeList;
34
35/// Callback taking an item index as input.
36type IndexCallback = Arc<dyn Fn(&mut Cursive, usize) + Send + Sync>;
37
38/// Callback taking as input the row ID, the collapsed state, and the child ID.
39type CollapseCallback = Arc<dyn Fn(&mut Cursive, usize, bool, usize) + Send + Sync>;
40
41/// A low level tree view.
42///
43/// Each view provides a number of low level methods for manipulating its
44/// contained items and their structure.
45///
46/// All interactions are performed via relative (i.e. visual) `row` indices which
47/// makes reasoning about behaviour much easier in the context of interactive
48/// user manipulation of the tree.
49///
50/// # Examples
51///
52/// ```rust
53/// # extern crate cursive;
54/// # extern crate cursive_tree_view;
55/// # use cursive_tree_view::{TreeView, Placement};
56/// # fn main() {
57/// let mut tree = TreeView::new();
58///
59/// tree.insert_item("root".to_string(), Placement::LastChild, 0);
60///
61/// tree.insert_item("1".to_string(), Placement::LastChild, 0);
62/// tree.insert_item("2".to_string(), Placement::LastChild, 1);
63/// tree.insert_item("3".to_string(), Placement::LastChild, 2);
64/// # }
65/// ```
66#[derive(DebugStub)]
67pub struct TreeView<T: Display + Debug> {
68    enabled: bool,
69
70    #[debug_stub(some = "Arc<Fn(&mut Cursive, usize)")]
71    on_submit: Option<IndexCallback>,
72
73    #[debug_stub(some = "Arc<Fn(&mut Cursive, usize)")]
74    on_select: Option<IndexCallback>,
75
76    #[debug_stub(some = "Arc<Fn(&mut Cursive, usize, bool, usize)>")]
77    on_collapse: Option<CollapseCallback>,
78
79    last_size: Vec2,
80    focus: usize,
81    list: TreeList<T>,
82}
83
84/// One character for the symbol, and one for a space between the sybol and the item
85const SYMBOL_WIDTH: usize = 2;
86
87impl<T: Display + Debug + Send + Sync> Default for TreeView<T> {
88    /// Creates a new, empty `TreeView`.
89    fn default() -> Self {
90        Self::new()
91    }
92}
93impl<T: Display + Debug + Send + Sync> TreeView<T> {
94    /// Creates a new, empty `TreeView`.
95    pub fn new() -> Self {
96        Self {
97            enabled: true,
98            on_submit: None,
99            on_select: None,
100            on_collapse: None,
101
102            last_size: (0, 0).into(),
103            focus: 0,
104            list: TreeList::new(),
105        }
106    }
107
108    /// Disables this view.
109    ///
110    /// A disabled view cannot be selected.
111    pub fn disable(&mut self) {
112        self.enabled = false;
113    }
114
115    /// Re-enables this view.
116    pub fn enable(&mut self) {
117        self.enabled = true;
118    }
119
120    /// Enable or disable this view.
121    pub fn set_enabled(&mut self, enabled: bool) {
122        self.enabled = enabled;
123    }
124
125    /// Returns `true` if this view is enabled.
126    pub fn is_enabled(&self) -> bool {
127        self.enabled
128    }
129
130    /// Sets a callback to be used when `<Enter>` is pressed while an item
131    /// is selected.
132    ///
133    /// # Example
134    ///
135    /// ```rust
136    /// # extern crate cursive;
137    /// # extern crate cursive_tree_view;
138    /// # use cursive::Cursive;
139    /// # use cursive_tree_view::TreeView;
140    /// # fn main() {
141    /// # let mut tree = TreeView::<String>::new();
142    /// tree.set_on_submit(|siv: &mut Cursive, row: usize| {
143    ///
144    /// });
145    /// # }
146    /// ```
147    pub fn set_on_submit<F>(&mut self, cb: F)
148    where
149        F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
150    {
151        self.on_submit = Some(Arc::new(move |s, row| cb(s, row)));
152    }
153
154    /// Sets a callback to be used when `<Enter>` is pressed while an item
155    /// is selected.
156    ///
157    /// Chainable variant.
158    ///
159    /// # Example
160    ///
161    /// ```rust
162    /// # extern crate cursive;
163    /// # extern crate cursive_tree_view;
164    /// # use cursive::Cursive;
165    /// # use cursive_tree_view::TreeView;
166    /// # fn main() {
167    /// # let mut tree = TreeView::<String>::new();
168    /// tree.on_submit(|siv: &mut Cursive, row: usize| {
169    ///
170    /// });
171    /// # }
172    /// ```
173    pub fn on_submit<F>(self, cb: F) -> Self
174    where
175        F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
176    {
177        self.with(|t| t.set_on_submit(cb))
178    }
179
180    /// Sets a callback to be used when an item is selected.
181    ///
182    /// # Example
183    ///
184    /// ```rust
185    /// # extern crate cursive;
186    /// # extern crate cursive_tree_view;
187    /// # use cursive::Cursive;
188    /// # use cursive_tree_view::TreeView;
189    /// # fn main() {
190    /// # let mut tree = TreeView::<String>::new();
191    /// tree.set_on_select(|siv: &mut Cursive, row: usize| {
192    ///
193    /// });
194    /// # }
195    /// ```
196    pub fn set_on_select<F>(&mut self, cb: F)
197    where
198        F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
199    {
200        self.on_select = Some(Arc::new(move |s, row| cb(s, row)));
201    }
202
203    /// Sets a callback to be used when an item is selected.
204    ///
205    /// Chainable variant.
206    ///
207    /// # Example
208    ///
209    /// ```rust
210    /// # extern crate cursive;
211    /// # extern crate cursive_tree_view;
212    /// # use cursive::Cursive;
213    /// # use cursive_tree_view::TreeView;
214    /// # fn main() {
215    /// # let mut tree = TreeView::<String>::new();
216    /// tree.on_select(|siv: &mut Cursive, row: usize| {
217    ///
218    /// });
219    /// # }
220    /// ```
221    pub fn on_select<F>(self, cb: F) -> Self
222    where
223        F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
224    {
225        self.with(|t| t.set_on_select(cb))
226    }
227
228    /// Sets a callback to be used when an item has its children collapsed or expanded.
229    ///
230    /// # Example
231    ///
232    /// ```rust
233    /// # extern crate cursive;
234    /// # extern crate cursive_tree_view;
235    /// # use cursive::Cursive;
236    /// # use cursive_tree_view::TreeView;
237    /// # fn main() {
238    /// # let mut tree = TreeView::<String>::new();
239    /// tree.set_on_collapse(|siv: &mut Cursive, row: usize, is_collapsed: bool, children: usize| {
240    ///
241    /// });
242    /// # }
243    /// ```
244    pub fn set_on_collapse<F>(&mut self, cb: F)
245    where
246        F: Fn(&mut Cursive, usize, bool, usize) + Send + Sync + 'static,
247    {
248        self.on_collapse = Some(Arc::new(move |s, row, collapsed, children| {
249            cb(s, row, collapsed, children)
250        }));
251    }
252
253    /// Sets a callback to be used when an item has its children collapsed or expanded.
254    ///
255    /// Chainable variant.
256    ///
257    /// # Example
258    ///
259    /// ```rust
260    /// # extern crate cursive;
261    /// # extern crate cursive_tree_view;
262    /// # use cursive::Cursive;
263    /// # use cursive_tree_view::TreeView;
264    /// # fn main() {
265    /// # let mut tree = TreeView::<String>::new();
266    /// tree.on_collapse(|siv: &mut Cursive, row: usize, is_collapsed: bool, children: usize| {
267    ///
268    /// });
269    /// # }
270    /// ```
271    pub fn on_collapse<F>(self, cb: F) -> Self
272    where
273        F: Fn(&mut Cursive, usize, bool, usize) + Send + Sync + 'static,
274    {
275        self.with(|t| t.set_on_collapse(cb))
276    }
277
278    /// Removes all items from this view.
279    pub fn clear(&mut self) {
280        self.list.clear();
281        self.focus = 0;
282    }
283
284    /// Removes all items from this view, returning them.
285    pub fn take_items(&mut self) -> Vec<T> {
286        let items = self.list.take_items();
287        self.focus = 0;
288        items
289    }
290
291    /// Returns the number of items in this tree.
292    pub fn len(&self) -> usize {
293        self.list.len()
294    }
295
296    /// Returns `true` if this tree has no items.
297    pub fn is_empty(&self) -> bool {
298        self.list.is_empty()
299    }
300
301    /// Returns the index of the currently selected tree row.
302    ///
303    /// `None` is returned in case of the tree being empty.
304    pub fn row(&self) -> Option<usize> {
305        if self.is_empty() {
306            None
307        } else {
308            Some(self.focus)
309        }
310    }
311
312    /// Returns position on the x axis of the symbol (first character of an item) at the given row.
313    ///
314    /// `None` is returned in case the specified `row` does not visually exist.
315    pub fn first_col(&self, row: usize) -> Option<usize> {
316        let index = self.list.row_to_item_index(row);
317        self.list.first_col(index)
318    }
319
320    /// Returns total width (including the symbol) of the item at the given row.
321    ///
322    /// `None` is returned in case the specified `row` does not visually exist.
323    pub fn item_width(&self, row: usize) -> Option<usize> {
324        let index = self.list.row_to_item_index(row);
325        self.list.width(index).map(|width| width + SYMBOL_WIDTH)
326    }
327
328    /// Selects the row at the specified index.
329    pub fn set_selected_row(&mut self, row: usize) {
330        self.focus = row;
331    }
332
333    /// Selects the row at the specified index.
334    ///
335    /// Chainable variant.
336    pub fn selected_row(self, row: usize) -> Self {
337        self.with(|t| t.set_selected_row(row))
338    }
339
340    /// Returns a immutable reference to the item at the given row.
341    ///
342    /// `None` is returned in case the specified `row` does not visually exist.
343    pub fn borrow_item(&self, row: usize) -> Option<&T> {
344        let index = self.list.row_to_item_index(row);
345        self.list.get(index)
346    }
347
348    /// Returns a mutable reference to the item at the given row.
349    ///
350    /// `None` is returned in case the specified `row` does not visually exist.
351    pub fn borrow_item_mut(&mut self, row: usize) -> Option<&mut T> {
352        let index = self.list.row_to_item_index(row);
353        self.list.get_mut(index)
354    }
355
356    /// Inserts a new `item` at the given `row` with the specified
357    /// [`Placement`](enum.Placement.html), returning the visual row of the item
358    /// occupies after its insertion.
359    ///
360    ///
361    /// `None` will be returned in case the item is not visible after insertion
362    /// due to one of its parents being in a collapsed state.
363    pub fn insert_item(&mut self, item: T, placement: Placement, row: usize) -> Option<usize> {
364        let index = self.list.row_to_item_index(row);
365        self.list.insert_item(placement, index, item)
366    }
367
368    /// Inserts a new `container` at the given `row` with the specified
369    /// [`Placement`](enum.Placement.html), returning the visual row of the
370    /// container occupies after its insertion.
371    ///
372    /// A container is identical to a normal item except for the fact that it
373    /// can always be collapsed even if it does not contain any children.
374    ///
375    /// > Note: If the container is not visible because one of its parents is
376    /// > collapsed `None` will be returned since there is no visible row for
377    /// > the container to occupy.
378    pub fn insert_container_item(
379        &mut self,
380        item: T,
381        placement: Placement,
382        row: usize,
383    ) -> Option<usize> {
384        let index = self.list.row_to_item_index(row);
385        self.list.insert_container_item(placement, index, item)
386    }
387
388    /// Removes the item at the given `row` along with all of its children.
389    ///
390    /// The returned vector contains the removed items in top to bottom order.
391    ///
392    /// `None` is returned in case the specified `row` does not visually exist.
393    pub fn remove_item(&mut self, row: usize) -> Option<Vec<T>> {
394        let index = self.list.row_to_item_index(row);
395        let removed = self.list.remove_with_children(index);
396        self.focus = cmp::min(self.focus, self.list.height() - 1);
397        removed
398    }
399
400    /// Removes all children of the item at the given `row`.
401    ///
402    /// The returned vector contains the removed children in top to bottom order.
403    ///
404    /// `None` is returned in case the specified `row` does not visually exist.
405    pub fn remove_children(&mut self, row: usize) -> Option<Vec<T>> {
406        let index = self.list.row_to_item_index(row);
407        let removed = self.list.remove_children(index);
408        self.focus = cmp::min(self.focus, self.list.height() - 1);
409        removed
410    }
411
412    /// Extracts the item at the given `row` from the tree.
413    ///
414    /// All of the items children will be moved up one level within the tree.
415    ///
416    /// `None` is returned in case the specified `row` does not visually exist.
417    pub fn extract_item(&mut self, row: usize) -> Option<T> {
418        let index = self.list.row_to_item_index(row);
419        let removed = self.list.remove(index);
420        self.focus = cmp::min(self.focus, self.list.height() - 1);
421        removed
422    }
423
424    /// Collapses the children of the given `row`.
425    pub fn collapse_item(&mut self, row: usize) {
426        let index = self.list.row_to_item_index(row);
427        self.list.set_collapsed(index, true);
428    }
429
430    /// Expands the children of the given `row`.
431    pub fn expand_item(&mut self, row: usize) {
432        let index = self.list.row_to_item_index(row);
433        self.list.set_collapsed(index, false);
434    }
435
436    /// Collapses or expands the children of the given `row`.
437    pub fn set_collapsed(&mut self, row: usize, collapsed: bool) {
438        let index = self.list.row_to_item_index(row);
439        self.list.set_collapsed(index, collapsed);
440    }
441
442    /// Collapses or expands the children of the given `row`.
443    ///
444    /// Chained variant.
445    pub fn collapsed(self, row: usize, collapsed: bool) -> Self {
446        self.with(|t| t.set_collapsed(row, collapsed))
447    }
448
449    /// Select item `n` rows up from the one currently selected.
450    pub fn focus_up(&mut self, n: usize) {
451        self.focus -= cmp::min(self.focus, n);
452    }
453
454    /// Select item `n` rows down from the one currently selected.
455    pub fn focus_down(&mut self, n: usize) {
456        self.focus = cmp::min(self.focus + n, self.list.height() - 1);
457    }
458
459    /// Returns position of the parent of the item located in `row`.
460    ///
461    /// `None` is returned if `row` is not currenlty visible or if the item has no ancestors.
462    pub fn item_parent(&self, row: usize) -> Option<usize> {
463        let item_index = self.list.row_to_item_index(row);
464        let parent_index = self.list.item_parent_index(item_index)?;
465        Some(self.list.item_index_to_row(parent_index))
466    }
467
468    fn submit(&mut self) -> EventResult {
469        let row = self.focus;
470        let index = self.list.row_to_item_index(row);
471
472        if self.list.is_container_item(index) {
473            let collapsed = self.list.get_collapsed(index);
474            let children = self.list.get_children(index);
475
476            self.list.set_collapsed(index, !collapsed);
477
478            if self.on_collapse.is_some() {
479                let cb = self.on_collapse.clone().unwrap();
480                return EventResult::Consumed(Some(Callback::from_fn(move |s| {
481                    cb(s, row, !collapsed, children)
482                })));
483            }
484        } else if self.on_submit.is_some() {
485            let cb = self.on_submit.clone().unwrap();
486            return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row))));
487        }
488
489        EventResult::Ignored
490    }
491}
492
493impl<T: Display + Send + Sync + Debug + 'static> View for TreeView<T> {
494    fn draw(&self, printer: &Printer<'_, '_>) {
495        let index = self.list.row_to_item_index(0);
496        let items = self.list.items();
497        let list_index = Arc::new(Mutex::new(index));
498
499        for i in 0..self.list.height() {
500            let printer = printer.offset((0, i));
501            let mut index = list_index.lock().unwrap();
502
503            let item = &items[*index];
504            *index += item.len();
505
506            let color = if i == self.focus {
507                if self.enabled && printer.focused {
508                    ColorStyle::highlight()
509                } else {
510                    ColorStyle::highlight_inactive()
511                }
512            } else {
513                ColorStyle::primary()
514            };
515
516            printer.print((item.offset(), 0), item.symbol());
517
518            printer.with_color(color, |printer| {
519                printer.print(
520                    (item.offset() + SYMBOL_WIDTH, 0),
521                    format!("{}", item.value()).as_str(),
522                );
523            });
524        }
525    }
526
527    fn required_size(&mut self, _req: Vec2) -> Vec2 {
528        let w: usize = self
529            .list
530            .items()
531            .iter()
532            .map(|item| item.level() * 2 + format!("{}", item.value()).len() + 2)
533            .max()
534            .unwrap_or(0);
535
536        let h = self.list.height();
537
538        (w, h).into()
539    }
540
541    fn layout(&mut self, size: Vec2) {
542        self.last_size = size;
543    }
544
545    fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
546        (self.enabled && !self.is_empty())
547            .then(EventResult::consumed)
548            .ok_or(CannotFocus)
549    }
550
551    fn on_event(&mut self, event: Event) -> EventResult {
552        if !self.enabled {
553            return EventResult::Ignored;
554        }
555
556        let last_focus = self.focus;
557        match event {
558            Event::Key(Key::Up) if self.focus > 0 => {
559                self.focus_up(1);
560            }
561            Event::Key(Key::Down) if self.focus + 1 < self.list.height() => {
562                self.focus_down(1);
563            }
564            Event::Key(Key::PageUp) => {
565                self.focus_up(10);
566            }
567            Event::Key(Key::PageDown) => {
568                self.focus_down(10);
569            }
570            Event::Key(Key::Home) => {
571                self.focus = 0;
572            }
573            Event::Key(Key::End) => {
574                self.focus = self.list.height() - 1;
575            }
576            Event::Key(Key::Enter) => {
577                if !self.is_empty() {
578                    return self.submit();
579                }
580            }
581            Event::Mouse {
582                position,
583                offset,
584                event: MouseEvent::Press(btn),
585            } => {
586                if let Some(position) = position.checked_sub(offset) {
587                    match position.y {
588                        y if y == self.focus && btn == MouseButton::Left => return self.submit(),
589                        y if y < self.list.height() => self.focus = position.y,
590                        _ => return EventResult::Ignored,
591                    }
592                }
593            }
594            _ => return EventResult::Ignored,
595        }
596
597        let focus = self.focus;
598
599        if !self.is_empty() && last_focus != focus {
600            let row = self.focus;
601            EventResult::Consumed(
602                self.on_select
603                    .clone()
604                    .map(|cb| Callback::from_fn(move |s| cb(s, row))),
605            )
606        } else {
607            EventResult::Ignored
608        }
609    }
610
611    fn important_area(&self, size: Vec2) -> Rect {
612        Rect::from_size((0, self.focus), (size.x, 1))
613    }
614}