some nice changes

This commit is contained in:
2024-11-07 23:58:29 +01:00
parent 739a5ea72a
commit 5ff2866f26
7 changed files with 158 additions and 77 deletions
+38 -19
View File
@@ -5,28 +5,37 @@
//! blocks of the TUI, each with properties such as size and position and //! blocks of the TUI, each with properties such as size and position and
//! methods for rendering and updating. //! methods for rendering and updating.
use dyn_clone::DynClone; use dyn_clone::{clone_trait_object, DynClone};
/// Enum representing the size of an `Element`. /// Enum representing the size of an `Element`.
/// ///
/// - `Default(usize)` - Uses a default size for the element. /// - `Default(usize)` - Uses a default size for the element.
/// - `Custom(usize)` - Allows specifying a custom size for the element. /// - `Custom(usize)` - Allows specifying a custom size for the element.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum ElementSize { pub enum Value<T: Copy> {
Default(usize), Default(T),
Custom(usize), Custom(T),
}
impl<T: Copy> Value<T> {
/// Creates a new Value.
///
/// # Returns
///
/// Value::Custom(usize)
pub fn new(value: T) -> Value<T> {
Value::Custom(value)
} }
impl ElementSize {
/// Retrieves the size as an `usize`. /// Retrieves the size as an `usize`.
/// ///
/// # Returns /// # Returns
/// ///
/// The size value, either the default or custom size. /// The size value, either the default or custom size.
pub fn get_size(&self) -> usize { pub fn get_size(&self) -> T {
match *self { match self {
ElementSize::Custom(s) => s, Value::Custom(s) => *s,
ElementSize::Default(s) => s, Value::Default(s) => *s,
} }
} }
@@ -37,9 +46,9 @@ impl ElementSize {
/// # Arguments /// # Arguments
/// ///
/// * `size` - The size to set for the `Element`. /// * `size` - The size to set for the `Element`.
pub fn try_set_size(&mut self, size: usize) { pub fn try_set_size(&mut self, value: T) {
if let ElementSize::Default(_) = *self { if let Value::Default(_) = *self {
*self = ElementSize::Default(size); *self = Value::Default(value);
} }
} }
} }
@@ -47,14 +56,14 @@ impl ElementSize {
/// A trait for defining UI elements in OSUI. /// A trait for defining UI elements in OSUI.
/// ///
/// Elements must implement methods for updating and rendering data. /// Elements must implement methods for updating and rendering data.
/// This trait supports dynamic dispatch and cloning. pub trait Element: DynClone {
pub trait Element: std::fmt::Debug + Send + DynClone {
/// Retrieves the data for the `Element`. /// Retrieves the data for the `Element`.
/// ///
/// # Returns /// # Returns
/// ///
/// `ElementData` containing position and size information. /// `ElementData` containing position and size information.
fn get_data(&self) -> ElementData; fn get_data(&self) -> ElementData;
fn set_data(&mut self, data: ElementData);
/// Updates the data of the `Element` based on the given dimensions. /// Updates the data of the `Element` based on the given dimensions.
/// ///
@@ -64,6 +73,10 @@ pub trait Element: std::fmt::Debug + Send + DynClone {
/// * `_height` - The height of the terminal window or parent element. /// * `_height` - The height of the terminal window or parent element.
fn update_data(&mut self, _width: usize, _height: usize); fn update_data(&mut self, _width: usize, _height: usize);
fn get_id(&mut self) -> String;
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>>;
/// Renders the `Element` as a `String`. /// Renders the `Element` as a `String`.
/// ///
/// # Arguments /// # Arguments
@@ -93,20 +106,26 @@ pub trait Element: std::fmt::Debug + Send + DynClone {
} }
} }
// Enable cloning of `Element` trait objects. clone_trait_object!(Element);
dyn_clone::clone_trait_object!(Element);
/// Struct holding data relevant to an `Element`, including position and size. /// Struct holding data relevant to an `Element`, including position and size.
#[derive(Debug)]
pub struct ElementData { pub struct ElementData {
/// X coordinate of the `Element`. /// X coordinate of the `Element`.
pub x: usize, pub x: usize,
/// Y coordinate of the `Element`. /// Y coordinate of the `Element`.
pub y: usize, pub y: usize,
/// Width of the `Element`, which can be default or custom. /// Width of the `Element`, which can be default or custom.
pub width: ElementSize, pub width: Value<usize>,
/// Height of the `Element`, which can be default or custom. /// Height of the `Element`, which can be default or custom.
pub height: ElementSize, pub height: Value<usize>,
/// Children of the `Element`
pub children: Vec<Box<dyn Element>>,
/// Active Child of the `Element`
pub child: usize,
/// Text of the `Element`
pub text: String,
/// Identifier of the `Element`
pub id: String,
} }
/// Enum representing the possible responses from an element update. /// Enum representing the possible responses from an element update.
+7 -3
View File
@@ -24,6 +24,7 @@
pub mod element; pub mod element;
pub mod key; pub mod key;
pub mod macros; pub mod macros;
mod test;
pub mod ui; pub mod ui;
pub mod utils; pub mod utils;
@@ -40,7 +41,7 @@ pub mod app {
use crate::{ use crate::{
clear, create_frame, flush, get_term_size, hide_cursor, clear, create_frame, flush, get_term_size, hide_cursor,
key::{read_key, Key}, key::{read_key, Key},
render_to_frame, show_cursor, Command, Element, ElementSize, UpdateResponse, render_to_frame, show_cursor, Command, Element, UpdateResponse, Value,
}; };
/// Renders a single frame of the UI to the terminal. /// Renders a single frame of the UI to the terminal.
@@ -55,8 +56,7 @@ pub mod app {
fn render(elem: &mut Box<dyn Element>, state: usize) { fn render(elem: &mut Box<dyn Element>, state: usize) {
let (width, height) = get_term_size(); let (width, height) = get_term_size();
elem.update_data(width, height); elem.update_data(width, height);
let mut frame: Vec<String> = let mut frame: Vec<String> = create_frame(Value::Custom(width), Value::Custom(height));
create_frame(ElementSize::Custom(width), ElementSize::Custom(height));
render_to_frame(state, &mut frame, elem); render_to_frame(state, &mut frame, elem);
clear(); clear();
print!("{}", frame.join("")); print!("{}", frame.join(""));
@@ -146,3 +146,7 @@ pub mod app {
} }
} }
} }
pub fn run_test() {
test::main_();
}
+90 -34
View File
@@ -33,26 +33,30 @@ macro_rules! element {
$( $functions:tt )* $( $functions:tt )*
) => { ) => {
$(#[$meta])* $(#[$meta])*
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct $name { pub struct $name<'a> {
pub x: usize, pub x: usize,
pub y: usize, pub y: usize,
pub width: ElementSize, pub width: Value<usize>,
pub height: ElementSize, pub height: Value<usize>,
pub children: Vec<Box<dyn Element>>, pub children: Vec<Box<dyn Element>>,
pub child: usize, pub child: usize,
pub text: String, pub text: String,
pub style: Style, pub id: &'a str,
$( $inner )* $( $inner )*
} }
impl Element for $name { impl Element for $name<'_> {
fn get_data(&self) -> ElementData { fn get_data(&self) -> ElementData {
ElementData { ElementData {
x: self.x, x: self.x,
y: self.y, y: self.y,
width: self.width.clone(), width: self.width.clone(),
height: self.height.clone(), height: self.height.clone(),
children: self.children.clone(),
child: self.child.clone(),
text: self.text.clone(),
id: self.id.to_string(),
} }
} }
@@ -63,21 +67,45 @@ macro_rules! element {
child.update_data(width, height); child.update_data(width, height);
} }
} }
fn get_id(&mut self) -> String {
self.id.to_string()
}
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>> {
for elem in &mut self.children {
if elem.get_id() == id {
return Some(elem);
}
}
None
}
fn set_data(&mut self, data: ElementData) {
self.x = data.x;
self.y = data.y;
self.width = data.width;
self.height = data.height;
self.children = data.children;
self.child = data.child;
self.text = data.text;
}
$( $functions )* $( $functions )*
} }
impl $name { impl<'a> $name<'_> {
/// Creates a new instance of the element with default values. /// Creates a new instance of the element with default values.
pub fn new() -> $name { pub fn new() -> $name<'a> {
$name { $name {
children: Vec::new(), children: Vec::new(),
x: 0, x: 0,
y: 0, y: 0,
width: ElementSize::Default(0), width: Value::Default(0),
height: ElementSize::Default(0), height: Value::Default(0),
child: 0, child: 0,
text: String::new(), text: String::new(),
style: Style::default(), id: "",
$( $defaults )* $( $defaults )*
} }
} }
@@ -119,48 +147,68 @@ macro_rules! command {
/// - Various key-value pairs, child elements, and text content. even for loops /// - Various key-value pairs, child elements, and text content. even for loops
#[macro_export] #[macro_export]
macro_rules! parse_rsx_param { macro_rules! parse_rsx_param {
($elem:expr, ) => {}; // Properties
($elem:expr, $($k:ident).+: $v:expr) => {
$elem.$($k).+ = $v;
};
($elem:expr, for ($item:ident in $expr:expr) {$($inner:tt)*} $($rest:tt)*) => { ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => {
for $item in $expr { $elem.$($k).+ = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
// for completion purposes
($elem:expr, $p:path) => {
$p;
};
($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, $($k:ident).+:$($rest:tt)*) => {
$elem.$($k).+ = ();
};
($elem:expr, $($k:ident).+:$($rest:tt)*) => {
$elem.$($k).+ = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
// For loop
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
for $($for)* {
$elem.children.push({$($inner)*}) $elem.children.push({$($inner)*})
} }
osui::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
}; };
($elem:expr, $($k:ident: $v:expr),*) => { // Expression
$(
$elem.$k = $v;
)*
};
($elem:expr, $($k:ident: $v:expr),*; $($rest:tt)*) => {
$(
$elem.$k = $v;
)*
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, {$ielem:expr} $($rest:tt)*) => { ($elem:expr, {$ielem:expr} $($rest:tt)*) => {
$elem.children.push($ielem); $elem.children.push($ielem);
osui::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
}; };
($elem:expr, $($k:ident: $v:expr),*, $text:expr) => { // Element
$(
$elem.$k = $v;
)*
$elem.text = format!($text);
};
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => { ($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
$elem.children.push(osui::rsx_elem!($pelem { $($inner)* })); $elem.children.push(osui::rsx_elem!($pelem { $($inner)* }));
osui::parse_rsx_param!($elem, $($rest)*) osui::parse_rsx_param!($elem, $($rest)*)
}; };
// Text
($elem:expr, $text:expr) => { ($elem:expr, $text:expr) => {
$elem.text = format!($text); $elem.text = format!($text);
}; };
// Empty
($elem:expr, ) => {};
} }
/// Macro for creating an OSUI element with parsed parameters. /// Macro for creating an OSUI element with parsed parameters.
@@ -190,6 +238,7 @@ macro_rules! parse_rsx_param {
#[macro_export] #[macro_export]
macro_rules! rsx_elem { macro_rules! rsx_elem {
($elem:path { $($inner:tt)* }) => {{ ($elem:path { $($inner:tt)* }) => {{
#[allow(unused_mut)]
let mut elem = $elem(); let mut elem = $elem();
osui::parse_rsx_param!(elem, $($inner)*); osui::parse_rsx_param!(elem, $($inner)*);
elem as Box<dyn osui::Element> elem as Box<dyn osui::Element>
@@ -237,3 +286,10 @@ macro_rules! css {
} }
}}; }};
} }
#[macro_export]
macro_rules! arc {
($a:expr) => {
std::sync::Arc::new(std::sync::Mutex::new($a))
};
}
+3 -8
View File
@@ -1,11 +1,6 @@
use osui::{rsx, ui::*}; use osui::rsx;
fn main() { fn main() {
osui::app::run(&mut app()); rsx! {};
} // osui::run_test()
fn app() -> Box<dyn osui::Element> {
rsx! {
text { "Hello, World!" }
}
} }
+12 -5
View File
@@ -1,8 +1,8 @@
use std::collections::HashMap; use std::{collections::HashMap, sync::{Arc, Mutex}};
use crate::{ use crate::{
command, element, key::Key, render_to_frame, ui::Style, Command, Direction, Element, command, element, key::Key, render_to_frame, ui::Style, Command, Direction, Element,
ElementData, ElementSize, UpdateResponse, ElementData, Value, UpdateResponse,
}; };
element! { element! {
@@ -15,6 +15,7 @@ element! {
self.text.clone() self.text.clone()
} }
} }
element! { element! {
/// A clickable button element. /// A clickable button element.
/// ///
@@ -22,11 +23,13 @@ element! {
/// based on its interaction state, such as being "clicked". /// based on its interaction state, such as being "clicked".
Button { Button {
/// A callback function executed when the button is clicked. /// A callback function executed when the button is clicked.
on_click: fn() pub on_click: Arc<Mutex<dyn FnMut(&mut Button)>>,
pub style: Style,
} }
defaults { defaults {
on_click: || {} on_click: Arc::new(Mutex::new(|_btn: &mut Button<'_>| {})),
style: Style::default(),
} }
fn render(&self, state: usize) -> String { fn render(&self, state: usize) -> String {
@@ -39,7 +42,11 @@ element! {
fn event(&mut self, _state: usize, k: Key) -> UpdateResponse { fn event(&mut self, _state: usize, k: Key) -> UpdateResponse {
if k == Key::Enter { if k == Key::Enter {
(self.on_click)(); let mut btn = self.clone();
let mut on_click = self.on_click.lock().unwrap();
(on_click)(&mut btn);
drop(on_click);
*self = btn;
return command!( return command!(
Command::Render(2), Command::Render(2),
Command::Sleep(120) Command::Sleep(120)
+3 -3
View File
@@ -14,7 +14,7 @@ pub use elements::*;
/// # Returns /// # Returns
/// ///
/// A `Box<Text>` containing a new `Text` element instance. /// A `Box<Text>` containing a new `Text` element instance.
pub fn text() -> Box<Text> { pub fn text<'a>() -> Box<Text<'a>> {
Box::new(Text::new()) Box::new(Text::new())
} }
@@ -26,7 +26,7 @@ pub fn text() -> Box<Text> {
/// # Returns /// # Returns
/// ///
/// A `Box<Button>` containing a new `Button` element instance. /// A `Box<Button>` containing a new `Button` element instance.
pub fn button() -> Box<Button> { pub fn button<'a>() -> Box<Button<'a>> {
let mut btn = Button::new(); let mut btn = Button::new();
btn.style.clicked_bg = styles::Color::White; btn.style.clicked_bg = styles::Color::White;
btn.style.clicked_fg = styles::Color::Black; btn.style.clicked_fg = styles::Color::Black;
@@ -41,6 +41,6 @@ pub fn button() -> Box<Button> {
/// # Returns /// # Returns
/// ///
/// A `Box<Div>` containing a new `Div` element instance. /// A `Box<Div>` containing a new `Div` element instance.
pub fn div() -> Box<Div> { pub fn div<'a>() -> Box<Div<'a>> {
Box::new(Div::new()) Box::new(Div::new())
} }
+1 -1
View File
@@ -138,7 +138,7 @@ 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: crate::ElementSize, height: crate::ElementSize) -> Vec<String> { pub fn create_frame(width: crate::Value<usize>, height: crate::Value<usize>) -> Vec<String> {
vec![" ".repeat(width.get_size()); height.get_size()] vec![" ".repeat(width.get_size()); height.get_size()]
} }