diff --git a/src/event_manager.rs b/src/event_manager.rs new file mode 100644 index 0000000..13ad128 --- /dev/null +++ b/src/event_manager.rs @@ -0,0 +1,57 @@ +use std::{ + any::{Any, TypeId}, + collections::HashMap, +}; + +use crate::Close; + +pub trait Event { + fn as_any(self: Box) -> Box; +} + +pub struct EventHandlerFn(pub Box)>); + +impl std::fmt::Debug for EventHandlerFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FnMut()") + } +} + +#[derive(Debug)] +pub struct EventManager { + handlers: HashMap>, +} + +impl EventManager { + pub fn new() -> EventManager { + EventManager { + handlers: HashMap::new(), + } + } + + pub fn on) + 'static>(&mut self, mut f: F) { + let wrapper = Box::new(move |evt: Box| { + if let Ok(concrete) = evt.as_any().downcast::() { + f(concrete); + } + }) as Box)>; + + self.handlers + .entry(TypeId::of::()) + .or_insert_with(Vec::new) + .push(EventHandlerFn(wrapper)); + } + + pub fn dispatch(&mut self, evt: E) { + let tid = evt.type_id(); + if let Some(handlers) = self.handlers.get_mut(&tid) { + for h in handlers { + (h.0)(Box::new(evt.clone())); + } + } + + if tid == TypeId::of::() { + std::process::exit(0); + } + } +} diff --git a/src/extensions/keypress.rs b/src/extensions/keypress.rs new file mode 100644 index 0000000..ce0a396 --- /dev/null +++ b/src/extensions/keypress.rs @@ -0,0 +1,46 @@ +use crossterm::event::{KeyCode, KeyModifiers}; + +use crate::{event_manager::EventManager, Close, Event, Extension, Screen}; + +#[derive(Clone)] +pub struct KeyPress(pub crossterm::event::KeyEvent); +impl Event for KeyPress { + fn as_any(self: Box) -> Box { + self + } +} + +pub trait KeyPressEventHandler { + fn on_keypress(&mut self, event: KeyPress); +} + +pub struct KeyPressExtension; + +impl Extension for KeyPressExtension { + fn init(&mut self, _: &mut Screen, events: &mut EventManager) { + crossterm::terminal::enable_raw_mode().unwrap(); + crate::utils::clear().unwrap(); + crate::utils::hide_cursor().unwrap(); + + events.on(|_: Box| { + crossterm::terminal::disable_raw_mode().unwrap(); + crate::utils::show_cursor().unwrap(); + crate::utils::clear().unwrap(); + }); + } + + fn tick_start(&mut self, _: &mut Screen, _: &mut EventManager) {} + + fn tick_end(&mut self, _: &mut Screen, events: &mut EventManager) { + match crossterm::event::read().unwrap() { + crossterm::event::Event::Key(e) => { + if e.modifiers.contains(KeyModifiers::CONTROL) && e.code == KeyCode::Char('c') { + events.dispatch(Close); + } else { + events.dispatch(KeyPress(e)); + } + } + _ => {} + } + } +} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..e1506f5 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1 @@ +pub mod keypress; diff --git a/src/lib.rs b/src/lib.rs index cbff3cf..d9521bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,23 +1,114 @@ -use std::{ - any::{Any, TypeId}, - collections::HashMap, +use std::any::Any; + +use crate::{ + event_manager::{Event, EventManager}, + render_scope::RenderScope, }; +pub mod event_manager; +pub mod extensions; +pub mod render_scope; pub mod text; +pub mod utils; -pub trait Event {} - -pub struct Screen { - element: Box, - event_handlers: HashMap)>>, +pub trait Element { + #[allow(unused)] + fn render(&mut self, scope: &mut RenderScope) {} + #[allow(unused)] + fn init(&mut self, events: &mut EventManager) {} + fn as_any(self: Box) -> Box; } -impl Screen { - pub fn on_event(&mut self, f: F) { - +pub trait Extension { + fn init(&mut self, screen: &mut Screen, events: &mut EventManager); + fn tick_start(&mut self, screen: &mut Screen, events: &mut EventManager); + fn tick_end(&mut self, screen: &mut Screen, events: &mut EventManager); +} + +pub struct ExtensionManager(Vec>); + +impl Extension for ExtensionManager { + fn init(&mut self, screen: &mut Screen, events: &mut EventManager) { + for ext in &mut self.0 { + ext.init(screen, events); + } + } + + fn tick_start(&mut self, screen: &mut Screen, events: &mut EventManager) { + for ext in &mut self.0 { + ext.tick_start(screen, events); + } + } + + fn tick_end(&mut self, screen: &mut Screen, events: &mut EventManager) { + for ext in &mut self.0 { + ext.tick_end(screen, events); + } } } -pub trait Element { - fn init(&mut self, screen: &mut Screen); +impl ExtensionManager { + pub fn new() -> ExtensionManager { + ExtensionManager(Vec::new()) + } + pub fn add(&mut self, extension: E) { + self.0.push(Box::new(extension)); + } +} + +pub struct EventHandlerFn(pub Box)>); + +impl std::fmt::Debug for EventHandlerFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FnMut()") + } +} + +#[derive(Debug, Clone)] +pub struct Close; + +impl Event for Close { + fn as_any(self: Box) -> Box { + self + } +} + +pub struct Screen { + pub elements: Vec>, +} + +impl Screen { + pub fn new() -> Screen { + Screen { + elements: Vec::new(), + } + } + + pub fn draw(&mut self, element: E) { + self.elements.push(Box::new(element)); + } + + pub fn run( + &mut self, + events: &mut EventManager, + extension_manager: &mut ExtensionManager, + ) -> std::io::Result<()> { + let mut scope = RenderScope {}; + + for elem in &mut self.elements { + elem.init(events); + } + + extension_manager.init(self, events); + + loop { + extension_manager.tick_start(self, events); + + for elem in &mut self.elements { + elem.render(&mut scope); + } + + extension_manager.tick_end(self, events); + } + } } diff --git a/src/main.rs b/src/main.rs index 58dfaaa..3c68908 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,15 @@ +use osui::{ + event_manager::EventManager, extensions::keypress::KeyPressExtension, ExtensionManager, Screen, +}; + fn main() { - + let mut screen = Screen::new(); + let mut extensions = ExtensionManager::new(); + let mut events = EventManager::new(); + + extensions.add(KeyPressExtension); + + screen.draw("Hello, World".to_string()); + + screen.run(&mut events, &mut extensions).unwrap(); } diff --git a/src/render_scope.rs b/src/render_scope.rs new file mode 100644 index 0000000..604b8f6 --- /dev/null +++ b/src/render_scope.rs @@ -0,0 +1,7 @@ +pub struct RenderScope {} + +impl RenderScope { + pub fn draw_text(&mut self, text: &str) { + println!("{text}"); + } +} diff --git a/src/text.rs b/src/text.rs index e69de29..d9cd8c8 100644 --- a/src/text.rs +++ b/src/text.rs @@ -0,0 +1,11 @@ +use crate::Element; + +impl Element for String { + fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + scope.draw_text(self); + } + + fn as_any(self: Box) -> Box { + Box::new(self) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..83e2c74 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,94 @@ +//! The `utils` module provides utility functions for terminal manipulation +//! and string operations, designed to enhance terminal-based applications. + +use std::io::{self, Write}; + +/// Clears the terminal screen and moves the cursor to the top-left corner. +/// +/// # Returns +/// A `io::Result<()>` indicating whether the operation succeeded. +/// +/// # Example +/// ``` +/// utils::clear().unwrap(); +/// ``` +pub fn clear() -> io::Result<()> { + print!("\x1B[2J\x1B[H"); + std::io::stdout().flush() +} + +/// Hides the terminal cursor. +/// +/// # Returns +/// A `io::Result<()>` indicating whether the operation succeeded. +/// +/// # Example +/// ``` +/// utils::hide_cursor().unwrap(); +/// ``` +pub fn hide_cursor() -> io::Result<()> { + print!("\x1b[?25l"); + std::io::stdout().flush() +} + +/// Shows the terminal cursor. +/// +/// # Returns +/// A `io::Result<()>` indicating whether the operation succeeded. +/// +/// # Example +/// ``` +/// utils::show_cursor().unwrap(); +/// ``` +pub fn show_cursor() -> io::Result<()> { + print!("\x1B[?25h"); + std::io::stdout().flush() +} + +/// Flushes the terminal's stdout buffer. +/// +/// # Returns +/// A `io::Result<()>` indicating whether the operation succeeded. +/// +/// # Example +/// ``` +/// utils::flush().unwrap(); +/// ``` +pub fn flush() -> io::Result<()> { + std::io::stdout().flush() +} + +/// Calculates the width and height of a string when rendered in a terminal. +/// +/// # Arguments +/// - `s`: The input string. +/// +/// # Returns +/// A tuple `(u16, u16)` where: +/// - The first value is the maximum width of the string in characters. +/// - The second value is the height of the string in lines. +/// +/// # Example +/// ``` +/// let (width, height) = utils::str_size("Hello\nWorld!"); +/// assert_eq!((5, 2), (width, height)); +/// ``` +pub fn str_size(s: &str) -> (u16, u16) { + let mut height = 1; + let mut max_width = 0; + let mut current_width = 0; + + for b in s.bytes() { + if b == b'\n' { + height += 1; + max_width = max_width.max(current_width); + current_width = 0; + } else { + current_width += 1; + } + } + + max_width = max_width.max(current_width); + + (max_width, height) +}