some new stuff

This commit is contained in:
2024-10-17 20:17:35 +02:00
parent 63a864061b
commit 44a227780b
7 changed files with 231 additions and 76 deletions
+2
View File
@@ -16,3 +16,5 @@ path = "src/main.rs"
[dependencies] [dependencies]
crossterm = "0.28.1" crossterm = "0.28.1"
regex = "*"
lazy_static = "1.4"
+12 -8
View File
@@ -10,25 +10,29 @@ Here is the OSUI Go version: https://github.com/orus-dev/osui
## Features ## Features
- **React-Like**: Easily write components.
- **Cross-Platform Support**: Works on various operating systems. - **Cross-Platform Support**: Works on various operating systems.
- **Flexible Layout**: Easily create and manage complex UIs. - **Flexible Layout**: Easily create and manage complex UIs.
- **Efficient Performance**: Designed for high performance and low latency. - **Efficient Performance**: Designed for high performance and low latency.
## Installation ## 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 `cargo add osui`
[dependencies]
osui = "^0.0.2"
```
```rust ```rust
use osui::{self, render_frame, ui}; use osui::{
components::text,
oml, App
};
fn main() { fn main() {
let txt = ui::text("Hello World!"); let mut app = App::new();
render_frame!(txt); app.set_component(oml!(
text("Hello, World!";)
));
app.run();
} }
``` ```
+35 -6
View File
@@ -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 { pub fn div(params: Params) -> Component {
Component::new(params, |s| { let mut c = Component::new(params);
let mut frame: Vec<String> = vec![" ".repeat(s.width); s.height]; c.render = |s: &mut Component| {
let mut frame: Vec<String> = create_frame!(s.width, s.height);
for c in &mut s.children { for c in &mut s.children {
render_to_frame(s.width, &mut frame, c); render_to_frame(&mut frame, c);
} }
frame.join("\n") frame.join("\n")
}) };
c
} }
pub fn text(params: Params) -> Component { 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
} }
+76 -29
View File
@@ -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 components;
pub mod key;
pub mod macros; pub mod macros;
pub mod utils; 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)] #[derive(Debug)]
pub struct Params { pub struct Params {
pub expr: String, pub expr: String,
pub children: Vec<Component>, pub children: Vec<Component>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Component { pub struct Component {
pub render: fn(&mut Component) -> String, 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 x: usize,
pub y: usize, pub y: usize,
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub expr: String, pub expr: String,
pub children: Vec<Component>, pub children: Vec<Component>,
pub clicked: bool,
pub toggle: bool,
pub data: HashMap<String, Value>,
} }
impl Component { impl Component {
fn new(params: Params, render: fn(&mut Component) -> String) -> Component { /// Creates a new component
fn new(params: Params) -> Component {
Component { Component {
render, render: |_| String::new(),
update: |_| {}, update: |_, _| {},
on_click: |_| {},
x: 0, x: 0,
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
expr: params.expr, expr: params.expr,
children: params.children, children: params.children,
clicked: false,
toggle: false,
data: HashMap::new(),
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Screen { pub struct App {
component: Component, component: Component,
} }
impl Screen { impl App {
pub fn new() -> Screen { /// Creates a new screen to render components
Screen { pub fn new() -> App {
component: Component::new( App {
Params { component: Component::new(Params {
expr: "".to_string(), expr: "".to_string(),
children: vec![], children: vec![],
}, }),
|_| String::new(),
),
} }
} }
/// Sets a component
pub fn set_component(&mut self, component: 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; self.component = component;
if self.component.width == 0 { if self.component.width == 0 {
self.component.width = width as usize; self.component.width = width as usize;
} }
if self.component.width == 0 { if self.component.height == 0 {
self.component.height = height as usize; self.component.height = height as usize;
} }
self.render(); self.render();
} }
/// Render to the screen
pub fn render(&mut self) { pub fn render(&mut self) {
let (width, height) = terminal::size().unwrap(); let (width, height) = crossterm::terminal::size().unwrap();
let mut frame: Vec<String> = vec![" ".repeat(width as usize); height as usize]; let mut frame: Vec<String> = create_frame!(width as usize, height as usize);
render_to_frame(width as usize, &mut frame, &mut self.component); utils::render_to_frame(&mut frame, &mut self.component);
clear(); utils::clear();
print!("{}", frame.join("\n")); print!("{}", frame.join(""));
utils::flush();
} }
/// Run the screen
pub fn run(&mut self) { pub fn run(&mut self) {
hide_cursor(); utils::hide_cursor();
// enable_raw_mode().unwrap(); utils::clear();
crossterm::terminal::enable_raw_mode().unwrap();
loop { loop {
self.render(); 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,
_ => {}
}
} }
} }
} }
+11
View File
@@ -70,6 +70,7 @@ macro_rules! __oml {
} }
#[macro_export] #[macro_export]
/// Write OSUI Markup Language directly with rust.
macro_rules! oml { macro_rules! oml {
// Props (P) // Props (P)
($tag:ident ($($k:ident = $v:expr),*)) => {{ ($tag:ident ($($k:ident = $v:expr),*)) => {{
@@ -117,3 +118,13 @@ macro_rules! oml {
$expr $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]
};
}
+12 -24
View File
@@ -1,34 +1,22 @@
use osui::{ use osui::{
components::{div, text}, components::{button, div},
oml, Component, Params, Screen, oml, App, Component, Params, UpdateResponse,
}; };
fn main() { fn main() {
let mut screen = Screen::new(); let mut app = App::new();
screen.set_component(oml!(app())); app.set_component(oml!(app_elem()));
screen.run(); app.run();
} }
fn app(_: Params) -> Component { fn app_elem(_: Params) -> Component {
oml!( oml!(
// Components (C) div {
div (width = 5, height = 3) { button("click me"; on_click = |c| {
c.response = UpdateResponse::SetComponent(
text("Hello, World! daddy"; width = 30) oml!(button("Clicked!";))
);
// // 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
// }
} }
) )
} }
+80 -6
View File
@@ -1,19 +1,89 @@
use std::io::{stdout, Write};
use crate::Component; 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<String>, 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<usize, String>) {
let mut matches_map = HashMap::new();
let mut res = String::new();
let input_chars: Vec<char> = 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<char> = frame_.chars().collect();
let line: Vec<char> = 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<String>, component: &mut Component) {
for (i, line) in (component.render)(component).split('\n').enumerate() { for (i, line) in (component.render)(component).split('\n').enumerate() {
if (component.y + i) < frame.len() { if (component.y + i) < frame.len() {
let frame_line = frame.get_mut(component.y + i).unwrap(); let frame_line = frame.get_mut(component.y + i).unwrap();
// *frame_line = (&line[..line.len().saturating_sub(width)]).to_string(); *frame_line = merge_line(&frame_line, line, component.x);
*frame_line = line.to_string();
} }
} }
} }
pub fn clear() { pub fn clear() {
print!("\x1b[2J\x1b[0;0H"); print!("\x1B[2J\x1B[H");
stdout().flush().unwrap(); stdout().flush().unwrap();
} }
@@ -26,3 +96,7 @@ pub fn show_cursor() {
print!("\x1b[?25H"); print!("\x1b[?25H");
stdout().flush().unwrap(); stdout().flush().unwrap();
} }
pub fn flush() {
stdout().flush().unwrap();
}