diff --git a/Cargo.toml b/Cargo.toml index 2461bfc..a5dfc70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "osui" -version = "0.0.1" +version = "0.0.2" edition = "2021" license = "Apache-2.0" description = "A TUI library" @@ -14,4 +14,4 @@ name = "osui" path = "src/main.rs" [dependencies] -k_board = { version = "1.2.1", features = ["standar", "f", "alt_lower_letter", "ctrl_lower_letter", "lower_letter"] } +crossterm = "0.28.1" diff --git a/README.md b/README.md index 5ae2653..c1ab75f 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ To use `osui` in your Rust project, add the following to your `Cargo.toml`: ```toml [dependencies] -osui = "^0.0.1" +osui = "^0.0.2" ``` ```rust -use osui::{self, ui}; +use osui::{self, render_frame, ui}; fn main() { - osui::clear(); - let mut txt = ui::text("hello, world!"); - txt.render(); + let txt = ui::text("Hello World!"); + render_frame(vec![txt.clone()]); } + ``` diff --git a/src/lib.rs b/src/lib.rs index 4d40d34..627fb10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ -use std::io::{self, Write}; - +use crossterm::terminal::{self, disable_raw_mode, enable_raw_mode}; +use std::{ + io::{self, Read, Write}, + str::from_utf8, +}; +pub mod key; pub mod ui; #[derive(Clone)] @@ -7,11 +11,10 @@ pub struct ComponentWrapper { pub component: T, pub x: usize, pub y: usize, - cleared_frame: String, } pub trait Component { - fn render(&mut self) -> String { + fn render(&self) -> String { String::new() } fn run(&mut self, x: usize, y: usize) -> String { @@ -27,31 +30,8 @@ impl ComponentWrapper { component, x: 0, y: 0, - cleared_frame: String::new(), } } - pub fn render(&mut self) { - self.clean(); - self.cleared_frame.clear(); - for (i, d) in format!("{}", self.component.render()) - .split("\n") - .enumerate() - { - print!("\x1B[{};{}H{}", self.y + (i + 1), self.x, d); - self.cleared_frame += format!( - "\x1B[{};{}H{}", - self.y + (i + 1), - self.x, - " ".repeat(d.len()) - ) - .as_str(); - } - flush(); - } - pub fn clean(&self) { - print!("{}", self.cleared_frame); - flush(); - } pub fn run(&mut self) -> String { self.component.run(self.x, self.y) } @@ -65,3 +45,49 @@ pub fn clear() { pub fn flush() { io::stdout().flush().unwrap(); } + +pub fn render_frame(components: Vec>) { + clear(); + let (w, h) = terminal::size().unwrap(); + let mut frame: Vec = vec![" ".repeat(w as usize); h as usize - 1]; + for c in components { + for (i, line) in c.component.render().split("\n").enumerate() { + frame[c.y + i] = render_line(frame[c.y + i].clone(), line.to_string(), c.x); + } + } + print!("{}", frame.join("\n")); + flush(); +} + +fn render_line(frame_line: String, line: String, x: usize) -> String { + let mut result = String::new(); + if x >= frame_line.len() { + return result; + } + let lchars: Vec = line.chars().collect(); + for (i, c) in frame_line.chars().enumerate() { + if i >= x && lchars.len() > (i - x) { + result += lchars[i - x].to_string().as_str(); + } else { + result += c.to_string().as_str(); + } + } + result +} + +fn read_key_() -> String { + let mut buf = [0u8; 2]; + io::stdin().read(&mut buf).unwrap(); + let mut res = from_utf8(&buf).unwrap().to_string(); + if !res.ends_with("\0") { + res += read_key_().as_str() + } + return res.strip_suffix("\0").unwrap_or(&res).to_string(); +} + +pub fn read_key() -> String { + enable_raw_mode().unwrap(); + let res = read_key_(); + disable_raw_mode().unwrap(); + return res; +} diff --git a/src/main.rs b/src/main.rs index 7bbfd40..77c6a62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ -use osui::{self, ui}; +use osui::{self, render_frame, ui}; fn main() { - osui::clear(); - let mut txt = ui::text("hello, world!"); - txt.render(); + let txt = ui::text("Hello World!"); + render_frame(vec![txt.clone()]); } diff --git a/src/ui.rs b/src/ui.rs index 234da0c..2f56677 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,8 +1,6 @@ -use crate::{Component, ComponentWrapper}; -use k_board::{keyboard::Keyboard, keys::Keys}; - +use crate::{flush, key, read_key, Component, ComponentWrapper}; impl Component for String { - fn render(&mut self) -> String { + fn render(&self) -> String { self.clone() } } @@ -11,51 +9,49 @@ pub fn text(text: &str) -> ComponentWrapper { ComponentWrapper::new(text.to_string()) } +#[derive(Clone)] pub struct Input { pub max_length: usize, } impl Component for Input { - fn render(&mut self) -> String { + fn render(&self) -> String { format!( - " {}\n|{}|\n {}\x1b[1A\x1b[{}D", + " {}\n|{}|\n {}", "_".repeat(self.max_length), " ".repeat(self.max_length), "‾".repeat(self.max_length), - self.max_length ) } fn run(&mut self, x: usize, y: usize) -> String { print!("\x1B[{};{}H", y + 2, x + 2); + flush(); let mut input = String::new(); loop { - let key = Keyboard::new().read_key(); - if key == Keys::Enter { - return input; - } - if input.len() < self.max_length { - match key { - Keys::Char(c) => { + match read_key().as_str() { + key::ENTER => { + print!("\n\n"); + return input; + } + key::BACKSPACE => { + if input.len() > 0 { + input.pop(); + print!("\x1b[1D \x1b[1D"); + flush(); + } + } + c => { + if input.len() < self.max_length { input += c.to_string().as_str(); print!("{c}"); + flush(); } - Keys::Space => { - input += " "; - print!(" "); - } - Keys::Delete => { - if input.len() > 0 { - input.pop(); - print!("\x1b[1D \x1b[1D"); - } - } - _ => {} } } } } } -pub fn input(max_length: usize) -> ComponentWrapper { +pub fn input_box(max_length: usize) -> ComponentWrapper { ComponentWrapper::new(Input { max_length }) }