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"
paste = "1.0.15"
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 })
.unwrap(),
);
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub style: Style })
.unwrap(),
);
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub id: &'a str })
@@ -92,6 +97,20 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
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.
//! - `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;
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
/// the UI framework easier to use.
pub mod prelude {
pub use crate::{app::run, rsx, rsx_elem, ui::*, StateChanger};
pub use crate::{Children, Component, Element, ElementCore, State, Value};
pub use crate::ui::Color::*;
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 fn sleep(ms: u64) {
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 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<crate::ui::StyleName, crate::ui::Style>);
}
pub trait Component: ElementCore + std::fmt::Debug {
@@ -98,14 +116,28 @@ pub type Element = Box<dyn Component>;
/// 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> {
pub fn new<F>(handler_fn: F) -> Handler<T>
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_rules! write {
(($self:ident, $state:expr), $expr:expr) => {{
if $state == 0 {
return format!(
"{}{}{}{}\x1b[0m",
$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
)
macro_rules! run_handler {
($self:ident.$handler:ident ($($inner:tt)*)) => {{
let a = std::sync::Arc::clone(&$self.$handler.0);
let mut o = a.lock().unwrap();
o($self, $($inner)*);
}};
($self:ident, $kind:ident, $expr:expr) => {{
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
)
($self:ident.$handler:ident) => {{
&$self.$handler;
}};
}
#[macro_export]
macro_rules! run_handler {
($self:ident.$handler:ident ($renderer:expr, $event:expr)) => {
let mut o = $self.$handler.0.lock().unwrap();
o($self, $renderer, $event);
};
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
}};
}
+10 -2
View File
@@ -5,7 +5,15 @@ fn main() {
}
fn app() -> Element {
rsx! {
button { "This is a button!" }
rsx! { styling: Some(styles()),
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.
/// 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:
/// - `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).
/// # Modules
/// - `styles`: A module for defining UI styles.
/// - `utils`: Utility functions for rendering and managing state.
pub mod styles;
pub use styles::*;
use crate::prelude::*;
use crate::prelude_extra::*;
use osui_element::{elem_fn, element};
/// The `Text` element represents a piece of static text in the terminal UI.
@@ -68,7 +61,9 @@ impl Component for Text<'_> {
#[element]
#[elem_fn]
#[derive(Default, Debug)]
pub struct Div {}
pub struct Div {
pub styling: Option<std::collections::HashMap<StyleName, Style>>,
}
impl Component for Div<'_> {
fn render(&self, state: State) -> String {
@@ -93,6 +88,9 @@ impl Component for Div<'_> {
}
fn event(&mut self, event: Event, state: &crate::StateChanger) {
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 {
@@ -142,13 +140,16 @@ impl Component for Div<'_> {
#[element]
#[elem_fn]
#[derive(Default, Debug)]
pub struct Button {}
pub struct Button {
pub on_click: Handler<Button<'a>>,
}
impl Component for Button<'_> {
fn render(&self, state: State) -> String {
match state {
State::Custom(_) => format!("\x1b[32m{}\x1b[0m", self.children.get_text()),
_ => self.children.get_text(),
State::Custom(_) => self.style.write("click", &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) => {
if key.code == KeyCode::Enter {
let prev_state = state.get_state();
run_handler!(self.on_click(event, state));
state.set_state(State::Custom(0));
sleep(100);
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,
//! fonts, padding, margins, and other visual properties that can be applied to UI components
//! 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 {
None,
Bold,
@@ -23,8 +24,8 @@ pub enum Font {
Mul(Vec<Font>),
}
impl Font {
pub fn ansi(&self) -> String {
impl StyleCore for Font {
fn ansi(&self) -> String {
String::from(match self {
Font::None => "",
Font::Bold => "\x1b[1m",
@@ -41,23 +42,14 @@ impl Font {
}
})
}
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
if secondary == &Font::None {
self
} else {
secondary
}
fn ansi_bg(&self) -> String {
self.ansi()
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Color {
None,
NoColor,
Rgb(u8, u8, u8),
Hex(String),
Black,
@@ -70,11 +62,10 @@ pub enum Color {
White,
}
impl Color {
pub fn ansi(&self) -> String {
impl StyleCore for Color {
fn ansi(&self) -> String {
String::from(match self {
Color::None => "",
Color::NoColor => "",
Color::Rgb(r, g, b) => {
return format!("\x1b[38;2;{r};{g};{b}m");
}
@@ -93,10 +84,9 @@ impl Color {
})
}
pub fn ansi_bg(&self) -> String {
fn ansi_bg(&self) -> String {
String::from(match self {
Color::None => "",
Color::NoColor => "",
Color::Rgb(r, g, b) => {
return format!("\x1b[48;2;{r};{g};{b}m");
}
@@ -114,21 +104,11 @@ impl Color {
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 {
fn default() -> Self {
Color::None
Color::NoColor
}
}
@@ -141,13 +121,15 @@ impl Default for Font {
fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let hex = hex.trim_start_matches('#');
let hex = if hex.len() == 3 {
format!(
"{}{}{}{}{}{}",
&hex[0..1], &hex[0..1],
&hex[1..2], &hex[1..2],
&hex[2..3], &hex[2..3]
&hex[0..1],
&hex[0..1],
&hex[1..2],
&hex[1..2],
&hex[2..3],
&hex[2..3]
)
} else if hex.len() == 6 {
hex.to_string()
@@ -155,10 +137,61 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
return (255, 255, 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 b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255);
(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>;