From 33843688d848643cfbea282f6a306762f5a58929 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 24 Nov 2024 20:00:29 +0100 Subject: [PATCH] styling --- src/app.rs | 10 ++--- src/css.rs | 25 ++++++++++++ src/examples.rs | 21 +++++++--- src/lib.rs | 11 +++-- src/ui/mod.rs | 52 +++++++++++++----------- src/ui/styles.rs | 77 ++++++++++++++++++++++++++++++++--- src/utils.rs | 102 ++++++++++++++++++++++++++++++++++------------- styling.md | 2 +- 8 files changed, 228 insertions(+), 72 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5ef1b60..501b0bb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,9 +4,7 @@ use crossterm::{ }; use crate::prelude::*; -use crate::utils::{ - clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor, -}; +use crate::utils::{clear, flush, get_term_size, hide_cursor, show_cursor, Frame}; use std::{ffi::c_void, ptr::null, sync::mpsc}; pub struct LArgs<'a> { @@ -125,10 +123,10 @@ extern "C" fn cmd_loop(ptr: *mut c_void) -> *const c_void { } Command::Render => { let (width, height) = get_term_size(); - let mut frame: Vec = create_frame(width, height); - render_to_frame(true, width, &mut frame, args.element); + let mut frame = Frame::new(width, height); + frame.render(true, args.element); clear(); - print!("{}", frame.join("")); + print!("{}", frame.output_nnl()); flush(); } } diff --git a/src/css.rs b/src/css.rs index 71da4bf..a7e88d4 100644 --- a/src/css.rs +++ b/src/css.rs @@ -1,6 +1,31 @@ #[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)*)?); + }}; + + // Normal ( $_style:expr, $name:ident: $value:expr $(, $($other:tt)*)? ) => {{ diff --git a/src/examples.rs b/src/examples.rs index 5d4ab98..9e1f807 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -2,10 +2,21 @@ use crate::prelude::*; pub fn event_logger() -> Element { rsx! { - id: "root", - text { on_event: Handler::new(|_, _, document| { - let root = document.get_element_by_id::
("root").unwrap(); - root.children.add_child(rsx_elem!( text { "Ok" } )); - }), id: "txt", "current event: None" } + styling: Some(styling()), + button { class: "txt", "Hello, World!" } + } +} + +pub fn styling() -> Css { + css! { + .txt { + x: Center, + y: Center, + color: Red, + } + + .txt: clicked { + color: Blue, + } } } diff --git a/src/lib.rs b/src/lib.rs index 38bd8b5..fe706c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub use app::run; /// the UI framework easier to use. pub mod prelude { pub use crate::ui::Color::*; + pub use crate::ui::Number::*; pub use crate::{self as osui, css, rsx, rsx_elem, ui::*, Handler}; pub use crate::{style, Command, Document, Element, Value}; // useful for Element making @@ -88,6 +89,11 @@ impl Default for Value { /// ElementCore Traits ////////////////////////////////////////////////////////////////////////////////////////////////// +/// ``` +/// RenderResult(output, (x, y), (width, height)) +/// ``` +pub struct RenderResult(String, (ui::Number, ui::Number)); + pub trait ElementCore: Send + Sync { fn get_data(&self) -> (Value, usize, String); fn update_data(&mut self, width: usize, height: usize); @@ -97,10 +103,7 @@ pub trait ElementCore: Send + Sync { } pub trait ElementWidget: ElementCore + std::fmt::Debug { - fn render(&self, focused: bool) -> String { - _ = focused; - String::new() - } + fn render(&self, focused: bool) -> RenderResult; fn event(&mut self, event: Event, document: &Document) { _ = (event, document) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 7006d83..6c64ef2 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -5,7 +5,7 @@ pub mod styles; pub use styles::*; -use crate::prelude::*; +use crate::{prelude::*, RenderResult}; use osui_element::{elem_fn, element}; /// The `Text` element represents a piece of static text in the terminal UI. @@ -31,12 +31,17 @@ pub struct Text { } impl ElementWidget for Text<'_> { - fn render(&self, _: bool) -> String { - if let Children::Text(text) = &self.children { - text.clone() - } else { - String::new() - } + fn render(&self, _: bool) -> RenderResult { + self.style.result( + "", + self.style.write("", &{ + if let Children::Text(text) = &self.children { + text.clone() + } else { + String::new() + } + }), + ) } fn event(&mut self, event: Event, document: &Document) { call!(self.on_event(event, document)); @@ -71,21 +76,16 @@ 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) -> RenderResult { + 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); } } - frame.join("\n") + self.style.result("", frame.output()) } fn event(&mut self, event: Event, document: &Document) { @@ -149,14 +149,20 @@ pub struct Button { } 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()) + fn render(&self, focused: bool) -> RenderResult { + if self.state != "" { + self.style.result( + self.state, + self.style.write(self.state, &self.children.get_text()), + ) + } else if focused { + self.style.result( + "hover", + self.style.write("hover", &self.children.get_text()), + ) } else { - self.style.write("", &self.children.get_text()) + self.style + .result("", self.style.write("", &self.children.get_text())) } } diff --git a/src/ui/styles.rs b/src/ui/styles.rs index 9498ff1..48a57d0 100644 --- a/src/ui/styles.rs +++ b/src/ui/styles.rs @@ -6,9 +6,12 @@ use std::{collections::HashMap, fmt::Debug}; use dyn_clone::{clone_trait_object, DynClone}; +use crate::RenderResult; + 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); @@ -45,6 +48,9 @@ impl StyleCore for Font { fn ansi_bg(&self) -> String { self.ansi() } + fn is_null(&self) -> bool { + *self == Self::None + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -104,6 +110,9 @@ impl StyleCore for Color { Color::White => "\x1b[47m", }) } + fn is_null(&self) -> bool { + *self == Self::NoColor + } } impl Default for Color { @@ -144,6 +153,15 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) { (r, g, b) } +#[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), @@ -154,6 +172,12 @@ pub enum StyleName { #[derive(Debug, Clone)] pub struct StyleElement { pub color: Color, + pub background: Color, + pub font: Font, + pub x: Number, + pub y: Number, + pub width: Number, + pub height: Number, pub other: HashMap>, } @@ -161,11 +185,44 @@ impl Default for StyleElement { fn default() -> Self { StyleElement { color: Color::NoColor, + background: Color::NoColor, + font: Font::None, + 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 + } +} + #[derive(Debug, Clone)] pub struct Style(pub StyleElement, pub HashMap); @@ -177,20 +234,30 @@ impl Default for Style { 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() + return self.0.write(s); + } + } + pub fn result(&self, state: &str, s: String) -> RenderResult { + if let Some(style_element) = self.1.get(state) { + let style_element = self.0.clone().prioritize(&style_element.clone()); + RenderResult(s, (style_element.x.clone(), style_element.y.clone())) + } else { + RenderResult(s, (self.0.x.clone(), self.0.y.clone())) } } } impl StyleElement { pub fn write(&self, s: &str) -> String { - format!("{}{s}\x1b[0m", self.color.ansi()) + format!( + "{}{}{}{s}\x1b[0m", + self.color.ansi(), + self.background.ansi_bg(), + self.font.ansi() + ) } } diff --git a/src/utils.rs b/src/utils.rs index 388cf13..dd1a8bb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,11 +5,10 @@ /// # 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 lazy_static::lazy_static; use regex::Regex; @@ -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,35 +81,86 @@ 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) { + // let data = element.get_data(); + let output = element.render(focused); + let x; + let mut y = 0; + match output.1 .1 { + crate::ui::Number::Px(_y) => { + y = _y; + } + 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; + } } - _ => { - for (x, c) in frame_line.chars().rev().enumerate() { - if c != ' ' { - *frame_line = merge_line(&frame_line, line, width - x); - return; - } + } + crate::ui::Number::Default => { + for (i, n) in self.used.iter().enumerate() { + if *n == 0 { + y = i; + break; } - *frame_line = merge_line(&frame_line, line, 0); } } } + match output.1 .0 { + 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("") } } @@ -180,10 +230,6 @@ pub fn 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 index be2752c..14b2dcd 100644 --- a/styling.md +++ b/styling.md @@ -6,4 +6,4 @@ `x`, `y`: `n`, `n%`, `center`, `auto`; -`display`: `row`, `column`, `overlap`; \ No newline at end of file + \ No newline at end of file