some new stuff
This commit is contained in:
@@ -16,3 +16,5 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
|
regex = "*"
|
||||||
|
lazy_static = "1.4"
|
||||||
@@ -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
@@ -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
@@ -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,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user