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}