cursive-tree 0.0.9

Tree view for the Cursive TUI library
Documentation
use super::{
    super::{backend::*, model::*},
    actions::*,
    tree::*,
};

use cursive::{direction::*, event::*, theme::*, view::*, *};

const TYPE_NAME: &str = "TreeView";

impl<BackendT> View for TreeView<BackendT>
where
    BackendT: 'static + TreeBackend + Send + Sync,
    BackendT::Context: Clone + Send + Sync,
    BackendT::Error: Send + Sync,
    BackendT::ID: Send + Sync,
    BackendT::Data: Send + Sync,
{
    fn type_name(&self) -> &'static str {
        TYPE_NAME
    }

    fn take_focus(&mut self, _source: Direction) -> Result<EventResult, CannotFocus> {
        if self.model.is_empty() { Err(CannotFocus) } else { Ok(EventResult::consumed()) }
    }

    fn required_size(&mut self, _constraint: Vec2) -> Vec2 {
        self.model.extents()
    }

    fn important_area(&self, view_size: Vec2) -> Rect {
        if let Some(selected_row) = self.selected_row {
            Rect::from_size((0, selected_row), (view_size.x, 1))
        } else {
            Rect::from_size((0, 0), view_size)
        }
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        let last_selected_row = self.selected_row;

        match self.actions.get(&event) {
            Some(action) => match action {
                Action::SelectTop => self.select_top_(),
                Action::SelectUp => self.move_selection_(-1),
                Action::SelectUpPage => self.move_selection_(-(self.page as isize)),
                Action::SelectBottom => self.select_bottom_(),
                Action::SelectDown => self.move_selection_(1),
                Action::SelectDownPage => self.move_selection_(self.page as isize),

                Action::Expand => {
                    let context = self.model.context.clone();
                    if let Some(node) = self.selected_node_mut() {
                        return Self::on_expand_branch(node, Some(1), context);
                    }
                }

                Action::ExpandRecursive => {
                    let context = self.model.context.clone();
                    if let Some(node) = self.selected_node_mut() {
                        return Self::on_expand_branch(node, None, context);
                    }
                }

                Action::ExpandAll => {
                    self.selected_row = None; // TODO: keep the selected node?
                    return self.on_expand_all();
                }

                Action::Collapse => {
                    if let Some(node) = self.selected_node_mut() {
                        node.collapse(Some(1));
                    }
                }

                Action::CollapseRecursive => {
                    if let Some(node) = self.selected_node_mut() {
                        node.collapse(None);
                    }
                }

                Action::CollapseAll => {
                    self.selected_row = None; // TODO: keep the selected node?
                    self.model.collapse(None);
                }

                Action::ToggleState => {
                    let context = self.model.context.clone();
                    if let Some(node) = self.selected_node_mut() {
                        return Self::on_toggle_branch_state(node, context);
                    }
                }

                Action::ToggleDebug => {
                    self.debug = !self.debug;
                }
            },

            None => {
                match event {
                    Event::Mouse { offset, position, event: MouseEvent::Press(_) } => {
                        self.selected_row = if let Some(position) = position.checked_sub(offset)
                            && let Some(node) = self.model.at_row(position.y)
                        {
                            if node.kind.is_branch() && (position.x < node.depth * 2 + 2) {
                                // If we click to the left of the label then toggle state
                                let context = self.model.context.clone();
                                if let Some(path) = self.model.path(node)
                                    && let Some(node) = self.model.at_path_mut(path)
                                {
                                    return Self::on_toggle_branch_state(node, context);
                                } else {
                                    None
                                }
                            } else {
                                // Otherwise just select
                                Some(position.y)
                            }
                        } else {
                            None
                        };
                    }

                    _ => return EventResult::Ignored,
                }
            }
        }

        self.on_selection_changed(last_selected_row)
    }

    fn draw(&self, printer: &Printer) {
        let mut start = Vec2::default();
        for node in self.model.iter(true) {
            if start.y >= printer.offset.y + printer.size.y {
                // We're beyond the print area
                break;
            }

            // Start printing only when we're within the print area
            if start.y >= printer.content_offset.y {
                // Symbol

                start.x = node.depth * 2;
                match node.symbol(self.model.context.clone()) {
                    Symbol::SpannedString(symbol) => printer.print_styled(start, &symbol),
                    Symbol::String(symbol) => printer.print(start, symbol),
                };
                start.x += 2;

                // Label

                let mut label = &node.label;

                let debug_label = if self.debug
                    && let Some(path) = self.model.path(node)
                {
                    let path: Vec<_> = path.into_iter().map(|i| i.to_string()).collect();
                    let mut label = label.clone();
                    label.append(format!(" {}", path.join(".")));
                    Some(label)
                } else {
                    None
                };

                if let Some(debug_label) = &debug_label {
                    label = debug_label;
                }

                let highlight = if self.is_selected(start.y) {
                    Some(if printer.focused { PaletteStyle::Highlight } else { PaletteStyle::HighlightInactive })
                } else {
                    None
                };

                let print = |printer: &Printer| printer.print_styled(start, label);

                match highlight {
                    Some(highlight) => printer.with_style(highlight, print),
                    None => print(printer),
                }
            }

            start.y += node.label_size.y;
        }
    }
}