From 15b61d57c81fe4c8a653b0f23a17e865624334b4 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 27 Jun 2025 12:10:25 -0500 Subject: [PATCH] slightly improved structure and added a few more elements --- src/element/input.rs | 91 ++++++++++++++++++++++++++++++++++++++ src/element/mod.rs | 3 ++ src/element/rect.rs | 20 +++++++++ src/{ => element}/text.rs | 8 +++- src/extensions/keypress.rs | 15 ++++--- src/lib.rs | 10 ++++- src/macros.rs | 37 ++++++++++++++++ src/main.rs | 21 +++++++-- src/render_scope.rs | 31 ++++++++++++- src/style.rs | 6 +++ src/utils.rs | 14 ++++++ 11 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 src/element/input.rs create mode 100644 src/element/mod.rs create mode 100644 src/element/rect.rs rename src/{ => element}/text.rs (55%) diff --git a/src/element/input.rs b/src/element/input.rs new file mode 100644 index 0000000..7482552 --- /dev/null +++ b/src/element/input.rs @@ -0,0 +1,91 @@ +use std::sync::{Arc, Mutex}; + +use crossterm::event::KeyEvent; + +use crate::{ + event, event_handler, events::EventManager, extensions::keypress::KeyPressEventHandler, Element, +}; + +pub struct Input(String, usize, Arc>); + +impl Input { + pub fn new() -> Input { + Input(String::new(), 0, Arc::new(Mutex::new(EventManager::new()))) + } + + pub fn get_event(&self) -> Arc> { + self.2.clone() + } +} + +impl Element for Input { + fn init(&mut self, events: &mut crate::events::EventManager) { + event_handler!(Self, self, events, on_keypress); + } + + fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + let mut s = self.0.clone(); + + if self.1 < self.0.len() { + s.insert_str(self.1 + 1, "\x1b[0m"); + s.insert_str(self.1, "\x1b[42m"); + } else { + s += "\x1b[42m \x1b[0m"; + } + + scope.draw_text(&s); + } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl KeyPressEventHandler for Input { + fn on_keypress(&mut self, event: Box) { + match event.code { + crossterm::event::KeyCode::Char(c) => { + self.0.insert(self.1, c); + self.1 += 1; + self.2 + .lock() + .unwrap() + .dispatch(InputUpdateEvent(self.0.clone())); + } + crossterm::event::KeyCode::Backspace => { + if self.1 > 0 { + self.0.remove(self.1 - 1); + self.1 -= 1; + self.2 + .lock() + .unwrap() + .dispatch(InputUpdateEvent(self.0.clone())); + } + } + crossterm::event::KeyCode::Left => { + if self.1 > 0 { + self.1 -= 1; + self.2 + .lock() + .unwrap() + .dispatch(InputUpdateEvent(self.0.clone())); + } + } + crossterm::event::KeyCode::Right => { + if self.1 < self.0.len() { + self.1 += 1; + self.2 + .lock() + .unwrap() + .dispatch(InputUpdateEvent(self.0.clone())); + } + } + _ => {} + } + } +} + +event!(InputUpdateEvent(pub String)); diff --git a/src/element/mod.rs b/src/element/mod.rs new file mode 100644 index 0000000..73ba4d7 --- /dev/null +++ b/src/element/mod.rs @@ -0,0 +1,3 @@ +pub mod rect; +pub mod text; +pub mod input; diff --git a/src/element/rect.rs b/src/element/rect.rs new file mode 100644 index 0000000..545c947 --- /dev/null +++ b/src/element/rect.rs @@ -0,0 +1,20 @@ +use crate::Element; + +pub struct Rect(pub u32); +impl Element for Rect { + fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + let (w, h) = scope.get_size(); + + if w > 0 && h > 0 { + scope.draw_rect(w, h, self.0); + } + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/src/text.rs b/src/element/text.rs similarity index 55% rename from src/text.rs rename to src/element/text.rs index d9cd8c8..da27b3f 100644 --- a/src/text.rs +++ b/src/element/text.rs @@ -5,7 +5,11 @@ impl Element for String { scope.draw_text(self); } - fn as_any(self: Box) -> Box { - Box::new(self) + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn as_any(&self) -> &dyn std::any::Any { + self } } diff --git a/src/extensions/keypress.rs b/src/extensions/keypress.rs index 9167386..c76efd5 100644 --- a/src/extensions/keypress.rs +++ b/src/extensions/keypress.rs @@ -1,16 +1,19 @@ -use crossterm::event::{KeyCode, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crate::{ - event, - events::{Close, EventManager}, + events::{Close, Event, EventManager}, extensions::Extension, Screen, }; -event!(KeyPress(pub crossterm::event::KeyEvent)); +impl Event for KeyEvent { + fn as_any(self: Box) -> Box { + self + } +} pub trait KeyPressEventHandler { - fn on_keypress(&mut self, event: KeyPress); + fn on_keypress(&mut self, event: Box); } pub struct KeyPressExtension; @@ -36,7 +39,7 @@ impl Extension for KeyPressExtension { if e.modifiers.contains(KeyModifiers::CONTROL) && e.code == KeyCode::Char('c') { events.dispatch(Close); } else { - events.dispatch(KeyPress(e)); + events.dispatch(e); } } _ => {} diff --git a/src/lib.rs b/src/lib.rs index 644bf33..14f79fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,12 @@ use crate::{ style::Transform, }; +pub mod element; pub mod events; pub mod extensions; pub mod macros; pub mod render_scope; pub mod style; -pub mod text; pub mod utils; pub trait Element { @@ -23,7 +23,8 @@ pub trait Element { fn render(&mut self, scope: &mut RenderScope) {} #[allow(unused)] fn init(&mut self, events: &mut EventManager) {} - fn as_any(self: Box) -> Box; + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; } pub trait Component { @@ -94,7 +95,12 @@ impl Screen { utils::clear().unwrap(); for elem in &mut self.elements { scope.clear(); + if let Some(t) = elem.get() { + scope.set_transform(t); + } + elem.0.render(&mut scope); + if let Some(t) = elem.get() { scope.set_transform(t); } diff --git a/src/macros.rs b/src/macros.rs index 4ceda2f..2b1da6b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -85,3 +85,40 @@ macro_rules! component { } }; } + +/// Macro to create an event handler closure that calls a method on `self` +/// +/// This macro automates the pattern of capturing `self` as a raw pointer, +/// then passing an event to a method on `self` inside an unsafe closure. +/// +/// # Parameters +/// - `$self_ty`: The self type. +/// - `$self`: The instance variable (usually `self`) to call the method on. +/// - `$events`: The event source which has an `.on` method to register the handler. +/// - `$method`: The method name on `self` to call when an event occurs. +/// +/// # Usage +/// ```rust +/// event_handler!(self, events, on_keypress); +/// ``` +/// +/// This expands roughly to: +/// ```rust +/// let self_ref = self as *mut Self; +/// events.on(move |event| unsafe { (*self_ref).on_keypress(event) }); +/// ``` +/// +/// # Safety +/// This macro uses `unsafe` code because it dereferences a raw pointer inside the closure. +/// Ensure that the `self` reference lives at least as long as the closure to avoid undefined behavior. +/// +/// # Why use raw pointers here? +/// Often, event handlers require `'static` closures, but `self` is a stack reference. +/// Capturing `self` directly is not possible, so this workaround uses a raw pointer. +#[macro_export] +macro_rules! event_handler { + ($self_ty:ty, $self:ident, $events:ident, $method:ident) => {{ + let self_ref = $self as *mut $self_ty; + $events.on(move |e| unsafe { (*self_ref).$method(e) }); + }}; +} diff --git a/src/main.rs b/src/main.rs index 0ba3eb1..798ee70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use osui::{ + element::input::{Input, InputUpdateEvent}, events::EventManager, extensions::{keypress::KeyPressExtension, ExtensionManager}, style::Transform, @@ -11,9 +12,23 @@ fn main() { let mut events = EventManager::new(); extensions.add(KeyPressExtension); - screen - .draw("Hello, World".to_string()) - .component(Transform::center()); + let widget = screen.draw(Input::new()).component(Transform::center()); + + let input_event = widget + .0 + .as_any() + .downcast_ref::() + .unwrap() + .get_event(); + + input_event + .lock() + .unwrap() + .on(|event: Box| { + if event.0 == "quit" { + std::process::exit(0) + } + }); screen.run(&mut events, &mut extensions).unwrap(); } diff --git a/src/render_scope.rs b/src/render_scope.rs index bd4dc19..eeae4ce 100644 --- a/src/render_scope.rs +++ b/src/render_scope.rs @@ -1,10 +1,11 @@ use crate::{ style::{RawTransform, Transform}, - utils, + utils::{self, hex_ansi_bg}, }; enum RenderMethod { Text(String), + Rectangle(u16, u16, u32), } pub struct RenderScope { @@ -37,17 +38,45 @@ impl RenderScope { self.transform.height = self.transform.height.max(h); } + pub fn draw_rect(&mut self, width: u16, height: u16, color: u32) { + self.render_stack + .push(RenderMethod::Rectangle(width, height, color)); + self.transform.width = self.transform.width.max(width); + self.transform.height = self.transform.height.max(height); + } + pub fn draw(&self) { for m in &self.render_stack { match m { RenderMethod::Text(t) => { println!("\x1b[{};{}H{t}", self.transform.y + 1, self.transform.x + 1) } + RenderMethod::Rectangle(width, height, color) => { + for i in 0..(*height) { + println!( + "\x1b[{};{}H{}{}\x1b[0m", + self.transform.y + 1 + i, + self.transform.x + 1, + hex_ansi_bg(*color), + " ".repeat(*width as usize) + ) + } + } } } } pub fn clear(&mut self) { self.render_stack.clear(); + self.transform.width = 0; + self.transform.height = 0; + } + + pub fn get_size(&self) -> (u16, u16) { + (self.transform.width, self.transform.height) + } + + pub fn get_parent_size(&self) -> (u16, u16) { + (self.parent_width, self.parent_height) } } diff --git a/src/style.rs b/src/style.rs index 161d8de..33d823b 100644 --- a/src/style.rs +++ b/src/style.rs @@ -54,6 +54,12 @@ impl Transform { } } + pub fn dimensions(&mut self, width: u16, height: u16) -> Self { + self.width = Dimension::Const(width); + self.height = Dimension::Const(height); + self.clone() + } + pub fn use_dimensions(&self, raw: &mut RawTransform) { self.width.use_dimension(&mut raw.width); self.height.use_dimension(&mut raw.height); diff --git a/src/utils.rs b/src/utils.rs index 83e2c74..c42b6a4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -92,3 +92,17 @@ pub fn str_size(s: &str) -> (u16, u16) { (max_width, height) } + +pub fn hex_ansi(hex: u32) -> String { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + format!("\x1b[38;2;{r};{g};{b}m") +} + +pub fn hex_ansi_bg(hex: u32) -> String { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + format!("\x1b[48;2;{r};{g};{b}m") +}