From 01448d955e00d24b53dd5a4b9d55981e58a17cc9 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 8 Nov 2024 13:43:34 +0100 Subject: [PATCH] some more structural changes --- Cargo.toml | 1 + src/lib.rs | 70 ++++++----------- src/macros.rs | 99 +++++++++++++++++++++++- src/ui/elements.rs | 74 +++++++++++------- src/ui/mod.rs | 5 +- src/ui/styles.rs | 189 +++------------------------------------------ 6 files changed, 180 insertions(+), 258 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d744a32..bef621d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ crossterm = "0.28.1" regex = "*" lazy_static = "1.4" dyn-clone = "1.0.17" +paste = "1.0.15" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 23d21fa..204c51e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! A terminal user interface (TUI) library providing customizable components //! to build command-line interfaces in Rust. OSUI enables users to create //! interactive CLI applications with various UI elements and handle keyboard -//! input for real-time updates. +//! input for real-time events. //! //! ## Example Usage //! @@ -21,14 +21,14 @@ //! - `utils` - Utility functions for common TUI tasks such as clearing the screen. //! - `ui` - Contains all user interface components, enabling rich CLI experiences. +pub mod event; pub mod key; pub mod macros; mod test; pub mod ui; pub mod utils; - +use event::{Event, EventResponse}; pub use utils::*; - pub mod app { //! Application entry point and main event loop for OSUI. //! @@ -37,9 +37,11 @@ pub mod app { //! using custom commands such as rendering, updating, or exiting. use crate::{ - clear, create_frame, flush, get_term_size, hide_cursor, - key::{read_key, Key}, - render_to_frame, show_cursor, Command, Element, UpdateResponse, Value, + clear, create_frame, + event::{Command, Event, EventResponse}, + flush, get_term_size, hide_cursor, + key::read_key, + render_to_frame, show_cursor, Element, Value, }; /// Renders a single frame of the UI to the terminal. @@ -63,7 +65,7 @@ pub mod app { /// Updates the UI element based on keyboard input and issues any commands in response. /// - /// Processes the result of `Element::update` to handle commands like rendering, + /// Processes the result of `Element::event` to handle commands like rendering, /// updating, and exiting. Commands can be a single action or a list of actions. /// /// # Arguments @@ -75,12 +77,12 @@ pub mod app { /// # Returns /// /// `true` if an `Exit` command is issued, signaling the application to terminate. - fn update(elem: &mut Box, state: usize, k: Key) -> bool { - match elem.event(state, k.clone()) { - UpdateResponse::Command(command) => run_command(command, elem, k.clone()), - UpdateResponse::CommandList(commands) => { + fn update(elem: &mut Box, event: Event) -> bool { + match elem.event(event) { + EventResponse::Command(command) => run_command(command, elem), + EventResponse::CommandList(commands) => { for command in commands { - if run_command(command, elem, k.clone()) { + if run_command(command, elem) { return true; } } @@ -102,13 +104,13 @@ pub mod app { /// # Returns /// /// `true` if the command is `Exit`, ending the application loop. - fn run_command(command: Command, elem: &mut Box, k: Key) -> bool { + fn run_command(command: Command, elem: &mut Box) -> bool { match command { Command::Render(state) => { render(elem, state); false } - Command::Update(state) => update(elem, state, k), + Command::Event(event) => update(elem, event), Command::Sleep(duration) => { std::thread::sleep(std::time::Duration::from_millis(duration)); false @@ -137,8 +139,7 @@ pub mod app { clear(); loop { render(elem, 1); - let k = read_key(); - if update(elem, 1, k) { + if update(elem, Event::Key(read_key())) { break; } } @@ -239,14 +240,13 @@ pub trait Element: DynClone { /// /// # Arguments /// - /// * `_state` - The current state of the element; if one or higher, it indicates the element is active. - /// * `_k` - The key input triggering the update. + /// * `_event` - The current event. /// /// # Returns /// - /// An `UpdateResponse` enum indicating the result of the update. - fn event(&mut self, _state: usize, _k: crate::key::Key) -> UpdateResponse { - UpdateResponse::None + /// An `EventResponse` enum indicating the result of the event. + fn event(&mut self, _event: Event) -> EventResponse { + EventResponse::None } } @@ -272,32 +272,6 @@ pub struct ElementData { pub id: String, } -/// Enum representing the possible responses from an element update. -#[derive(Debug, Clone, PartialEq)] -pub enum UpdateResponse { - /// Indicates that the update is complete. - Done, - /// Indicates no response. - None, - /// Issues a single command. - Command(Command), - /// Issues a list of commands. - CommandList(Vec), -} - -/// Enum defining commands that can be issued by an `Element`. -#[derive(Debug, Clone, PartialEq)] -pub enum Command { - /// Renders the element with the specified state. - Render(usize), - /// Exits the application. - Exit, - /// Updates the element with the specified state. - Update(usize), - /// Pauses execution for the specified duration in milliseconds. - Sleep(u64), -} - pub fn run_test() { - test::main_(); + app::run(&mut test::app()); } diff --git a/src/macros.rs b/src/macros.rs index 00a1eb1..1b450da 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -24,6 +24,57 @@ /// - `functions`: Functions to implement for the element (e.g., rendering, updating). #[macro_export] macro_rules! element { + ( + $(#[$meta_style:meta])* + style $style:ident { + $($sn:ident: $st:ty),*$(,)? + } + $(#[$meta:meta])* + $name:ident { + $( $inner:tt )* + } + defaults {$( $defaults:tt )*} + $( $functions:tt )* + ) => { + $(#[$meta_style])* + #[derive(Clone)] + pub struct $style { + pub color: Color, + pub background: Color, + pub font: Font, + pub hover_color: Color, + pub hover_background: Color, + pub hover_font: Font, + $(pub $sn: $st),* + } + + impl Default for $style { + fn default() -> $style { + $style { + color: Color::default(), + background: Color::default(), + font: Font::default(), + hover_color: Color::default(), + hover_background: Color::default(), + hover_font: Font::default(), + $($sn: <$st>::default()),* + } + } + } + + element! { + $(#[$meta])* + $name { + pub style: $style, + $( $inner )* + } + defaults { + style: $style::default(), + $($defaults)* + } + $( $functions )* + } + }; ( $(#[$meta:meta])* $name:ident { @@ -129,11 +180,11 @@ macro_rules! element { /// ``` /// /// # Parameters -/// - A list of commands to be included in the `UpdateResponse::CommandList`. +/// - A list of commands to be included in the `EventResponse::CommandList`. #[macro_export] macro_rules! command { ($($cmd:expr),*) => { - UpdateResponse::CommandList(vec![$($cmd),*]) + EventResponse::CommandList(vec![$($cmd),*]) }; } @@ -271,7 +322,7 @@ macro_rules! rsx { /// /// # Example /// ``` -/// css!(Style; color: "red"; background: "white"); +/// css!(Style; color: Color::Red; background: "white"); /// ``` /// /// This macro creates a new style based on the provided properties, applying default values @@ -289,6 +340,7 @@ macro_rules! css { }}; } +/// Macro for defining a `Arc>` #[macro_export] macro_rules! arc { ($a:expr) => { @@ -296,9 +348,50 @@ macro_rules! arc { }; } +/// Macro for defining a `Value< T >` #[macro_export] macro_rules! val { ($a:expr) => { osui::Value::new($a) }; } + +/// Macro for writing when requested to render +/// +/// # Example +/// ``` +/// write!(self, clicked, "Hello, World!") // For custom styles, You need (color, background, font) +/// +/// write!((self, state), "Hello, World!") // When you don't need a custom style, you use () and put self and the state of the render +/// ``` +/// +/// This macro returns a `String` depending on the state or the kind of style +/// When on "state" mode it returns the default style if the state is 0 +#[macro_export] +macro_rules! write { + // Default state styling + (($self:ident, $state:expr), $expr:expr) => {{ + if $state == 0 { + return format!("{}{}{}{}\x1b[0m", $self.style.color.ansi(), $self.style.background.ansi_bg(), $self.style.font.ansi(), $expr); + } + format!("{}{}{}{}\x1b[0m", $self.style.hover_color.ansi(), $self.style.hover_background.ansi_bg(), $self.style.hover_font.ansi(), $expr) + }}; + + // Custom styles + ($self:ident, $kind:ident, $expr:expr) => {{ + use paste::paste; + format!( + "{}{}{}{}\x1b[0m", + paste! { + $self.style.[<$kind _color>].ansi() + }, + paste! { + $self.style.[<$kind _background>].ansi_bg() + }, + paste! { + $self.style.[<$kind _font>].ansi() + }, + $expr + ) + }}; +} diff --git a/src/ui/elements.rs b/src/ui/elements.rs index 242e9ab..960958b 100644 --- a/src/ui/elements.rs +++ b/src/ui/elements.rs @@ -1,8 +1,15 @@ -use std::{collections::HashMap, sync::{Arc, Mutex}}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use crate::{ - command, element, key::Key, render_to_frame, ui::Style, Command, Direction, Element, - ElementData, Value, UpdateResponse, + command, element, + event::{Command, Event}, + key::Key, + render_to_frame, + ui::{Color, Font}, + write, Direction, Element, ElementData, EventResponse, Value, }; element! { @@ -17,6 +24,12 @@ element! { } element! { + style ButtonStyle { + clicked_color: Color, + clicked_background: Color, + clicked_font: Font, + } + /// A clickable button element. /// /// The `Button` element can be clicked, triggering an `on_click` function. Its appearance changes @@ -24,35 +37,38 @@ element! { Button { /// A callback function executed when the button is clicked. pub on_click: Arc>, - pub style: Style, } defaults { on_click: Arc::new(Mutex::new(|_btn: &mut Button<'_>| {})), - style: Style::default(), } fn render(&self, state: usize) -> String { - let writer = self.style.use_style(&state); if state == 2 { - return writer.write_clicked(&self.text); + return write!(self, clicked, self.text); } - writer.write(&self.text) + write!((self, state), self.text) } - fn event(&mut self, _state: usize, k: Key) -> UpdateResponse { - if k == Key::Enter { - let mut btn = self.clone(); - let mut on_click = self.on_click.lock().unwrap(); - (on_click)(&mut btn); - drop(on_click); - *self = btn; - return command!( - Command::Render(2), - Command::Sleep(120) - ); + fn event(&mut self, event: Event) -> EventResponse { + match event { + Event::Key(k) => { + if k == Key::Enter { + let mut btn = self.clone(); + let mut on_click = self.on_click.lock().unwrap(); + (on_click)(&mut btn); + drop(on_click); + *self = btn; + return command!( + Command::Render(2), + Command::Sleep(120) + ); + } + } + _ => {} } - UpdateResponse::None + + EventResponse::None } } @@ -86,12 +102,18 @@ element! { frame.join("\n") } - fn event(&mut self, state: usize, k: Key) -> UpdateResponse { - if let Some(direction) = self.keybinds.get(&k) { - self.child = crate::closest_component(&self.children, self.child, direction.clone()); - } else if let Some(child) = self.get_child() { - return child.event(state, k); + fn event(&mut self, event: Event) -> EventResponse { + match event.clone() { + Event::Key(k) => { + if let Some(direction) = self.keybinds.get(&k) { + self.child = crate::closest_component(&self.children, self.child, direction.clone()); + } else if let Some(child) = self.get_child() { + return child.event(event.clone()); + } + } + _ => {} } - UpdateResponse::None + + EventResponse::None } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6518bd9..b14de6a 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -27,10 +27,7 @@ pub fn text<'a>() -> Box> { /// /// A `Box