diff --git a/src/lib.rs b/src/lib.rs index 25044b5..bf3ca42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod style; pub mod utils; pub use std::io::Result; -use std::sync::Arc; +use std::{io::Write, sync::Arc}; pub mod prelude { pub use crate::{ @@ -39,10 +39,11 @@ pub struct Props { } /// Represents the console state, containing a frame for rendering and a mouse capture flag. -pub struct Console { +pub struct Console<'a> { mouse: bool, width: u16, height: u16, + handle: Option>, pub mouse_position: Option<(u16, u16)>, } @@ -100,11 +101,21 @@ impl Props { self.style_state.insert(state.to_string(), style); self.clone() } + + pub fn width(&mut self, w: u16) -> Self { + self.width = style::Dimension::Num(w); + self.clone() + } + + pub fn height(&mut self, h: u16) -> Self { + self.height = style::Dimension::Num(h); + self.clone() + } } -impl Framing for Console { +impl Framing for Console<'_> { fn draw(&mut self, element: &dyn Element, props: &Props) { - let written = element.render(); + let mut written = element.render(); let (written_width, written_height) = utils::str_size(&written); @@ -123,28 +134,54 @@ impl Framing for Console { 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; + style = hover_style.clone(); } } } - for (i, line) in written.lines().enumerate() { - println!( - "\x1b[{};{}H{}{}", - y as usize + i + 1, - x + 1, - style.color.to_ansi(true), - line - ); + let ansi = if style.background == style::Color::NoColor { + format!("\x1b[{}m", style.color.ansi_number(true)) + } else { + format!( + "\x1b[{};{}m", + style.color.ansi_number(true), + style.background.ansi_number(false) + ) + }; + + let (x, y) = (x - style.px, y - style.py); + let px = " ".repeat(style.px as usize); + written.push_str(&format!("\n{}", " ".repeat(width as usize)).repeat(style.py as usize)); + written.insert_str( + 0, + &format!("{}\n", " ".repeat(width as usize)).repeat(style.py as usize), + ); + + if let Some(handle) = &mut self.handle { + for (i, line) in written.lines().enumerate() { + write!( + handle, + "\x1b[{};{}H{ansi}{px}{}{px}\x1b[0m", + y as usize + i + 1, + x + 1, + line, + ) + .unwrap(); + } } } } -impl Console { +impl Console<'_> { pub fn render(&mut self, ui: Ui) -> Result<()> { - utils::clear()?; (self.width, self.height) = crossterm::terminal::size()?; + let stdout = std::io::stdout(); + self.handle = Some(stdout.lock()); + utils::clear()?; ui(self); + if let Some(handle) = &mut self.handle { + handle.flush()?; + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 88b3ea0..c15ab7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,16 +9,15 @@ fn main() -> Result<()> { 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; + match crossterm::event::read()? { + crossterm::event::Event::Mouse(crossterm::event::MouseEvent { + kind: crossterm::event::MouseEventKind::Moved, + row, + column, + .. + }) => console.mouse_position = Some((column, row)), + crossterm::event::Event::Resize(_, _) => {} + _ => break, } } @@ -29,7 +28,12 @@ pub fn app() -> Ui { Arc::new(|frame| { frame.draw( &"Hello, World", - &Props::center().state("hover", Style { color: Color::Blue }), + &Props::center().state("hover", Style { + color: Color::Red, + background: Color::Red, + px: 1, + py: 0, + }), ); }) } diff --git a/src/style.rs b/src/style.rs index 299cb28..1368ee8 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Color { NoColor, Black, @@ -22,10 +22,37 @@ pub enum Color { impl Color { /// Converts the enum to its ANSI escape code + pub fn ansi_number(&self, fg: bool) -> String { + let f = if fg { "3" } else { "4" }; + match self { + Self::NoColor => String::new(), + Self::Black => format!("{f}0"), + Self::Red => format!("{f}1"), + Self::Green => format!("{f}2"), + Self::Yellow => format!("{f}3"), + Self::Blue => format!("{f}4"), + Self::Magenta => format!("{f}5"), + Self::Cyan => format!("{f}6"), + Self::White => format!("{f}7"), + Self::BrightBlack => format!("{f}90"), + Self::BrightRed => format!("{f}91"), + Self::BrightGreen => format!("{f}92"), + Self::BrightYellow => format!("{f}93"), + Self::BrightBlue => format!("{f}94"), + Self::BrightMagenta => format!("{f}95"), + Self::BrightCyan => format!("{f}96"), + Self::BrightWhite => format!("{f}97"), + Self::Rgb(r, g, b) => { + let code = if fg { "38" } else { "48" }; + format!("{code};2;{r};{g};{b}") + } + } + } + pub fn to_ansi(&self, foreground: bool) -> String { let f = if foreground { "3" } else { "4" }; match self { - Self::NoColor => format!("\x1b[0m"), + Self::NoColor => String::new(), Self::Black => format!("\x1b[{f}0m"), Self::Red => format!("\x1b[{f}1m"), Self::Green => format!("\x1b[{f}2m"), @@ -44,7 +71,7 @@ impl Color { 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) + format!("\x1b[{code};2;{r};{g};{b}m") } } } @@ -56,9 +83,12 @@ impl Default for Color { } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct Style { pub color: Color, + pub background: Color, + pub px: u16, + pub py: u16, } #[derive(Debug, Clone, Copy)] diff --git a/src/utils.rs b/src/utils.rs index 3a54e59..5b93987 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -100,7 +100,7 @@ pub fn str_size(s: &str) -> (u16, u16) { /// /// # Returns /// A `Console` instance wrapped in a `Result`. -pub fn init(mouse: bool) -> crate::Result { +pub fn init<'a>(mouse: bool) -> crate::Result> { crossterm::terminal::enable_raw_mode()?; clear()?; hide_cursor()?; @@ -112,6 +112,20 @@ pub fn init(mouse: bool) -> crate::Result { width, height, mouse, + handle: None, mouse_position: None, }) } + +pub fn calculate_padding(string_length: usize, width: usize) -> (usize, usize) { + if width <= string_length { + // No padding needed if the string length is greater than or equal to the width + return (0, 0); + } + + let total_padding = width - string_length; + let left_padding = total_padding / 2; // Floor division for left padding + let right_padding = total_padding - left_padding; // Remaining for right padding + + (left_padding, right_padding) +}