use super::{ utils::scroll_vertical::VerticalScroll, CommandBlocking, CommandInfo, Component, DrawableComponent, EventState, }; use crate::event::Key; use crate::ui::common_nav; use crate::ui::scrolllist::draw_list_block; use anyhow::Result; use database_tree::{DatabaseTree, DatabaseTreeItem}; use std::convert::From; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Style}, text::Span, widgets::{Block, Borders}, Frame, }; // ▸ const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; // ▾ const FOLDER_ICON_EXPANDED: &str = "\u{25be}"; const EMPTY_STR: &str = ""; pub struct DatabasesComponent { pub tree: DatabaseTree, pub scroll: VerticalScroll, pub focused: bool, } impl DatabasesComponent { pub fn new() -> Self { Self { tree: DatabaseTree::default(), scroll: VerticalScroll::new(), focused: true, } } fn tree_item_to_span(item: &DatabaseTreeItem, selected: bool, width: u16) -> Span<'_> { let name = item.kind().name(); let indent = item.info().indent(); let indent_str = if indent == 0 { String::from("") } else { format!("{:w$}", " ", w = (indent as usize) * 2) }; let is_database = item.kind().is_database(); let path_arrow = if is_database { if item.kind().is_database_collapsed() { FOLDER_ICON_COLLAPSED } else { FOLDER_ICON_EXPANDED } } else { EMPTY_STR }; let name = format!( "{}{}{:w$}", indent_str, path_arrow, name, w = width as usize ); Span::styled( name, if selected { Style::default().fg(Color::Magenta).bg(Color::Green) } else { Style::default() }, ) } fn draw_tree(&self, f: &mut Frame, area: Rect) { let tree_height = usize::from(area.height.saturating_sub(2)); self.tree.visual_selection().map_or_else( || { self.scroll.reset(); }, |selection| { self.scroll .update(selection.index, selection.count, tree_height); }, ); let items = self .tree .iterate(self.scroll.get_top(), tree_height) .map(|(item, selected)| Self::tree_item_to_span(item, selected, area.width)); let title = "Databases"; draw_list_block( f, area, Block::default() .title(Span::styled(title, Style::default())) .style(if self.focused { Style::default() } else { Style::default().fg(Color::DarkGray) }) .borders(Borders::ALL) .border_style(Style::default()), items, ); self.scroll.draw(f, area); } } impl DrawableComponent for DatabasesComponent { fn draw(&self, f: &mut Frame, area: Rect) -> Result<()> { if true { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(100)].as_ref()) .split(area); self.draw_tree(f, chunks[0]); } Ok(()) } } impl Component for DatabasesComponent { fn commands(&self, _out: &mut Vec, _force_all: bool) -> CommandBlocking { CommandBlocking::PassingOn } fn event(&mut self, key: Key) -> Result { if tree_nav(&mut self.tree, key) { return Ok(EventState::Consumed); } Ok(EventState::NotConsumed) } } fn tree_nav(tree: &mut DatabaseTree, key: Key) -> bool { if let Some(common_nav) = common_nav(key) { tree.move_selection(common_nav) } else { false } }