diff --git a/src/lib.rs b/src/lib.rs index 2696da9..25044b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,64 +1,104 @@ pub mod elements; +pub mod style; pub mod utils; pub use std::io::Result; use std::sync::Arc; pub mod prelude { - pub use crate::{utils::init, Props, Result, Ui}; + pub use crate::{ + style::{Color, Style}, + utils::init, + Props, Result, Ui, + }; } // Traits - pub trait Framing { fn draw(&mut self, element: &dyn Element, props: &Props); } pub trait Element { fn render(&self) -> String; + fn get_colors(&self) -> std::collections::HashMap { + std::collections::HashMap::new() + } } // Types pub type Ui = Arc; -pub enum Position { - Num(u16), - Center, +#[derive(Debug, Clone)] +pub struct Props { + x: style::Position, + y: style::Position, + width: style::Dimension, + height: style::Dimension, + style: style::Style, + style_state: std::collections::HashMap, } -pub struct Props(Position, Position); - /// Represents the console state, containing a frame for rendering and a mouse capture flag. pub struct Console { mouse: bool, width: u16, height: u16, -} - -impl Position { - pub fn get(&self, written: u16, frame: u16) -> u16 { - match self { - Self::Center => (frame - written) / 2, - Self::Num(n) => *n, - } - } + pub mouse_position: Option<(u16, u16)>, } impl Props { pub fn new(x: u16, y: u16) -> Self { - Self(Position::Num(x), Position::Num(y)) + Self { + x: style::Position::Num(x), + y: style::Position::Num(y), + width: style::Dimension::Auto, + height: style::Dimension::Auto, + style: style::Style::default(), + style_state: std::collections::HashMap::new(), + } } pub fn center() -> Self { - Self(Position::Center, Position::Center) + Self { + x: style::Position::Center, + y: style::Position::Center, + width: style::Dimension::Auto, + height: style::Dimension::Auto, + style: style::Style::default(), + style_state: std::collections::HashMap::new(), + } } pub fn center_horizontal() -> Self { - Self(Position::Center, Position::Num(0)) + Self { + x: style::Position::Center, + y: style::Position::Num(0), + width: style::Dimension::Auto, + height: style::Dimension::Auto, + style: style::Style::default(), + style_state: std::collections::HashMap::new(), + } } pub fn center_vertical() -> Self { - Self(Position::Num(0), Position::Center) + Self { + x: style::Position::Num(0), + y: style::Position::Center, + width: style::Dimension::Auto, + height: style::Dimension::Auto, + style: style::Style::default(), + style_state: std::collections::HashMap::new(), + } + } + + pub fn style(&mut self, style: style::Style) -> Self { + self.style = style; + self.clone() + } + + pub fn state(&mut self, state: &str, style: style::Style) -> Self { + self.style_state.insert(state.to_string(), style); + self.clone() } } @@ -68,13 +108,34 @@ impl Framing for Console { let (written_width, written_height) = utils::str_size(&written); - let (x, y) = ( - props.0.get(written_width, self.width), - props.1.get(written_height, self.height), + let (width, height) = ( + props.width.get_root(written_width, self.width), + props.height.get_root(written_height, self.height), ); + let (x, y) = ( + props.x.get(width, self.width), + props.y.get(height, self.height), + ); + + let mut style = props.style; + + if let Some((mouse_x, mouse_y)) = self.mouse_position { + if (x..x + width).contains(&mouse_x) && (y..y + height).contains(&mouse_y) { + if let Some(hover_style) = props.style_state.get("hover") { + style.color = hover_style.color; + } + } + } + for (i, line) in written.lines().enumerate() { - println!("\x1b[{};{}H{}", y as usize + i + 1, x + 1, line); + println!( + "\x1b[{};{}H{}{}", + y as usize + i + 1, + x + 1, + style.color.to_ansi(true), + line + ); } } } diff --git a/src/main.rs b/src/main.rs index 441f923..88b3ea0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,33 @@ use std::sync::Arc; use osmium_engine::prelude::*; fn main() -> Result<()> { - let mut console = init(false)?; + let mut console = init(true)?; - console.render(app())?; + let ui = app(); - crossterm::event::read()?; + loop { + console.render(ui.clone())?; + if let crossterm::event::Event::Mouse(crossterm::event::MouseEvent { + kind: crossterm::event::MouseEventKind::Moved, + row, + column, + .. + }) = crossterm::event::read()? + { + console.mouse_position = Some((column, row)); + } else { + break; + } + } console.close() } pub fn app() -> Ui { Arc::new(|frame| { - frame.draw(&"Hello, World", &Props::center()); + frame.draw( + &"Hello, World", + &Props::center().state("hover", Style { color: Color::Blue }), + ); }) } diff --git a/src/style.rs b/src/style.rs new file mode 100644 index 0000000..299cb28 --- /dev/null +++ b/src/style.rs @@ -0,0 +1,97 @@ +#[derive(Debug, Clone, Copy)] +pub enum Color { + NoColor, + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + BrightBlack, + BrightRed, + BrightGreen, + BrightYellow, + BrightBlue, + BrightMagenta, + BrightCyan, + BrightWhite, + Rgb(u8, u8, u8), // Custom RGB color +} + +impl Color { + /// Converts the enum to its ANSI escape code + pub fn to_ansi(&self, foreground: bool) -> String { + let f = if foreground { "3" } else { "4" }; + match self { + Self::NoColor => format!("\x1b[0m"), + Self::Black => format!("\x1b[{f}0m"), + Self::Red => format!("\x1b[{f}1m"), + Self::Green => format!("\x1b[{f}2m"), + Self::Yellow => format!("\x1b[{f}3m"), + Self::Blue => format!("\x1b[{f}4m"), + Self::Magenta => format!("\x1b[{f}5m"), + Self::Cyan => format!("\x1b[{f}6m"), + Self::White => format!("\x1b[{f}7m"), + Self::BrightBlack => format!("\x1b[{f}90m"), + Self::BrightRed => format!("\x1b[{f}91m"), + Self::BrightGreen => format!("\x1b[{f}92m"), + Self::BrightYellow => format!("\x1b[{f}93m"), + Self::BrightBlue => format!("\x1b[{f}94m"), + Self::BrightMagenta => format!("\x1b[{f}95m"), + Self::BrightCyan => format!("\x1b[{f}96m"), + Self::BrightWhite => format!("\x1b[{f}97m"), + Self::Rgb(r, g, b) => { + let code = if foreground { "38" } else { "48" }; + format!("\x1b[{};2;{};{};{}m", code, r, g, b) + } + } + } +} + +impl Default for Color { + fn default() -> Self { + Self::NoColor + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct Style { + pub color: Color, +} + +#[derive(Debug, Clone, Copy)] +pub enum Position { + Num(u16), + Center, +} +impl Position { + pub fn get(&self, written: u16, frame: u16) -> u16 { + match self { + Self::Center => (frame - written) / 2, + Self::Num(n) => *n, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Dimension { + Num(u16), + Auto, +} +impl Dimension { + pub fn get(&self, _written: u16, frame: u16) -> u16 { + match self { + Self::Auto => frame, + Self::Num(n) => *n, + } + } + + pub fn get_root(&self, written: u16, _frame: u16) -> u16 { + match self { + Self::Auto => written, + Self::Num(n) => *n, + } + } +} diff --git a/src/utils.rs b/src/utils.rs index 2b2aa5f..3a54e59 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -112,5 +112,6 @@ pub fn init(mouse: bool) -> crate::Result { width, height, mouse, + mouse_position: None, }) }