From 44a227780bbb9d286d4ae1ce3c891446f98516c7 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 17 Oct 2024 20:17:35 +0200 Subject: [PATCH] some new stuff --- Cargo.toml | 4 +- README.md | 20 +++++---- src/components.rs | 41 ++++++++++++++--- src/lib.rs | 109 +++++++++++++++++++++++++++++++++------------- src/macros.rs | 11 +++++ src/main.rs | 36 +++++---------- src/utils.rs | 86 +++++++++++++++++++++++++++++++++--- 7 files changed, 231 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a90f369..091155f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,6 @@ name = "osui" path = "src/main.rs" [dependencies] -crossterm = "0.28.1" \ No newline at end of file +crossterm = "0.28.1" +regex = "*" +lazy_static = "1.4" \ No newline at end of file diff --git a/README.md b/README.md index 9b29f49..698b4df 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,29 @@ Here is the OSUI Go version: https://github.com/orus-dev/osui ## Features +- **React-Like**: Easily write components. - **Cross-Platform Support**: Works on various operating systems. - **Flexible Layout**: Easily create and manage complex UIs. - **Efficient Performance**: Designed for high performance and low latency. ## Installation -To use `osui` in your Rust project, add the following to your `Cargo.toml`: +To use `osui` in your Rust project, run the following: -```toml -[dependencies] -osui = "^0.0.2" -``` +`cargo add osui` ```rust -use osui::{self, render_frame, ui}; +use osui::{ + components::text, + oml, App +}; fn main() { - let txt = ui::text("Hello World!"); - render_frame!(txt); + let mut app = App::new(); + app.set_component(oml!( + text("Hello, World!";) + )); + app.run(); } ``` diff --git a/src/components.rs b/src/components.rs index ab41e9f..e942d53 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,15 +1,44 @@ -use crate::{utils::render_to_frame, Component, Params}; +use crate::{ + create_frame, key::KeyKind, utils::render_to_frame, Component, Params, UpdateContext, + UpdateRequest, +}; pub fn div(params: Params) -> Component { - Component::new(params, |s| { - let mut frame: Vec = vec![" ".repeat(s.width); s.height]; + let mut c = Component::new(params); + c.render = |s: &mut Component| { + let mut frame: Vec = create_frame!(s.width, s.height); for c in &mut s.children { - render_to_frame(s.width, &mut frame, c); + render_to_frame(&mut frame, c); } frame.join("\n") - }) + }; + c } pub fn text(params: Params) -> Component { - Component::new(params, |c| c.expr.clone()) + let mut c = Component::new(params); + c.render = |s: &mut Component| s.expr.clone(); + c +} + +pub fn button(params: Params) -> Component { + let mut c = Component::new(params); + c.update = |s: &mut Component, ctx: &mut UpdateContext| { + s.expr = "nah".to_string(); + match ctx.request.clone() { + UpdateRequest::Key(k) => { + if k.kind == KeyKind::Enter { + s.clicked = !s.clicked; + let mut cctx = UpdateContext { + request: UpdateRequest::Key(k), + response: crate::UpdateResponse::None, + }; + (s.on_click)(&mut cctx); + ctx.response = cctx.response; + } + } + } + }; + c.render = |s: &mut Component| s.expr.clone(); + c } diff --git a/src/lib.rs b/src/lib.rs index fc49c9b..1f8f83d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,88 +1,135 @@ -use std::{thread::sleep, time::Duration}; - -use crossterm::terminal; -use utils::{clear, hide_cursor, render_to_frame}; - pub mod components; +pub mod key; pub mod macros; pub mod utils; +use crossterm; +use key::Key; +use std::collections::HashMap; +use utils::{clear, show_cursor, Value}; + +#[derive(Debug, Clone, PartialEq)] +pub enum UpdateResponse { + Exit, + Done, + None, + SetComponent(Component), +} + +#[derive(Debug, Clone)] +pub enum UpdateRequest { + Key(Key), +} + +#[derive(Debug, Clone)] +pub struct UpdateContext { + pub request: UpdateRequest, + pub response: UpdateResponse, +} + #[derive(Debug)] pub struct Params { pub expr: String, pub children: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Component { pub render: fn(&mut Component) -> String, - pub update: fn(&mut Component), + pub update: fn(&mut Component, &mut UpdateContext), + pub on_click: fn(&mut UpdateContext), pub x: usize, pub y: usize, pub width: usize, pub height: usize, pub expr: String, pub children: Vec, + pub clicked: bool, + pub toggle: bool, + pub data: HashMap, } impl Component { - fn new(params: Params, render: fn(&mut Component) -> String) -> Component { + /// Creates a new component + fn new(params: Params) -> Component { Component { - render, - update: |_| {}, + render: |_| String::new(), + update: |_, _| {}, + on_click: |_| {}, x: 0, y: 0, width: 0, height: 0, expr: params.expr, children: params.children, + clicked: false, + toggle: false, + data: HashMap::new(), } } } #[derive(Debug, Clone)] -pub struct Screen { +pub struct App { component: Component, } -impl Screen { - pub fn new() -> Screen { - Screen { - component: Component::new( - Params { - expr: "".to_string(), - children: vec![], - }, - |_| String::new(), - ), +impl App { + /// Creates a new screen to render components + pub fn new() -> App { + App { + component: Component::new(Params { + expr: "".to_string(), + children: vec![], + }), } } + + /// Sets a component pub fn set_component(&mut self, component: Component) { - let (width, height) = terminal::size().unwrap(); + let (width, height) = crossterm::terminal::size().unwrap(); self.component = component; if self.component.width == 0 { self.component.width = width as usize; } - if self.component.width == 0 { + if self.component.height == 0 { self.component.height = height as usize; } self.render(); } + /// Render to the screen pub fn render(&mut self) { - let (width, height) = terminal::size().unwrap(); - let mut frame: Vec = vec![" ".repeat(width as usize); height as usize]; - render_to_frame(width as usize, &mut frame, &mut self.component); - clear(); - print!("{}", frame.join("\n")); + let (width, height) = crossterm::terminal::size().unwrap(); + let mut frame: Vec = create_frame!(width as usize, height as usize); + utils::render_to_frame(&mut frame, &mut self.component); + utils::clear(); + print!("{}", frame.join("")); + utils::flush(); } + /// Run the screen pub fn run(&mut self) { - hide_cursor(); - // enable_raw_mode().unwrap(); + utils::hide_cursor(); + utils::clear(); + crossterm::terminal::enable_raw_mode().unwrap(); loop { self.render(); - sleep(Duration::from_millis(500)); + let mut ctx = UpdateContext { + response: UpdateResponse::None, + request: UpdateRequest::Key(key::read_key()), + }; + (self.component.update)(&mut self.component, &mut ctx); + match ctx.response { + UpdateResponse::Exit => { + crossterm::terminal::disable_raw_mode().unwrap(); + clear(); + show_cursor(); + return; + } + UpdateResponse::SetComponent(c) => self.component = c, + _ => {} + } } } } diff --git a/src/macros.rs b/src/macros.rs index 17f70ab..a2c4df2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -70,6 +70,7 @@ macro_rules! __oml { } #[macro_export] +/// Write OSUI Markup Language directly with rust. macro_rules! oml { // Props (P) ($tag:ident ($($k:ident = $v:expr),*)) => {{ @@ -117,3 +118,13 @@ macro_rules! oml { $expr }}; } + +#[macro_export] +///```create_frame!(width, height)``` +/// +/// Create a frame for rendering multiple components +macro_rules! create_frame { + ($width:expr, $height:expr) => { + vec![" ".repeat($width); $height] + }; +} diff --git a/src/main.rs b/src/main.rs index ab16053..469f30b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,22 @@ use osui::{ - components::{div, text}, - oml, Component, Params, Screen, + components::{button, div}, + oml, App, Component, Params, UpdateResponse, }; fn main() { - let mut screen = Screen::new(); - screen.set_component(oml!(app())); - screen.run(); + let mut app = App::new(); + app.set_component(oml!(app_elem())); + app.run(); } -fn app(_: Params) -> Component { +fn app_elem(_: Params) -> Component { oml!( - // Components (C) - div (width = 5, height = 3) { - - text("Hello, World! daddy"; width = 30) - - // // Props, With components (PC) - // div () { - // // Props (P) - // text(/* Properties go here, Example: x = 20 y = 5 */) - // } - - // // Expression (E) - // text("Hello";) - - // // Expression, With components (EC) - // text("World!";) { - // // Components in here - // } + div { + button("click me"; on_click = |c| { + c.response = UpdateResponse::SetComponent( + oml!(button("Clicked!";)) + ); + }) } ) } diff --git a/src/utils.rs b/src/utils.rs index f0ec35a..4f625af 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,19 +1,89 @@ -use std::io::{stdout, Write}; - use crate::Component; +use lazy_static::lazy_static; +use regex::Regex; +use std::{ + collections::HashMap, + io::{stdout, Write}, +}; +lazy_static! { + pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap(); +} -pub fn render_to_frame(width: usize, frame: &mut Vec, component: &mut Component) { +#[derive(Debug, PartialEq, Clone)] +pub enum Value { + String(String), + Int(i32), + Float(f64), + Bool(bool), +} + +/// Compress a string by a regex pattern +pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap) { + let mut matches_map = HashMap::new(); + let mut res = String::new(); + let input_chars: Vec = input.chars().collect(); + + let mut i = 0; + while i < input_chars.len() { + if let Some(loc) = re.find(&input[i..]) { + if loc.start() == 0 { + let ansi_seq = &input[i..i + loc.end()]; + matches_map.insert(res.len(), ansi_seq.to_string()); + i += ansi_seq.chars().count(); + } else { + res.push(input_chars[i]); + i += 1; + } + } else { + res.push(input_chars[i]); + i += 1; + } + } + + (res, matches_map) +} + +/// Merges a frame withe a line by x +fn merge_line(frame_: &str, line_: &str, x: usize) -> String { + let (frame_, fm) = compress_string(frame_, &ANSI); + let (line_, lm) = compress_string(line_, &ANSI); + + let mut res = String::new(); + let frame: Vec = frame_.chars().collect(); + let line: Vec = line_.chars().collect(); + + let flen = frame.len(); + let llen = line.len(); + + for i in 0..flen { + if i >= x && i - x < llen && line[i - x] != '\t' { + if let Some(v) = lm.get(&(i - x)) { + res.push_str(v); + } + res.push(line[i - x]); + } else { + if let Some(v) = fm.get(&i) { + res.push_str(v); + } + res.push(frame[i]); + } + } + + res +} + +/// Render to a frame +pub fn render_to_frame(frame: &mut Vec, component: &mut Component) { for (i, line) in (component.render)(component).split('\n').enumerate() { if (component.y + i) < frame.len() { let frame_line = frame.get_mut(component.y + i).unwrap(); - // *frame_line = (&line[..line.len().saturating_sub(width)]).to_string(); - *frame_line = line.to_string(); + *frame_line = merge_line(&frame_line, line, component.x); } } } pub fn clear() { - print!("\x1b[2J\x1b[0;0H"); + print!("\x1B[2J\x1B[H"); stdout().flush().unwrap(); } @@ -26,3 +96,7 @@ pub fn show_cursor() { print!("\x1b[?25H"); stdout().flush().unwrap(); } + +pub fn flush() { + stdout().flush().unwrap(); +}