Skip to main content

cursive_tree/view/
tree.rs

1use super::super::{backend::*, model::*};
2
3use cursive::{direction::*, event::*, theme::*, view::*, *};
4
5const TYPE_NAME: &str = "TreeView";
6
7//
8// TreeView
9//
10
11/// Tree view.
12///
13/// Nodes can be leaves (no children) or branches (potentially have children) and can have custom
14/// data (`DataT`) attached to them. Their representation can be stylized with multiple colors and
15/// effects.
16///
17/// Supported events:
18///
19/// * Up/Down keys: move selection
20/// * PgUp/PgDown keys: move selection by 10 (configurable)
21/// * Left/Right keys: collapse/expand branch node
22/// * Enter key: toggle branch collapse/expand
23/// * Mouse click on node: select
24/// * Mouse click to the left of the node: toggle branch collapse/expand
25///
26/// The view's data and behavior are backed by a [TreeBackend]. The nodes are stored and managed by
27/// a [TreeModel]. The model can be populated in advance and can also fetch data from the backend
28/// on demand, e.g. when a branch node is expanded.
29pub struct TreeView<BackendT, ContextT, ErrorT, IdT, DataT> {
30    /// Tree model.
31    pub model: TreeModel<BackendT, ContextT, ErrorT, IdT, DataT>,
32
33    /// Selected row.
34    pub selected_row: Option<usize>,
35
36    /// Page size for PgUp/PgDown.
37    pub page: usize,
38
39    /// Whether we want to display debug information.
40    pub debug: bool,
41}
42
43impl<BackendT, ContextT, ErrorT, IdT, DataT> View for TreeView<BackendT, ContextT, ErrorT, IdT, DataT>
44where
45    BackendT: 'static + TreeBackend<ContextT, ErrorT, IdT, DataT> + Send + Sync,
46    ContextT: 'static + Clone + Send + Sync,
47    ErrorT: 'static + Send + Sync,
48    IdT: 'static + Send + Sync,
49    DataT: 'static + Send + Sync,
50{
51    fn type_name(&self) -> &'static str {
52        TYPE_NAME
53    }
54
55    fn take_focus(&mut self, _source: Direction) -> Result<EventResult, CannotFocus> {
56        if self.model.is_empty() { Err(CannotFocus) } else { Ok(EventResult::consumed()) }
57    }
58
59    fn required_size(&mut self, _constraint: XY<usize>) -> XY<usize> {
60        self.model.size()
61    }
62
63    fn important_area(&self, view_size: XY<usize>) -> Rect {
64        if let Some(selected_row) = self.selected_row {
65            Rect::from_size((0, selected_row), (view_size.x, selected_row))
66        } else {
67            Rect::from_size((0, 0), view_size)
68        }
69    }
70
71    fn on_event(&mut self, event: Event) -> EventResult {
72        let selected_row = self.selected_row;
73
74        match event {
75            Event::Key(Key::Up) => self.move_selection(-1),
76            Event::Key(Key::Down) => self.move_selection(1),
77            Event::Key(Key::PageUp) => self.move_selection(-(self.page as isize)),
78            Event::Key(Key::PageDown) => self.move_selection(self.page as isize),
79            Event::Key(Key::Home) => self.select_top(),
80            Event::Key(Key::End) => self.select_bottom(),
81
82            Event::Key(Key::Left) => {
83                if let Some(node) = self.selected_node_mut()
84                    && node.kind.is_branch()
85                {
86                    node.collapse_branch();
87                }
88            }
89
90            Event::Key(Key::Right) => {
91                let context = self.model.context.clone();
92                if let Some(node) = self.selected_node_mut()
93                    && node.kind.is_branch()
94                {
95                    return Self::expand_branch(node, context);
96                }
97            }
98
99            Event::Key(Key::Enter) => {
100                let context = self.model.context.clone();
101                if let Some(node) = self.selected_node_mut()
102                    && node.kind.is_branch()
103                {
104                    return Self::toggle_branch_state(node, context);
105                }
106            }
107
108            Event::Mouse { offset, position, event: MouseEvent::Press(_) } => {
109                self.selected_row = if let Some(position) = position.checked_sub(offset)
110                    && let Some(node) = self.model.at_row(position.y)
111                {
112                    if node.kind.is_branch() && (position.x < node.depth * 2 + 2) {
113                        // If we click to the left of the representation then toggle state
114                        let context = self.model.context.clone();
115                        if let Some(path) = self.model.path(node)
116                            && let Some(node) = self.model.at_path_mut(path)
117                        {
118                            return Self::toggle_branch_state(node, context);
119                        } else {
120                            None
121                        }
122                    } else {
123                        // Otherwise just select
124                        Some(position.y)
125                    }
126                } else {
127                    None
128                };
129            }
130
131            _ => return EventResult::Ignored,
132        }
133
134        EventResult::Consumed(if self.selected_row != selected_row {
135            // Selection has changed
136            Some(Callback::from_fn_once(|cursive| {
137                BackendT::handle_selection_changed(cursive);
138            }))
139        } else {
140            None
141        })
142    }
143
144    fn draw(&self, printer: &Printer) {
145        let mut start = XY::<usize>::default();
146        for node in self.model.iter(true) {
147            // Symbol
148
149            start.x = node.depth * 2;
150            match node.symbol(self.model.context.clone()) {
151                Symbol::Representation(symbol) => printer.print_styled(start, &symbol),
152                Symbol::String(symbol) => printer.print(start, symbol),
153            };
154            start.x += 2;
155
156            // Representation
157
158            let mut representation = &node.representation;
159
160            let debug_representation = if self.debug
161                && let Some(path) = self.model.path(node)
162            {
163                let path: Vec<_> = path.into_iter().map(|i| i.to_string()).collect();
164                let mut representation = representation.clone();
165                representation.append(format!(" {}", path.join(".")));
166                Some(representation)
167            } else {
168                None
169            };
170
171            if let Some(debug_representation) = &debug_representation {
172                representation = debug_representation;
173            }
174
175            let highlight = if self.is_selected(start.y) {
176                Some(if printer.focused { PaletteStyle::Highlight } else { PaletteStyle::HighlightInactive })
177            } else {
178                None
179            };
180
181            let print = |printer: &Printer| printer.print_styled(start, representation);
182
183            match highlight {
184                Some(highlight) => printer.with_style(highlight, print),
185                None => print(printer),
186            }
187
188            start.y += node.representation_size.y;
189        }
190    }
191}
192
193impl<BackendT, ContextT, ErrorT, IdT, DataT> From<TreeModel<BackendT, ContextT, ErrorT, IdT, DataT>>
194    for TreeView<BackendT, ContextT, ErrorT, IdT, DataT>
195{
196    fn from(model: TreeModel<BackendT, ContextT, ErrorT, IdT, DataT>) -> Self {
197        Self { model, selected_row: None, page: 10, debug: false }
198    }
199}