updated a few things
This commit is contained in:
@@ -19,3 +19,4 @@ crossterm = "0.28.1"
|
||||
regex = "*"
|
||||
lazy_static = "1.4"
|
||||
paste = "1.0.15"
|
||||
osui-element = { path = "./osui-element" }
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "osui-element"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
path = "./lib.rs"
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
proc-macro2 = "*"
|
||||
@@ -0,0 +1,125 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, parse_macro_input, DeriveInput, Fields, LifetimeParam};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &ast.ident;
|
||||
|
||||
match &mut ast.data {
|
||||
syn::Data::Struct(ref mut struct_data) => {
|
||||
if ast.generics.lifetimes().count() == 0 {
|
||||
ast.generics
|
||||
.params
|
||||
.push(syn::GenericParam::Lifetime(LifetimeParam::new(
|
||||
syn::Lifetime::new("'a", proc_macro2::Span::call_site()),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Fields::Named(fields) = &mut struct_data.fields {
|
||||
// Example fields, modify according to your needs
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub x: Value<usize> })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub y: usize })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub width: Value<usize> })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub height: Value<usize> })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub children: Children })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub id: &'a str })
|
||||
.unwrap(),
|
||||
);
|
||||
fields.named.push(
|
||||
syn::Field::parse_named
|
||||
.parse2(quote! { pub class: &'a str })
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => panic!("`element` can only be used with structs"),
|
||||
}
|
||||
|
||||
let expanded_impl = quote! {
|
||||
impl<'a> ElementComponent for #struct_name<'a> {
|
||||
fn get_data(&self) -> (Value<usize>, usize, String) {
|
||||
(self.x, self.y, self.id.to_string())
|
||||
}
|
||||
fn update_data(&mut self, width: usize, height: usize) {
|
||||
self.width.try_set_value(width);
|
||||
self.height.try_set_value(height);
|
||||
if let Children::Children(children, _) = &mut self.children {
|
||||
for child in children {
|
||||
child.update_data(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>> {
|
||||
if let Children::Children(children, _) = &mut self.children {
|
||||
for elem in children {
|
||||
if elem.get_data().2 == id {
|
||||
return Some(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
|
||||
if let Children::Children(children, child) = &mut self.children {
|
||||
children.get_mut(*child)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
#ast
|
||||
#expanded_impl
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn elem_fn(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &ast.ident;
|
||||
let func_name = syn::Ident::new(
|
||||
&struct_name.to_string().to_lowercase(),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
|
||||
let elem_fn = quote! {
|
||||
pub fn #func_name<'a>() -> Box<#struct_name<'a>> {
|
||||
Box::new(#struct_name::default())
|
||||
}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
#ast
|
||||
#elem_fn
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
@@ -11,6 +11,7 @@ typedef struct {
|
||||
|
||||
extern void render(void* element, uint32_t state);
|
||||
extern void event(void* element, uint32_t* state);
|
||||
extern void init_event(void* element, uint32_t* state);
|
||||
|
||||
void sleep_ms(long milliseconds) {
|
||||
struct timespec ts;
|
||||
@@ -22,6 +23,7 @@ void sleep_ms(long milliseconds) {
|
||||
void *event_checker(void *args)
|
||||
{
|
||||
EventArgs* event_args = (EventArgs*)args;
|
||||
init_event(event_args->element, event_args->state);
|
||||
while (1) {
|
||||
if (event_args->state == 0) { return NULL; }
|
||||
event(event_args->element, event_args->state);
|
||||
@@ -32,7 +34,7 @@ void *event_checker(void *args)
|
||||
boolean_t c_run(void* element)
|
||||
{
|
||||
pthread_t thread;
|
||||
uint32_t state = 2;
|
||||
uint32_t state = 3;
|
||||
EventArgs event_args = { .element = element, .state = &state };
|
||||
|
||||
if (pthread_create(&thread, NULL, event_checker, &event_args) != 0) {
|
||||
@@ -49,6 +51,6 @@ boolean_t c_run(void* element)
|
||||
return 1;
|
||||
}
|
||||
render(element, state);
|
||||
sleep_ms(200);
|
||||
sleep_ms(20);
|
||||
}
|
||||
}
|
||||
+25
-4
@@ -1,6 +1,9 @@
|
||||
use crossterm::terminal::enable_raw_mode;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
|
||||
use crate::{clear, create_frame, flush, get_term_size, render_to_frame, Element, State, StateChanger, Value};
|
||||
use crate::{
|
||||
utils::{clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor}, Element,
|
||||
State, StateChanger,
|
||||
};
|
||||
use std::ffi::c_void;
|
||||
|
||||
pub struct ElementWrapper<'a> {
|
||||
@@ -27,7 +30,7 @@ extern "C" {
|
||||
pub extern "C" fn render(ptr: *mut c_void, state: u32) {
|
||||
let element = unsafe { ElementWrapper::from_raw(ptr) };
|
||||
let (width, height) = get_term_size();
|
||||
let mut frame: Vec<String> = create_frame(Value::new(width), Value::new(height));
|
||||
let mut frame: Vec<String> = create_frame(width, height);
|
||||
render_to_frame(State::from_u32(state), width, &mut frame, element);
|
||||
clear();
|
||||
print!("{}", frame.join(""));
|
||||
@@ -38,13 +41,31 @@ pub extern "C" fn render(ptr: *mut c_void, state: u32) {
|
||||
pub extern "C" fn event(ptr: *mut c_void, state: *mut u32) {
|
||||
let event = crossterm::event::read().unwrap();
|
||||
let element = unsafe { ElementWrapper::from_raw(ptr) };
|
||||
if let crossterm::event::Event::Resize(width, height) = event {
|
||||
element.update_data(width as usize, height as usize);
|
||||
}
|
||||
element.event(event, &StateChanger(state));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn init_event(ptr: *mut c_void, state: *mut u32) {
|
||||
let event = crossterm::event::Event::FocusGained;
|
||||
let element = unsafe { ElementWrapper::from_raw(ptr) };
|
||||
let (width, height) = get_term_size();
|
||||
element.update_data(width, height);
|
||||
element.event(event, &StateChanger(state));
|
||||
}
|
||||
|
||||
pub fn run(data: &mut Box<dyn Element>) -> bool {
|
||||
hide_cursor();
|
||||
enable_raw_mode().unwrap();
|
||||
clear();
|
||||
unsafe {
|
||||
let element_ptr = ElementWrapper::new(data);
|
||||
return c_run(element_ptr);
|
||||
let c = c_run(element_ptr);
|
||||
show_cursor();
|
||||
disable_raw_mode().unwrap();
|
||||
clear();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
+38
-38
@@ -16,20 +16,15 @@
|
||||
//! ```
|
||||
//!
|
||||
//! ## Modules
|
||||
//! - `utils` - Utility functions for common TUI tasks such as clearing the screen.
|
||||
//! - `app` - The main function for rendering.
|
||||
//! - `ui` - Contains all user interface components, enabling rich CLI experiences.
|
||||
//! - `utils` - Utility functions for common TUI tasks such as clearing the screen.
|
||||
|
||||
pub mod app;
|
||||
pub mod macros;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
pub use utils::*;
|
||||
pub mod app;
|
||||
|
||||
/// Enum representing the wether it's the default or custom.
|
||||
///
|
||||
/// - `Default(usize)` - Uses a default value.
|
||||
/// - `Custom(usize)` - Allows specifying a custom value.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Value<T: Copy + PartialEq> {
|
||||
Default(T),
|
||||
@@ -37,20 +32,6 @@ pub enum Value<T: Copy + PartialEq> {
|
||||
}
|
||||
|
||||
impl<T: Copy + PartialEq> Value<T> {
|
||||
/// Creates a new Value.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Value::Custom(usize)
|
||||
pub fn new(value: T) -> Value<T> {
|
||||
Value::Custom(value)
|
||||
}
|
||||
|
||||
/// Retrieves the value.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The value by the type T.
|
||||
pub fn get_value(&self) -> T {
|
||||
match self {
|
||||
Value::Custom(s) => *s,
|
||||
@@ -58,13 +39,6 @@ impl<T: Copy + PartialEq> Value<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to set the value if it is currently set to `Default`.
|
||||
///
|
||||
/// If the size is `Custom`, this function will not modify it.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The value to change to.
|
||||
pub fn try_set_value(&mut self, value: T) {
|
||||
if let Value::Default(_) = *self {
|
||||
*self = Value::Default(value);
|
||||
@@ -72,16 +46,20 @@ impl<T: Copy + PartialEq> Value<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for defining UI elements in OSUI.
|
||||
///
|
||||
/// Elements must implement methods for updating and rendering data.
|
||||
pub trait Element: Send + Sync {
|
||||
impl<T: Copy + PartialEq + Default> Default for Value<T> {
|
||||
fn default() -> Self {
|
||||
Self::Default(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementComponent: Send + Sync {
|
||||
fn get_data(&self) -> (Value<usize>, usize, String);
|
||||
|
||||
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_child(&mut self) -> Option<&mut Box<dyn Element>>;
|
||||
}
|
||||
|
||||
pub trait Element: std::fmt::Debug + Send + Sync + ElementComponent {
|
||||
fn render(&self, state: State) -> String {
|
||||
_ = state;
|
||||
String::new()
|
||||
@@ -92,18 +70,18 @@ pub trait Element: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Handler<T>(pub Box<dyn FnMut(&T, &StateChanger, crossterm::event::Event) + Send>);
|
||||
pub struct Handler<T>(pub Box<dyn FnMut(&T, crossterm::event::Event, &StateChanger) + Send>);
|
||||
|
||||
impl<T> Handler<T> {
|
||||
pub fn new<F>(handler_fn: F) -> Handler<T>
|
||||
where
|
||||
F: FnMut(&T, &StateChanger, crossterm::event::Event) + 'static + Send,
|
||||
F: FnMut(&T, crossterm::event::Event, &StateChanger) + 'static + Send,
|
||||
{
|
||||
Handler(Box::new(handler_fn))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum State {
|
||||
Exit,
|
||||
Rebuild,
|
||||
@@ -145,3 +123,25 @@ impl StateChanger {
|
||||
State::from_u32(unsafe { *self.0 })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Children {
|
||||
None,
|
||||
Text(String),
|
||||
Children(Vec<Box<dyn Element>>, usize),
|
||||
}
|
||||
|
||||
impl Default for Children {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Children {
|
||||
pub fn is_none(&self) -> bool {
|
||||
match self {
|
||||
Children::None => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-199
@@ -1,180 +1,3 @@
|
||||
//! # Macros Module
|
||||
//!
|
||||
//! This module defines various macros for creating and manipulating OSUI elements.
|
||||
//! The macros provide a clean and concise syntax for defining UI components and
|
||||
//! handling their parameters, commands, and rendering.
|
||||
|
||||
/// Macro for defining an OSUI `Element`.
|
||||
///
|
||||
/// This macro generates a new element type with specified parameters and default values.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// element! {
|
||||
/// MyElem {}
|
||||
/// defaults {}
|
||||
/// // functions (render, update)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `name`: The name of the element type being defined.
|
||||
/// - `inner`: Additional fields or methods for the element.
|
||||
/// - `defaults`: Default values for the element's properties.
|
||||
/// - `functions`: Functions to implement for the element (e.g., rendering, updating).
|
||||
#[macro_export]
|
||||
macro_rules! element {
|
||||
(
|
||||
$(#[$meta_style:meta])*
|
||||
style $style:ident {
|
||||
$($sn:ident: $st:ty),*$(,)?
|
||||
}
|
||||
$(#[$meta:meta])*
|
||||
$name:ident {
|
||||
$( $inner:tt )*
|
||||
}
|
||||
defaults {$( $defaults:tt )*}
|
||||
$( $functions:tt )*
|
||||
) => {
|
||||
$(#[$meta_style])*
|
||||
#[derive(Clone)]
|
||||
pub struct $style {
|
||||
pub color: Color,
|
||||
pub background: Color,
|
||||
pub font: Font,
|
||||
pub hover_color: Color,
|
||||
pub hover_background: Color,
|
||||
pub hover_font: Font,
|
||||
$(pub $sn: $st),*
|
||||
}
|
||||
|
||||
impl Default for $style {
|
||||
fn default() -> $style {
|
||||
$style {
|
||||
color: Color::default(),
|
||||
background: Color::default(),
|
||||
font: Font::default(),
|
||||
hover_color: Color::default(),
|
||||
hover_background: Color::default(),
|
||||
hover_font: Font::default(),
|
||||
$($sn: <$st>::default()),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element! {
|
||||
$(#[$meta])*
|
||||
$name {
|
||||
pub style: $style,
|
||||
$( $inner )*
|
||||
}
|
||||
defaults {
|
||||
style: $style::default(),
|
||||
$($defaults)*
|
||||
}
|
||||
$( $functions )*
|
||||
}
|
||||
};
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$name:ident {
|
||||
$( $inner:tt )*
|
||||
}
|
||||
defaults {$( $defaults:tt )*}
|
||||
$( $functions:tt )*
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
pub struct $name<'a> {
|
||||
pub x: $crate::Value<usize>,
|
||||
pub y: usize,
|
||||
pub width: $crate::Value<usize>,
|
||||
pub height: $crate::Value<usize>,
|
||||
pub children: Vec<Box<dyn $crate::Element>>,
|
||||
pub child: usize,
|
||||
pub text: String,
|
||||
pub id: &'a str,
|
||||
$( $inner )*
|
||||
}
|
||||
|
||||
impl $crate::Element for $name<'_> {
|
||||
fn get_data(&self) -> ($crate::Value<usize>, usize, String) {
|
||||
(self.x, self.y, self.id.to_string())
|
||||
}
|
||||
|
||||
fn update_data(&mut self, width: usize, height: usize) {
|
||||
self.width.try_set_value(width);
|
||||
self.height.try_set_value(height);
|
||||
for child in &mut self.children {
|
||||
child.update_data(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn $crate::Element>> {
|
||||
for elem in &mut self.children {
|
||||
if elem.get_data().2 == id {
|
||||
return Some(elem);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
$( $functions )*
|
||||
}
|
||||
|
||||
impl<'a> $name<'_> {
|
||||
/// Creates a new instance of the element with default values.
|
||||
pub fn new() -> $name<'a> {
|
||||
$name {
|
||||
children: Vec::new(),
|
||||
x: $crate::Value::Default(0),
|
||||
y: 0,
|
||||
width: $crate::Value::Default(0),
|
||||
height: $crate::Value::Default(0),
|
||||
child: 0,
|
||||
text: String::new(),
|
||||
id: "",
|
||||
$( $defaults )*
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a mutable reference to the current child element, if any.
|
||||
///
|
||||
/// # Returns
|
||||
/// An `Option` containing a mutable reference to the child `Element` or `None`.
|
||||
pub fn get_child(&mut self) -> Option<&mut Box<dyn $crate::Element>> {
|
||||
self.children.get_mut(self.child)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! def_ {
|
||||
($name:ident { $($k:ident : $v:expr),* $(,)? }) => {
|
||||
$name {
|
||||
$(
|
||||
$k : std::sync::Mutex::new($v)
|
||||
),*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for creating a command response from a list of commands.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// command!(Update(1), Render(2));
|
||||
/// ```
|
||||
///
|
||||
/// # Parameters
|
||||
/// - A list of commands to be included in the `EventResponse::CommandList`.
|
||||
#[macro_export]
|
||||
macro_rules! command {
|
||||
($($cmd:expr),*) => {
|
||||
EventResponse::CommandList(vec![$($cmd),*])
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for parsing parameters and children for an OSUI element.
|
||||
///
|
||||
/// This macro updates the properties of an element based on provided key-value pairs,
|
||||
@@ -219,30 +42,39 @@ macro_rules! parse_rsx_param {
|
||||
|
||||
// For loop
|
||||
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
|
||||
for $($for)* {
|
||||
$elem.children.push({$($inner)*})
|
||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||
for $($for)* {
|
||||
children.push({$($inner)*})
|
||||
}
|
||||
}
|
||||
osui::parse_rsx_param!($elem, $($rest)*);
|
||||
};
|
||||
|
||||
// Expression
|
||||
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
|
||||
$elem.children.push($ielem);
|
||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||
children.push({$ielem});
|
||||
}
|
||||
osui::parse_rsx_param!($elem, $($rest)*);
|
||||
};
|
||||
|
||||
// Element
|
||||
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
|
||||
$elem.children.push(osui::rsx_elem!($pelem { $($inner)* }));
|
||||
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
|
||||
if let $crate::Children::Children(children, _) = &mut $elem.children {
|
||||
children.push(osui::rsx_elem!($pelem { $($inner)* }));
|
||||
}
|
||||
osui::parse_rsx_param!($elem, $($rest)*)
|
||||
};
|
||||
|
||||
// Text
|
||||
($elem:expr, $text:expr) => {
|
||||
$elem.text = format!($text);
|
||||
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))}
|
||||
};
|
||||
($elem:expr, $text:expr, $($inner:tt)*) => {
|
||||
$elem.text = format!($text, $($inner)*);
|
||||
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))}
|
||||
};
|
||||
|
||||
// Empty
|
||||
@@ -309,7 +141,7 @@ macro_rules! rsx_elem_raw {
|
||||
#[macro_export]
|
||||
macro_rules! rsx {
|
||||
($($inner:tt)*) => {{
|
||||
$crate::rsx_elem_raw!( $crate::ui::div { $($inner)* } )
|
||||
$crate::rsx_elem!( $crate::ui::div { $($inner)* } )
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -335,21 +167,6 @@ macro_rules! css {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Macro for defining a `Arc<Mutex< T >>`
|
||||
#[macro_export]
|
||||
macro_rules! mux {
|
||||
($a:expr) => {
|
||||
std::sync::Mutex::new($a)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! arc {
|
||||
($a:expr) => {
|
||||
std::sync::Arc::new(std::sync::Mutex::new($a))
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for defining a `Value< T >`
|
||||
#[macro_export]
|
||||
macro_rules! val {
|
||||
|
||||
+8
-2
@@ -1,5 +1,11 @@
|
||||
use osui::{app::run, rsx_elem, ui::text};
|
||||
use osui::{app::run, rsx, ui::*, Element};
|
||||
|
||||
fn main() {
|
||||
run(&mut rsx_elem! { text { "Hello, World!" } });
|
||||
while run(&mut app()) {}
|
||||
}
|
||||
|
||||
fn app() -> Box<dyn Element> {
|
||||
rsx! {
|
||||
text { "Hello, World!" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
use crate::{element, State};
|
||||
|
||||
element! {
|
||||
Text {}
|
||||
defaults {}
|
||||
fn render(&self, _state: State) -> String {
|
||||
self.text.clone()
|
||||
}
|
||||
}
|
||||
+84
-5
@@ -1,13 +1,92 @@
|
||||
/// The `ui` module provides user interface components and utilities for building
|
||||
/// a text-based user interface (TUI). It includes predefined styles and elements
|
||||
/// such as text, buttons, and containers.
|
||||
|
||||
pub mod styles;
|
||||
pub use styles::*;
|
||||
pub mod elements;
|
||||
pub use elements::*;
|
||||
|
||||
use crate::{Children, Element, ElementComponent, Value};
|
||||
use osui_element::{elem_fn, element};
|
||||
|
||||
pub fn text<'a>() -> Box<Text<'a>> {
|
||||
Box::new(Text::new())
|
||||
#[element]
|
||||
#[elem_fn]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Text {}
|
||||
|
||||
impl Element for Text<'_> {
|
||||
fn render(&self, _: crate::State) -> String {
|
||||
if let Children::Text(text) = &self.children {
|
||||
text.clone()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[element]
|
||||
#[elem_fn]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Div {}
|
||||
|
||||
impl Element for Div<'_> {
|
||||
fn render(&self, state: crate::State) -> String {
|
||||
let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value());
|
||||
|
||||
if let Children::Children(children, child) = &self.children {
|
||||
for (i, elem) in children.iter().enumerate() {
|
||||
if i == *child {
|
||||
crate::utils::render_to_frame(state, self.width.get_value(), &mut frame, elem);
|
||||
} else {
|
||||
crate::utils::render_to_frame(
|
||||
crate::State::Normal,
|
||||
self.width.get_value(),
|
||||
&mut frame,
|
||||
elem,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame.join("\n")
|
||||
}
|
||||
|
||||
fn event(&mut self, event: crossterm::event::Event, state: &crate::StateChanger) {
|
||||
match event {
|
||||
crossterm::event::Event::Key(key) => {
|
||||
if let Children::Children(children, child) = &mut self.children {
|
||||
*child = match key.code {
|
||||
crossterm::event::KeyCode::Up => crate::utils::closest_component(
|
||||
children,
|
||||
*child,
|
||||
crate::utils::Direction::Up,
|
||||
),
|
||||
|
||||
crossterm::event::KeyCode::Down => crate::utils::closest_component(
|
||||
children,
|
||||
*child,
|
||||
crate::utils::Direction::Down,
|
||||
),
|
||||
|
||||
crossterm::event::KeyCode::Left => crate::utils::closest_component(
|
||||
children,
|
||||
*child,
|
||||
crate::utils::Direction::Left,
|
||||
),
|
||||
|
||||
crossterm::event::KeyCode::Right => crate::utils::closest_component(
|
||||
children,
|
||||
*child,
|
||||
crate::utils::Direction::Right,
|
||||
),
|
||||
|
||||
_ => *child,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(child) = self.get_child() {
|
||||
child.event(event, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-16
@@ -78,7 +78,7 @@ pub fn render_to_frame(
|
||||
state: crate::State,
|
||||
width: usize,
|
||||
frame: &mut Vec<String>,
|
||||
element: &mut Box<dyn Element>,
|
||||
element: &Box<dyn Element>,
|
||||
) {
|
||||
let data = element.get_data();
|
||||
for (i, line) in element.render(state).split('\n').enumerate() {
|
||||
@@ -130,7 +130,7 @@ pub enum Direction {
|
||||
}
|
||||
|
||||
pub fn closest_component(
|
||||
components: &[&mut dyn Element],
|
||||
components: &[Box<dyn Element>],
|
||||
current_index: usize,
|
||||
direction: Direction,
|
||||
) -> usize {
|
||||
@@ -144,39 +144,32 @@ pub fn closest_component(
|
||||
match direction {
|
||||
Direction::Left => match comp.0 {
|
||||
Value::Custom(_) => {
|
||||
comp.0.get_value() < current.0.get_value()
|
||||
&& comp.1 == current.1
|
||||
comp.0.get_value() < current.0.get_value() && comp.1 == current.1
|
||||
}
|
||||
Value::Default(_) => *i < current_index,
|
||||
}, // Left
|
||||
Direction::Right => match comp.0 {
|
||||
Value::Custom(_) => {
|
||||
comp.0.get_value() > current.0.get_value()
|
||||
&& comp.1 == current.1
|
||||
comp.0.get_value() > current.0.get_value() && comp.1 == current.1
|
||||
}
|
||||
Value::Default(_) => *i > current_index,
|
||||
}, // Right
|
||||
Direction::Up => {
|
||||
comp.1 < current.1
|
||||
&& comp.0.get_value() == current.0.get_value()
|
||||
} // Up
|
||||
Direction::Up => comp.1 < current.1 && comp.0.get_value() == current.0.get_value(), // Up
|
||||
Direction::Down => {
|
||||
comp.1 > current.1
|
||||
&& comp.0.get_value() == current.0.get_value()
|
||||
comp.1 > current.1 && comp.0.get_value() == current.0.get_value()
|
||||
} // Down
|
||||
}
|
||||
})
|
||||
.min_by_key(|(_, comp_)| {
|
||||
let comp = comp_.get_data();
|
||||
current.0.get_value().abs_diff(comp.0.get_value())
|
||||
+ current.1.abs_diff(comp.1)
|
||||
current.0.get_value().abs_diff(comp.0.get_value()) + current.1.abs_diff(comp.1)
|
||||
}) // Find the closest component
|
||||
.map(|(index, _)| index) // Return the index of the closest component
|
||||
.unwrap_or(current_index) // If no component is found, return the current index
|
||||
}
|
||||
|
||||
pub fn create_frame(width: Value<usize>, height: Value<usize>) -> Vec<String> {
|
||||
vec![" ".repeat(width.get_value()); height.get_value()]
|
||||
pub fn create_frame(width: usize, height: usize) -> Vec<String> {
|
||||
vec![" ".repeat(width); height]
|
||||
}
|
||||
|
||||
pub fn get_term_size() -> (usize, usize) {
|
||||
|
||||
Reference in New Issue
Block a user