use std::time::Instant; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; use indextree::NodeId; use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; use crate::{ app::{ event::{ComponentEventResult, SelectionAction}, layout_manager::BottomWidgetType, }, canvas::Painter, options::layout_options::LayoutRule, }; mod tui_stuff; pub mod base; pub use base::*; pub mod dialogs; pub use dialogs::*; pub mod bottom_widgets; pub use bottom_widgets::*; use self::tui_stuff::BlockBuilder; use super::{data_farmer::DataCollection, event::EventResult}; /// A trait for things that are drawn with state. #[enum_dispatch] #[allow(unused_variables)] pub trait Component { /// Handles a [`KeyEvent`]. /// /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event. fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { ComponentEventResult::Unhandled } /// Handles a [`MouseEvent`]. /// /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event. fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { ComponentEventResult::Unhandled } /// Returns a [`Component`]'s bounding box. Note that these are defined in *global*, *absolute* /// coordinates. fn bounds(&self) -> Rect; /// Updates a [`Component`]'s bounding box to `new_bounds`. fn set_bounds(&mut self, new_bounds: Rect); /// Returns a [`Component`]'s bounding box, *including the border*. Defaults to just returning the normal bounds. /// Note that these are defined in *global*, *absolute* coordinates. fn border_bounds(&self) -> Rect { self.bounds() } /// Updates a [`Component`]'s bounding box to `new_bounds`. Defaults to just setting the normal bounds. fn set_border_bounds(&mut self, new_bounds: Rect) { self.set_bounds(new_bounds); } /// Returns whether a [`MouseEvent`] intersects a [`Component`]'s bounds. fn does_bounds_intersect_mouse(&self, event: &MouseEvent) -> bool { let x = event.column; let y = event.row; let bounds = self.bounds(); does_bound_intersect_coordinate(x, y, bounds) } /// Returns whether a [`MouseEvent`] intersects a [`Component`]'s bounds, including any borders, if there are. fn does_border_intersect_mouse(&self, event: &MouseEvent) -> bool { let x = event.column; let y = event.row; let bounds = self.border_bounds(); does_bound_intersect_coordinate(x, y, bounds) } } pub fn does_bound_intersect_coordinate(x: u16, y: u16, bounds: Rect) -> bool { x >= bounds.left() && x < bounds.right() && y >= bounds.top() && y < bounds.bottom() } /// A trait for actual fully-fledged widgets to be displayed in bottom. #[enum_dispatch] #[allow(unused_variables)] pub trait Widget { /// Handles what to do when trying to respond to a widget selection movement to the left. /// Defaults to just moving to the next-possible widget in that direction. fn handle_widget_selection_left(&mut self) -> SelectionAction { SelectionAction::NotHandled } /// Handles what to do when trying to respond to a widget selection movement to the right. /// Defaults to just moving to the next-possible widget in that direction. fn handle_widget_selection_right(&mut self) -> SelectionAction { SelectionAction::NotHandled } /// Handles what to do when trying to respond to a widget selection movement upward. /// Defaults to just moving to the next-possible widget in that direction. fn handle_widget_selection_up(&mut self) -> SelectionAction { SelectionAction::NotHandled } /// Handles what to do when trying to respond to a widget selection movement downward. /// Defaults to just moving to the next-possible widget in that direction. fn handle_widget_selection_down(&mut self) -> SelectionAction { SelectionAction::NotHandled } /// Returns a [`Widget`]'s "pretty" display name. fn get_pretty_name(&self) -> &'static str; /// Returns a new [`BlockBuilder`], which can become a [`tui::widgets::Block`] if [`BlockBuilder::build`] is called. /// The default implementation builds a [`Block`] that has all 4 borders with no selection or expansion. fn block(&self) -> BlockBuilder { BlockBuilder::new(self.get_pretty_name()) } /// Draws a [`Widget`]. The default implementation draws nothing. fn draw( &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool, expanded: bool, ) { } /// How a [`Widget`] updates its internal data that'll be displayed. Called after every data harvest. /// The default implementation does nothing with the data. fn update_data(&mut self, data_collection: &DataCollection) {} /// Returns the desired width from the [`Widget`]. fn width(&self) -> LayoutRule; /// Returns the desired height from the [`Widget`]. fn height(&self) -> LayoutRule; /// Returns whether this [`Widget`] can be expanded. The default implementation returns `true`. fn expandable(&self) -> bool { true } /// Returns whether this [`Widget`] can be selected. The default implementation returns [`SelectableType::Selectable`]. fn selectable_type(&self) -> SelectableType { SelectableType::Selectable } /// Resets state in a [`Widget`]; used when a reset signal is given. The default implementation does nothing. fn reset(&mut self) {} } /// Whether a widget can be selected, not selected, or redirected upon selection. pub enum SelectableType { Selectable, Unselectable, Redirect(NodeId), } /// The "main" widgets that are used by bottom to display information! #[allow(clippy::large_enum_variant)] #[enum_dispatch(Component, Widget)] pub enum TmpBottomWidget { MemGraph, TempTable, DiskTable, CpuGraph, NetGraph, OldNetGraph, ProcessManager, BatteryTable, BasicCpu, BasicMem, BasicNet, Carousel, Empty, } /// The states a dialog can be in. Consists of either: /// - [`DialogState::Hidden`] - the dialog is currently not showing. /// - [`DialogState::Shown`] - the dialog is showing. #[derive(Debug)] pub enum DialogState { Hidden, Shown(D), } impl Default for DialogState where D: Default + Component, { fn default() -> Self { DialogState::Hidden } } impl DialogState where D: Default + Component, { pub fn is_showing(&self) -> bool { matches!(self, DialogState::Shown(_)) } pub fn hide(&mut self) { *self = DialogState::Hidden; } pub fn show(&mut self) { *self = DialogState::Shown(D::default()); } } // ----- Old stuff below ----- #[derive(Debug)] pub enum ScrollDirection { // UP means scrolling up --- this usually DECREMENTS Up, // DOWN means scrolling down --- this usually INCREMENTS Down, } impl Default for ScrollDirection { fn default() -> Self { ScrollDirection::Down } } #[derive(Debug)] pub enum CursorDirection { Left, Right, } /// AppScrollWidgetState deals with fields for a scrollable app's current state. #[derive(Default)] pub struct AppScrollWidgetState { pub current_scroll_position: usize, pub previous_scroll_position: usize, pub scroll_direction: ScrollDirection, pub table_state: TableState, } #[derive(PartialEq)] pub enum KillSignal { Cancel, Kill(usize), } impl Default for KillSignal { #[cfg(target_family = "unix")] fn default() -> Self { KillSignal::Kill(15) } #[cfg(target_os = "windows")] fn default() -> Self { KillSignal::Kill(1) } } #[derive(Default)] pub struct AppDeleteDialogState { pub is_showing_dd: bool, pub selected_signal: KillSignal, /// tl x, tl y, br x, br y, index/signal pub button_positions: Vec<(u16, u16, u16, u16, usize)>, pub keyboard_signal_select: usize, pub last_number_press: Option, pub scroll_pos: usize, } pub struct AppHelpDialogState { pub is_showing_help: bool, pub scroll_state: ParagraphScrollState, pub index_shortcuts: Vec, } impl Default for AppHelpDialogState { fn default() -> Self { AppHelpDialogState { is_showing_help: false, scroll_state: ParagraphScrollState::default(), index_shortcuts: vec![], } } } impl AppHelpDialogState { pub fn increment(&mut self) -> EventResult { if self.scroll_state.current_scroll_index < self.scroll_state.max_scroll_index { self.scroll_state.current_scroll_index += 1; EventResult::Redraw } else { EventResult::NoRedraw } } pub fn decrement(&mut self) -> EventResult { if self.scroll_state.current_scroll_index > 0 { self.scroll_state.current_scroll_index -= 1; EventResult::Redraw } else { EventResult::NoRedraw } } } /// Meant for canvas operations involving table column widths. #[derive(Default)] pub struct CanvasTableWidthState { pub desired_column_widths: Vec, pub calculated_column_widths: Vec, } pub struct BasicTableWidgetState { // Since this is intended (currently) to only be used for ONE widget, that's // how it's going to be written. If we want to allow for multiple of these, // then we can expand outwards with a normal BasicTableState and a hashmap pub currently_displayed_widget_type: BottomWidgetType, pub currently_displayed_widget_id: u64, pub widget_id: i64, pub left_tlc: Option<(u16, u16)>, pub left_brc: Option<(u16, u16)>, pub right_tlc: Option<(u16, u16)>, pub right_brc: Option<(u16, u16)>, } #[derive(Default)] pub struct ParagraphScrollState { pub current_scroll_index: u16, pub max_scroll_index: u16, }