style update

This commit is contained in:
2024-11-18 18:55:29 +01:00
parent 15fb611870
commit f3a376d418
7 changed files with 342 additions and 121 deletions
+1
View File
@@ -20,3 +20,4 @@ regex = "*"
lazy_static = "1.4" lazy_static = "1.4"
paste = "1.0.15" paste = "1.0.15"
osui-element = { path = "./osui-element" } osui-element = { path = "./osui-element" }
dyn-clone = "1.0.17"
+19
View File
@@ -46,6 +46,11 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
.parse2(quote! { pub children: Children }) .parse2(quote! { pub children: Children })
.unwrap(), .unwrap(),
); );
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub style: Style })
.unwrap(),
);
fields.named.push( fields.named.push(
syn::Field::parse_named syn::Field::parse_named
.parse2(quote! { pub id: &'a str }) .parse2(quote! { pub id: &'a str })
@@ -92,6 +97,20 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
None None
} }
} }
fn set_styling(&mut self, styling: &std::collections::HashMap<StyleName, Style>) {
if let Some(style) = styling.get(&StyleName::Class(self.class.to_string())) {
self.style = style.clone();
} else if let Some(style) = styling.get(&StyleName::Id(self.id.to_string())) {
self.style = style.clone();
} else if let Some(style) = styling.get(&StyleName::Component(stringify!(#struct_name).to_string())) {
self.style = style.clone();
}
if let Children::Children(children, _) = &mut self.children {
for child in children {
child.set_styling(styling);
}
}
}
} }
}; };
+37 -5
View File
@@ -20,6 +20,12 @@
//! - `ui` - Contains all user interface components, enabling rich CLI experiences. //! - `ui` - Contains all user interface components, enabling rich CLI experiences.
//! - `utils` - Utility functions for common TUI tasks such as clearing the screen. //! - `utils` - Utility functions for common TUI tasks such as clearing the screen.
use std::{
collections::HashMap,
fmt::Debug,
sync::{Arc, Mutex},
};
use crossterm::event::Event; use crossterm::event::Event;
pub mod app; pub mod app;
@@ -31,8 +37,19 @@ pub mod utils;
/// of UI elements. It re-exports commonly used traits, structs, and utility functions to make /// of UI elements. It re-exports commonly used traits, structs, and utility functions to make
/// the UI framework easier to use. /// the UI framework easier to use.
pub mod prelude { pub mod prelude {
pub use crate::{app::run, rsx, rsx_elem, ui::*, StateChanger}; pub use crate::ui::Color::*;
pub use crate::{Children, Component, Element, ElementCore, State, Value}; pub use crate::{app::run, css, rsx, rsx_elem, ui::*, Handler};
pub use crate::{Children, Element, ElementCore, State, Value};
pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
pub fn sleep(ms: u64) {
std::thread::sleep(std::time::Duration::from_millis(ms));
}
}
pub mod prelude_extra{
pub use crate::ui::Color::*;
pub use crate::{app::run, css, rsx, rsx_elem, ui::*, Handler, StateChanger};
pub use crate::{Children, Component, Element, ElementCore, State, Value, run_handler};
pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
pub fn sleep(ms: u64) { pub fn sleep(ms: u64) {
std::thread::sleep(std::time::Duration::from_millis(ms)); std::thread::sleep(std::time::Duration::from_millis(ms));
@@ -79,6 +96,7 @@ pub trait ElementCore: Send + Sync {
fn update_data(&mut self, width: usize, height: usize); fn update_data(&mut self, width: usize, height: usize);
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>; fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>;
fn get_child(&mut self) -> Option<&mut Element>; fn get_child(&mut self) -> Option<&mut Element>;
fn set_styling(&mut self, styling: &HashMap<crate::ui::StyleName, crate::ui::Style>);
} }
pub trait Component: ElementCore + std::fmt::Debug { pub trait Component: ElementCore + std::fmt::Debug {
@@ -98,14 +116,28 @@ pub type Element = Box<dyn Component>;
/// Handler Struct /// Handler Struct
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Handler<T>(pub Box<dyn FnMut(&T, Event, &StateChanger) + Send>); pub struct Handler<T>(pub Arc<Mutex<dyn FnMut(&mut T, Event, &StateChanger) + Send + Sync>>);
impl<T> Handler<T> { impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T> pub fn new<F>(handler_fn: F) -> Handler<T>
where where
F: FnMut(&T, Event, &StateChanger) + 'static + Send, F: FnMut(&mut T, Event, &StateChanger) + 'static + Send + Sync,
{ {
Handler(Box::new(handler_fn)) Handler(Arc::new(Mutex::new(handler_fn)))
}
}
impl<T> Default for Handler<T> {
fn default() -> Self {
Handler(Arc::new(Mutex::new(
|_: &mut T, _: Event, _: &StateChanger| {},
)))
}
}
impl<T> std::fmt::Debug for Handler<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Handler()")
} }
} }
+165 -39
View File
@@ -165,48 +165,174 @@ macro_rules! val {
} }
#[macro_export] #[macro_export]
macro_rules! write { macro_rules! run_handler {
(($self:ident, $state:expr), $expr:expr) => {{ ($self:ident.$handler:ident ($($inner:tt)*)) => {{
if $state == 0 { let a = std::sync::Arc::clone(&$self.$handler.0);
return format!( let mut o = a.lock().unwrap();
"{}{}{}{}\x1b[0m", o($self, $($inner)*);
$self.style.color.ansi(),
$self.style.background.ansi_bg(),
$self.style.font.ansi(),
$expr
);
}
format!(
"{}{}{}{}\x1b[0m",
$self.style.hover_color.ansi(),
$self.style.hover_background.ansi_bg(),
$self.style.hover_font.ansi(),
$expr
)
}}; }};
($self:ident.$handler:ident) => {{
($self:ident, $kind:ident, $expr:expr) => {{ &$self.$handler;
use paste::paste;
format!(
"{}{}{}{}\x1b[0m",
paste! {
$self.style.[<$kind _color>].ansi()
},
paste! {
$self.style.[<$kind _background>].ansi_bg()
},
paste! {
$self.style.[<$kind _font>].ansi()
},
$expr
)
}}; }};
} }
#[macro_export] #[macro_export]
macro_rules! run_handler { macro_rules! __css {
($self:ident.$handler:ident ($renderer:expr, $event:expr)) => { ($style:expr,)=>{};
let mut o = $self.$handler.0.lock().unwrap(); (
o($self, $renderer, $event); $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
}};
} }
+10 -2
View File
@@ -5,7 +5,15 @@ fn main() {
} }
fn app() -> Element { fn app() -> Element {
rsx! { rsx! { styling: Some(styles()),
button { "This is a button!" } button { class: "btn", "This is a button!" }
}
}
fn styles() -> Css {
css! {
.btn::click {
color: Blue
}
} }
} }
+15 -13
View File
@@ -1,18 +1,11 @@
/// The `ui` module defines the basic building blocks for creating a terminal-based UI framework. /// The `ui` module defines the basic building blocks for creating a terminal-based UI framework.
/// This includes elements like `Text` and `Div`, which can be combined to create complex layouts
/// and interactive UIs in the terminal. It also provides utility functions for rendering, event
/// handling, and managing child components.
/// ///
/// Key Components: /// # Modules
/// - `Text`: A simple text element that renders a string to the terminal.
/// - `Div`: A container element that holds and renders child components, allowing navigation
/// through them with keyboard events (Up, Down, Left, Right).
/// - `styles`: A module for defining UI styles. /// - `styles`: A module for defining UI styles.
/// - `utils`: Utility functions for rendering and managing state.
pub mod styles; pub mod styles;
pub use styles::*; pub use styles::*;
use crate::prelude::*; use crate::prelude_extra::*;
use osui_element::{elem_fn, element}; use osui_element::{elem_fn, element};
/// The `Text` element represents a piece of static text in the terminal UI. /// The `Text` element represents a piece of static text in the terminal UI.
@@ -68,7 +61,9 @@ impl Component for Text<'_> {
#[element] #[element]
#[elem_fn] #[elem_fn]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Div {} pub struct Div {
pub styling: Option<std::collections::HashMap<StyleName, Style>>,
}
impl Component for Div<'_> { impl Component for Div<'_> {
fn render(&self, state: State) -> String { fn render(&self, state: State) -> String {
@@ -93,6 +88,9 @@ impl Component for Div<'_> {
} }
fn event(&mut self, event: Event, state: &crate::StateChanger) { fn event(&mut self, event: Event, state: &crate::StateChanger) {
if let Some(styling) = self.styling.clone() {
self.set_styling(&styling);
}
match event { match event {
Event::Key(key) => { Event::Key(key) => {
if let Children::Children(children, child) = &mut self.children { if let Children::Children(children, child) = &mut self.children {
@@ -142,13 +140,16 @@ impl Component for Div<'_> {
#[element] #[element]
#[elem_fn] #[elem_fn]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Button {} pub struct Button {
pub on_click: Handler<Button<'a>>,
}
impl Component for Button<'_> { impl Component for Button<'_> {
fn render(&self, state: State) -> String { fn render(&self, state: State) -> String {
match state { match state {
State::Custom(_) => format!("\x1b[32m{}\x1b[0m", self.children.get_text()), State::Custom(_) => self.style.write("click", &self.children.get_text()),
_ => self.children.get_text(), State::Hover => self.style.write("hover", &self.children.get_text()),
_ => self.style.write("", &self.children.get_text()),
} }
} }
@@ -157,6 +158,7 @@ impl Component for Button<'_> {
Event::Key(key) => { Event::Key(key) => {
if key.code == KeyCode::Enter { if key.code == KeyCode::Enter {
let prev_state = state.get_state(); let prev_state = state.get_state();
run_handler!(self.on_click(event, state));
state.set_state(State::Custom(0)); state.set_state(State::Custom(0));
sleep(100); sleep(100);
state.set_state(prev_state); state.set_state(prev_state);
+82 -49
View File
@@ -1,18 +1,19 @@
//! The `styles` module defines various styling options for UI elements. This includes colors, //! The `styles` module defines various styling options for UI elements. This includes colors,
//! fonts, padding, margins, and other visual properties that can be applied to UI components //! fonts, padding, margins, and other visual properties that can be applied to UI components
//! to change their appearance. //! to change their appearance.
//!
//! # Example
//! ```rust
//! use crate::ui::styles::{Style, Color};
//!
//! let style = Style {
//! color: Color::Blue,
//! padding: 5,
//! };
//! ```
#[derive(Debug, Clone, PartialEq)] use std::{collections::HashMap, fmt::Debug};
use dyn_clone::{clone_trait_object, DynClone};
pub trait StyleCore: Debug + Send + Sync + DynClone {
fn ansi(&self) -> String;
fn ansi_bg(&self) -> String;
}
clone_trait_object!(StyleCore);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Font { pub enum Font {
None, None,
Bold, Bold,
@@ -23,8 +24,8 @@ pub enum Font {
Mul(Vec<Font>), Mul(Vec<Font>),
} }
impl Font { impl StyleCore for Font {
pub fn ansi(&self) -> String { fn ansi(&self) -> String {
String::from(match self { String::from(match self {
Font::None => "", Font::None => "",
Font::Bold => "\x1b[1m", Font::Bold => "\x1b[1m",
@@ -41,23 +42,14 @@ impl Font {
} }
}) })
} }
fn ansi_bg(&self) -> String {
self.ansi()
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
if secondary == &Font::None {
self
} else {
secondary
}
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq)]
pub enum Color { pub enum Color {
None, NoColor,
Rgb(u8, u8, u8), Rgb(u8, u8, u8),
Hex(String), Hex(String),
Black, Black,
@@ -70,11 +62,10 @@ pub enum Color {
White, White,
} }
impl Color { impl StyleCore for Color {
fn ansi(&self) -> String {
pub fn ansi(&self) -> String {
String::from(match self { String::from(match self {
Color::None => "", Color::NoColor => "",
Color::Rgb(r, g, b) => { Color::Rgb(r, g, b) => {
return format!("\x1b[38;2;{r};{g};{b}m"); return format!("\x1b[38;2;{r};{g};{b}m");
} }
@@ -93,10 +84,9 @@ impl Color {
}) })
} }
fn ansi_bg(&self) -> String {
pub fn ansi_bg(&self) -> String {
String::from(match self { String::from(match self {
Color::None => "", Color::NoColor => "",
Color::Rgb(r, g, b) => { Color::Rgb(r, g, b) => {
return format!("\x1b[48;2;{r};{g};{b}m"); return format!("\x1b[48;2;{r};{g};{b}m");
} }
@@ -114,21 +104,11 @@ impl Color {
Color::White => "\x1b[47m", Color::White => "\x1b[47m",
}) })
} }
pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color {
if secondary == &Color::None {
self
} else {
secondary
}
}
} }
impl Default for Color { impl Default for Color {
fn default() -> Self { fn default() -> Self {
Color::None Color::NoColor
} }
} }
@@ -141,13 +121,15 @@ impl Default for Font {
fn hex_to_rgb(hex: &str) -> (u8, u8, u8) { fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let hex = hex.trim_start_matches('#'); let hex = hex.trim_start_matches('#');
let hex = if hex.len() == 3 { let hex = if hex.len() == 3 {
format!( format!(
"{}{}{}{}{}{}", "{}{}{}{}{}{}",
&hex[0..1], &hex[0..1], &hex[0..1],
&hex[1..2], &hex[1..2], &hex[0..1],
&hex[2..3], &hex[2..3] &hex[1..2],
&hex[1..2],
&hex[2..3],
&hex[2..3]
) )
} else if hex.len() == 6 { } else if hex.len() == 6 {
hex.to_string() hex.to_string()
@@ -155,10 +137,61 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
return (255, 255, 255); return (255, 255, 255);
}; };
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255); let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(255); let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(255);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255); let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255);
(r, g, b) (r, g, b)
} }
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName {
Class(String),
Id(String),
Component(String),
}
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub other: HashMap<String, Box<dyn StyleCore>>,
}
impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
other: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>);
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()
}
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!("{}{s}\x1b[0m", self.color.ansi())
}
}
pub type Css = HashMap<StyleName, Style>;