diff --git a/Cargo.toml b/Cargo.toml index bef621d..f13b9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,5 +18,4 @@ path = "src/main.rs" 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/app.rs b/src/app.rs new file mode 100644 index 0000000..c1589ad --- /dev/null +++ b/src/app.rs @@ -0,0 +1,67 @@ +use std::sync::{mpsc, Arc}; +use std::thread; + +use crate::{ + clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, Element, State, Value, +}; + +/// Renders an element with a given state to the terminal. +/// +/// This macro handles the process of creating a frame, rendering the element to it, +/// clearing the terminal, and then printing the rendered frame. +/// +/// # Parameters +/// +/// * `$element` - The element to be rendered. +/// * `$state` - The current state of the element. +/// +/// # Returns +/// +/// This macro does not return a value. It performs side effects by printing to the terminal. +macro_rules! render { + ($element:expr, $state:expr) => { + let (width, height) = get_term_size(); + let mut frame: Vec = create_frame(Value::new(width), Value::new(height)); + render_to_frame($state, width, &mut frame, $element); + clear(); + print!("{}", frame.join("")); + flush(); + }; +} + +pub fn run(elem: Box) { + hide_cursor(); + crossterm::terminal::enable_raw_mode().unwrap(); + clear(); + + let (tx, rx) = mpsc::channel(); + let element = Arc::new(elem); + + thread::spawn({ + let element_thread = Arc::clone(&element); + move || loop { + let event = crossterm::event::read().unwrap(); + let e = Arc::clone(&element_thread); + let command_handler = crate::CommandHandler(tx.clone()); + + thread::spawn(move || { + e.event(&command_handler, event); + }); + } + }); + + let mut current_state = State::Hover; + loop { + if let Ok(command) = rx.try_recv() { + match command { + crate::Command::SetState(state) => { + current_state = state; + } + crate::Command::Exit => break, + } + } else { + render!(&element, current_state.clone()); + } + thread::sleep(std::time::Duration::from_millis(20)); + } +} diff --git a/src/event.rs b/src/event.rs deleted file mode 100644 index cc78c4e..0000000 --- a/src/event.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{arc, key::Key, Element}; - -/// Enum defining commands that can be issued by an `Element`. -#[derive(Clone)] -pub enum Command { - /// Renders the element with the specified state. - Render(usize), - /// Exits the application. - Exit, - /// Updates the element with the specified state. - Event(Event), - /// Pauses execution for the specified duration in milliseconds. - Sleep(u64), -} - -/// Enum representing the possible responses from an element event. -#[derive(Clone)] -pub enum EventResponse { - /// Indicates that the event is complete. - Done, - /// Indicates no response. - None, - /// Issues a single command. - /// Issues a list of commands. - CommandList(Vec), - /// Update the current element - UpdateSelf(Box), - /// Update a element by a id - UpdateElementById(String, Box), - /// Multiple Responses - /// # DO NOT USE MANUALLY - Mul(Vec), -} - -impl EventResponse { - pub fn add_command(&mut self, command: Command) { - match self { - EventResponse::CommandList(commands) => { - commands.push(command); - } - EventResponse::Mul(responses) => { - for response in responses { - match response { - EventResponse::CommandList(commands) => { - commands.push(command.clone()); - return; - } - _ => {} - } - } - self.add_response(EventResponse::CommandList(vec![command.clone()])); - } - _ => {} - } - } - pub fn update_self(&mut self, elem: Box) { - self.add_response(Self::UpdateSelf(elem)); - } - pub fn update_element_by_id(&mut self, id: &str, elem: Box) { - self.add_response(Self::UpdateElementById(id.to_string(), elem)); - } - pub fn add_response(&mut self, response: EventResponse) { - match self { - EventResponse::Mul(responses) => { - responses.insert(0, response); - } - _ => *self = EventResponse::Mul(vec![response, self.clone()]), - } - } - pub fn execute(&self) -> Vec { - return match self { - EventResponse::Mul(v) => v.clone(), - _ => vec![self.clone()], - }; - } -} - -/// Enum representing the possible responses from an element update. -#[derive(Debug, Clone, PartialEq)] -pub enum Event { - Key(Key), - State(usize), - Hover, -} - -/// A handler is a function that runs on a event of a element -/// -/// # Params -/// - &mut T: The element, -/// - &mut EventResponse: The event response -pub type Handler = std::sync::Arc>; - -/// Creates a new `Handler` from a given function closure. -/// -/// This function takes a closure and wraps it in an `Arc>`, allowing it to be -/// used as a thread-safe, mutable handler that can be shared across threads. The handler function -/// receives a mutable reference to a `T` type and a mutable reference to an `EventResponse`, allowing -/// it to mutate both the state and response. -/// -/// # Type Parameters -/// -/// - `T`: The type of the data that the handler will operate on. -/// - `F`: The type of the handler function, which must implement `FnMut(&mut T, &mut EventResponse) + 'static`. -/// -/// # Parameters -/// -/// - `handler_fn`: The closure or function that will serve as the handler, taking mutable references -/// to both the `T` data and an `EventResponse`. -/// -/// # Returns -/// -/// Returns a `Handler`, which is an `Arc>`. -/// -/// This type alias makes it easy to manage the handler in a multi-threaded context, allowing shared ownership -/// with safe, mutable access to the underlying function. -/// -/// # Examples -/// -/// ```rust -/// use osui::ui::button; -/// rsx! { -/// button { -/// // Create a new handler and assign it to on_click -/// on_click: new_handler(move |btn: &mut Button, _response| { -/// btn.text = "Clicked".to_string(); -/// } -/// ), "click me" } -/// } -/// ``` -pub fn new_handler(handler_fn: F) -> Handler -where - F: FnMut(&mut T, &mut EventResponse) + 'static, -{ - arc!(handler_fn) -} diff --git a/src/key.rs b/src/key.rs deleted file mode 100644 index cd90fb2..0000000 --- a/src/key.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::io::{self, Read}; - -/// Represents different key inputs that can be detected from the terminal. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum Key { - /// Represents the Enter key (carriage return). - Enter, - /// Represents the Tab key. - Tab, - /// Represents the Shift+Tab key combination. - ShiftTab, - /// Represents the Escape key. - Escape, - /// Represents the Up arrow key. - Up, - /// Represents the Down arrow key. - Down, - /// Represents the Left arrow key. - Left, - /// Represents the Right arrow key. - Right, - /// Represents any other character or sequence that does not match a predefined key. - Other(String), -} - -impl Key { - /// Creates a new `Key` instance based on the input string. - /// - /// This function matches specific strings with known keys (e.g., arrow keys, Enter, Tab) - /// and returns the corresponding `Key` variant. If the string does not match any of the - /// predefined keys, it returns `Key::Other` with the string content. - /// - /// # Arguments - /// - /// * `k` - A `String` representing the raw key input. - /// - /// # Returns - /// - /// A `Key` enum variant corresponding to the input. - pub fn new(k: String) -> Key { - match k.as_str() { - "\r" => Key::Enter, - "\t" => Key::Tab, - "\x1b[Z" => Key::ShiftTab, - "\x1b" => Key::Escape, - "\x1b[A" => Key::Up, - "\x1b[B" => Key::Down, - "\x1b[C" => Key::Right, - "\x1b[D" => Key::Left, - _ => Key::Other(k), - } - } -} - -/// Reads a key from standard input and returns it as a `Key` enum variant. -/// -/// This function calls `read_key_raw` to get the raw key input as a `String`, -/// then uses `Key::new` to convert it to a `Key` variant. -/// -/// # Returns -/// -/// A `Key` enum variant corresponding to the input read from standard input. -pub fn read_key() -> Key { - Key::new(read_key_raw()) -} - -/// Reads raw key input from standard input as a UTF-8 encoded string. -/// -/// This function attempts to read up to 3 bytes from standard input and -/// returns them as a `String`. It uses a buffer size of 3, which is enough -/// to capture most common key sequences, including basic escape sequences -/// for arrow keys and other special keys. -/// -/// # Returns -/// -/// A `String` representing the raw key input read from standard input. -/// -/// # Panics -/// -/// This function will panic if reading from `stdin` fails or if the bytes -/// cannot be converted to a valid UTF-8 `String`. -pub fn read_key_raw() -> String { - let mut buffer = vec![0; 3]; - io::stdin().read(&mut buffer).unwrap(); - String::from_utf8(buffer) - .unwrap() - .trim_matches('\0') - .to_string() -} diff --git a/src/lib.rs b/src/lib.rs index 4b17ce9..9d243bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,134 +16,18 @@ //! ``` //! //! ## Modules -//! - `element` - Defines base elements for constructing UI components. -//! - `key` - Handles keyboard input, providing key event management. //! - `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}; +use std::sync::mpsc; + pub use utils::*; -pub mod app { - //! Application entry point and main event loop for OSUI. - //! - //! Provides functions to render and update UI elements based on keyboard - //! input. Manages cursor visibility, terminal size, and controls UI behavior - //! using custom commands such as rendering, updating, or exiting. +pub mod app; - use crate::{ - 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. - /// - /// Sets up a new frame based on the terminal's current size, updates - /// the element dimensions, and renders the UI element to the frame. - /// - /// # Arguments - /// - /// * `elem` - A mutable reference to a boxed UI element that implements the `Element` trait. - /// * `state` - Current state of the element, typically used to track the UI's state in the app loop. - fn render(elem: &mut Box, state: usize) { - let (width, height) = get_term_size(); - elem.update_data(width, height); - let mut frame: Vec = create_frame(Value::Custom(width), Value::Custom(height)); - render_to_frame(state, width, &mut frame, elem); - clear(); - print!("{}", frame.join("")); - flush(); - } - - /// Updates the UI element based on keyboard input and issues any commands in response. - /// - /// 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 - /// - /// * `elem` - A mutable reference to a boxed UI element. - /// * `state` - The current UI state, used for conditional updates. - /// * `k` - A `Key` input, typically read from the user’s keyboard input. - /// - /// # Returns - /// - /// `true` if an `Exit` command is issued, signaling the application to terminate. - fn update(elem: &mut Box, event: Event) -> bool { - match elem.event(event) { - EventResponse::CommandList(commands) => { - for command in commands { - if run_command(command, elem) { - return true; - } - } - false - } - _ => false, - } - } - - /// Executes a command, performing actions such as rendering, updating, - /// sleeping, or exiting the application. - /// - /// # Arguments - /// - /// * `command` - The command to execute, such as rendering or updating the UI. - /// * `elem` - A mutable reference to the UI element. - /// * `k` - A `Key` input passed along for further processing. - /// - /// # Returns - /// - /// `true` if the command is `Exit`, ending the application loop. - fn run_command(command: Command, elem: &mut Box) -> bool { - match command { - Command::Render(state) => { - render(elem, state); - false - } - Command::Event(event) => update(elem, event), - Command::Sleep(duration) => { - std::thread::sleep(std::time::Duration::from_millis(duration)); - false - } - Command::Exit => { - show_cursor(); - crossterm::terminal::disable_raw_mode().unwrap(); - clear(); - true - } - } - } - - /// Runs the main event loop for the application. - /// - /// Enables raw mode, hides the cursor, and continuously renders and updates - /// the UI based on user input. The loop will break if the `Exit` command is triggered. - /// - /// # Arguments - /// - /// * `elem` - A mutable reference to the main UI element to be rendered and updated. - pub fn run(elem: &mut Box) { - // Initialize terminal settings - hide_cursor(); - crossterm::terminal::enable_raw_mode().unwrap(); - clear(); - loop { - render(elem, 1); - if update(elem, Event::Key(read_key())) { - break; - } - } - } -} +mod test; /// Enum representing the wether it's the default or custom. /// @@ -191,86 +75,65 @@ impl Value { } } -pub trait ToValue { - fn to_value(&self) -> Value; -} - -use dyn_clone::{clone_trait_object, DynClone}; - /// A trait for defining UI elements in OSUI. /// /// Elements must implement methods for updating and rendering data. -pub trait Element: DynClone { - /// Retrieves the data for the `Element`. - /// - /// # Returns - /// - /// `ElementData` containing position and size information. - fn get_data(&self) -> ElementData; - fn set_data(&mut self, data: ElementData); +pub trait Element: Send + Sync { + fn get_data(&self) -> (Value, usize, String); - /// Updates the data of the `Element` based on the given dimensions. - /// - /// # Arguments - /// - /// * `_width` - The width of the terminal window or parent element. - /// * `_height` - The height of the terminal window or parent element. - fn update_data(&mut self, _width: usize, _height: usize); - - fn get_id(&self) -> String; + fn update_data(&mut self, width: usize, height: usize); fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box>; - /// Renders the `Element` as a `String`. - /// - /// # Arguments - /// - /// * `_state` - The current state of the element; if one or higher, it indicates the element is active. - /// If zero, the element is just being rendered. - /// - /// # Returns - /// - /// A `String` representing the rendered output of the `Element`. - fn render(&self, _state: usize) -> String { + fn render(&self, state: State) -> String { + _ = state; String::new() } - /// Updates the `Element` based on a `Key` event and the current state. - /// - /// # Arguments - /// - /// * `_event` - The current event. - /// - /// # Returns - /// - /// An `EventResponse` enum indicating the result of the event. - fn event(&mut self, _event: Event) -> EventResponse { - EventResponse::None + fn event(&self, ch: &CommandHandler, event: crossterm::event::Event) { + _ = (ch, event); } } -clone_trait_object!(Element); +pub struct Handler( + pub std::sync::Mutex>, +); -/// Struct holding data relevant to an `Element`, including position and size. -pub struct ElementData { - /// X coordinate of the `Element`. - pub x: Value, - /// Y coordinate of the `Element`. - pub y: usize, - /// Width of the `Element`, which can be default or custom. - pub width: Value, - /// Height of the `Element`, which can be default or custom. - pub height: Value, - /// Children of the `Element` - pub children: Vec>, - /// Active Child of the `Element` - pub child: usize, - /// Text of the `Element` - pub text: String, - /// Identifier of the `Element` - pub id: String, +impl Handler { + pub fn new(handler_fn: F) -> Handler + where + F: FnMut(&T, &CommandHandler, crossterm::event::Event) + 'static + Send, + { + Handler(std::sync::Mutex::new(Box::new(handler_fn))) + } +} + +pub struct CommandHandler(mpsc::Sender); + +impl CommandHandler { + pub fn set_state(&self, state: State) { + self.0.send(Command::SetState(state)).unwrap(); + } + + pub fn exit(&self) { + self.0.send(Command::Exit).unwrap(); + } +} + +#[derive(Debug, Clone)] +pub enum Command { + SetState(State), + Exit, +} + +#[derive(Debug, Clone)] +pub enum State { + Normal, + Hover, + Custom(usize), + CustomString(String), } pub fn run_test() { - app::run(&mut test::app()); -} + app::run(test::app()); +} \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs index 96d85c4..f431dfb 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -84,31 +84,21 @@ macro_rules! element { $( $functions:tt )* ) => { $(#[$meta])* - #[derive(Clone)] pub struct $name<'a> { - pub x: Value, + pub x: $crate::Value, pub y: usize, - pub width: Value, - pub height: Value, - pub children: Vec>, + pub width: $crate::Value, + pub height: $crate::Value, + pub children: Vec>, pub child: usize, pub text: String, pub id: &'a str, $( $inner )* } - impl Element for $name<'_> { - fn get_data(&self) -> ElementData { - ElementData { - x: self.x, - y: self.y, - width: self.width.clone(), - height: self.height.clone(), - children: self.children.clone(), - child: self.child.clone(), - text: self.text.clone(), - id: self.id.to_string(), - } + impl $crate::Element for $name<'_> { + fn get_data(&self) -> ($crate::Value, usize, String) { + (self.x.clone(), self.y.clone(), self.id.to_string()) } fn update_data(&mut self, width: usize, height: usize) { @@ -119,29 +109,15 @@ macro_rules! element { } } - fn get_id(&self) -> String { - self.id.to_string() - } - - fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box> { + fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box> { for elem in &mut self.children { - if elem.get_id() == id { + if elem.get_data().2 == id { return Some(elem); } } None } - fn set_data(&mut self, data: ElementData) { - self.x = data.x; - self.y = data.y; - self.width = data.width; - self.height = data.height; - self.children = data.children; - self.child = data.child; - self.text = data.text; - } - $( $functions )* } @@ -150,10 +126,10 @@ macro_rules! element { pub fn new() -> $name<'a> { $name { children: Vec::new(), - x: Value::Default(0), + x: $crate::Value::Default(0), y: 0, - width: Value::Default(0), - height: Value::Default(0), + width: $crate::Value::Default(0), + height: $crate::Value::Default(0), child: 0, text: String::new(), id: "", @@ -165,7 +141,7 @@ macro_rules! element { /// /// # Returns /// An `Option` containing a mutable reference to the child `Element` or `None`. - pub fn get_child(&mut self) -> Option<&mut Box> { + pub fn get_child(&mut self) -> Option<&mut Box> { self.children.get_mut(self.child) } } @@ -205,7 +181,7 @@ macro_rules! parse_rsx_param { ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => { $elem.$($k).+ = $v; - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; // for completion purposes @@ -214,12 +190,12 @@ macro_rules! parse_rsx_param { }; ($elem:expr, $($k:ident).+, $($rest:tt)*) => { $elem.$($k).+; - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; ($elem:expr, $($k:ident).+., $($rest:tt)*) => { $elem.$($k). +.; - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; ($elem:expr, $($k:ident).+.: $v:expr) => { @@ -227,7 +203,7 @@ macro_rules! parse_rsx_param { }; ($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => { $elem.$($k).+. = $v; - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; // For loop @@ -235,19 +211,19 @@ macro_rules! parse_rsx_param { for $($for)* { $elem.children.push({$($inner)*}) } - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; // Expression ($elem:expr, {$ielem:expr} $($rest:tt)*) => { $elem.children.push($ielem); - osui::parse_rsx_param!($elem, $($rest)*); + $crate::parse_rsx_param!($elem, $($rest)*); }; // Element ($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => { - $elem.children.push(osui::rsx_elem!($pelem { $($inner)* })); - osui::parse_rsx_param!($elem, $($rest)*) + $elem.children.push($crate::rsx_elem!($pelem { $($inner)* })); + $crate::parse_rsx_param!($elem, $($rest)*) }; // Text @@ -291,8 +267,8 @@ macro_rules! rsx_elem { ($elem:path { $($inner:tt)* }) => {{ #[allow(unused_mut)] let mut elem = $elem(); - osui::parse_rsx_param!(elem, $($inner)*); - elem as Box + $crate::parse_rsx_param!(elem, $($inner)*); + elem as Box }}; } @@ -301,7 +277,7 @@ macro_rules! rsx_elem_raw { ($elem:path { $($inner:tt)* }) => {{ #[allow(unused_mut)] let mut elem = $elem(); - osui::parse_rsx_param!(elem, $($inner)*); + $crate::parse_rsx_param!(elem, $($inner)*); elem }}; } @@ -322,7 +298,7 @@ macro_rules! rsx_elem_raw { #[macro_export] macro_rules! rsx { ($($inner:tt)*) => {{ - osui::rsx_elem_raw!( osui::ui::div { $($inner)* } ) + $crate::rsx_elem_raw!( $crate::ui::div { $($inner)* } ) }}; } @@ -349,6 +325,13 @@ macro_rules! css { } /// Macro for defining a `Arc>` +#[macro_export] +macro_rules! mux { + ($a:expr) => { + std::sync::Mutex::new($a) + }; +} + #[macro_export] macro_rules! arc { ($a:expr) => { @@ -360,19 +343,19 @@ macro_rules! arc { #[macro_export] macro_rules! val { ($a:expr) => { - osui::Value::new($a) + $crate::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] @@ -380,9 +363,21 @@ 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); + 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) + format!( + "{}{}{}{}\x1b[0m", + $self.style.hover_color.ansi(), + $self.style.hover_background.ansi_bg(), + $self.style.hover_font.ansi(), + $expr + ) }}; // Custom styles @@ -427,4 +422,12 @@ macro_rules! execute_response { } } }; -} \ No newline at end of file +} + +#[macro_export] +macro_rules! run_handler { + ($self:ident.$handler:ident ($renderer:expr, $event:expr)) => { + let mut o = $self.$handler.0.lock().unwrap(); + o($self, $renderer, $event); + }; +} diff --git a/src/main.rs b/src/main.rs index aabae6e..d720597 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use osui::run_test; + fn main() { - osui::run_test(); -} \ No newline at end of file + run_test(); +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..f808f50 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,5 @@ +use crate::{rsx_elem, Element, ui::*}; + +pub(crate) fn app() -> Box { + rsx_elem! { text { "Hello, World!" } } +} \ No newline at end of file diff --git a/src/test/mod.rs b/src/test/mod.rs deleted file mode 100644 index e162b4a..0000000 --- a/src/test/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::{self as osui, event::new_handler, rsx, ui::*, Element}; - -pub fn app() -> Box { - rsx! { - button { on_click: new_handler(move |btn: &mut Button, _response| { - btn.text = "Clicked".to_string(); - }), "click me" } - } -} diff --git a/src/ui/elements.rs b/src/ui/elements.rs index 758f306..9313fe7 100644 --- a/src/ui/elements.rs +++ b/src/ui/elements.rs @@ -1,130 +1,9 @@ -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -use crate::{ - command, create_frame, element, - event::{Command, Event, Handler, }, - execute_response, - key::Key, - render_to_frame, - ui::{Color, Font}, - write, Direction, Element, ElementData, EventResponse, Value, -}; +use crate::{element, State}; element! { - /// A text element for displaying static text in the TUI. - /// - /// The `Text` element displays text and does not respond to user interactions. Text {} defaults {} - fn render(&self, _: usize) -> String { + fn render(&self, _: State) -> String { self.text.clone() } } - -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 - /// based on its interaction state, such as being "clicked". - Button { - /// A callback function executed when the button is clicked. use `arc!` to use function - pub on_click: Handler>, - } - - defaults { - on_click: Arc::new(Mutex::new(|_btn: &mut Button<'_>, _: &mut EventResponse| {})), - } - - fn render(&self, state: usize) -> String { - if state == 2 { - return write!(self, clicked, self.text); - } - write!((self, state), self.text) - } - - 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(); - let mut response = command!( - Command::Render(2), - Command::Sleep(120) - ); - (on_click)(&mut btn, &mut response); - drop(on_click); - *self = btn; - return response; - } - } - _ => {} - } - - EventResponse::None - } -} - -element! { - /// A container element that can hold multiple child elements and handle directional key input. - /// - /// The `Div` element serves as a container for other elements, allowing navigation between them - /// using directional keys. - Div { - pub keybinds: HashMap - } - - defaults { - keybinds: HashMap::from([ - (Key::Up, Direction::Up), - (Key::Down, Direction::Down), - (Key::Left, Direction::Left), - (Key::Right, Direction::Right), - ]) - } - - fn render(&self, state: usize) -> String { - let mut frame = create_frame(self.width, self.height); - for (i, child) in (&self.children).iter().enumerate() { - if i==self.child { - render_to_frame(state, self.width.get_value(), &mut frame, child); - } else { - render_to_frame(0, self.width.get_value(), &mut frame, child); - } - } - frame.join("\n") - } - - fn event(&mut self, event: Event) -> EventResponse { - let mut res = EventResponse::None; - 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()); - if let Some(child) = self.get_child() { - child.event(Event::Hover); - } - } else if let Some(child) = self.get_child() { - res = child.event(event); - } - } - Event::Hover => { - if let Some(child) = self.get_child() { - res = child.event(event); - } - } - _ => {} - } - execute_response!(self, res); - res - } -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b14de6a..5278ae0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,37 +7,6 @@ pub use styles::*; pub mod elements; pub use elements::*; -/// Creates a new `Text` element. -/// -/// The `Text` element displays static text in the TUI, suitable for labels or headers. -/// -/// # Returns -/// -/// A `Box` containing a new `Text` element instance. pub fn text<'a>() -> Box> { Box::new(Text::new()) -} - -/// Creates a new `Button` element with a customized style. -/// -/// The `Button` element responds to user clicks, triggering an action. This function -/// customizes the button's clicked background and foreground colors. -/// -/// # Returns -/// -/// A `Box