diff --git a/Cargo.toml b/Cargo.toml index 3334b55..e2ad459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ repository = "https://github.com/osui-rs/osui" homepage = "https://osui.netlify.app" documentation = "https://osui.netlify.app/docs/" +[features] +rsx = [] + [lib] name = "osui" path = "src/lib.rs" diff --git a/src/lib.rs b/src/lib.rs index faa11fe..d965919 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,86 +1,129 @@ pub mod utils; +pub mod widgets; -type Result = std::io::Result; +pub use std::io::Result; pub trait Widget { - fn draw(&self, w: &mut Writer); + fn render(&self) -> String; } -pub struct Writer(String); - -impl Writer { - pub fn write(&mut self, s: &str) { - self.0 += s; - } +#[derive(Debug, Clone, Copy)] +pub struct Area { + width: u16, + height: u16, + x: u16, + y: u16, + center_x: bool, + center_y: bool, + width_auto: bool, + height_auto: bool, } -pub type Element = Box; - -///////////////////////////////////////// - +#[derive(Debug, Default, Clone, Copy)] pub struct Frame { - pub area: (u16, u16), - pub x: u16, - pub y: u16, - parent: Option<*const Frame>, + pub area: Area, } impl Frame { - pub fn new(area: (u16, u16), parent: &Frame) -> Frame { - Frame { - area, - x: 0, - y: 0, - parent: Some(parent), - } - } - - pub fn new_root(area: (u16, u16)) -> Frame { - Frame { - area, - x: 0, - y: 0, - parent: None, - } - } - - pub fn render(&self, elem: E) + pub fn draw(&self, w: &W, mut props: Area) -> Result<()> where - E: Widget, + W: Widget, { - let mut w = Writer(String::new()); - elem.draw(&mut w); - if let Some(_f) = self.parent { - } else { - println!("{}", w.0); + let written = w.render(); + + let (ww, wh) = utils::str_size(&written); + + if props.width_auto { + props.width = ww; + } + if props.height_auto { + props.height = wh; } - } -} -pub struct Terminal(Frame); + if props.width > self.area.width { + props.width -= props.width - self.area.width; + } -impl Terminal { - pub fn new() -> Result { - Ok(Terminal(Frame::new_root(crossterm::terminal::size()?))) - } + if props.width > self.area.width { + props.width -= props.width - self.area.width; + } + + if props.center_x { + props.x = (self.area.width - ww) / 2; + } + if props.center_y { + props.y = (self.area.height - wh) / 2; + } + + for (i, line) in written.lines().enumerate() { + if i as u16 > props.height { + break; + } + + println!( + "\x1b[{};{}H{}", + props.x + (i as u16) + 1, + props.y + 1, + line.chars().take(props.width as usize).collect::() + ); + } - pub fn draw(&mut self, f: F) -> Result<()> - where - F: Fn(&mut Frame), - { - f(&mut self.0); Ok(()) } -} -impl Widget for &str { - fn draw(&self, w: &mut Writer) { - w.write(self); + pub fn new(width: u16, height: u16) -> Self { + let mut f = Self::default(); + f.area.width(width); + f.area.height(height); + f } } -pub fn init() -> Result { - utils::clear()?; - utils::hide_cursor()?; - Terminal::new() +impl Area { + pub fn new() -> Self { + Self::default() + } + + pub fn center_x(&mut self) -> Self { + self.center_x = true; + *self + } + + pub fn center_y(&mut self) -> Self { + self.center_y = true; + *self + } + + pub fn center(&mut self) -> Self { + self.center_x = true; + self.center_y = true; + *self + } + + pub fn width(&mut self, w: u16) -> Self { + self.width = w; + self.width_auto = false; + *self + } + + pub fn height(&mut self, h: u16) -> Self { + self.height = h; + self.height_auto = false; + *self + } +} + +impl Default for Area { + fn default() -> Self { + Self { + width: 0, + height: 0, + x: 0, + y: 0, + center_x: false, + center_y: false, + width_auto: true, + height_auto: true, + } + } } diff --git a/src/main.rs b/src/main.rs index 5a7b63d..d96cc29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ use osui::*; -fn main() { - let mut term = init().unwrap(); - loop { - term.draw(app).unwrap(); - } -} +fn main() -> osui::Result<()> { + utils::init()?; -fn app(frame: &mut Frame) { - frame.render("hi"); + let (width, height) = crossterm::terminal::size()?; + + let frame = Frame::new(width, height); + + frame.draw(&"Hello?", Area::new())?; + + utils::read()?; + + utils::end() } diff --git a/src/utils.rs b/src/utils.rs index 2d6d2b7..30214eb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,23 +2,73 @@ use std::io::Write; pub fn clear() -> crate::Result<()> { print!("\x1B[2J\x1B[H"); - std::io::stdout().flush()?; - Ok(()) + std::io::stdout().flush() } pub fn hide_cursor() -> crate::Result<()> { print!("\x1b[?25l"); - std::io::stdout().flush()?; - Ok(()) + std::io::stdout().flush() } pub fn show_cursor() -> crate::Result<()> { print!("\x1B[?25h"); - std::io::stdout().flush()?; - Ok(()) + std::io::stdout().flush() } pub fn flush() -> crate::Result<()> { - std::io::stdout().flush()?; - Ok(()) + std::io::stdout().flush() +} + +pub fn init() -> crate::Result<()> { + crossterm::terminal::enable_raw_mode()?; + clear()?; + hide_cursor() +} + +pub fn end() -> crate::Result<()> { + crossterm::terminal::disable_raw_mode()?; + clear()?; + show_cursor() +} + +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) +} + +pub enum Event { + Key(crossterm::event::KeyEvent), + Resize(u16, u16), + Mouse(crossterm::event::MouseEvent), + Paste(String), + FocusGained, + FocusLost, +} + +pub fn read() -> crate::Result { + let event = crossterm::event::read()?; + + Ok(match event { + crossterm::event::Event::Key(k) => Event::Key(k), + crossterm::event::Event::Resize(w, h) => Event::Resize(w, h), + crossterm::event::Event::FocusGained => Event::FocusGained, + crossterm::event::Event::FocusLost => Event::FocusLost, + crossterm::event::Event::Mouse(m) => Event::Mouse(m), + crossterm::event::Event::Paste(p) => Event::Paste(p), + }) } diff --git a/src/widgets.rs b/src/widgets.rs new file mode 100644 index 0000000..b689ace --- /dev/null +++ b/src/widgets.rs @@ -0,0 +1,7 @@ +use crate::Widget; + +impl Widget for &str { + fn render(&self) -> String { + self.to_string() + } +}