Components.rs
In components/mod.rs, we implement a trait called Component:
pub trait Component { /// Register an action handler that can send actions for processing if necessary. /// /// # Arguments /// /// * `tx` - An unbounded sender that can send actions. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { let _ = tx; // to appease clippy Ok(()) } /// Register a configuration handler that provides configuration settings if necessary. /// /// # Arguments /// /// * `config` - Configuration settings. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn register_config_handler(&mut self, config: Config) -> Result<()> { let _ = config; // to appease clippy Ok(()) } /// Initialize the component with a specified area if necessary. /// /// # Arguments /// /// * `area` - Rectangular area to initialize the component within. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn init(&mut self, area: Size) -> Result<()> { let _ = area; // to appease clippy Ok(()) } /// Handle incoming events and produce actions if necessary. /// /// # Arguments /// /// * `event` - An optional event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> { let action = match event { Some(Event::Key(key_event)) => self.handle_key_event(key_event)?, Some(Event::Mouse(mouse_event)) => self.handle_mouse_event(mouse_event)?, _ => None, }; Ok(action) } /// Handle key events and produce actions if necessary. /// /// # Arguments /// /// * `key` - A key event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { let _ = key; // to appease clippy Ok(None) } /// Handle mouse events and produce actions if necessary. /// /// # Arguments /// /// * `mouse` - A mouse event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result<Option<Action>> { let _ = mouse; // to appease clippy Ok(None) } /// Update the state of the component based on a received action. (REQUIRED) /// /// # Arguments /// /// * `action` - An action that may modify the state of the component. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn update(&mut self, action: Action) -> Result<Option<Action>> { let _ = action; // to appease clippy Ok(None) } /// Render the component on the screen. (REQUIRED) /// /// # Arguments /// /// * `f` - A frame used for rendering. /// * `area` - The area in which the component should be drawn. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>;}I personally like keeping the functions for handle_events (i.e. event -> action mapping),
dispatch (i.e. action -> state update mapping) and render (i.e. state -> drawing mapping) all in
one file for each component of my application.
Full code for the components.rs file is:
use color_eyre::Result;use crossterm::event::{KeyEvent, MouseEvent};use ratatui::{ Frame, layout::{Rect, Size},};use tokio::sync::mpsc::UnboundedSender;
use crate::{action::Action, config::Config, tui::Event};
pub mod fps;pub mod home;
/// `Component` is a trait that represents a visual and interactive element of the user interface.////// Implementors of this trait can be registered with the main application loop and will be able to/// receive events, update state, and be rendered on the screen.pub trait Component { /// Register an action handler that can send actions for processing if necessary. /// /// # Arguments /// /// * `tx` - An unbounded sender that can send actions. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { let _ = tx; // to appease clippy Ok(()) } /// Register a configuration handler that provides configuration settings if necessary. /// /// # Arguments /// /// * `config` - Configuration settings. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn register_config_handler(&mut self, config: Config) -> Result<()> { let _ = config; // to appease clippy Ok(()) } /// Initialize the component with a specified area if necessary. /// /// # Arguments /// /// * `area` - Rectangular area to initialize the component within. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn init(&mut self, area: Size) -> Result<()> { let _ = area; // to appease clippy Ok(()) } /// Handle incoming events and produce actions if necessary. /// /// # Arguments /// /// * `event` - An optional event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> { let action = match event { Some(Event::Key(key_event)) => self.handle_key_event(key_event)?, Some(Event::Mouse(mouse_event)) => self.handle_mouse_event(mouse_event)?, _ => None, }; Ok(action) } /// Handle key events and produce actions if necessary. /// /// # Arguments /// /// * `key` - A key event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { let _ = key; // to appease clippy Ok(None) } /// Handle mouse events and produce actions if necessary. /// /// # Arguments /// /// * `mouse` - A mouse event to be processed. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result<Option<Action>> { let _ = mouse; // to appease clippy Ok(None) } /// Update the state of the component based on a received action. (REQUIRED) /// /// # Arguments /// /// * `action` - An action that may modify the state of the component. /// /// # Returns /// /// * `Result<Option<Action>>` - An action to be processed or none. fn update(&mut self, action: Action) -> Result<Option<Action>> { let _ = action; // to appease clippy Ok(None) } /// Render the component on the screen. (REQUIRED) /// /// # Arguments /// /// * `f` - A frame used for rendering. /// * `area` - The area in which the component should be drawn. /// /// # Returns /// /// * `Result<()>` - An Ok result or an error. fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>;}