small structure changes
This commit is contained in:
+5
-3
@@ -2,6 +2,8 @@ use proc_macro::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse::Parser, parse_macro_input, DeriveInput, Fields, LifetimeParam};
|
use syn::{parse::Parser, parse_macro_input, DeriveInput, Fields, LifetimeParam};
|
||||||
|
|
||||||
|
/// Sets the required values and traits for a struct to be a element.
|
||||||
|
/// You will still need to implement ElementCore yourself
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||||
@@ -60,7 +62,7 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let expanded_impl = quote! {
|
let expanded_impl = quote! {
|
||||||
impl<'a> ElementComponent for #struct_name<'a> {
|
impl<'a> ElementCore for #struct_name<'a> {
|
||||||
fn get_data(&self) -> (Value<usize>, usize, String) {
|
fn get_data(&self) -> (Value<usize>, usize, String) {
|
||||||
(self.x, self.y, self.id.to_string())
|
(self.x, self.y, self.id.to_string())
|
||||||
}
|
}
|
||||||
@@ -73,7 +75,7 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>> {
|
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element> {
|
||||||
if let Children::Children(children, _) = &mut self.children {
|
if let Children::Children(children, _) = &mut self.children {
|
||||||
for elem in children {
|
for elem in children {
|
||||||
if elem.get_data().2 == id {
|
if elem.get_data().2 == id {
|
||||||
@@ -83,7 +85,7 @@ pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
|
fn get_child(&mut self) -> Option<&mut Element> {
|
||||||
if let Children::Children(children, child) = &mut self.children {
|
if let Children::Children(children, child) = &mut self.children {
|
||||||
children.get_mut(*child)
|
children.get_mut(*child)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-14
@@ -1,24 +1,18 @@
|
|||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
utils::{clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor}, Element,
|
utils::{clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor},
|
||||||
State, StateChanger,
|
Element, State, StateChanger,
|
||||||
};
|
};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
pub struct ElementWrapper<'a> {
|
struct ElementWrapper<'a>(&'a mut Element);
|
||||||
element: &'a mut Box<dyn Element>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ElementWrapper<'a> {
|
impl<'a> ElementWrapper<'a> {
|
||||||
pub fn new(element: &'a mut Box<dyn Element>) -> *mut c_void {
|
pub unsafe fn from_raw(ptr: *mut c_void) -> &'a mut Element {
|
||||||
Box::into_raw(Box::new(ElementWrapper { element })) as *mut c_void
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn from_raw(ptr: *mut c_void) -> &'a mut Box<dyn Element> {
|
|
||||||
assert!(!ptr.is_null(), "Pointer to ElementWrapper is null");
|
assert!(!ptr.is_null(), "Pointer to ElementWrapper is null");
|
||||||
let wrapper = ptr as *mut ElementWrapper;
|
let wrapper = ptr as *mut ElementWrapper;
|
||||||
(*wrapper).element
|
(*wrapper).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,13 +50,12 @@ pub extern "C" fn init_event(ptr: *mut c_void, state: *mut u32) {
|
|||||||
element.event(event, &StateChanger(state));
|
element.event(event, &StateChanger(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(data: &mut Box<dyn Element>) -> bool {
|
pub fn run(element: &mut Element) -> bool {
|
||||||
hide_cursor();
|
hide_cursor();
|
||||||
enable_raw_mode().unwrap();
|
enable_raw_mode().unwrap();
|
||||||
clear();
|
clear();
|
||||||
unsafe {
|
unsafe {
|
||||||
let element_ptr = ElementWrapper::new(data);
|
let c = c_run(&mut ElementWrapper(element) as *mut ElementWrapper as *mut c_void);
|
||||||
let c = c_run(element_ptr);
|
|
||||||
show_cursor();
|
show_cursor();
|
||||||
disable_raw_mode().unwrap();
|
disable_raw_mode().unwrap();
|
||||||
clear();
|
clear();
|
||||||
|
|||||||
+53
-9
@@ -8,7 +8,7 @@
|
|||||||
//! ## Example Usage
|
//! ## Example Usage
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use osui::{self, rsx, ui::*};
|
//! use osui::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! osui::app::run(&mut rsx! {
|
//! osui::app::run(&mut rsx! {
|
||||||
//! text { "Hello, World!" }
|
//! text { "Hello, World!" }
|
||||||
@@ -20,11 +20,29 @@
|
|||||||
//! - `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 crossterm::event::Event;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
/// The `prelude` module provides commonly used imports and macros to simplify the development
|
||||||
|
/// 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 crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
|
||||||
|
pub fn sleep(ms: u64) {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Value Enum
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Value<T: Copy + PartialEq> {
|
pub enum Value<T: Copy + PartialEq> {
|
||||||
Default(T),
|
Default(T),
|
||||||
@@ -52,35 +70,49 @@ impl<T: Copy + PartialEq + Default> Default for Value<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ElementComponent: Send + Sync {
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// ElementCore Traits
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
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);
|
||||||
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>>;
|
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>;
|
||||||
fn get_child(&mut self) -> Option<&mut Box<dyn Element>>;
|
fn get_child(&mut self) -> Option<&mut Element>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Element: std::fmt::Debug + Send + Sync + ElementComponent {
|
pub trait Component: ElementCore + std::fmt::Debug {
|
||||||
fn render(&self, state: State) -> String {
|
fn render(&self, state: State) -> String {
|
||||||
_ = state;
|
_ = state;
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, event: crossterm::event::Event, state: &StateChanger) {
|
fn event(&mut self, event: Event, state: &StateChanger) {
|
||||||
(_, _) = (event, state)
|
(_, _) = (event, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Handler<T>(pub Box<dyn FnMut(&T, crossterm::event::Event, &StateChanger) + Send>);
|
pub type Element = Box<dyn Component>;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Handler Struct
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub struct Handler<T>(pub Box<dyn FnMut(&T, Event, &StateChanger) + Send>);
|
||||||
|
|
||||||
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, crossterm::event::Event, &StateChanger) + 'static + Send,
|
F: FnMut(&T, Event, &StateChanger) + 'static + Send,
|
||||||
{
|
{
|
||||||
Handler(Box::new(handler_fn))
|
Handler(Box::new(handler_fn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// State Enum and StateChanger Struct
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Exit,
|
Exit,
|
||||||
@@ -100,6 +132,7 @@ impl State {
|
|||||||
_ => State::Custom((s - 4) as usize),
|
_ => State::Custom((s - 4) as usize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_u32(&self) -> u32 {
|
pub fn to_u32(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
State::Exit => 0,
|
State::Exit => 0,
|
||||||
@@ -119,16 +152,21 @@ impl StateChanger {
|
|||||||
(*self.0) = s.to_u32();
|
(*self.0) = s.to_u32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state(&self) -> State {
|
pub fn get_state(&self) -> State {
|
||||||
State::from_u32(unsafe { *self.0 })
|
State::from_u32(unsafe { *self.0 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Children Enum
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Children {
|
pub enum Children {
|
||||||
None,
|
None,
|
||||||
Text(String),
|
Text(String),
|
||||||
Children(Vec<Box<dyn Element>>, usize),
|
Children(Vec<Element>, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Children {
|
impl Default for Children {
|
||||||
@@ -144,4 +182,10 @@ impl Children {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_text(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Children::Text(text) => text.clone(),
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-125
@@ -1,15 +1,51 @@
|
|||||||
/// Macro for parsing parameters and children for an OSUI element.
|
/// **parse_rsx_param!** - A macro for parsing and setting parameters on elements, allowing for dynamic handling of element properties and children.
|
||||||
|
///
|
||||||
|
/// This macro enables flexible configuration of UI elements. It handles setting fields, adding children, invoking paths, and processing complex structures.
|
||||||
///
|
///
|
||||||
/// This macro updates the properties of an element based on provided key-value pairs,
|
/// ## Example
|
||||||
/// and adds child elements to the parent element.
|
/// ```rust
|
||||||
|
/// parse_rsx_param!(elem, title: "Hello", for (i in 0..5) { <child> }, text: "World");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Syntax
|
||||||
|
///
|
||||||
|
/// **Set property or field of an element:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, $($k:ident).+: $v:expr);
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// **Process multiple parameters:**
|
||||||
/// - `elem`: The element being updated.
|
/// ```rust
|
||||||
/// - Various key-value pairs, child elements, and text content. even for loops
|
/// parse_rsx_param!($elem, $($k:ident).+: $v:expr, $($rest:tt)*);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Handle path expression:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, $p:path);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Add nested children:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Add child element directly:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, {$ielem:expr} $($rest:tt)*);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Set element text:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, $text:expr);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Handle empty case:**
|
||||||
|
/// ```rust
|
||||||
|
/// parse_rsx_param!($elem, );
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! parse_rsx_param {
|
macro_rules! parse_rsx_param {
|
||||||
// Properties
|
($elem:expr, $($k:ident).+: $v:expr) => {
|
||||||
($elem:expr, $($k:ident).+: $v:expr) => {
|
|
||||||
$elem.$($k).+ = $v;
|
$elem.$($k).+ = $v;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,8 +54,7 @@ macro_rules! parse_rsx_param {
|
|||||||
osui::parse_rsx_param!($elem, $($rest)*);
|
osui::parse_rsx_param!($elem, $($rest)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// for completion purposes
|
($elem:expr, $p:path) => {
|
||||||
($elem:expr, $p:path) => {
|
|
||||||
$p;
|
$p;
|
||||||
};
|
};
|
||||||
($elem:expr, $($k:ident).+, $($rest:tt)*) => {
|
($elem:expr, $($k:ident).+, $($rest:tt)*) => {
|
||||||
@@ -40,8 +75,7 @@ macro_rules! parse_rsx_param {
|
|||||||
osui::parse_rsx_param!($elem, $($rest)*);
|
osui::parse_rsx_param!($elem, $($rest)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// For loop
|
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
|
||||||
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
|
|
||||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||||
for $($for)* {
|
for $($for)* {
|
||||||
@@ -51,8 +85,7 @@ macro_rules! parse_rsx_param {
|
|||||||
osui::parse_rsx_param!($elem, $($rest)*);
|
osui::parse_rsx_param!($elem, $($rest)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression
|
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
|
||||||
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
|
|
||||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||||
children.push({$ielem});
|
children.push({$ielem});
|
||||||
@@ -60,8 +93,7 @@ macro_rules! parse_rsx_param {
|
|||||||
osui::parse_rsx_param!($elem, $($rest)*);
|
osui::parse_rsx_param!($elem, $($rest)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Element
|
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
|
||||||
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
|
|
||||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||||
children.push(osui::rsx_elem!($pelem { $($inner)* }));
|
children.push(osui::rsx_elem!($pelem { $($inner)* }));
|
||||||
@@ -69,75 +101,55 @@ macro_rules! parse_rsx_param {
|
|||||||
osui::parse_rsx_param!($elem, $($rest)*)
|
osui::parse_rsx_param!($elem, $($rest)*)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Text
|
($elem:expr, $text:expr) => {
|
||||||
($elem:expr, $text:expr) => {
|
|
||||||
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))}
|
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))}
|
||||||
};
|
};
|
||||||
($elem:expr, $text:expr, $($inner:tt)*) => {
|
($elem:expr, $text:expr, $($inner:tt)*) => {
|
||||||
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))}
|
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Empty
|
($elem:expr, ) => {};
|
||||||
($elem:expr, ) => {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro for creating an OSUI element with parsed parameters.
|
/// **rsx_elem!** - A macro to instantiate a UI element and apply parameters to it.
|
||||||
|
///
|
||||||
|
/// This macro simplifies creating an element and applying a set of properties or children to it, enabling a declarative style of UI building.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// ## Example
|
||||||
|
/// ```rust
|
||||||
|
/// let element = rsx_elem!(ui::Button { text: "Click me" });
|
||||||
/// ```
|
/// ```
|
||||||
/// rsx! {
|
///
|
||||||
/// text { "Hello, World!" }
|
/// ## Syntax
|
||||||
/// div {
|
/// ```rust
|
||||||
/// button { y: 2, "click me" }
|
/// rsx_elem!($elem:path { $($inner:tt)* })
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// # For loops
|
|
||||||
/// ```
|
|
||||||
/// rsx! {
|
|
||||||
/// text { "Welcome!" }
|
|
||||||
/// for (i in 1..5) { // must be contained with ()
|
|
||||||
/// rsx_elem!{text { y: i, "Text: {i}" }}
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This macro provides a clean way to express OSUI elements, functioning like a div that can
|
|
||||||
/// contain multiple components.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! rsx_elem {
|
macro_rules! rsx_elem {
|
||||||
($elem:path { $($inner:tt)* }) => {{
|
($elem:path { $($inner:tt)* }) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut elem = $elem();
|
let mut elem = $elem();
|
||||||
$crate::parse_rsx_param!(elem, $($inner)*);
|
$crate::parse_rsx_param!(elem, $($inner)*);
|
||||||
elem as Box<dyn $crate::Element>
|
elem as $crate::Element
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
/// **rsx!** - A shorthand macro to create a `div` element and apply nested content to it.
|
||||||
macro_rules! rsx_elem_raw {
|
|
||||||
($elem:path { $($inner:tt)* }) => {{
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut elem = $elem();
|
|
||||||
$crate::parse_rsx_param!(elem, $($inner)*);
|
|
||||||
elem
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro for defining a structured representation of UI elements in OSUI.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// This macro is a simple wrapper for `rsx_elem!`, making it easy to create a `div` element and add children to it.
|
||||||
/// ```
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```rust
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// text { "Hello, World!" }
|
/// text { "Hello, World" }
|
||||||
/// div {
|
/// div { button {"Click me!"} }
|
||||||
/// button { y: 2, "click me" }
|
/// }
|
||||||
/// }
|
/// ```
|
||||||
/// }
|
///
|
||||||
|
/// ## Syntax
|
||||||
|
/// ```rust
|
||||||
|
/// rsx!($($inner:tt)*)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// This macro allows the creation of a root element that can contain other elements.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! rsx {
|
macro_rules! rsx {
|
||||||
($($inner:tt)*) => {{
|
($($inner:tt)*) => {{
|
||||||
@@ -145,29 +157,6 @@ macro_rules! rsx {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro for defining CSS styles in OSUI.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// css!(Style; color: Color::Red; background: "white");
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This macro creates a new style based on the provided properties, applying default values
|
|
||||||
/// for any unspecified fields.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! css {
|
|
||||||
(
|
|
||||||
$style:path;
|
|
||||||
$($inner:tt)*
|
|
||||||
) => {{
|
|
||||||
$style {
|
|
||||||
$($inner)*
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro for defining a `Value< T >`
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! val {
|
macro_rules! val {
|
||||||
($a:expr) => {
|
($a:expr) => {
|
||||||
@@ -175,21 +164,9 @@ macro_rules! val {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro for writing when requested to render
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// write!(self, clicked, "Hello, World!") // For custom styles, You need (color, background, font)
|
|
||||||
///
|
|
||||||
/// write!((self, state), "Hello, World!") // When you don't need a custom style, you use () and put self and the state of the render
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This macro returns a `String` depending on the state or the kind of style
|
|
||||||
/// When on "state" mode it returns the default style if the state is 0
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! write {
|
macro_rules! write {
|
||||||
// Default state styling
|
(($self:ident, $state:expr), $expr:expr) => {{
|
||||||
(($self:ident, $state:expr), $expr:expr) => {{
|
|
||||||
if $state == 0 {
|
if $state == 0 {
|
||||||
return format!(
|
return format!(
|
||||||
"{}{}{}{}\x1b[0m",
|
"{}{}{}{}\x1b[0m",
|
||||||
@@ -208,8 +185,7 @@ macro_rules! write {
|
|||||||
)
|
)
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Custom styles
|
($self:ident, $kind:ident, $expr:expr) => {{
|
||||||
($self:ident, $kind:ident, $expr:expr) => {{
|
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
format!(
|
format!(
|
||||||
"{}{}{}{}\x1b[0m",
|
"{}{}{}{}\x1b[0m",
|
||||||
@@ -227,31 +203,6 @@ macro_rules! write {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! execute_response {
|
|
||||||
($self:expr, $res:expr) => {
|
|
||||||
for response in $res.execute() {
|
|
||||||
match response {
|
|
||||||
EventResponse::UpdateElementById(id, elem) => {
|
|
||||||
for old in &mut $self.children {
|
|
||||||
if old.get_id() == id {
|
|
||||||
*old = elem.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EventResponse::UpdateSelf(elem) => {
|
|
||||||
if let Some(child) = $self.get_child() {
|
|
||||||
*child = elem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! run_handler {
|
macro_rules! run_handler {
|
||||||
($self:ident.$handler:ident ($renderer:expr, $event:expr)) => {
|
($self:ident.$handler:ident ($renderer:expr, $event:expr)) => {
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
use osui::{app::run, rsx, ui::*, Element};
|
use osui::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
while run(&mut app()) {}
|
while run(&mut app()) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app() -> Box<dyn Element> {
|
fn app() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
text { "Hello, World!" }
|
button { "This is a button!" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-14
@@ -1,18 +1,41 @@
|
|||||||
/// The `ui` module provides user interface components and utilities for building
|
/// The `ui` module defines the basic building blocks for creating a terminal-based UI framework.
|
||||||
/// a text-based user interface (TUI). It includes predefined styles and elements
|
/// This includes elements like `Text` and `Div`, which can be combined to create complex layouts
|
||||||
/// such as text, buttons, and containers.
|
/// 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).
|
||||||
|
/// - `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::{Children, Element, ElementComponent, Value};
|
use crate::prelude::*;
|
||||||
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.
|
||||||
|
///
|
||||||
|
/// It is used to render plain text within a UI component. The `Text` element does not interact
|
||||||
|
/// with events and only outputs the text when rendered.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// let text = Text::default();
|
||||||
|
/// let rendered_text = text.render(crate::State::Normal);
|
||||||
|
/// println!("{}", rendered_text);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Methods
|
||||||
|
/// - `render(state: crate::State) -> String`: Renders the text to a string, depending on the state.
|
||||||
|
/// If the element contains text as a child, it returns that text. Otherwise, it returns an empty string.
|
||||||
#[element]
|
#[element]
|
||||||
#[elem_fn]
|
#[elem_fn]
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Text {}
|
pub struct Text {}
|
||||||
|
|
||||||
impl Element for Text<'_> {
|
impl Component for Text<'_> {
|
||||||
fn render(&self, _: crate::State) -> String {
|
fn render(&self, _: crate::State) -> String {
|
||||||
if let Children::Text(text) = &self.children {
|
if let Children::Text(text) = &self.children {
|
||||||
text.clone()
|
text.clone()
|
||||||
@@ -22,13 +45,33 @@ impl Element for Text<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `Div` element represents a container for other UI elements. It can hold child components
|
||||||
|
/// and render them in a specific layout. The `Div` element also handles keyboard navigation through
|
||||||
|
/// its children.
|
||||||
|
///
|
||||||
|
/// The `Div` element supports rendering a list of children and allows interactive navigation using
|
||||||
|
/// arrow keys (Up, Down, Left, Right). It renders the selected child element differently based on the state.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// let div = Div::default();
|
||||||
|
/// let rendered_div = div.render(crate::State::Normal);
|
||||||
|
/// println!("{}", rendered_div);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Methods
|
||||||
|
/// - `render(state: crate::State) -> String`: Renders the `Div` and its child elements to a string.
|
||||||
|
/// The child elements are rendered according to the current selection (`child` index).
|
||||||
|
/// - `event(event: Event, state: &crate::StateChanger)`: Handles keyboard events for
|
||||||
|
/// navigation through child elements. Arrow keys (Up, Down, Left, Right) change the currently selected child.
|
||||||
|
|
||||||
#[element]
|
#[element]
|
||||||
#[elem_fn]
|
#[elem_fn]
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Div {}
|
pub struct Div {}
|
||||||
|
|
||||||
impl Element for Div<'_> {
|
impl Component for Div<'_> {
|
||||||
fn render(&self, state: crate::State) -> String {
|
fn render(&self, state: State) -> String {
|
||||||
let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value());
|
let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value());
|
||||||
|
|
||||||
if let Children::Children(children, child) = &self.children {
|
if let Children::Children(children, child) = &self.children {
|
||||||
@@ -49,36 +92,41 @@ impl Element for Div<'_> {
|
|||||||
frame.join("\n")
|
frame.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, event: crossterm::event::Event, state: &crate::StateChanger) {
|
fn event(&mut self, event: Event, state: &crate::StateChanger) {
|
||||||
match event {
|
match event {
|
||||||
crossterm::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 {
|
||||||
*child = match key.code {
|
*child = match key.code {
|
||||||
crossterm::event::KeyCode::Up => crate::utils::closest_component(
|
KeyCode::Up => crate::utils::closest_component(
|
||||||
children,
|
children,
|
||||||
*child,
|
*child,
|
||||||
crate::utils::Direction::Up,
|
crate::utils::Direction::Up,
|
||||||
),
|
),
|
||||||
|
|
||||||
crossterm::event::KeyCode::Down => crate::utils::closest_component(
|
KeyCode::Down => crate::utils::closest_component(
|
||||||
children,
|
children,
|
||||||
*child,
|
*child,
|
||||||
crate::utils::Direction::Down,
|
crate::utils::Direction::Down,
|
||||||
),
|
),
|
||||||
|
|
||||||
crossterm::event::KeyCode::Left => crate::utils::closest_component(
|
KeyCode::Left => crate::utils::closest_component(
|
||||||
children,
|
children,
|
||||||
*child,
|
*child,
|
||||||
crate::utils::Direction::Left,
|
crate::utils::Direction::Left,
|
||||||
),
|
),
|
||||||
|
|
||||||
crossterm::event::KeyCode::Right => crate::utils::closest_component(
|
KeyCode::Right => crate::utils::closest_component(
|
||||||
children,
|
children,
|
||||||
*child,
|
*child,
|
||||||
crate::utils::Direction::Right,
|
crate::utils::Direction::Right,
|
||||||
),
|
),
|
||||||
|
|
||||||
_ => *child,
|
_ => {
|
||||||
|
if let Some(c) = children.get_mut(*child) {
|
||||||
|
c.event(event, state);
|
||||||
|
}
|
||||||
|
*child
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,3 +138,31 @@ impl Element for Div<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[element]
|
||||||
|
#[elem_fn]
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Button {}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, event: Event, state: &StateChanger) {
|
||||||
|
match event {
|
||||||
|
Event::Key(key) => {
|
||||||
|
if key.code == KeyCode::Enter {
|
||||||
|
let prev_state = state.get_state();
|
||||||
|
state.set_state(State::Custom(0));
|
||||||
|
sleep(100);
|
||||||
|
state.set_state(prev_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+27
-15
@@ -1,4 +1,17 @@
|
|||||||
/// Represents different font styles that can be applied to TUI elements.
|
//! 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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Font {
|
pub enum Font {
|
||||||
None,
|
None,
|
||||||
@@ -7,11 +20,10 @@ pub enum Font {
|
|||||||
Italic,
|
Italic,
|
||||||
Reverse,
|
Reverse,
|
||||||
Strike,
|
Strike,
|
||||||
Mul(Vec<Font>), // Allows combining multiple font styles.
|
Mul(Vec<Font>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
/// Converts the font style to an ANSI sequence.
|
|
||||||
pub fn ansi(&self) -> String {
|
pub fn ansi(&self) -> String {
|
||||||
String::from(match self {
|
String::from(match self {
|
||||||
Font::None => "",
|
Font::None => "",
|
||||||
@@ -30,8 +42,8 @@ impl Font {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the prioritized font between `self` and `secondary`, favoring `secondary`
|
|
||||||
/// if it is not `None`.
|
|
||||||
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
|
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
|
||||||
if secondary == &Font::None {
|
if secondary == &Font::None {
|
||||||
self
|
self
|
||||||
@@ -41,12 +53,12 @@ impl Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents color options for elements, supporting both named colors and RGB values.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
None,
|
None,
|
||||||
/// Red, Green, blue
|
|
||||||
Rgb(u8, u8, u8), // RGB color representation.
|
Rgb(u8, u8, u8),
|
||||||
Hex(String),
|
Hex(String),
|
||||||
Black,
|
Black,
|
||||||
Red,
|
Red,
|
||||||
@@ -59,7 +71,7 @@ pub enum Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
/// Converts the color to an ANSI foreground sequence.
|
|
||||||
pub fn ansi(&self) -> String {
|
pub fn ansi(&self) -> String {
|
||||||
String::from(match self {
|
String::from(match self {
|
||||||
Color::None => "",
|
Color::None => "",
|
||||||
@@ -81,7 +93,7 @@ impl Color {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the color to an ANSI background sequence.
|
|
||||||
pub fn ansi_bg(&self) -> String {
|
pub fn ansi_bg(&self) -> String {
|
||||||
String::from(match self {
|
String::from(match self {
|
||||||
Color::None => "",
|
Color::None => "",
|
||||||
@@ -103,8 +115,8 @@ impl Color {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the prioritized color between `self` and `secondary`, favoring `secondary`
|
|
||||||
/// if it is not `None`.
|
|
||||||
pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color {
|
pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color {
|
||||||
if secondary == &Color::None {
|
if secondary == &Color::None {
|
||||||
self
|
self
|
||||||
@@ -129,7 +141,7 @@ 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('#');
|
||||||
|
|
||||||
// Expand 3-character hex codes to 6 characters
|
|
||||||
let hex = if hex.len() == 3 {
|
let hex = if hex.len() == 3 {
|
||||||
format!(
|
format!(
|
||||||
"{}{}{}{}{}{}",
|
"{}{}{}{}{}{}",
|
||||||
@@ -140,10 +152,10 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
|
|||||||
} else if hex.len() == 6 {
|
} else if hex.len() == 6 {
|
||||||
hex.to_string()
|
hex.to_string()
|
||||||
} else {
|
} else {
|
||||||
return (255, 255, 255); // Return white for invalid hex length
|
return (255, 255, 255);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse each pair of characters as u8
|
|
||||||
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);
|
||||||
|
|||||||
+14
-2
@@ -1,3 +1,15 @@
|
|||||||
|
/// The `utils` module contains helper functions and utilities that assist in rendering and
|
||||||
|
/// managing UI state. These functions are used by UI elements like `Div` and `Text` for common
|
||||||
|
/// tasks such as frame creation, event handling, and component selection.
|
||||||
|
///
|
||||||
|
/// # 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 crate::{Element, Value};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -78,7 +90,7 @@ pub fn render_to_frame(
|
|||||||
state: crate::State,
|
state: crate::State,
|
||||||
width: usize,
|
width: usize,
|
||||||
frame: &mut Vec<String>,
|
frame: &mut Vec<String>,
|
||||||
element: &Box<dyn Element>,
|
element: &Element,
|
||||||
) {
|
) {
|
||||||
let data = element.get_data();
|
let data = element.get_data();
|
||||||
for (i, line) in element.render(state).split('\n').enumerate() {
|
for (i, line) in element.render(state).split('\n').enumerate() {
|
||||||
@@ -130,7 +142,7 @@ pub enum Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn closest_component(
|
pub fn closest_component(
|
||||||
components: &[Box<dyn Element>],
|
components: &[Element],
|
||||||
current_index: usize,
|
current_index: usize,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
|
|||||||
Reference in New Issue
Block a user