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>
30where
31    BackendT: TreeBackend,
32{
33    /// Tree model.
34    pub model: TreeModel<BackendT>,
35
36    /// Selected row.
37    pub selected_row: Option<usize>,
38
39    /// Page size for PgUp/PgDown.
40    pub page: usize,
41
42    /// Whether we want to display debug information.
43    pub debug: bool,
44}
45
46impl<BackendT> View for TreeView<BackendT>
47where
48    BackendT: 'static + TreeBackend + Send + Sync,
49    BackendT::Context: Clone + Send + Sync,
50    BackendT::Error: Send + Sync,
51    BackendT::ID: Send + Sync,
52    BackendT::Data: Send + Sync,
53{
54    fn type_name(&self) -> &'static str {
55        TYPE_NAME
56    }
57
58    fn take_focus(&mut self, _source: Direction) -> Result<EventResult, CannotFocus> {
59        if self.model.is_empty() { Err(CannotFocus) } else { Ok(EventResult::consumed()) }
60    }
61
62    fn required_size(&mut self, _constraint: Vec2) -> Vec2 {
63        self.model.size()
64    }
65
66    fn important_area(&self, view_size: Vec2) -> Rect {
67        if let Some(selected_row) = self.selected_row {
68            Rect::from_size((0, selected_row), (view_size.x, selected_row))
69        } else {
70            Rect::from_size((0, 0), view_size)
71        }
72    }
73
74    fn on_event(&mut self, event: Event) -> EventResult {
75        let selected_row = self.selected_row;
76
77        match event {
78            Event::Key(Key::Up) => self.move_selection(-1),
79            Event::Key(Key::Down) => self.move_selection(1),
80            Event::Key(Key::PageUp) => self.move_selection(-(self.page as isize)),
81            Event::Key(Key::PageDown) => self.move_selection(self.page as isize),
82            Event::Key(Key::Home) => self.select_top(),
83            Event::Key(Key::End) => self.select_bottom(),
84
85            Event::Key(Key::Left) => {
86                if let Some(node) = self.selected_node_mut()
87                    && node.kind.is_branch()
88                {
89                    node.collapse_branch();
90                }
91            }
92
93            Event::Key(Key::Right) => {
94                let context = self.model.context.clone();
95                if let Some(node) = self.selected_node_mut()
96                    && node.kind.is_branch()
97                {
98                    return Self::expand_branch(node, context);
99                }
100            }
101
102            Event::Key(Key::Enter) => {
103                let context = self.model.context.clone();
104                if let Some(node) = self.selected_node_mut()
105                    && node.kind.is_branch()
106                {
107                    return Self::toggle_branch_state(node, context);
108                }
109            }
110
111            Event::Mouse { offset, position, event: MouseEvent::Press(_) } => {
112                self.selected_row = if let Some(position) = position.checked_sub(offset)
113                    && let Some(node) = self.model.at_row(position.y)
114                {
115                    if node.kind.is_branch() && (position.x < node.depth * 2 + 2) {
116                        // If we click to the left of the representation then toggle state
117                        let context = self.model.context.clone();
118                        if let Some(path) = self.model.path(node)
119                            && let Some(node) = self.model.at_path_mut(path)
120                        {
121                            return Self::toggle_branch_state(node, context);
122                        } else {
123                            None
124                        }
125                    } else {
126                        // Otherwise just select
127                        Some(position.y)
128                    }
129                } else {
130                    None
131                };
132            }
133
134            _ => return EventResult::Ignored,
135        }
136
137        EventResult::Consumed(if self.selected_row != selected_row {
138            // Selection has changed
139            Some(Callback::from_fn_once(|cursive| {
140                BackendT::handle_selection_changed(cursive);
141            }))
142        } else {
143            None
144        })
145    }
146
147    fn draw(&self, printer: &Printer) {
148        let mut start = XY::<usize>::default();
149        for node in self.model.iter(true) {
150            // Symbol
151
152            start.x = node.depth * 2;
153            match node.symbol(self.model.context.clone()) {
154                Symbol::Representation(symbol) => printer.print_styled(start, &symbol),
155                Symbol::String(symbol) => printer.print(start, symbol),
156            };
157            start.x += 2;
158
159            // Representation
160
161            let mut representation = &node.representation;
162
163            let debug_representation = if self.debug
164                && let Some(path) = self.model.path(node)
165            {
166                let path: Vec<_> = path.into_iter().map(|i| i.to_string()).collect();
167                let mut representation = representation.clone();
168                representation.append(format!(" {}", path.join(".")));
169                Some(representation)
170            } else {
171                None
172            };
173
174            if let Some(debug_representation) = &debug_representation {
175                representation = debug_representation;
176            }
177
178            let highlight = if self.is_selected(start.y) {
179                Some(if printer.focused { PaletteStyle::Highlight } else { PaletteStyle::HighlightInactive })
180            } else {
181                None
182            };
183
184            let print = |printer: &Printer| printer.print_styled(start, representation);
185
186            match highlight {
187                Some(highlight) => printer.with_style(highlight, print),
188                None => print(printer),
189            }
190
191            start.y += node.representation_size.y;
192        }
193    }
194}
195
196impl<BackendT> From<TreeModel<BackendT>> for TreeView<BackendT>
197where
198    BackendT: TreeBackend,
199{
200    fn from(model: TreeModel<BackendT>) -> Self {
201        Self { model, selected_row: None, page: 10, debug: false }
202    }
203}