some refractoring

This commit is contained in:
2024-11-25 03:10:28 +01:00
parent 33843688d8
commit f8fd990c67
9 changed files with 370 additions and 343 deletions
+2 -8
View File
@@ -4,24 +4,18 @@
#include <pthread.h>
#include <time.h>
extern boolean_t render_loop(void* args);
extern void *event_loop(void* args);
extern void *cmd_loop(void* args);
boolean_t c_run(void* args)
{
pthread_t event_thread;
pthread_t cmd_thread;
if (pthread_create(&event_thread, NULL, event_loop, args) != 0)
{
perror("Failed to create thread");
}
if (pthread_create(&cmd_thread, NULL, cmd_loop, args) != 0)
{
perror("Failed to create thread");
}
return render_loop(args);
cmd_loop(args);
return FALSE;
}
+3 -17
View File
@@ -7,7 +7,7 @@ use crate::prelude::*;
use crate::utils::{clear, flush, get_term_size, hide_cursor, show_cursor, Frame};
use std::{ffi::c_void, ptr::null, sync::mpsc};
pub struct LArgs<'a> {
struct LArgs<'a> {
pub element: &'a mut Element,
// for sending commands
@@ -55,18 +55,6 @@ pub fn run(element: &mut Element) -> bool {
}
}
#[no_mangle]
extern "C" fn render_loop(ptr: *mut c_void) -> bool {
if ptr.is_null() {
return false;
}
let args = unsafe { &mut *(ptr as *mut LArgs) };
while args.running {
sleep(1000);
}
false
}
#[no_mangle]
extern "C" fn event_loop(ptr: *mut c_void) -> *const c_void {
if ptr.is_null() {
@@ -87,10 +75,7 @@ extern "C" fn event_loop(ptr: *mut c_void) -> *const c_void {
);
document.render();
while args.running {
if ptr.is_null() {
break;
}
while args.running || !ptr.is_null() {
event(args.element, crossterm::event::read().unwrap(), &document);
document.render();
}
@@ -102,6 +87,7 @@ extern "C" fn cmd_loop(ptr: *mut c_void) -> *const c_void {
if ptr.is_null() {
return null() as *const c_void;
}
let args = unsafe { &mut *(ptr as *mut LArgs) };
loop {
+8 -6
View File
@@ -1,21 +1,23 @@
use crate::prelude::*;
pub fn event_logger() -> Element {
pub fn examples() -> Element {
rsx! {
styling: Some(styling()),
button { class: "txt", "Hello, World!" }
if (2 == 2) {
rsx_elem! { button { class: "btn", "Hello, World!" } }
}
}
}
pub fn styling() -> Css {
css! {
.txt {
x: Center,
y: Center,
.btn {
x: 30%,
y: Auto,
color: Red,
}
.txt: clicked {
.btn: clicked {
color: Blue,
}
}
+149 -125
View File
@@ -38,16 +38,12 @@ pub mod utils;
pub use app::run;
/// 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::ui::Color::*;
pub use crate::ui::Number::*;
pub use crate::{self as osui, css, rsx, rsx_elem, ui::*, Handler};
pub use crate::{style, Command, Document, Element, Value};
// useful for Element making
pub use crate::{call, Children, ElementCore, ElementWidget};
pub use crate::{style, Command, Document, Element, Value};
pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
pub fn sleep(ms: u64) {
std::thread::sleep(std::time::Duration::from_millis(ms));
@@ -55,7 +51,54 @@ pub mod prelude {
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Value Enum
// Type aliases
//////////////////////////////////////////////////////////////////////////////////////////////////
pub type Element = Box<dyn ElementWidget>;
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
pub trait ElementCore: 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 Element>;
fn get_child(&mut self) -> Option<&mut Element>;
fn set_styling(&mut self, styling: &HashMap<crate::ui::StyleName, crate::ui::Style>);
}
pub trait ElementWidget: ElementCore + std::fmt::Debug {
fn render(&self, focused: bool) -> RenderResult;
fn event(&mut self, event: Event, document: &Document) {
_ = (event, document)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Structs
//////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Handler<T>(pub Arc<Mutex<dyn FnMut(&mut T, Event, &Document) + Send + Sync>>);
pub struct Document {
cmd_sender: std::sync::mpsc::Sender<Command>,
cmd_recv: *const std::ffi::c_void,
}
/// ```
/// RenderResult(output, (x, y))
/// ```
pub struct RenderResult(String, ui::StyleElement);
pub struct RenderWriter {
w: String,
style: ui::Style,
focused: bool,
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Enums
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Copy)]
@@ -64,6 +107,48 @@ pub enum Value<T: Copy + PartialEq> {
Custom(T),
}
/// Child enum of an `Element`
///
/// # Example
/// ```
/// // Check if it's text
/// if let Children::Text(text) {
/// // do something
/// }
/// // Check if it's inner elements
/// if let Children::Children(text) {
/// // do something
/// }
/// // recommended: use get_text
/// children.get_text() // returns a empty string if it's not text
/// ```
///
/// # Useful for
/// - `Element::event`
/// - `Element::render`
/// - `Handler`
#[derive(Debug)]
pub enum Children {
None,
Text(String),
Children(Vec<Element>, usize),
}
pub enum Command {
Exit,
Render,
GetElementById(String),
}
pub enum CommandResult {
Element(*mut Element),
None,
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementations
//////////////////////////////////////////////////////////////////////////////////////////////////
impl<T: Copy + PartialEq> Value<T> {
pub fn get_value(&self) -> T {
match self {
@@ -85,89 +170,33 @@ impl<T: Copy + PartialEq + Default> Default for Value<T> {
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// ElementCore Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
/// ```
/// RenderResult(output, (x, y), (width, height))
/// ```
pub struct RenderResult(String, (ui::Number, ui::Number));
pub trait ElementCore: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String);
fn update_data(&mut self, width: usize, height: usize);
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>;
fn get_child(&mut self) -> Option<&mut Element>;
fn set_styling(&mut self, styling: &HashMap<crate::ui::StyleName, crate::ui::Style>);
impl Document {
pub fn exit(&self) {
self.cmd_sender.send(Command::Exit).unwrap();
}
pub trait ElementWidget: ElementCore + std::fmt::Debug {
fn render(&self, focused: bool) -> RenderResult;
fn event(&mut self, event: Event, document: &Document) {
_ = (event, document)
pub fn get_element_by_id<T>(&self, id: &str) -> Option<&mut Box<T>> {
if let Some(e) = self.get_element_by_id_raw(id) {
Some(convert(unsafe { &mut *e }))
} else {
None
}
}
pub type Element = Box<dyn ElementWidget>;
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Handler Struct
//////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Handler<T>(pub Arc<Mutex<dyn FnMut(&mut T, Event, &Document) + Send + Sync>>);
impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&mut T, Event, &Document) + 'static + Send + Sync,
{
Handler(Arc::new(Mutex::new(handler_fn)))
pub fn get_element_by_id_raw(&self, id: &str) -> Option<*mut Element> {
self.cmd_sender
.send(Command::GetElementById(id.to_string()))
.unwrap();
let rx = unsafe { &*(self.cmd_recv as *const std::sync::mpsc::Receiver<CommandResult>) };
if let Ok(CommandResult::Element(e)) = rx.recv() {
Some(e)
} else {
None
}
}
impl<T> Default for Handler<T> {
fn default() -> Self {
Handler(Arc::new(Mutex::new(|_: &mut T, _: Event, _: &Document| {})))
pub fn render(&self) {
self.cmd_sender.send(Command::Render).unwrap();
}
}
impl<T> std::fmt::Debug for Handler<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Handler()")
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Children Enum
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Child enum of an `Element`
///
/// # Example
/// ```
/// // Check if it's text
/// if let Children::Text(text) {
/// // do something
/// }
/// // Check if it's inner elements
/// if let Children::Children(text) {
/// // do something
/// }
/// ```
///
/// # Useful for
/// - `Element::event`
/// - `Element::render`
/// - `Handler`
#[derive(Debug)]
pub enum Children {
None,
Text(String),
Children(Vec<Element>, usize),
}
impl Default for Children {
fn default() -> Self {
Self::None
@@ -218,50 +247,45 @@ impl Children {
}
}
/// Converts a `Element` into the struct
impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&mut T, Event, &Document) + 'static + Send + Sync,
{
Handler(Arc::new(Mutex::new(handler_fn)))
}
}
impl RenderWriter {
pub fn new(focused: bool, style: ui::Style) -> RenderWriter {
RenderWriter {
w: String::new(),
style,
focused,
}
}
pub fn write(&mut self, s: &str) {
self.w += self.style.get(self.focused).write(s).as_str();
}
pub fn result(&self) -> RenderResult {
RenderResult(self.w.clone(), self.style.get(self.focused).clone())
}
}
impl<T> Default for Handler<T> {
fn default() -> Self {
Handler(Arc::new(Mutex::new(|_: &mut T, _: Event, _: &Document| {})))
}
}
impl<T> std::fmt::Debug for Handler<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Handler()")
}
}
pub fn convert<T>(widget: &mut Box<dyn ElementWidget>) -> &mut Box<T> {
unsafe { &mut *(widget as *mut _ as *mut Box<T>) }
}
pub enum Command {
Exit,
Render,
GetElementById(String),
}
pub enum CommandResult {
Element(*mut Element),
None,
}
pub struct Document {
cmd_sender: std::sync::mpsc::Sender<Command>,
cmd_recv: *const std::ffi::c_void,
}
impl Document {
pub fn exit(&self) {
self.cmd_sender.send(Command::Exit).unwrap();
}
pub fn get_element_by_id<T>(&self, id: &str) -> Option<&mut Box<T>> {
if let Some(e) = self.get_element_by_id_raw(id) {
Some(convert(unsafe { &mut *e }))
} else {
None
}
}
pub fn get_element_by_id_raw(&self, id: &str) -> Option<*mut Element> {
self.cmd_sender
.send(Command::GetElementById(id.to_string()))
.unwrap();
let rx = unsafe { &*(self.cmd_recv as *const std::sync::mpsc::Receiver<CommandResult>) };
if let Ok(CommandResult::Element(e)) = rx.recv() {
Some(e)
} else {
None
}
}
pub fn render(&self) {
self.cmd_sender.send(Command::Render).unwrap();
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
use osui::prelude::*;
use osui::examples;
use osui::prelude::*;
fn main() {
while osui::run(&mut examples::event_logger()) {}
while osui::run(&mut examples::examples()) {}
}
+17 -10
View File
@@ -1,5 +1,22 @@
#[macro_export]
macro_rules! parse_rsx_param {
($elem:expr, for ($($for:tt)*) $code:block $($rest:tt)*) => {
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($code)
}
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, if ($($if:tt)*) $code:block $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
if $($if)* {children.push($code)}
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+: $v:expr) => {
$elem.$($k).+ = $v;
};
@@ -29,16 +46,6 @@ macro_rules! parse_rsx_param {
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
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)*);
};
($elem:expr, {$inner_elem:expr} $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
+16 -25
View File
@@ -5,7 +5,7 @@
pub mod styles;
pub use styles::*;
use crate::{prelude::*, RenderResult};
use crate::{prelude::*, RenderResult, RenderWriter};
use osui_element::{elem_fn, element};
/// The `Text` element represents a piece of static text in the terminal UI.
@@ -31,17 +31,18 @@ pub struct Text {
}
impl ElementWidget for Text<'_> {
fn render(&self, _: bool) -> RenderResult {
self.style.result(
"",
self.style.write("", &{
fn render(&self, focused: bool) -> RenderResult {
let mut writer = RenderWriter::new(focused, self.style.clone());
writer.write(&{
if let Children::Text(text) = &self.children {
text.clone()
} else {
String::new()
}
}),
)
});
writer.result()
}
fn event(&mut self, event: Event, document: &Document) {
call!(self.on_event(event, document));
@@ -77,6 +78,7 @@ pub struct Div {
impl ElementWidget for Div<'_> {
fn render(&self, focused: bool) -> RenderResult {
let mut writer = RenderWriter::new(focused, self.style.clone());
let mut frame = crate::utils::Frame::new(crate::utils::get_term_size().0, 12);
if let Children::Children(children, child) = &self.children {
@@ -84,8 +86,9 @@ impl ElementWidget for Div<'_> {
frame.render(focused && i == *child, elem);
}
}
writer.write(&frame.output());
self.style.result("", frame.output())
writer.result()
}
fn event(&mut self, event: Event, document: &Document) {
@@ -145,25 +148,13 @@ impl ElementWidget for Div<'_> {
#[derive(Default, Debug)]
pub struct Button {
pub on_click: Handler<Button<'a>>,
pub state: &'a str,
}
impl ElementWidget for Button<'_> {
fn render(&self, focused: bool) -> RenderResult {
if self.state != "" {
self.style.result(
self.state,
self.style.write(self.state, &self.children.get_text()),
)
} else if focused {
self.style.result(
"hover",
self.style.write("hover", &self.children.get_text()),
)
} else {
self.style
.result("", self.style.write("", &self.children.get_text()))
}
let mut writer = RenderWriter::new(focused, self.style.clone());
writer.write(&self.children.get_text());
writer.result()
}
fn event(&mut self, event: Event, document: &Document) {
@@ -171,11 +162,11 @@ impl ElementWidget for Button<'_> {
Event::Key(key) => {
if key.code == KeyCode::Enter {
document.render();
self.state = "clicked";
self.style.set_state("clicked");
call!(self.on_click(event, document));
document.render();
sleep(100);
self.state = "";
self.style.set_state("");
}
}
_ => {}
+155 -132
View File
@@ -6,16 +6,27 @@ use std::{collections::HashMap, fmt::Debug};
use dyn_clone::{clone_trait_object, DynClone};
use crate::RenderResult;
//////////////////////////////////////////////////////////////////////////////////////////////////
// Type aliases
//////////////////////////////////////////////////////////////////////////////////////////////////
pub type Css = HashMap<StyleName, Style>;
//////////////////////////////////////////////////////////////////////////////////////////////////
// Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
pub trait StyleCore: Debug + Send + Sync + DynClone {
fn ansi(&self) -> String;
fn ansi_bg(&self) -> String;
fn is_null(&self) -> bool;
}
clone_trait_object!(StyleCore);
//////////////////////////////////////////////////////////////////////////////////////////////////
// Enums
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Font {
None,
@@ -27,32 +38,6 @@ pub enum Font {
Mul(Vec<Font>),
}
impl StyleCore for Font {
fn ansi(&self) -> String {
String::from(match self {
Font::None => "",
Font::Bold => "\x1b[1m",
Font::Underline => "\x1b[4m",
Font::Italic => "\x1b[3m",
Font::Reverse => "\x1b[7m",
Font::Strike => "\x1b[9m",
Font::Mul(v) => {
let mut s = String::new();
for n in v {
s += n.ansi().as_str();
}
return s;
}
})
}
fn ansi_bg(&self) -> String {
self.ansi()
}
fn is_null(&self) -> bool {
*self == Self::None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Color {
NoColor,
@@ -68,6 +53,128 @@ pub enum Color {
White,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Number {
Px(usize),
Pe(usize),
Center,
Auto,
Default,
}
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName {
Class(String),
Id(String),
Component(String),
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Structs
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub background: Color,
pub font: Font,
pub x: Number,
pub y: Number,
pub width: Number,
pub height: Number,
pub other: HashMap<String, Box<dyn StyleCore>>,
}
#[derive(Debug, Clone)]
pub struct Style(
pub StyleElement,
pub HashMap<String, StyleElement>,
pub String,
);
//////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations
//////////////////////////////////////////////////////////////////////////////////////////////////
impl Default for Style {
fn default() -> Self {
Style(StyleElement::default(), HashMap::new(), String::new())
}
}
impl Style {
pub fn get(&self, hover: bool) -> StyleElement {
if let Some(style_element) = self.1.get(&self.2) {
self.0.clone().prioritize(style_element)
} else if hover {
if let Some(style_element) = self.1.get("hover") {
self.0.clone().prioritize(style_element)
} else {
self.0.clone()
}
} else {
self.0.clone()
}
}
pub fn set_state(&mut self, state: &str) {
self.2 = state.to_string();
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!(
"{}{}{}{s}\x1b[0m",
self.color.ansi(),
self.background.ansi_bg(),
self.font.ansi()
)
}
}
impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
background: Color::NoColor,
font: Font::None,
x: Number::Default,
y: Number::Default,
width: Number::Default,
height: Number::Default,
other: HashMap::new(),
}
}
}
impl StyleElement {
pub fn prioritize(mut self, other: &Self) -> StyleElement {
if !other.color.is_null() {
self.color = other.color.clone();
}
if !other.background.is_null() {
self.background = other.background.clone();
}
if !other.font.is_null() {
self.font = other.font.clone();
}
if other.x != Number::Default {
self.x = other.x.clone();
}
if other.y != Number::Default {
self.y = other.y.clone();
}
if other.width != Number::Default {
self.width = other.width.clone();
}
if other.height != Number::Default {
self.height = other.height.clone();
}
self
}
}
impl StyleCore for Color {
fn ansi(&self) -> String {
String::from(match self {
@@ -153,112 +260,28 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
(r, g, b)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Number {
Px(usize),
Pe(usize),
Center,
Auto,
Default,
impl StyleCore for Font {
fn ansi(&self) -> String {
String::from(match self {
Font::None => "",
Font::Bold => "\x1b[1m",
Font::Underline => "\x1b[4m",
Font::Italic => "\x1b[3m",
Font::Reverse => "\x1b[7m",
Font::Strike => "\x1b[9m",
Font::Mul(v) => {
let mut s = String::new();
for n in v {
s += n.ansi().as_str();
}
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName {
Class(String),
Id(String),
Component(String),
return s;
}
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub background: Color,
pub font: Font,
pub x: Number,
pub y: Number,
pub width: Number,
pub height: Number,
pub other: HashMap<String, Box<dyn StyleCore>>,
})
}
impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
background: Color::NoColor,
font: Font::None,
x: Number::Default,
y: Number::Default,
width: Number::Default,
height: Number::Default,
other: HashMap::new(),
fn ansi_bg(&self) -> String {
self.ansi()
}
fn is_null(&self) -> bool {
*self == Self::None
}
}
}
impl StyleElement {
pub fn prioritize(mut self, other: &Self) -> StyleElement {
if !other.color.is_null() {
self.color = other.color.clone();
}
if !other.background.is_null() {
self.background = other.background.clone();
}
if !other.font.is_null() {
self.font = other.font.clone();
}
if other.x != Number::Default {
self.x = other.x.clone();
}
if other.y != Number::Default {
self.y = other.y.clone();
}
if other.width != Number::Default {
self.width = other.width.clone();
}
if other.height != Number::Default {
self.height = other.height.clone();
}
self
}
}
#[derive(Debug, Clone)]
pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>);
impl Default for Style {
fn default() -> Self {
Style(StyleElement::default(), HashMap::new())
}
}
impl Style {
pub fn write(&self, state: &str, s: &str) -> String {
if let Some(style_element) = self.1.get(state) {
style_element.write(s)
} else {
return self.0.write(s);
}
}
pub fn result(&self, state: &str, s: String) -> RenderResult {
if let Some(style_element) = self.1.get(state) {
let style_element = self.0.clone().prioritize(&style_element.clone());
RenderResult(s, (style_element.x.clone(), style_element.y.clone()))
} else {
RenderResult(s, (self.0.x.clone(), self.0.y.clone()))
}
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!(
"{}{}{}{s}\x1b[0m",
self.color.ansi(),
self.background.ansi_bg(),
self.font.ansi()
)
}
}
pub type Css = HashMap<StyleName, Style>;
+2 -2
View File
@@ -103,7 +103,7 @@ impl Frame {
let output = element.render(focused);
let x;
let mut y = 0;
match output.1 .1 {
match output.1.y {
crate::ui::Number::Px(_y) => {
y = _y;
}
@@ -128,7 +128,7 @@ impl Frame {
}
}
}
match output.1 .0 {
match output.1.x {
crate::ui::Number::Px(_x) => {
x = _x;
}