From 6554c2d33ced0db1deff235a565963a662d0a141 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 4 May 2021 23:41:51 -0400 Subject: Reset. I'm starting over in another repo for this rewrite. --- .vscode/settings.json | 159 ------- src/canvas.rs | 2 - src/canvas/canvas_colours/colour_utils.rs | 1 - src/canvas/components.rs | 11 - src/canvas/components/base_widget.rs | 22 - src/canvas/components/chart.rs | 0 src/canvas/components/container.rs | 261 ------------ src/canvas/components/table.rs | 552 ------------------------- src/canvas/components/text_input.rs | 0 src/canvas/components/widget_event_handlers.rs | 44 -- src/canvas/widgets/scroll_search_table.rs | 99 ----- 11 files changed, 1151 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 src/canvas/components.rs delete mode 100644 src/canvas/components/base_widget.rs delete mode 100644 src/canvas/components/chart.rs delete mode 100644 src/canvas/components/container.rs delete mode 100644 src/canvas/components/table.rs delete mode 100644 src/canvas/components/text_input.rs delete mode 100644 src/canvas/components/widget_event_handlers.rs diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c84a6aba..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "cSpell.words": [ - "ABRT", - "ALRM", - "Artem", - "CHLD", - "COPR", - "Condvar", - "DWORD", - "Deque", - "EINVAL", - "EPERM", - "ESRCH", - "Erlend", - "Fini", - "GIBI", - "GIBIBYTE", - "GIGA", - "Hamberg", - "KIBI", - "LUKS", - "Lukas", - "MEBI", - "MEBIBYTE", - "MSRV", - "Mahmoud", - "Marcin", - "Mousebindings", - "NAS's", - "Nonexhaustive", - "PEBI", - "PETA", - "PKGBUILD", - "PKGBUILDs", - "Polishchuk", - "Qudsi", - "RTMAX", - "RTMIN", - "Rysavy", - "SEGV", - "SIGTERM", - "STKFLT", - "Swatinem", - "TEBI", - "TERA", - "TSTP", - "TTIN", - "TTOU", - "Tebibytes", - "Toolset", - "Ungrouped", - "VTALRM", - "WASD", - "Wojnarowski", - "XCPU", - "XFSZ", - "aarch", - "andys", - "armhf", - "armv", - "atim", - "autohide", - "choco", - "chocolateyinstall", - "cmdline", - "commandline", - "concat", - "crossterm", - "curr", - "cvar", - "cvars", - "czvf", - "denylist", - "doctest", - "dont", - "drprasad", - "eselect", - "fedoracentos", - "fpath", - "fract", - "fxhash", - "getpwuid", - "gnueabihf", - "gotop", - "gotop's", - "gtop", - "haase", - "heim", - "hjkl", - "htop", - "indexmap", - "iwlwifi", - "keybinds", - "le", - "libc", - "markdownlint", - "memb", - "minmax", - "minwindef", - "musl", - "musleabihf", - "n'th", - "nixos", - "noheader", - "nord", - "ntdef", - "nuget", - "nvme", - "paren", - "pcpu", - "piasecki", - "pids", - "pmem", - "powerpc", - "powerpc le", - "ppid", - "prepush", - "processthreadsapi", - "pvanheus", - "regexes", - "rposition", - "rsplitn", - "runlevel", - "rustc", - "rustflags", - "rustfmt", - "shilangyu", - "smol", - "softirq", - "splitn", - "statm", - "stime", - "subwidget", - "svenstaro", - "sysconf", - "sysinfo", - "tebibyte", - "tokei", - "trung", - "tui's", - "twrite", - "usage", - "use", - "use curr usage", - "utime", - "virbr", - "virt", - "vsize", - "whitespaces", - "wifi", - "winapi", - "winget", - "winnt", - "wixtoolset", - "xargs", - "xzvf", - "ytop" - ] -} diff --git a/src/canvas.rs b/src/canvas.rs index 5decbaf8..d491dd2d 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -12,7 +12,6 @@ use tui::{ // use ordered_float::OrderedFloat; use canvas_colours::*; -use components::*; use dialogs::*; use widgets::*; @@ -31,7 +30,6 @@ use crate::{ }; pub mod canvas_colours; -mod components; mod dialogs; mod drawing_utils; mod widgets; diff --git a/src/canvas/canvas_colours/colour_utils.rs b/src/canvas/canvas_colours/colour_utils.rs index 47ab49f5..e9428eaa 100644 --- a/src/canvas/canvas_colours/colour_utils.rs +++ b/src/canvas/canvas_colours/colour_utils.rs @@ -5,7 +5,6 @@ use tui::style::{Color, Style}; use crate::utils::error; -// Approx, good enough for use (also Clippy gets mad if it's too long) pub const STANDARD_FIRST_COLOUR: Color = Color::LightMagenta; pub const STANDARD_SECOND_COLOUR: Color = Color::LightYellow; pub const STANDARD_THIRD_COLOUR: Color = Color::LightCyan; diff --git a/src/canvas/components.rs b/src/canvas/components.rs deleted file mode 100644 index 79021094..00000000 --- a/src/canvas/components.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod table; -pub use table::*; - -pub mod widget_event_handlers; -pub use widget_event_handlers::*; - -pub mod base_widget; -pub use base_widget::*; - -pub mod container; -pub use container::*; diff --git a/src/canvas/components/base_widget.rs b/src/canvas/components/base_widget.rs deleted file mode 100644 index 09e9ab96..00000000 --- a/src/canvas/components/base_widget.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::borrow::Cow; - -use tui::{backend::Backend, layout::Rect, Frame}; - -pub trait BaseWidget -where - B: Backend, -{ - /// How a widget is to be drawn. - fn draw(&mut self, frame: &mut Frame<'_, B>); - - /// Return the widget's ID. - fn get_widget_id(&self) -> u16; - - /// Set new drawing bounds for a widget. - fn set_draw_bounds(&mut self, new_bounds: Rect); - - /// Returns the name of the widget if it exists. The default implementation returns `None`. - fn get_name(&self) -> Option> { - None - } -} diff --git a/src/canvas/components/chart.rs b/src/canvas/components/chart.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/canvas/components/container.rs b/src/canvas/components/container.rs deleted file mode 100644 index 468023d3..00000000 --- a/src/canvas/components/container.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use indexmap::IndexMap; -use itertools::izip; -use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - Frame, -}; - -use super::{BaseWidget, ClickHandler, KeyHandler, ScrollHandler}; - -pub struct Container -where - B: Backend, -{ - /// The children of the container and their corresponding constraints. - children: IndexMap>, Constraint)>, - - /// The widget ID of the container. - widget_id: u16, - - /// The bounds of the container. - draw_bounds: Rect, - - /// Which direction to align children in. - direction: Direction, - - /// The margins between the children of the container. - child_margin: u16, -} - -impl Container -where - B: Backend, -{ - /// Creates a new container. - pub fn new_container( - direction: Direction, - children: IndexMap>, Constraint)>, - widget_id: u16, - children_margin: u16, - //horizontal_alignment: Alignment, vertical_alignment: Alignment, - ) -> Self { - Container { - children, - widget_id, - draw_bounds: Rect::default(), - direction, - child_margin: children_margin, - // horizontal_alignment, - // vertical_alignment, - } - } - - /// Creates a new row container (children are horizontally separated). - - pub fn new_row( - children: IndexMap>, Constraint)>, - widget_id: u16, - children_margin: u16, - // horizontal_alignment: Alignment, vertical_alignment: Alignment, - ) -> Self { - Self::new_container( - Direction::Horizontal, - children, - widget_id, - children_margin, - // horizontal_alignment, - // vertical_alignment, - ) - } - - /// Creates a new column container (children are vertically separated). - pub fn new_column( - children: IndexMap>, Constraint)>, - widget_id: u16, - children_margin: u16, - // horizontal_alignment: Alignment, vertical_alignment: Alignment, - ) -> Self { - Self::new_container( - Direction::Vertical, - children, - widget_id, - children_margin, - // horizontal_alignment, - // vertical_alignment, - ) - } - - /// Updates the bounds of the container with new constraints. - pub fn update_constraints(&mut self, new_constraints: &[Constraint]) { - let new_bounds = { - let layout = Layout::default() - .direction(self.direction.clone()) - .constraints(new_constraints); - - match self.direction { - Direction::Horizontal => layout.horizontal_margin(self.child_margin), - Direction::Vertical => layout.vertical_margin(self.child_margin), - } - } - .split(self.draw_bounds); - - izip!(&mut self.children, new_constraints, new_bounds).for_each( - |((_child_id, child), new_constraint, new_bound)| { - child.0.set_draw_bounds(new_bound); - child.1 = *new_constraint; - }, - ); - } - - /// Sets the children of a `Container`. - pub fn set_children( - mut self, new_children: IndexMap>, Constraint)>, - ) -> Self { - self.children = new_children; - self.update_child_bounds(); - - self - } - - /// Adds a child and corresponding constraint to the end of the container, and updates the new bounds. - pub fn add_child(&mut self, new_child: Box>, new_constraint: Constraint) { - self.children - .insert(new_child.get_widget_id(), (new_child, new_constraint)); - self.update_child_bounds(); - } - - /// Updates the bounds of each child in the container given its current state. - /// This should be called after any updates to either the container's own bounds or - /// when adding a new child + constraint to the container. - fn update_child_bounds(&mut self) { - let new_bounds = { - let layout = Layout::default() - .direction(self.direction.clone()) - .constraints( - self.children - .iter() - .map(|(_child_id, (_child, constraint))| *constraint) - .collect::>(), - ); - - match self.direction { - Direction::Horizontal => layout.horizontal_margin(self.child_margin), - Direction::Vertical => layout.vertical_margin(self.child_margin), - } - } - .split(self.draw_bounds); - - self.children.iter_mut().zip(new_bounds).for_each( - |((_child_id, (child, _constraint)), new_bound)| { - child.set_draw_bounds(new_bound); - }, - ); - } -} - -impl BaseWidget for Container -where - B: Backend, -{ - fn draw(&mut self, frame: &mut Frame<'_, B>) - where - B: Backend, - { - for (_child_id, (child, _constraint)) in &mut self.children { - child.draw(frame); - } - } - - fn get_widget_id(&self) -> u16 { - self.widget_id - } - - fn set_draw_bounds(&mut self, new_bounds: Rect) { - self.draw_bounds = new_bounds; - - self.update_child_bounds(); - } -} - -impl ClickHandler for Container -where - B: Backend, -{ - type SignalType = (); - - fn is_widget_in_bounds(&self, x: u16, y: u16) -> bool { - x >= self.draw_bounds.x - && x < self.draw_bounds.x + self.draw_bounds.width - && y >= self.draw_bounds.y - && y < self.draw_bounds.y + self.draw_bounds.height - } - - fn on_left_click(&mut self, _x: u16, _y: u16) -> Option { - None - } - - fn on_middle_click(&mut self, _x: u16, _y: u16) -> Option { - None - } - - fn on_right_click(&mut self, _x: u16, _y: u16) -> Option { - None - } -} - -impl ScrollHandler for Container -where - B: Backend, -{ - type SignalType = (); - - fn on_scroll(&mut self) -> Option { - // TODO: This - None - } -} - -impl KeyHandler for Container -where - B: Backend, -{ - type SignalType = (); - - fn on_key(&mut self, event: crossterm::event::KeyEvent) -> Option { - if event.modifiers.is_empty() { - match event.code { - _ => None, - } - } else { - match event.modifiers { - KeyModifiers::CONTROL | KeyModifiers::SHIFT => match event.code { - KeyCode::Left => { - // Try to move to the next widget in this direction; - // if we fail, then propagate back up and see if a parent `Container` - // can handle the movement. - - None - } - KeyCode::Right => None, - KeyCode::Up => None, - KeyCode::Down => None, - KeyCode::Char(c) => { - // This is a workaround as in some cases, if you type in, say, a capital 'G', - // that's recorded as a shift + 'G', and not just 'G'. - // So, just recurse and call the `on_key` function with no modifier! - if event.modifiers == KeyModifiers::SHIFT { - self.on_key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty())) - } else { - None - } - } - _ => None, - }, - - _ => None, - } - } - } -} diff --git a/src/canvas/components/table.rs b/src/canvas/components/table.rs deleted file mode 100644 index bdfeed26..00000000 --- a/src/canvas/components/table.rs +++ /dev/null @@ -1,552 +0,0 @@ -use std::convert::TryFrom; -use std::{borrow::Cow, cell::RefCell}; - -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use tui::{ - backend::Backend, - layout::{Constraint, Rect}, - widgets::{Block, Borders, Cell, Row, Table, TableState}, - Frame, -}; -use unicode_segmentation::UnicodeSegmentation; - -use crate::{app::AppState, constants::TABLE_GAP_HEIGHT_LIMIT}; - -use super::{ - widget_event_handlers::{ClickHandler, KeyHandler, ScrollHandler}, - BaseWidget, -}; - -/// Flexible constraints. -pub enum FlexConstraint { - Length(u16), - Percentage(f64), -} - -pub struct Coordinate { - pub x: u16, - pub y: u16, -} - -/// Signals to propagate back up from a table. -pub enum TableKeySignal { - OpenSort, - OpenSearch, -} - -pub struct TableColumn { - /// The desired width of the column. - pub desired_width: u16, - - /// The desired upper flex bound. If it is not present (`None`), then - /// the column is assumed to be inflexible. - pub upper_bound: Option, - - /// The column header - pub column_header: Cow<'static, str>, - - /// Whether this column is the column we are sorting by. - pub is_sorting_column: bool, - - /// Whether this column is currently hidden. - pub is_hidden: bool, - - /// The relative mouse x bounds of a column. We don't store the y, since that's implicitly - /// known. Since the column may be hidden, the bounds are optional. - pub x_bounds: Option<(u16, u16)>, -} - -#[derive(Default)] -struct HorizontalScrollState { - offset_multiplier: usize, -} - -#[derive(Debug)] -pub enum ScrollDirection { - /// Up means scrolling up, which decrements an index. - Up, - /// Down means scrolling down, which increments an index. - Down, -} - -impl Default for ScrollDirection { - fn default() -> Self { - ScrollDirection::Down - } -} - -#[derive(Default)] -struct VerticalScrollState { - pub current_position: usize, - pub previous_position: usize, - pub scroll_direction: ScrollDirection, -} - -enum TableWidthStrategy { - MaxNumColumns, - MaxColumnInfo, -} - -pub struct TextTable<'d> { - /// Representing the columns and headers of the table. Each column contains its data. - columns: &'d Vec, - - /// Represents our processed and sorted data as per the table's state. - data: &'d Vec>>, - - /// Represents the application's state. - app_state: &'static AppState, - - /// Represents the drawing bounds of the table. - draw_bounds: Rect, - - /// Represents the horizontal scrolling state. - horizontal_state: HorizontalScrollState, - - /// Represents the vertical scrolling state. - vertical_state: VerticalScrollState, - - /// The vertical start index from where to slice our data from. - /// Determined by the vertical scroll state. - vertical_start_index: usize, - - /// The vertical end index from where to stop the slice of our data from. - /// Determined by the vertical scroll state. - vertical_end_index: usize, - - /// Represents how column widths are calculated. - width_strategy: TableWidthStrategy, - - /// Calculated column widths. - column_widths: Vec, - - /// A constant offset to the table's actual height to account for the border and table gaps. - table_height_offset: u16, - - /// The underlying tui-rs table state - table_state: RefCell, - - /// The widget's ID. - widget_id: u16, - - /// The gap size between the table headers and data. Overrides `table_gap`. - table_offset: u16, - - /// The border type of the table. - border_type: Borders, -} - -impl<'d> TextTable<'d> { - /// Creates a new `TextTable`. - pub fn new( - widget_id: u16, columns: &'d Vec, data: &'d Vec>>, - app_state: &'static AppState, - ) -> Self { - // TextTable { - // columns, - // data, - // app_state, - // draw_bounds: Rect::default(), - // horizontal_state: HorizontalScrollState::default(), - // vertical_state: VerticalScrollState::default(), - // vertical_start_index: 0, - // vertical_end_index: 0, - // width_strategy: TableWidthStrategy::MaxColumnInfo, - // column_widths, - // table_height_offset: 0, - // table_state: (), - // widget_id, - // table_offset: 0, - // given_table_gap: (), - // border_type: Borders::ALL, - // } - - todo!() - } - - /// This column width strategy takes into account either a given width, or a set of width bounds + desired width. - /// It then determines how to best maximize the number of columns while still respecting the bounds. - /// - /// This is the old behaviour used before the widget system rewrite. - fn get_column_widths_maximize_num_columns(&self) -> Vec { - let mut total_width = self.draw_bounds.width; - let mut bailed_early = false; - let mut calculated_widths: Vec = vec![]; - - vec![] - } - - /// This column width strategy uses the maximal size of the column to calculate - /// the column widths. It's basically just a greedy algorithm. - fn get_column_widths_maximize_column_info(&self) -> Vec { - let mut total_width = self.draw_bounds.width; - let mut bailed_early = false; - let mut calculated_widths: Vec = vec![]; - - if self.horizontal_state.offset_multiplier > 0 { - // If there is any horizontal scrolling to the right, - // enforce a one unit loss to the total available width and - // set aside one column for the horizontal scroll marker. - - total_width -= 1; - calculated_widths.push(1); - } - - for column in self.columns { - if !column.is_hidden { - if total_width < column.desired_width { - // Darn, we can't add it. - bailed_early = true; - break; - } else { - total_width -= column.desired_width; - calculated_widths.push(column.desired_width); - } - } - } - - if bailed_early { - // Basically redo the entire thing with a 1 pixel removal. We can work with - // a smaller set of column widths though. - let mut new_total_width = self.draw_bounds.width - 1; - let mut new_calculated_widths: Vec = vec![]; - - if self.horizontal_state.offset_multiplier > 0 { - new_total_width -= 1; - new_calculated_widths.push(1); - } - - for column_width in calculated_widths { - if new_total_width < column_width { - // Stop adding. Halt. - break; - } else { - new_total_width -= column_width; - new_calculated_widths.push(column_width); - } - } - - new_calculated_widths.push(1); - calculated_widths = new_calculated_widths; - total_width = new_total_width; - } - - // Now distribute any remaining space. - let per_col_space = - u16::try_from(usize::from(total_width) / calculated_widths.len()).unwrap_or(0); - let mut remaining_col_space = - u16::try_from(usize::from(total_width) % calculated_widths.len()).unwrap_or(0); - - for itx in 0..calculated_widths.len() { - let remaining = if remaining_col_space > 0 { - remaining_col_space -= 1; - 1 - } else { - 0 - }; - calculated_widths[itx] += per_col_space + remaining; - } - - calculated_widths - .into_iter() - .map(|width| Constraint::Length(width)) - .collect() - } - - /// Gets the starting index position of a vertically scrolled table. - fn get_vertical_start_position(&mut self, num_rows: usize) { - self.vertical_start_index = match self.vertical_state.scroll_direction { - ScrollDirection::Down => { - if self.vertical_state.current_position - < self.vertical_state.previous_position + num_rows - { - // If, using previous_scrolled_position, we can see the element - // (so within that and + num_rows) just reuse the current previously scrolled position - self.vertical_state.previous_position - } else if self.vertical_state.current_position >= num_rows { - // Else if the current position past the last element visible in the list, omit - // until we can see that element - self.vertical_state.previous_position = - self.vertical_state.current_position - num_rows; - self.vertical_state.previous_position - } else { - // Else, if it is not past the last element visible, do not omit anything - 0 - } - } - ScrollDirection::Up => { - if self.vertical_state.current_position <= self.vertical_state.previous_position { - // If it's past the first element, then show from that element downwards - self.vertical_state.previous_position = self.vertical_state.current_position; - } else if self.vertical_state.current_position - >= self.vertical_state.previous_position + num_rows - { - self.vertical_state.previous_position = - self.vertical_state.current_position - num_rows; - } - // Else, don't change what our start position is from whatever it is set to! - self.vertical_state.previous_position - } - }; - - // TODO: Not sure if the upper bound is right... - self.vertical_end_index = self.vertical_start_index + usize::from(self.draw_bounds.height) - - usize::from(self.table_offset); - } - - /// Update the stored data within the widget with newer data. - /// - /// Main thing here is re-sorting any data and updating any desired column widths, - /// calculated column widths, etc. - fn update_data(&mut self, new_data: &'d Vec>>) { - self.data = new_data; - - // Update desired column widths - self.column_widths = match self.width_strategy { - TableWidthStrategy::MaxNumColumns => self.get_column_widths_maximize_num_columns(), - TableWidthStrategy::MaxColumnInfo => self.get_column_widths_maximize_column_info(), - }; - - // Calculate column widths if needed and store for later use - } -} - -impl<'d, B> BaseWidget for TextTable<'d> -where - B: Backend, -{ - fn draw(&mut self, frame: &mut Frame<'_, B>) { - // Note that self is mutable, but this is really not needed outside of managing - // the state of tui's TableState. - - // Gather data as required, and put it into Rows. We assume that this data is sorted as required. - let gathered_data = { - let sliced_rows = &self.data[self.vertical_start_index..self.vertical_end_index]; - - sliced_rows - .iter() - .zip(&self.column_widths) - .map(|(data_row, constraint)| { - Row::new( - data_row - .iter() - .zip(self.columns) - .filter_map(|(data, column)| { - if column.is_hidden { - None - } else { - if let Constraint::Length(length) = constraint { - let graphemes = - UnicodeSegmentation::graphemes(data.as_ref(), true) - .collect::>(); - let mut truncated_data = String::default(); - let length_usize = usize::from(*length); - - for (itx, s) in graphemes.iter().enumerate() { - if itx >= length_usize { - break; - } - truncated_data.push_str(s); - } - - Some(Cell::from(truncated_data)) - } else { - Some(Cell::from(data.as_ref())) - } - } - }) - .collect::>(), - ) - }) - .collect::>() - }; - - // Get headers. - let headers = Row::new( - self.columns - .iter() - .map(|column| column.column_header.as_ref()), - ) - .style(self.app_state.colours.table_header_style) - .bottom_margin(self.table_offset); - - // Is this widget selected? If so, use a selected border colour and highlight current entry. - let (border_style, highlighted_entry_style) = - if self.widget_id == self.app_state.selected_widget_id { - ( - self.app_state.colours.highlighted_border_style, - self.app_state.colours.currently_selected_text_style, - ) - } else { - ( - self.app_state.colours.border_style, - self.app_state.colours.text_style, - ) - }; - - // The block - let block = Block::default() - .borders(self.border_type) - .border_style(border_style); - - // And finally, draw. - frame.render_stateful_widget( - Table::new(gathered_data) - .block(block) - .highlight_style(highlighted_entry_style) - .style(self.app_state.colours.text_style) - .header(headers) - .widths(&self.column_widths), - self.draw_bounds, - &mut self.table_state.borrow_mut(), - ); - } - - fn get_widget_id(&self) -> u16 { - self.widget_id - } - - fn set_draw_bounds(&mut self, new_bounds: Rect) { - if new_bounds != self.draw_bounds { - self.draw_bounds = new_bounds; - - // Update table offset... - self.table_offset = if self.draw_bounds.height < TABLE_GAP_HEIGHT_LIMIT { - 0 - } else { - self.app_state.settings.table_gap - }; - - // Update click bounds of the table and the columns - } - } -} - -impl<'d> KeyHandler for TextTable<'d> { - type SignalType = TableKeySignal; - - fn on_key(&mut self, event: KeyEvent) -> Option { - if event.modifiers.is_empty() { - match event.code { - KeyCode::Char('/') => Some(TableKeySignal::OpenSearch), - KeyCode::Char('g') => { - // TODO: Detect second 'g', if so, skip to the start of the list. - None - } - KeyCode::Char('G') => { - // Skip to end of the list. - self.vertical_state.current_position = self.data.len() - 1; - self.vertical_state.scroll_direction = ScrollDirection::Down; - - None - } - KeyCode::F(6) => Some(TableKeySignal::OpenSort), - KeyCode::Up | KeyCode::Char('k') => { - // Increment list - self.vertical_state.current_position = - self.vertical_state.current_position.saturating_sub(1); - self.vertical_state.scroll_direction = ScrollDirection::Up; - - None - } - KeyCode::Down | KeyCode::Char('j') => { - // Decrement list - if self.vertical_state.current_position + 1 < self.data.len() { - self.vertical_state.current_position += 1; - } - self.vertical_state.scroll_direction = ScrollDirection::Down; - - None - } - KeyCode::Left | KeyCode::Char('h') => { - // Scroll left - - self.horizontal_state.offset_multiplier = - self.horizontal_state.offset_multiplier.saturating_sub(1); - - None - } - KeyCode::Right | KeyCode::Char('l') => { - // Scroll right - - if self.horizontal_state.offset_multiplier + 1 < self.columns.len() { - self.horizontal_state.offset_multiplier += 1; - } - - None - } - _ => None, - } - } else { - match event.modifiers { - KeyModifiers::CONTROL => { - if let KeyCode::Char('f') = event.code { - Some(TableKeySignal::OpenSearch) - } else { - None - } - } - KeyModifiers::SHIFT => { - // This is a workaround as in some cases, if you type in, say, a capital 'G', - // that's recorded as a shift + 'G', and not just 'G'. - // So, just recurse and call the `on_key` function with no modifier! - if let KeyCode::Char(c) = event.code { - self.on_key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty())) - } else { - None - } - } - _ => None, - } - } - } -} - -impl<'d> ScrollHandler for TextTable<'d> { - type SignalType = (); - - fn on_scroll(&mut self) -> Option<()> { - self.get_vertical_start_position(usize::from( - (self.draw_bounds.height + (1 - self.table_offset)) - .saturating_sub(self.table_height_offset), - )); - - None - } -} - -impl<'d> ClickHandler for TextTable<'d> { - type SignalType = usize; - - fn on_left_click(&mut self, x: u16, y: u16) -> Option { - // Click logic for a table. This function assumes *absolute* x and y - // coordinates to the displayed table! - - // Let's convert those absolute coordinates to *relative* coordinates. - let relative_x = x - self.draw_bounds.x; - let relative_y = y - self.draw_bounds.y; - - if relative_y == 0 { - for (index, column) in self.columns.iter().enumerate() { - if !column.is_hidden { - if let Some((left_x, right_x)) = column.x_bounds { - if relative_x >= left_x && relative_x < right_x { - // We assume this means we've clicked on the header. - - Some(index); - } - } - } - } - } - - None - } - - fn is_widget_in_bounds(&self, x: u16, y: u16) -> bool { - x >= self.draw_bounds.x - && x < self.draw_bounds.x + self.draw_bounds.width - && y >= self.draw_bounds.y - && y < self.draw_bounds.y + self.draw_bounds.height - } -} diff --git a/src/canvas/components/text_input.rs b/src/canvas/components/text_input.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/canvas/components/widget_event_handlers.rs b/src/canvas/components/widget_event_handlers.rs deleted file mode 100644 index 93c2055a..00000000 --- a/src/canvas/components/widget_event_handlers.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crossterm::event::KeyEvent; - -pub trait KeyHandler { - type SignalType; - - /// The handler for a key input in a widget. - fn on_key(&mut self, event: KeyEvent) -> Option; -} - -pub trait ScrollHandler { - type SignalType; - - /// The handler for scrolling in a widget. - fn on_scroll(&mut self) -> Option; -} - -/// Handlers for clicking. -/// The handler functions are pre-defined to be functions that simply return `None`, -/// with functions for left, middle, and right click. For the left, middle, and right click handlers, -/// they all accept x and y coordinates that are absolute. -/// -/// Meanwhile, the `is_widget_in_bounds` function takes in an absolute coordinate and returns a boolean. -pub trait ClickHandler { - type SignalType; - - /// Returns whether the widget is in the bounds of the given coordinate. - /// Assumes absolute coordinates to the widget. - fn is_widget_in_bounds(&self, x: u16, y: u16) -> bool; - - /// The handler for a left mouse click in a widget. Assumes absolute coordinates to the widget. - fn on_left_click(&mut self, _x: u16, _y: u16) -> Option { - None - } - - /// The handler for a middle mouse click in a widget. Assumes absolute coordinates to the widget. - fn on_middle_click(&mut self, _x: u16, _y: u16) -> Option { - None - } - - /// The handler for a right mouse click in a widget. Assumes absolute coordinates to the widget. - fn on_right_click(&mut self, _x: u16, _y: u16) -> Option { - None - } -} diff --git a/src/canvas/widgets/scroll_search_table.rs b/src/canvas/widgets/scroll_search_table.rs index 96c4941c..8b137891 100644 --- a/src/canvas/widgets/scroll_search_table.rs +++ b/src/canvas/widgets/scroll_search_table.rs @@ -1,100 +1 @@ -use std::borrow::Cow; -use indexmap::IndexMap; - -use tui::{backend::Backend, layout::Constraint}; - -use crate::{ - app::AppState, - canvas::components::{BaseWidget, Container, TableColumn, TextTable}, -}; - -/// A scrollable and searchable `Container` that wraps a table, text input, and sort window. -/// Manages the table, column state, and sort state. -pub struct ScrollSearchTable -where - B: Backend, -{ - /// Whether to allow searching. - is_searchable: bool, - - /// Whether to allow opening the sort menu. - has_sort_menu: bool, - - /// Whether the search widget is open. - is_search_open: bool, - - /// Whether the sort widget is open. - is_sort_open: bool, - - /// The main wrapper `Container`. - child: Container, - - /// The stored data - data: Vec>>, - - /// The columns - columns: Vec, - - /// Application state - app_state: &'static AppState, -} - -impl ScrollSearchTable -where - B: Backend, -{ - /// Creates a new `ScrollSearchTable`. - pub fn new( - widget_id: u16, columns: Vec, data: Vec>>, - app_state: &'static AppState, - ) -> Self { - let row_container_children: IndexMap>, Constraint)> = - IndexMap::new(); - - let mut child = Container::new_row(row_container_children, widget_id, 1); - - let mut ss_table = ScrollSearchTable { - is_searchable: true, - has_sort_menu: true, - is_search_open: false, - is_sort_open: false, - child, - data, - columns, - app_state, - }; - - ss_table.child.add_child( - Box::from(TextTable::new(widget_id + 3, &vec![], &vec![], app_state)), - Constraint::Length(1), - ); - - ss_table - } - - /// Sets whether the `ScrollSearchTable` is searchable. - pub fn is_searchable(mut self, is_searchable: bool) -> Self { - self.is_searchable = is_searchable; - self - } -} - -impl BaseWidget for ScrollSearchTable -where - B: Backend, -{ - fn draw(&mut self, frame: &mut tui::Frame<'_, B>) { - self.child.draw(frame); - } - - fn get_widget_id(&self) -> u16 { - self.child.get_widget_id() - } - - fn set_draw_bounds(&mut self, new_bounds: tui::layout::Rect) {} - - fn get_name(&self) -> Option> { - None - } -} -- cgit v1.2.3