This commit is contained in:
2024-11-24 20:00:29 +01:00
parent e463ca1e7c
commit 33843688d8
8 changed files with 228 additions and 72 deletions
+4 -6
View File
@@ -4,9 +4,7 @@ use crossterm::{
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::utils::{ use crate::utils::{clear, flush, get_term_size, hide_cursor, show_cursor, Frame};
clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor,
};
use std::{ffi::c_void, ptr::null, sync::mpsc}; use std::{ffi::c_void, ptr::null, sync::mpsc};
pub struct LArgs<'a> { pub struct LArgs<'a> {
@@ -125,10 +123,10 @@ extern "C" fn cmd_loop(ptr: *mut c_void) -> *const c_void {
} }
Command::Render => { Command::Render => {
let (width, height) = get_term_size(); let (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(width, height); let mut frame = Frame::new(width, height);
render_to_frame(true, width, &mut frame, args.element); frame.render(true, args.element);
clear(); clear();
print!("{}", frame.join("")); print!("{}", frame.output_nnl());
flush(); flush();
} }
} }
+25
View File
@@ -1,6 +1,31 @@
#[macro_export] #[macro_export]
macro_rules! __css { macro_rules! __css {
($_style:expr,)=>{}; ($_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)*)? $_style:expr, $name:ident: $value:expr $(, $($other:tt)*)?
) => {{ ) => {{
+16 -5
View File
@@ -2,10 +2,21 @@ use crate::prelude::*;
pub fn event_logger() -> Element { pub fn event_logger() -> Element {
rsx! { rsx! {
id: "root", styling: Some(styling()),
text { on_event: Handler::new(|_, _, document| { button { class: "txt", "Hello, World!" }
let root = document.get_element_by_id::<Div>("root").unwrap(); }
root.children.add_child(rsx_elem!( text { "Ok" } )); }
}), id: "txt", "current event: None" }
pub fn styling() -> Css {
css! {
.txt {
x: Center,
y: Center,
color: Red,
}
.txt: clicked {
color: Blue,
}
} }
} }
+7 -4
View File
@@ -43,6 +43,7 @@ pub use app::run;
/// the UI framework easier to use. /// the UI framework easier to use.
pub mod prelude { pub mod prelude {
pub use crate::ui::Color::*; 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::{self as osui, css, rsx, rsx_elem, ui::*, Handler};
pub use crate::{style, Command, Document, Element, Value}; pub use crate::{style, Command, Document, Element, Value};
// useful for Element making // useful for Element making
@@ -88,6 +89,11 @@ impl<T: Copy + PartialEq + Default> Default for Value<T> {
/// ElementCore Traits /// ElementCore Traits
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
/// ```
/// RenderResult(output, (x, y), (width, height))
/// ```
pub struct RenderResult(String, (ui::Number, ui::Number));
pub trait ElementCore: Send + Sync { pub trait ElementCore: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String); fn get_data(&self) -> (Value<usize>, usize, String);
fn update_data(&mut self, width: usize, height: usize); 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 { pub trait ElementWidget: ElementCore + std::fmt::Debug {
fn render(&self, focused: bool) -> String { fn render(&self, focused: bool) -> RenderResult;
_ = focused;
String::new()
}
fn event(&mut self, event: Event, document: &Document) { fn event(&mut self, event: Event, document: &Document) {
_ = (event, document) _ = (event, document)
+29 -23
View File
@@ -5,7 +5,7 @@
pub mod styles; pub mod styles;
pub use styles::*; pub use styles::*;
use crate::prelude::*; use crate::{prelude::*, RenderResult};
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.
@@ -31,12 +31,17 @@ pub struct Text {
} }
impl ElementWidget for Text<'_> { impl ElementWidget for Text<'_> {
fn render(&self, _: bool) -> String { fn render(&self, _: bool) -> RenderResult {
if let Children::Text(text) = &self.children { self.style.result(
text.clone() "",
} else { self.style.write("", &{
String::new() if let Children::Text(text) = &self.children {
} text.clone()
} else {
String::new()
}
}),
)
} }
fn event(&mut self, event: Event, document: &Document) { fn event(&mut self, event: Event, document: &Document) {
call!(self.on_event(event, document)); call!(self.on_event(event, document));
@@ -71,21 +76,16 @@ pub struct Div {
} }
impl ElementWidget for Div<'_> { impl ElementWidget for Div<'_> {
fn render(&self, focused: bool) -> String { fn render(&self, focused: bool) -> RenderResult {
let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value()); let mut frame = crate::utils::Frame::new(crate::utils::get_term_size().0, 12);
if let Children::Children(children, child) = &self.children { if let Children::Children(children, child) = &self.children {
for (i, elem) in children.iter().enumerate() { for (i, elem) in children.iter().enumerate() {
crate::utils::render_to_frame( frame.render(focused && i == *child, elem);
focused && i == *child,
self.width.get_value(),
&mut frame,
elem,
);
} }
} }
frame.join("\n") self.style.result("", frame.output())
} }
fn event(&mut self, event: Event, document: &Document) { fn event(&mut self, event: Event, document: &Document) {
@@ -149,14 +149,20 @@ pub struct Button {
} }
impl ElementWidget for Button<'_> { impl ElementWidget for Button<'_> {
fn render(&self, focused: bool) -> String { fn render(&self, focused: bool) -> RenderResult {
if self.state == "clicked" { if self.state != "" {
return self.style.write("clicked", &self.children.get_text()); self.style.result(
} self.state,
if focused { self.style.write(self.state, &self.children.get_text()),
self.style.write("hover", &self.children.get_text()) )
} else if focused {
self.style.result(
"hover",
self.style.write("hover", &self.children.get_text()),
)
} else { } else {
self.style.write("", &self.children.get_text()) self.style
.result("", self.style.write("", &self.children.get_text()))
} }
} }
+72 -5
View File
@@ -6,9 +6,12 @@ use std::{collections::HashMap, fmt::Debug};
use dyn_clone::{clone_trait_object, DynClone}; use dyn_clone::{clone_trait_object, DynClone};
use crate::RenderResult;
pub trait StyleCore: Debug + Send + Sync + DynClone { pub trait StyleCore: Debug + Send + Sync + DynClone {
fn ansi(&self) -> String; fn ansi(&self) -> String;
fn ansi_bg(&self) -> String; fn ansi_bg(&self) -> String;
fn is_null(&self) -> bool;
} }
clone_trait_object!(StyleCore); clone_trait_object!(StyleCore);
@@ -45,6 +48,9 @@ impl StyleCore for Font {
fn ansi_bg(&self) -> String { fn ansi_bg(&self) -> String {
self.ansi() self.ansi()
} }
fn is_null(&self) -> bool {
*self == Self::None
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -104,6 +110,9 @@ impl StyleCore for Color {
Color::White => "\x1b[47m", Color::White => "\x1b[47m",
}) })
} }
fn is_null(&self) -> bool {
*self == Self::NoColor
}
} }
impl Default for Color { impl Default for Color {
@@ -144,6 +153,15 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
(r, g, b) (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)] #[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName { pub enum StyleName {
Class(String), Class(String),
@@ -154,6 +172,12 @@ pub enum StyleName {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StyleElement { pub struct StyleElement {
pub color: Color, pub color: Color,
pub background: Color,
pub font: Font,
pub x: Number,
pub y: Number,
pub width: Number,
pub height: Number,
pub other: HashMap<String, Box<dyn StyleCore>>, pub other: HashMap<String, Box<dyn StyleCore>>,
} }
@@ -161,11 +185,44 @@ impl Default for StyleElement {
fn default() -> Self { fn default() -> Self {
StyleElement { StyleElement {
color: Color::NoColor, color: Color::NoColor,
background: Color::NoColor,
font: Font::None,
x: Number::Default,
y: Number::Default,
width: Number::Default,
height: Number::Default,
other: HashMap::new(), 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)] #[derive(Debug, Clone)]
pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>); pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>);
@@ -177,20 +234,30 @@ impl Default for Style {
impl Style { impl Style {
pub fn write(&self, state: &str, s: &str) -> String { 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) { if let Some(style_element) = self.1.get(state) {
style_element.write(s) style_element.write(s)
} else { } 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 { impl StyleElement {
pub fn write(&self, s: &str) -> String { 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()
)
} }
} }
+74 -28
View File
@@ -5,11 +5,10 @@
/// # Example /// # Example
/// ```rust /// ```rust
/// use crate::ui::utils::{create_frame, render_to_frame}; /// use crate::ui::utils::{create_frame, render_to_frame};
/// ///
/// let frame = create_frame(50, 10); /// let frame = create_frame(50, 10);
/// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element); /// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element);
/// ``` /// ```
use crate::{Element, Value}; use crate::{Element, Value};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
@@ -49,7 +48,7 @@ pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap<usize, Strin
} }
/// Merges a frame withe a line by x /// Merges a frame withe a line by x
fn merge_line(frame_: &str, line_: &str, x: usize) -> String { fn merge_line(frame_: &str, line_: &str, x: usize) -> (String, String) {
let (frame_, fm) = compress_string(frame_, &ANSI); let (frame_, fm) = compress_string(frame_, &ANSI);
let (mut line_, lm) = compress_string(line_, &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 struct Frame {
pub fn render_to_frame( frame: Vec<String>,
focused: bool,
width: usize, width: usize,
frame: &mut Vec<String>, used: Vec<usize>,
element: &Element, }
) {
let data = element.get_data(); impl Frame {
for (i, line) in element.render(focused).split('\n').enumerate() { pub fn new(width: usize, height: usize) -> Self {
if (data.1 + i) < frame.len() { Self {
let frame_line = frame.get_mut(data.1 + i).unwrap(); frame: vec![" ".repeat(width); height],
match data.0 { width,
Value::Custom(x) => { used: vec![0; height],
*frame_line = merge_line(&frame_line, line, x); }
}
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() { crate::ui::Number::Default => {
if c != ' ' { for (i, n) in self.used.iter().enumerate() {
*frame_line = merge_line(&frame_line, line, width - x); if *n == 0 {
return; 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 .unwrap_or(current_index) // If no component is found, return the current index
} }
pub fn create_frame(width: usize, height: usize) -> Vec<String> {
vec![" ".repeat(width); height]
}
pub fn get_term_size() -> (usize, usize) { pub fn get_term_size() -> (usize, usize) {
let (width, height) = crossterm::terminal::size().unwrap(); let (width, height) = crossterm::terminal::size().unwrap();
(width as usize, height as usize) (width as usize, height as usize)
+1 -1
View File
@@ -6,4 +6,4 @@
`x`, `y`: `n`, `n%`, `center`, `auto`; `x`, `y`: `n`, `n%`, `center`, `auto`;
`display`: `row`, `column`, `overlap`; <!-- `display`: `row`, `column`, `overlap`; -->