diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..3b35e3e Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 890fa69..56edcc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "osui" -version = "0.0.5" +version = "0.0.6" edition = "2021" license = "Apache-2.0" description = "A rsx TUI library" @@ -22,5 +22,6 @@ crossterm = "0.28.1" regex = "1.11.1" lazy_static = "1.4" paste = "1.0.15" -osui-element = "0.1.0" dyn-clone = "1.0.17" +flume = "=0.10.12" +osui-element = "0.1.11" diff --git a/README.md b/README.md index efbfe8f..b62090d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@

- OSUI + OSUI

+

osui

+

Crates.io Version @@ -28,11 +30,8 @@ ## Getting Started -To use OSUI in your project, include it in your `Cargo.toml`: - -```toml -[dependencies] -osui = "0.2" # Replace with the latest version +```bash +cargo add osui ``` ## Example Usage diff --git a/build.rs b/build.rs deleted file mode 100644 index 5806a52..0000000 --- a/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - cc::Build::new() - .file("src/app.c").compile("app"); -} \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 3bc7006..0000000 --- a/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -rm -rf ./lib/ -mkdir lib -gcc -c -o lib/app.o src/app.c -ar rcs lib/libapp.a lib/app.o \ No newline at end of file diff --git a/github/OSUI.svg b/github/OSUI.svg new file mode 100644 index 0000000..9339c50 --- /dev/null +++ b/github/OSUI.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/github/OSUI.png b/github/OSUIt.png similarity index 100% rename from github/OSUI.png rename to github/OSUIt.png diff --git a/github/OSUIw.svg b/github/OSUIw.svg new file mode 100644 index 0000000..79a536b --- /dev/null +++ b/github/OSUIw.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app.c b/src/app.c deleted file mode 100644 index 50a5475..0000000 --- a/src/app.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include -#include - -extern boolean_t render(void* element); -extern void *event_loop(void* element); - -void sleep_ms(long milliseconds) -{ - struct timespec ts; - ts.tv_sec = milliseconds / 1000; - ts.tv_nsec = (milliseconds % 1000) * 1000000; - nanosleep(&ts, NULL); -} - -boolean_t c_run(void* element) -{ - pthread_t thread; - - if (pthread_create(&thread, NULL, event_loop, element) != 0) - { - perror("Failed to create thread"); - } - - while (render(element)) - { - sleep_ms(20); - } - return 0; -} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 8e22e3f..0000000 --- a/src/app.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crossterm::{ - event::Event, - terminal::{disable_raw_mode, enable_raw_mode}, -}; - -use crate::{ - utils::{clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor}, - Element, -}; -use std::{ffi::c_void, ptr::null}; - -extern "C" { - fn c_run(element: *mut c_void) -> bool; -} - -#[no_mangle] -extern "C" fn render(ptr: *mut c_void) -> bool { - if ptr.is_null() { - return false; - } - let element = unsafe { &mut *(ptr as *mut Element) }; - let (width, height) = get_term_size(); - let mut frame: Vec = create_frame(width, height); - render_to_frame(true, width, &mut frame, element); - clear(); - print!("{}", frame.join("")); - flush(); - true -} - -#[no_mangle] -extern "C" fn event_loop(ptr: *mut c_void) -> *const c_void { - if ptr.is_null() { - return null() as *const c_void; - } - let element = unsafe { &mut *(ptr as *mut Element) }; - let (width, height) = get_term_size(); - element.update_data(width, height); - loop { - if ptr.is_null() { - break; - } - event(element, crossterm::event::read().unwrap()) - } - null() as *const c_void -} - -fn event(element: &mut Element, event: Event) { - if let crossterm::event::Event::Resize(width, height) = event { - element.update_data(width as usize, height as usize); - } - element.event(event); -} - -pub fn run(element: &mut Element) -> bool { - hide_cursor(); - enable_raw_mode().unwrap(); - clear(); - unsafe { - let c = c_run(element as *mut Element as *mut c_void); - if !c { - show_cursor(); - disable_raw_mode().unwrap(); - clear(); - } - return c; - } -} diff --git a/src/css.rs b/src/css.rs new file mode 100644 index 0000000..19eddcc --- /dev/null +++ b/src/css.rs @@ -0,0 +1,261 @@ +#[macro_export] +macro_rules! __css { + ($_style:expr,)=>{}; + // percentage (n%) + ( + $_style:expr, $name:ident: $value:literal% $(, $($other:tt)*)? + ) => {{ + $_style.$name = Number::Pe($value); + $crate::__css!($_style, $($($other)*)?); + }}; + ( + $_style:expr, ($name:ident): $value:literal% $(, $($other:tt)*)? + ) => {{ + $_style.other.insert(stringify!($name).to_string(), Box::new( + Number::Pe($value) + )); + $crate::__css!($_style, $($($other)*)?); + }}; + + // Number + ( + $_style:expr, $name:ident: $value:literal $(, $($other:tt)*)? + ) => {{ + $_style.$name = Number::Px($value); + $crate::__css!($_style, $($($other)*)?); + }}; + + // Vector + ( + $_style:expr, $name:ident: [$($inner:tt),*] $(, $($other:tt)*)? + ) => {{ + $_style.$name = vec![$($inner),*]; + $crate::__css!($_style, $($($other)*)?); + }}; + + // Normal + ( + $_style:expr, $name:ident: $value:expr $(, $($other:tt)*)? + ) => {{ + $_style.$name = $value; + $crate::__css!($_style, $($($other)*)?); + }}; + + ( + $_style:expr, ($name:ident): $value:expr $(, $($other:tt)*)? + ) => {{ + $_style.other.insert(stringify!($name).to_string(), Box::new($value)); + $crate::__css!($_style, $($($other)*)?); + }}; +} + +#[macro_export] +macro_rules! _css { + ($_style:expr,) => {}; + ( + $hm:expr, .$name:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Class(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + $crate::__css!(_style.0, $($inner)*); + } else { + let mut _style = $crate::ui::Style::default(); + + $crate::__css!(_style.0, $($inner)*); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; + ( + $hm:expr, .$name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Class(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + } else { + let mut _style = $crate::ui::Style::default(); + + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; + + + ( + $hm:expr, #$name:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Id(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + $crate::__css!(_style.0, $($inner)*); + } else { + let mut _style = $crate::ui::Style::default(); + + $crate::__css!(_style.0, $($inner)*); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; + ( + $hm:expr, #$name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Id(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + } else { + let mut _style = $crate::ui::Style::default(); + + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; + + + ( + $hm:expr, $name:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Component(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + $crate::__css!(_style.0, $($inner)*); + } else { + let mut _style = $crate::ui::Style::default(); + + $crate::__css!(_style.0, $($inner)*); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; + ( + $hm:expr, $name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)* + ) => {{ + let name = $crate::ui::StyleName::Component(stringify!($name).to_string()); + if let Some(_style) = $hm.get_mut(&name) { + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + } else { + let mut _style = $crate::ui::Style::default(); + + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + + $hm.insert( + name, + _style, + ); + } + $crate::_css!($hm, $($rest)*); + }}; +} + +/// Creates a styling map `Css` +/// +/// # Examples +/// ``` +/// .btn: clicked { +/// color: Blue +/// } +/// ``` +/// +/// # Returns +/// `Css` +#[macro_export] +macro_rules! css { + ( + $($inner:tt)* + ) => {{ + let mut hm: $crate::ui::Css = std::collections::HashMap::new(); + $crate::_css!(hm, $($inner)*); + hm + }}; +} + +#[macro_export] +macro_rules! style { + ( + $key:ident: $value:expr $(, $($rest:tt)*)? + ) => {{ + let mut _style = $crate::ui::Style::default(); + $crate::__css!(_style.0, $key: $value); + $crate::style!{ _style; $($($rest)*)? } + }}; + ( + $style:expr; $key:ident: $value:expr $(, $($rest:tt)*)? + ) => {{ + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!($style.0, $key: $value); + $crate::style!{ $style; $($($rest)*)? } + }}; + ( + ($key:ident): $value:expr $(, $($rest:tt)*)? + ) => {{ + let mut _style = $crate::ui::Style::default(); + $crate::__css!(_style.0, ($key): $value); + $crate::style!{ _style; $($($rest)*)? } + }}; + ( + $style:expr; ($key:ident): $value:expr $(, $($rest:tt)*)? + ) => {{ + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!($style.0, ($key): $value); + $crate::style!{ $style; $($($rest)*)? } + }}; + + ( + $kind:ident {$($inner:tt)*} $(, $($rest:tt)*)? + ) => {{ + let mut _style = $crate::ui::Style::default(); + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + _style.1.insert(stringify!($kind).to_string(), style_elem); + $crate::style!{ _style; $($($rest)*)? } + }}; + ( + $style:expr; $kind:ident {$($inner:tt)*} $(, $($rest:tt)*)? + ) => {{ + let mut style_elem = $crate::ui::StyleElement::default(); + $crate::__css!(style_elem, $($inner)*); + $style.1.insert(stringify!($kind).to_string(), style_elem); + $crate::style!{ $style; $($($rest)*)? } + }}; + + ( + $style:expr; + ) => {{ + $style + }}; + + () => {{ + $crate::ui::Style::default() + }}; +} diff --git a/src/examples.rs b/src/examples.rs new file mode 100644 index 0000000..24deab3 --- /dev/null +++ b/src/examples.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; + +pub fn examples() -> Element { + rsx! { + styling: Some(styling()), + + button { class: "btn", "Click me!" } + + text { "Welcome!" } + } +} + +pub fn styling() -> Css { + css! { + .btn { + x: Center, + y: Center, + color: Red, + } + + .btn: clicked { + color: Blue, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 92b23b6..a28f81c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,13 +10,12 @@ //! ```rust //! use osui::prelude::*; //! -//! run(&mut rsx! { +//! launch(rsx! { //! text { "Hello, World!" } //! }); //! ``` //! //! ## Modules -//! - `app` - The main function for rendering. //! - `ui` - Contains all user interface components, enabling rich CLI experiences. //! - `utils` - Utility functions for common TUI tasks such as clearing the screen. @@ -26,24 +25,20 @@ use std::{ sync::{Arc, Mutex}, }; -use crossterm::event::Event; - -pub mod app; +pub mod css; +pub mod examples; pub mod macros; +pub mod rsx; pub mod ui; pub mod utils; -pub use app::run; - -/// The `prelude` module provides commonly used imports and macros to simplify the development -/// of UI elements. It re-exports commonly used traits, structs, and utility functions to make -/// the UI framework easier to use. pub mod prelude { pub use crate::ui::Color::*; - pub use crate::{self as osui, css, rsx, rsx_elem, ui::*, Handler}; - pub use crate::{Element, Value}; - // useful for Element making - pub use crate::{run_handler, Children, ElementCore, ElementWidget}; + pub use crate::ui::Number::*; + pub use crate::ui::Font::*; + pub use crate::{self as osui, css, ersx, launch, rsx, ui::*, Handler}; + pub use crate::{call, Children, Element, ElementCore, ElementWidget}; + pub use crate::{style, Document, RenderResult, RenderWriter}; pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; pub fn sleep(ms: u64) { std::thread::sleep(std::time::Duration::from_millis(ms)); @@ -51,90 +46,53 @@ pub mod prelude { } ////////////////////////////////////////////////////////////////////////////////////////////////// -/// Value Enum +// Type aliases ////////////////////////////////////////////////////////////////////////////////////////////////// -#[derive(Debug, Clone, Copy)] -pub enum Value { - Default(T), - Custom(T), -} - -impl Value { - pub fn get_value(&self) -> T { - match self { - Value::Custom(s) => *s, - Value::Default(s) => *s, - } - } - - pub fn try_set_value(&mut self, value: T) { - if let Value::Default(_) = *self { - *self = Value::Default(value); - } - } -} - -impl Default for Value { - fn default() -> Self { - Self::Default(T::default()) - } -} - -////////////////////////////////////////////////////////////////////////////////////////////////// -/// ElementCore Traits -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub trait ElementCore: Send + Sync { - fn get_data(&self) -> (Value, usize, String); - fn update_data(&mut self, width: usize, height: usize); - fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>; - fn get_child(&mut self) -> Option<&mut Element>; - fn set_styling(&mut self, styling: &HashMap); -} - -pub trait ElementWidget: ElementCore + std::fmt::Debug { - fn render(&self, focused: bool) -> String { - _ = focused; - String::new() - } - - fn event(&mut self, event: Event) { - _ = event - } -} - pub type Element = Box; ////////////////////////////////////////////////////////////////////////////////////////////////// -/// Handler Struct +/// Traits ////////////////////////////////////////////////////////////////////////////////////////////////// -pub struct Handler(pub Arc>); - -impl Handler { - pub fn new(handler_fn: F) -> Handler - where - F: FnMut(&mut T, Event) + 'static + Send + Sync, - { - Handler(Arc::new(Mutex::new(handler_fn))) - } +pub trait ElementCore { + fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>; + fn set_styling(&mut self, styling: &HashMap); + fn get_id(&self) -> String; } -impl Default for Handler { - fn default() -> Self { - Handler(Arc::new(Mutex::new(|_: &mut T, _: Event| {}))) - } -} - -impl std::fmt::Debug for Handler { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Handler()") +pub trait ElementWidget: ElementCore + std::fmt::Debug + Send + Sync { + fn render(&self, focused: bool) -> Option; + fn event(&mut self, event: crossterm::event::Event, document: &Document) { + _ = (event, document) } } ////////////////////////////////////////////////////////////////////////////////////////////////// -/// Children Enum +/// Structs +////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Handler( + pub Arc>, +); + +pub struct Document { + element: *mut Element, + running: *mut Option, +} + +/// ``` +/// RenderResult(output, (x, y)) +/// ``` +pub struct RenderResult(String, ui::StyleElement); +pub struct RenderWriter { + w: String, + style: ui::Style, + focused: bool, +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Enums ////////////////////////////////////////////////////////////////////////////////////////////////// #[derive(Debug)] @@ -144,6 +102,57 @@ pub enum Children { Children(Vec, usize), } +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Implementations +////////////////////////////////////////////////////////////////////////////////////////////////// + +impl Document { + pub fn exit(&self) { + if !self.running.is_null() { + unsafe { + *self.running = Some(false); + } + } + } + pub fn rebuild(&self) { + if !self.running.is_null() { + unsafe { + *self.running = Some(true); + } + } + } + pub fn get_element_by_id(&self, id: &str) -> Option<&mut Box> { + if let Some(e) = self.get_element_by_id_raw(id) { + Some(unsafe { &mut *(e as *mut Element as *mut Box) }) + } else { + None + } + } + pub fn get_element_by_id_raw(&self, id: &str) -> Option<&mut Element> { + if !self.element.is_null() { + unsafe { + if (*self.element).get_id() == id { + Some(&mut *self.element) + } else { + (*self.element).get_element_by_id(id) + } + } + } else { + None + } + } + pub fn render(&self) { + if !self.running.is_null() { + let (width, height) = utils::get_term_size(); + let mut frame = utils::Frame::new(width, height); + frame.render(true, unsafe { &*self.element }); + utils::clear(); + print!("{}", frame.output_nnl()); + utils::flush(); + } + } +} + impl Default for Children { fn default() -> Self { Self::None @@ -163,4 +172,109 @@ impl Children { _ => String::new(), } } + pub fn get_text_mut(&mut self) -> Option<&mut String> { + match self { + Children::Text(text) => Some(text), + _ => None, + } + } + pub fn set_text(&mut self, text: &str) { + match self { + Children::Text(t) => { + *t = text.to_string(); + } + _ => {} + } + } + pub fn set_text_force(&mut self, text: &str) { + match self { + Children::Text(t) => { + *t = text.to_string(); + } + _ => { + *self = Children::Text(text.to_string()); + } + } + } + pub fn add_child(&mut self, element: Element) { + if let Children::Children(children, _) = self { + children.push(element); + } + } + pub fn get_child(&mut self) -> Option<&mut Element> { + if let Children::Children(children, child) = self { + children.get_mut(*child) + } else { + None + } + } +} + +impl Handler { + pub fn new(handler_fn: F) -> Handler + where + F: FnMut(&mut T, crossterm::event::Event, &Document) + 'static + Send + Sync, + { + Handler(Arc::new(Mutex::new(handler_fn))) + } +} + +impl RenderWriter { + pub fn new(focused: bool, style: ui::Style) -> RenderWriter { + RenderWriter { + w: String::new(), + style, + focused, + } + } + + pub fn write(&mut self, s: &str) { + self.w += self.style.get(self.focused).write(s).as_str(); + } + + pub fn result(&self) -> RenderResult { + RenderResult(self.w.clone(), self.style.get(self.focused).clone()) + } +} + +impl Default for Handler { + fn default() -> Self { + Handler(Arc::new(Mutex::new( + |_: &mut T, _: crossterm::event::Event, _: &Document| {}, + ))) + } +} + +impl std::fmt::Debug for Handler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Handler()") + } +} + +pub fn run(element: &mut Element) -> bool { + let document = Document { + element: element, + running: &mut None, + }; + let (width, height) = crate::utils::get_term_size(); + element.event( + crossterm::event::Event::Resize(width as u16, height as u16), + &document, + ); + element.event(crossterm::event::Event::FocusGained, &document); + + utils::hide_cursor(); + crossterm::terminal::enable_raw_mode().unwrap(); + utils::clear(); + + while unsafe { *document.running } == None { + document.render(); + element.event(crossterm::event::read().unwrap(), &document); + } + + utils::show_cursor(); + crossterm::terminal::disable_raw_mode().unwrap(); + utils::clear(); + + unsafe { (*document.running).unwrap() } } diff --git a/src/macros.rs b/src/macros.rs index 88c3966..b5db83a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,171 +1,5 @@ -/// **parse_rsx_param!** - A macro for parsing and setting parameters on elements, allowing for dynamic handling of element properties and children. -/// -/// This macro enables flexible configuration of UI elements. It handles setting fields, adding children, invoking paths, and processing complex structures. -/// -/// ## Example -/// ```rust -/// parse_rsx_param!(elem, title: "Hello", for (i in 0..5) { }, text: "World"); -/// ``` -/// -/// ## Syntax -/// -/// **Set property or field of an element:** -/// ```rust -/// parse_rsx_param!($elem, $($k:ident).+: $v:expr); -/// ``` -/// -/// **Process multiple parameters:** -/// ```rust -/// parse_rsx_param!($elem, $($k:ident).+: $v:expr, $($rest:tt)*); -/// ``` -/// -/// **Handle path expression:** -/// ```rust -/// parse_rsx_param!($elem, $p:path); -/// ``` -/// -/// **Add nested children:** -/// ```rust -/// parse_rsx_param!($elem, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*); -/// ``` -/// -/// **Add child element directly:** -/// ```rust -/// parse_rsx_param!($elem, {$ielem:expr} $($rest:tt)*); -/// ``` -/// -/// **Set element text:** -/// ```rust -/// parse_rsx_param!($elem, $text:expr); -/// ``` -/// -/// **Handle empty case:** -/// ```rust -/// parse_rsx_param!($elem, ); -/// ``` #[macro_export] -macro_rules! parse_rsx_param { - ($elem:expr, $($k:ident).+: $v:expr) => { - $elem.$($k).+ = $v; - }; - - ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => { - $elem.$($k).+ = $v; - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $p:path) => { - $p; - }; - ($elem:expr, $($k:ident).+, $($rest:tt)*) => { - $elem.$($k).+; - osui::parse_rsx_param!($elem, $($rest)*); - }; - ($elem:expr, $($k:ident).+., $($rest:tt)*) => { - $elem.$($k). - +.; - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $($k:ident).+.: $v:expr) => { - $elem.$($k).+. = $v; - }; - ($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => { - $elem.$($k).+. = $v; - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} - if let $crate::Children::Children(children, _) = &mut $elem.children { - for $($for)* { - children.push({$($inner)*}) - } - } - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, {$ielem:expr} $($rest:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} - if let $crate::Children::Children(children, _) = &mut $elem.children { - children.push({$ielem}); - } - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} - if let $crate::Children::Children(children, _) = &mut $elem.children { - children.push(osui::rsx_elem!($pelem { $($inner)* })); - } - osui::parse_rsx_param!($elem, $($rest)*) - }; - - ($elem:expr, $text:expr) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))} - }; - ($elem:expr, $text:expr, $($inner:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))} - }; - - ($elem:expr, ) => {}; -} - -/// **rsx_elem!** - A macro to instantiate a UI element and apply parameters to it. -/// -/// This macro simplifies creating an element and applying a set of properties or children to it, enabling a declarative style of UI building. -/// -/// ## Example -/// ```rust -/// let element = rsx_elem!(ui::Button { text: "Click me" }); -/// ``` -/// -/// ## Syntax -/// ```rust -/// rsx_elem!($elem:path { $($inner:tt)* }) -/// ``` -#[macro_export] -macro_rules! rsx_elem { - ($elem:path { $($inner:tt)* }) => {{ - #[allow(unused_mut)] - let mut elem = $elem(); - $crate::parse_rsx_param!(elem, $($inner)*); - elem as $crate::Element - }}; -} - -/// **rsx!** - A shorthand macro to create a `div` element and apply nested content to it. -/// -/// This macro is a simple wrapper for `rsx_elem!`, making it easy to create a `div` element and add children to it. -/// -/// ## Example -/// ```rust -/// rsx! { -/// text { "Hello, World" } -/// div { button {"Click me!"} } -/// } -/// ``` -/// -/// ## Syntax -/// ```rust -/// rsx!($($inner:tt)*) -/// ``` -#[macro_export] -macro_rules! rsx { - ($($inner:tt)*) => {{ - $crate::rsx_elem!( $crate::ui::div { $($inner)* } ) - }}; -} - -#[macro_export] -macro_rules! val { - ($a:expr) => { - $crate::Value::new($a) - }; -} - -#[macro_export] -macro_rules! run_handler { +macro_rules! call { ($self:ident.$handler:ident ($($inner:tt)*)) => {{ let a = std::sync::Arc::clone(&$self.$handler.0); let mut o = a.lock().unwrap(); @@ -177,162 +11,8 @@ macro_rules! run_handler { } #[macro_export] -macro_rules! __css { - ($style:expr,)=>{}; - ( - $style:expr, ($name:ident): $value:expr - ) => {{ - $style.other.insert(stringify!($name).to_string(), Box::new($value)); - }}; - ( - $style:expr, ($name:ident): $value:expr, $($other:tt)* - ) => {{ - $style.other.insert(stringify!($name).to_string(), Box::new($value)); - $crate::__css!($($other)*); - }}; - ( - $style:expr, $name:ident: $value:expr - ) => {{ - $style.$name = $value; - }}; -} - -#[macro_export] -macro_rules! _css { - ($style:expr,) => {}; - ( - $hm:expr, .$name:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Class(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - $crate::__css!(style.0, $($inner)*); - } else { - let mut style = $crate::ui::Style::default(); - - $crate::__css!(style.0, $($inner)*); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, .$name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Class(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - } else { - let mut style = $crate::ui::Style::default(); - - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; - - - ( - $hm:expr, #$name:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Id(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - $crate::__css!(style.0, $($inner)*); - } else { - let mut style = $crate::ui::Style::default(); - - $crate::__css!(style.0, $($inner)*); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, #$name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Id(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - } else { - let mut style = $crate::ui::Style::default(); - - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; - - - ( - $hm:expr, $name:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Component(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - $crate::__css!(style.0, $($inner)*); - } else { - let mut style = $crate::ui::Style::default(); - - $crate::__css!(style.0, $($inner)*); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, $name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)* - ) => {{ - let name = $crate::ui::StyleName::Component(stringify!($name).to_string()); - if let Some(style) = $hm.get_mut(&name) { - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - } else { - let mut style = $crate::ui::Style::default(); - - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - style.1.insert(stringify!($kind).to_string(), style_elem); - - $hm.insert( - name, - style, - ); - } - $crate::_css!($hm, $($rest)*); - }}; -} - -#[macro_export] -macro_rules! css { - ( - $($inner:tt)* - ) => {{ - let mut hm: $crate::ui::Css = std::collections::HashMap::new(); - $crate::_css!(hm, $($inner)*); - hm - }}; +macro_rules! launch { + ($expr:expr) => { + while $crate::run(&mut $expr) {} + }; } diff --git a/src/main.rs b/src/main.rs index 3e7931e..6a2f33b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,6 @@ +use osui::examples; use osui::prelude::*; fn main() { - while osui::run(&mut app()) {} -} - -fn app() -> Element { - rsx! { styling: Some(styles()), - button { class: "btn", "This is a button!" } - } -} - -fn styles() -> Css { - css! { - .btn::clicked { - color: Blue - } - } + launch!(examples::examples()); } diff --git a/src/rsx.rs b/src/rsx.rs new file mode 100644 index 0000000..8015222 --- /dev/null +++ b/src/rsx.rs @@ -0,0 +1,95 @@ +#[macro_export] +macro_rules! parse_rsx_param { + ($elem:expr, for ($($for:tt)*) $code:block $($rest:tt)*) => { + if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} + if let $crate::Children::Children(children, _) = &mut $elem.children { + for $($for)* { + children.push($code) + } + } + osui::parse_rsx_param!($elem, $($rest)*); + }; + + ($elem:expr, $($k:ident).+: $v:expr) => { + $elem.$($k).+ = $v; + }; + + ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => { + $elem.$($k).+ = $v; + osui::parse_rsx_param!($elem, $($rest)*); + }; + ($elem:expr, $p:path) => { + $p; + }; + ($elem:expr, $($k:ident).+, $($rest:tt)*) => { + $elem.$($k).+; + osui::parse_rsx_param!($elem, $($rest)*); + }; + ($elem:expr, $($k:ident).+., $($rest:tt)*) => { + $elem.$($k). + +.; + osui::parse_rsx_param!($elem, $($rest)*); + }; + + ($elem:expr, $($k:ident).+.: $v:expr) => { + $elem.$($k).+. = $v; + }; + ($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => { + $elem.$($k).+. = $v; + osui::parse_rsx_param!($elem, $($rest)*); + }; + + ($elem:expr, $code:block $($rest:tt)*) => { + if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} + if let $crate::Children::Children(children, _) = &mut $elem.children { + children.push($code); + } + osui::parse_rsx_param!($elem, $($rest)*); + }; + + ($elem:expr, $elem_path:path { $($inner:tt)* } $($rest:tt)*) => { + if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} + if let $crate::Children::Children(children, _) = &mut $elem.children { + children.push(osui::ersx!($elem_path { $($inner)* })); + } + osui::parse_rsx_param!($elem, $($rest)*) + }; + + ($elem:expr, $text:expr) => { + if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))} + }; + ($elem:expr, $text:expr, $($inner:tt)*) => { + if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))} + }; + + ($elem:expr, ) => {}; +} + +#[macro_export] +macro_rules! ersx { + ($elem:path { $($inner:tt)* }) => {{ + #[allow(unused_mut)] + let mut elem = $elem(); + $crate::parse_rsx_param!(elem, $($inner)*); + elem as $crate::Element + }}; +} + +/// Makes a div and puts elements into it +/// +/// # Example +/// ``` +/// rsx! { +/// button { class: "btn", "Click me!" } +/// } +/// ``` +/// +/// # Returns +/// A `osui::Element` - Which is a `Box` +#[macro_export] +macro_rules! rsx { + ($($inner:tt)*) => { + $crate::ersx!{ $crate::ui::div { $($inner)* } } + }; + +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 981fdf8..f4813d2 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,60 +3,37 @@ /// # Modules /// - `styles`: A module for defining UI styles. pub mod styles; + pub use styles::*; use crate::prelude::*; use osui_element::{elem_fn, element}; -/// The `Text` element represents a piece of static text in the terminal UI. -/// -/// It is used to render plain text within a UI component. The `Text` element does not interact -/// with events and only outputs the text when rendered. -/// -/// # Example -/// ```rust -/// let text = Text::default(); -/// let rendered_text = text.render(crate::State::Normal); -/// println!("{}", rendered_text); -/// ``` -/// -/// ## Methods -/// - `render(state: crate::State) -> String`: Renders the text to a string, depending on the state. -/// If the element contains text as a child, it returns that text. Otherwise, it returns an empty string. #[element] #[elem_fn] #[derive(Default, Debug)] -pub struct Text {} - -impl ElementWidget for Text<'_> { - fn render(&self, _: bool) -> String { - if let Children::Text(text) = &self.children { - text.clone() - } else { - String::new() - } - } +pub struct Text { + pub on_event: Handler>, } -/// The `Div` element represents a container for other UI elements. It can hold child components -/// and render them in a specific layout. The `Div` element also handles keyboard navigation through -/// its children. -/// -/// The `Div` element supports rendering a list of children and allows interactive navigation using -/// arrow keys (Up, Down, Left, Right). It renders the selected child element differently based on the state. -/// -/// # Example -/// ```rust -/// let div = Div::default(); -/// let rendered_div = div.render(crate::State::Normal); -/// println!("{}", rendered_div); -/// ``` -/// -/// ## Methods -/// - `render(state: crate::State) -> String`: Renders the `Div` and its child elements to a string. -/// The child elements are rendered according to the current selection (`child` index). -/// - `event(event: Event, state: &crate::StateChanger)`: Handles keyboard events for -/// navigation through child elements. Arrow keys (Up, Down, Left, Right) change the currently selected child. +impl ElementWidget for Text<'_> { + fn render(&self, focused: bool) -> Option { + let mut writer = RenderWriter::new(focused, self.style.clone()); + + writer.write(&{ + if let Children::Text(text) = &self.children { + text.clone() + } else { + String::new() + } + }); + + Some(writer.result()) + } + fn event(&mut self, event: Event, document: &Document) { + call!(self.on_event(event, document)); + } +} #[element] #[elem_fn] @@ -66,69 +43,28 @@ pub struct Div { } impl ElementWidget for Div<'_> { - fn render(&self, focused: bool) -> String { - let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value()); + fn render(&self, focused: bool) -> Option { + let mut writer = RenderWriter::new(focused, self.style.clone()); + let mut frame = crate::utils::Frame::new(crate::utils::get_term_size().0, 12); if let Children::Children(children, child) = &self.children { for (i, elem) in children.iter().enumerate() { - crate::utils::render_to_frame( - focused && i == *child, - self.width.get_value(), - &mut frame, - elem, - ); + frame.render(focused && i == *child, elem); } } + writer.write(&frame.output()); - frame.join("\n") + Some(writer.result()) } - fn event(&mut self, event: Event) { - if let Some(styling) = self.styling.clone() { - self.set_styling(&styling); + fn event(&mut self, event: Event, document: &Document) { + if event == Event::FocusGained { + if let Some(styling) = self.styling.clone() { + self.set_styling(&styling); + } } - match event { - Event::Key(key) => { - if let Children::Children(children, child) = &mut self.children { - *child = match key.code { - KeyCode::Up => crate::utils::closest_component( - children, - *child, - crate::utils::Direction::Up, - ), - - KeyCode::Down => crate::utils::closest_component( - children, - *child, - crate::utils::Direction::Down, - ), - - KeyCode::Left => crate::utils::closest_component( - children, - *child, - crate::utils::Direction::Left, - ), - - KeyCode::Right => crate::utils::closest_component( - children, - *child, - crate::utils::Direction::Right, - ), - - _ => { - if let Some(c) = children.get_mut(*child) { - c.event(event); - } - *child - } - } - } - } - _ => { - if let Some(child) = self.get_child() { - child.event(event); - } - } + if let Some(child) = self.children.get_child() { + child.event(event, document); } } } @@ -138,32 +74,51 @@ impl ElementWidget for Div<'_> { #[derive(Default, Debug)] pub struct Button { pub on_click: Handler>, - pub state: &'a str, } impl ElementWidget for Button<'_> { - fn render(&self, focused: bool) -> String { - if self.state == "clicked" { - return self.style.write("clicked", &self.children.get_text()); - } - if focused { - self.style.write("hover", &self.children.get_text()) - } else { - self.style.write("", &self.children.get_text()) - } + fn render(&self, focused: bool) -> Option { + let mut writer = RenderWriter::new(focused, self.style.clone()); + writer.write(&self.children.get_text()); + Some(writer.result()) } - fn event(&mut self, event: Event) { + fn event(&mut self, event: Event, document: &Document) { match event { Event::Key(key) => { if key.code == KeyCode::Enter { - self.state = "clicked"; - run_handler!(self.on_click(event)); + document.render(); + self.style.set_state("clicked"); + call!(self.on_click(event, document)); + document.render(); sleep(100); - self.state = ""; + self.style.set_state(""); + document.rebuild(); } } _ => {} } } } + +#[element] +#[derive(Debug)] +pub struct DataHolder<'a, T> { + pub data: T, +} + +pub fn dataholder<'a, T: std::default::Default>() -> Box> { + Box::new(DataHolder { + style: Style::default(), + data: T::default(), + children: Children::None, + class: "", + id: "", + }) +} + +impl<'a, T: std::fmt::Debug + Send + Sync> ElementWidget for DataHolder<'a, T> { + fn render(&self, _: bool) -> Option { + None + } +} diff --git a/src/ui/styles.rs b/src/ui/styles.rs index 9498ff1..68eac40 100644 --- a/src/ui/styles.rs +++ b/src/ui/styles.rs @@ -6,45 +6,34 @@ use std::{collections::HashMap, fmt::Debug}; use dyn_clone::{clone_trait_object, DynClone}; +////////////////////////////////////////////////////////////////////////////////////////////////// +// Type aliases +////////////////////////////////////////////////////////////////////////////////////////////////// + +pub type Css = HashMap; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Traits +////////////////////////////////////////////////////////////////////////////////////////////////// + pub trait StyleCore: Debug + Send + Sync + DynClone { fn ansi(&self) -> String; fn ansi_bg(&self) -> String; + fn is_null(&self) -> bool; } - clone_trait_object!(StyleCore); +////////////////////////////////////////////////////////////////////////////////////////////////// +// Enums +////////////////////////////////////////////////////////////////////////////////////////////////// + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Font { - None, Bold, Underline, Italic, Reverse, Strike, - Mul(Vec), -} - -impl StyleCore for Font { - fn ansi(&self) -> String { - String::from(match self { - Font::None => "", - Font::Bold => "\x1b[1m", - Font::Underline => "\x1b[4m", - Font::Italic => "\x1b[3m", - Font::Reverse => "\x1b[7m", - Font::Strike => "\x1b[9m", - Font::Mul(v) => { - let mut s = String::new(); - for n in v { - s += n.ansi().as_str(); - } - return s; - } - }) - } - fn ansi_bg(&self) -> String { - self.ansi() - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -62,6 +51,128 @@ pub enum Color { White, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Number { + Px(usize), + Pe(usize), + Center, + Auto, + Default, +} + +#[derive(PartialEq, Eq, Hash, Debug, Clone)] +pub enum StyleName { + Class(String), + Id(String), + Component(String), +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Structs +////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug, Clone)] +pub struct StyleElement { + pub color: Color, + pub background: Color, + pub font: Vec, + pub x: Number, + pub y: Number, + pub width: Number, + pub height: Number, + pub other: HashMap>, +} + +#[derive(Debug, Clone)] +pub struct Style( + pub StyleElement, + pub HashMap, + pub String, +); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementations +////////////////////////////////////////////////////////////////////////////////////////////////// + +impl Default for Style { + fn default() -> Self { + Style(StyleElement::default(), HashMap::new(), String::new()) + } +} + +impl Style { + pub fn get(&self, hover: bool) -> StyleElement { + if let Some(style_element) = self.1.get(&self.2) { + self.0.clone().prioritize(style_element) + } else if hover { + if let Some(style_element) = self.1.get("hover") { + self.0.clone().prioritize(style_element) + } else { + self.0.clone() + } + } else { + self.0.clone() + } + } + + pub fn set_state(&mut self, state: &str) { + self.2 = state.to_string(); + } +} + +impl StyleElement { + pub fn write(&self, s: &str) -> String { + format!( + "{}{}{}{s}\x1b[0m", + self.color.ansi(), + self.background.ansi_bg(), + self.font.ansi() + ) + } +} + +impl Default for StyleElement { + fn default() -> Self { + StyleElement { + color: Color::NoColor, + background: Color::NoColor, + font: Vec::new(), + x: Number::Default, + y: Number::Default, + width: Number::Default, + height: Number::Default, + other: HashMap::new(), + } + } +} + +impl StyleElement { + pub fn prioritize(mut self, other: &Self) -> StyleElement { + if !other.color.is_null() { + self.color = other.color.clone(); + } + if !other.background.is_null() { + self.background = other.background.clone(); + } + if !other.font.is_null() { + self.font = other.font.clone(); + } + if other.x != Number::Default { + self.x = other.x.clone(); + } + if other.y != Number::Default { + self.y = other.y.clone(); + } + if other.width != Number::Default { + self.width = other.width.clone(); + } + if other.height != Number::Default { + self.height = other.height.clone(); + } + self + } +} + impl StyleCore for Color { fn ansi(&self) -> String { String::from(match self { @@ -104,6 +215,9 @@ impl StyleCore for Color { Color::White => "\x1b[47m", }) } + fn is_null(&self) -> bool { + *self == Self::NoColor + } } impl Default for Color { @@ -112,12 +226,6 @@ impl Default for Color { } } -impl Default for Font { - fn default() -> Self { - Font::None - } -} - fn hex_to_rgb(hex: &str) -> (u8, u8, u8) { let hex = hex.trim_start_matches('#'); @@ -144,54 +252,42 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) { (r, g, b) } -#[derive(PartialEq, Eq, Hash, Debug, Clone)] -pub enum StyleName { - Class(String), - Id(String), - Component(String), +impl StyleCore for Font { + fn ansi(&self) -> String { + String::from(match self { + Font::Bold => "\x1b[1m", + Font::Underline => "\x1b[4m", + Font::Italic => "\x1b[3m", + Font::Reverse => "\x1b[7m", + Font::Strike => "\x1b[9m", + }) + } + fn ansi_bg(&self) -> String { + self.ansi() + } + fn is_null(&self) -> bool { + false + } } -#[derive(Debug, Clone)] -pub struct StyleElement { - pub color: Color, - pub other: HashMap>, -} - -impl Default for StyleElement { - fn default() -> Self { - StyleElement { - color: Color::NoColor, - other: HashMap::new(), +impl StyleCore for Vec { + fn ansi(&self) -> String { + let mut a = String::new(); + for i in self { + a += i.ansi().as_str(); } + a } -} -#[derive(Debug, Clone)] -pub struct Style(pub StyleElement, pub HashMap); - -impl Default for Style { - fn default() -> Self { - Style(StyleElement::default(), HashMap::new()) - } -} - -impl Style { - pub fn write(&self, state: &str, s: &str) -> String { - if state == "" { - return self.0.write(s); - } - if let Some(style_element) = self.1.get(state) { - style_element.write(s) - } else { - s.to_string() + fn ansi_bg(&self) -> String { + let mut a = String::new(); + for i in self { + a += i.ansi_bg().as_str(); } + a + } + + fn is_null(&self) -> bool { + self.len() == 0 } } - -impl StyleElement { - pub fn write(&self, s: &str) -> String { - format!("{}{s}\x1b[0m", self.color.ansi()) - } -} - -pub type Css = HashMap; diff --git a/src/utils.rs b/src/utils.rs index 388cf13..ef3df37 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,12 +5,11 @@ /// # Example /// ```rust /// use crate::ui::utils::{create_frame, render_to_frame}; -/// +/// /// let frame = create_frame(50, 10); /// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element); /// ``` - -use crate::{Element, Value}; +use crate::Element; use lazy_static::lazy_static; use regex::Regex; use std::{ @@ -49,7 +48,7 @@ pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap String { +fn merge_line(frame_: &str, line_: &str, x: usize) -> (String, String) { let (frame_, fm) = compress_string(frame_, &ANSI); let (mut line_, lm) = compress_string(line_, &ANSI); @@ -82,36 +81,87 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String { } } - res + (res, line_) } -/// Render to a frame -pub fn render_to_frame( - focused: bool, +pub struct Frame { + frame: Vec, width: usize, - frame: &mut Vec, - element: &Element, -) { - let data = element.get_data(); - for (i, line) in element.render(focused).split('\n').enumerate() { - if (data.1 + i) < frame.len() { - let frame_line = frame.get_mut(data.1 + i).unwrap(); - match data.0 { - Value::Custom(x) => { - *frame_line = merge_line(&frame_line, line, x); + used: Vec, +} + +impl Frame { + pub fn new(width: usize, height: usize) -> Self { + Self { + frame: vec![" ".repeat(width); height], + width, + used: vec![0; height], + } + } + pub fn render(&mut self, focused: bool, element: &Element) { + if let Some(output) = element.render(focused) { + let x; + let mut y = 0; + match output.1.y { + crate::ui::Number::Px(_y) => { + y = _y; } - _ => { - for (x, c) in frame_line.chars().rev().enumerate() { - if c != ' ' { - *frame_line = merge_line(&frame_line, line, width - x); - return; + crate::ui::Number::Pe(p) => y = (self.frame.len() * p) / 100, + crate::ui::Number::Center => { + y = (self.frame.len() - output.0.matches('\n').count() + 1) / 2 + } + crate::ui::Number::Auto => { + for (i, n) in self.used.iter().enumerate() { + if *n == 0 { + y = i; + break; } } - *frame_line = merge_line(&frame_line, line, 0); + } + crate::ui::Number::Default => { + for (i, n) in self.used.iter().enumerate() { + if *n == 0 { + y = i; + break; + } + } + } + } + match output.1.x { + crate::ui::Number::Px(_x) => { + x = _x; + } + crate::ui::Number::Pe(p) => x = (self.width * p) / 100, + crate::ui::Number::Center => { + x = (self.width + - ANSI + .replace_all(&output.0.lines().next().unwrap_or(""), "") + .len()) + / 2; + } + crate::ui::Number::Auto => { + x = *self.used.get(y).unwrap(); + } + crate::ui::Number::Default => { + x = 0; + } + } + for (i, line) in output.0.split('\n').enumerate() { + if self.frame.len() > y + i { + let frame_line = self.frame.get_mut(y + i).unwrap(); + let merged = merge_line(&frame_line, line, x); + *frame_line = merged.0; + *self.used.get_mut(y + i).unwrap() += merged.1.len() - 1; } } } } + pub fn output(&self) -> String { + self.frame.join("\n") + } + pub fn output_nnl(&self) -> String { + self.frame.join("") + } } pub fn clear() { @@ -133,57 +183,6 @@ pub fn flush() { stdout().flush().unwrap(); } -#[derive(Debug, Clone)] -pub enum Direction { - Left, - Right, - Up, - Down, -} - -pub fn closest_component( - components: &[Element], - current_index: usize, - direction: Direction, -) -> usize { - let current = &components[current_index].get_data(); - - components - .iter() - .enumerate() - .filter(|(i, comp_)| { - let comp = comp_.get_data(); - match direction { - Direction::Left => match comp.0 { - Value::Custom(_) => { - comp.0.get_value() < current.0.get_value() && comp.1 == current.1 - } - Value::Default(_) => *i < current_index, - }, // Left - Direction::Right => match comp.0 { - Value::Custom(_) => { - comp.0.get_value() > current.0.get_value() && comp.1 == current.1 - } - Value::Default(_) => *i > current_index, - }, // Right - Direction::Up => comp.1 < current.1 && comp.0.get_value() == current.0.get_value(), // Up - Direction::Down => { - comp.1 > current.1 && comp.0.get_value() == current.0.get_value() - } // Down - } - }) - .min_by_key(|(_, comp_)| { - let comp = comp_.get_data(); - current.0.get_value().abs_diff(comp.0.get_value()) + current.1.abs_diff(comp.1) - }) // Find the closest component - .map(|(index, _)| index) // Return the index of the closest component - .unwrap_or(current_index) // If no component is found, return the current index -} - -pub fn create_frame(width: usize, height: usize) -> Vec { - vec![" ".repeat(width); height] -} - pub fn get_term_size() -> (usize, usize) { let (width, height) = crossterm::terminal::size().unwrap(); (width as usize, height as usize) diff --git a/styling.md b/styling.md new file mode 100644 index 0000000..14b2dcd --- /dev/null +++ b/styling.md @@ -0,0 +1,9 @@ +`color`, `background`: `$color{}\x1b[0m`; + +`font`: `$font{}\x1b[0m`; + +`width`, `height`: `n`, `n%`, `auto`; + +`x`, `y`: `n`, `n%`, `center`, `auto`; + + \ No newline at end of file