From e031f9bcef370977db1bbb21b1bd82ccae4f068f Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 18 Nov 2025 20:04:14 +0100 Subject: [PATCH] Testing new structure --- examples/iter.rs | 48 ---- examples/tour.rs | 83 ------- examples/velocity.rs | 52 ---- src/elements/div.rs | 89 ------- src/elements/flex/column.rs | 98 -------- src/elements/flex/mod.rs | 5 - src/elements/flex/row.rs | 104 -------- src/elements/heading.rs | 71 ------ src/elements/input.rs | 86 ------- src/elements/mod.rs | 39 --- src/elements/paginator.rs | 101 -------- src/elements/scroll.rs | 132 ---------- src/elements/velocity_handler.rs | 164 ------------ src/error.rs | 12 + src/extensions/focus.rs | 280 --------------------- src/extensions/id.rs | 27 -- src/extensions/input_handling.rs | 43 ---- src/extensions/mod.rs | 137 ---------- src/extensions/tick.rs | 25 -- src/frontend.rs | 172 ------------- src/lib.rs | 259 ++----------------- src/macros.rs | 363 --------------------------- src/render_scope.rs | 378 ---------------------------- src/renderer.rs | 91 +++++++ src/state.rs | 100 -------- src/style.rs | 263 -------------------- src/utils.rs | 182 -------------- src/widget.rs | 412 ------------------------------- 28 files changed, 127 insertions(+), 3689 deletions(-) delete mode 100644 examples/iter.rs delete mode 100644 examples/tour.rs delete mode 100644 examples/velocity.rs delete mode 100644 src/elements/div.rs delete mode 100644 src/elements/flex/column.rs delete mode 100644 src/elements/flex/mod.rs delete mode 100644 src/elements/flex/row.rs delete mode 100644 src/elements/heading.rs delete mode 100644 src/elements/input.rs delete mode 100644 src/elements/mod.rs delete mode 100644 src/elements/paginator.rs delete mode 100644 src/elements/scroll.rs delete mode 100644 src/elements/velocity_handler.rs create mode 100644 src/error.rs delete mode 100644 src/extensions/focus.rs delete mode 100644 src/extensions/id.rs delete mode 100644 src/extensions/input_handling.rs delete mode 100644 src/extensions/mod.rs delete mode 100644 src/extensions/tick.rs delete mode 100644 src/frontend.rs delete mode 100644 src/macros.rs delete mode 100644 src/render_scope.rs create mode 100644 src/renderer.rs delete mode 100644 src/state.rs delete mode 100644 src/style.rs delete mode 100644 src/utils.rs delete mode 100644 src/widget.rs diff --git a/examples/iter.rs b/examples/iter.rs deleted file mode 100644 index 61162ca..0000000 --- a/examples/iter.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::sync::Arc; - -use osui::prelude::*; - -fn main() -> std::io::Result<()> { - let screen = Screen::new(); - screen.extension(InputExtension); - - app(screen.clone()).draw(&screen); - - screen.run() -} - -pub fn app(screen: Arc) -> Rsx { - let items: State> = use_state(Vec::new()); - let count = use_state(1); - - run! { - ref count, items { - loop { - items.get().push(count.get_dl()); - **count.get() += 1; - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } - } - - rsx! { - @AlwaysFocused; - @Handler::new({ - let screen = screen.clone(); - move |_, e: &crossterm::event::Event| { - if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e { - if *code == crossterm::event::KeyCode::Esc { - screen.close(); - } - } - } - }); - Scroll { - FlexRow { - for item in items { - static "{item}" - } - } - } - } -} diff --git a/examples/tour.rs b/examples/tour.rs deleted file mode 100644 index 8a85a9f..0000000 --- a/examples/tour.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::sync::Arc; - -use osui::prelude::*; - -fn main() -> std::io::Result<()> { - let screen = Screen::new(); - screen.extension(InputExtension); - screen.extension(RelativeFocusExtension::new()); - - app(screen.clone()).draw(&screen); - - screen.run() -} - -pub fn app(screen: Arc) -> Rsx { - let count = use_state(0); - - std::thread::spawn({ - let count = count.clone(); - move || loop { - **count.get() += 1; - std::thread::sleep(std::time::Duration::from_secs(1)); - } - }); - - rsx! { - @Handler::new({ - let screen = screen.clone(); - move |_, e: &crossterm::event::Event| { - if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e { - if *code == crossterm::event::KeyCode::Esc { - screen.close(); - } - } - }}); - @AlwaysFocused; - Paginator { - FlexRow { - Heading, smooth: false, { "OSUI" } - "Welcome to the OSUI demo!" - "Press tab to switch to the next page or shift+tab to the previous page" - } - - FlexCol, gap: 3, { - @Transform::new().padding(2, 2); - @Style { foreground: None, background: Background::RoundedOutline(0x00ff00) }; - Div { - "This is text inside a div" - } - - @Transform::new().padding(2, 2); - @Style { foreground: None, background: Background::Outline(0x00ff00) }; - Div { - "This is text inside a div with square outlines" - } - - // TODO: div with width full - } - - FlexRow, gap: 1, { - %count - "This will increment every second: {count}" - - FlexRow - { - "Username" - @Transform::new().padding(1, 1).dimensions_const(40, 1); - @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) }; - @Focused; - Input { } - } - - FlexRow - { - "Password" - @Transform::new().padding(1, 1).dimensions_const(40, 1); - @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) }; - Input { } - } - } - } - } -} diff --git a/examples/velocity.rs b/examples/velocity.rs deleted file mode 100644 index 3d6043a..0000000 --- a/examples/velocity.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::sync::Arc; - -use osui::prelude::*; - -fn main() -> std::io::Result<()> { - let screen = Screen::new(); - screen.extension(InputExtension); - - app(screen.clone()).draw(&screen); - - screen.run() -} - -pub fn app(screen: Arc) -> Rsx { - rsx! { - @Handler::new({ - let screen = screen.clone(); - move |_, e: &crossterm::event::Event| { - if let crossterm::event::Event::Key(e) = e { - if e.code == crossterm::event::KeyCode::Esc { - screen.close(); - } - } - } - }); - @Style { foreground: None, background: Background::Outline(0xff0000) }; - VelocityHandler { - @Velocity(700, 0); - @Handler::new({ - move |w, e: &crossterm::event::Event| { - if let crossterm::event::Event::Key(e) = e { - if e.code == crossterm::event::KeyCode::Char(' ') { - if let Some(Velocity(v, _)) = w.get() { - w.set_component(Velocity(-v, 0)); - } else { - w.component(Velocity(700, 0)); - } - } - } - } - }); - "(•_•)" - } - - @Transform::new().dimensions(Dimension::Full, Dimension::Full); - VelocityHandler { - @Velocity(0, -150); - @Transform::center().bottom(); - "^" - } - } -} diff --git a/src/elements/div.rs b/src/elements/div.rs deleted file mode 100644 index efd5478..0000000 --- a/src/elements/div.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::sync::Arc; - -use crate::{ - prelude::ElementRenderer, - style::RawTransform, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct DivRenderer<'a>(pub &'a mut RawTransform); - -pub struct Div { - children: Vec>, - render: (u16, u16), -} - -impl Div { - pub fn new() -> Self { - Div { - children: Vec::new(), - render: (0, 0), - } - } -} - -impl Element for Div { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.width = 0; - transform.height = 0; - let mut renderer = DivRenderer(&mut transform); - for widget in &self.children { - scope.render_widget(&mut renderer, render_context.get_context(), widget); - } - - self.render = (transform.width, transform.height); - } - - fn draw_child(&mut self, element: &Arc) { - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl ElementRenderer for DivRenderer<'_> { - fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, _widget: &Arc) { - let t = scope.get_transform_mut(); - self.0.width = self.0.width.max(t.width + (t.px * 2)); - self.0.height = self.0.height.max(t.height + (t.py * 2)); - t.x += self.0.x + self.0.px; - t.y += self.0.y + self.0.py; - t.offset_y = self.0.offset_y; - } -} diff --git a/src/elements/flex/column.rs b/src/elements/flex/column.rs deleted file mode 100644 index b1a4436..0000000 --- a/src/elements/flex/column.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::Arc; - -use crate::{ - prelude::ElementRenderer, - style::RawTransform, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct ColumnRenderer<'a>(&'a mut RawTransform, u16, &'a mut u16); - -pub struct FlexCol { - pub gap: u16, - children: Vec>, - render: (u16, u16), -} - -impl FlexCol { - pub fn new() -> Self { - Self { - children: Vec::new(), - gap: 0, - render: (0, 0), - } - } -} - -impl Element for FlexCol { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.width = 0; - let mut v = 0; - let mut renderer = ColumnRenderer(&mut transform, self.gap, &mut v); - for widget in &self.children { - scope.render_widget(&mut renderer, render_context.get_context(), widget); - } - - self.render = (transform.width, transform.height); - } - - fn draw_child(&mut self, element: &Arc) { - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl ElementRenderer for ColumnRenderer<'_> { - fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, _widget: &Arc) { - let t = scope.get_transform_mut(); - // self.0.width = self.0.width.max(*self.2 + t.width + (t.px * 2)); - self.0.height = self.0.height.max(t.height + (t.py * 2)); - - t.x += self.0.x + *self.2; - t.y += self.0.y; - *self.2 += t.width + self.1 + (t.px * 2); - - t.px += self.0.px; - t.py += self.0.py; - - self.0.width += t.width + (t.px * 2) + self.1; - t.offset_y = self.0.offset_y; - } -} diff --git a/src/elements/flex/mod.rs b/src/elements/flex/mod.rs deleted file mode 100644 index 48c6444..0000000 --- a/src/elements/flex/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod row; -pub use row::*; - -mod column; -pub use column::*; diff --git a/src/elements/flex/row.rs b/src/elements/flex/row.rs deleted file mode 100644 index 902ceb3..0000000 --- a/src/elements/flex/row.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::sync::Arc; - -use crate::{ - prelude::ElementRenderer, - style::RawTransform, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct RowRenderer<'a>(&'a mut RawTransform, u16, &'a mut u16); - -pub struct FlexRow { - pub gap: u16, - children: Vec>, - render: (u16, u16), -} - -impl FlexRow { - pub fn new() -> Self { - Self { - children: Vec::new(), - gap: 0, - render: (0, 0), - } - } -} - -impl Element for FlexRow { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.height = 0; - let mut v = 0; - let mut renderer = RowRenderer(&mut transform, self.gap, &mut v); - for widget in &self.children { - scope.render_widget(&mut renderer, render_context.get_context(), widget); - } - - self.render = (transform.width, transform.height); - } - - fn draw_child(&mut self, element: &Arc) { - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn undraw_child(&mut self, element: &Arc) { - if let Some(i) = self.children.iter().position(|v| Arc::ptr_eq(v, element)) { - self.children.remove(i); - } - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl ElementRenderer for RowRenderer<'_> { - fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, _widget: &Arc) { - let t = scope.get_transform_mut(); - self.0.width = self.0.width.max(*self.2 + t.width + (t.px * 2)); - // self.0.height = self.0.height.max(t.height + (t.py * 2)); - - t.x += self.0.x; - t.y += self.0.y + *self.2; - *self.2 += t.height + self.1 + (t.py * 2); - - t.px += self.0.px; - t.py += self.0.py; - - self.0.height += t.height + (t.py * 2) + self.1; - t.offset_y = self.0.offset_y; - } -} diff --git a/src/elements/heading.rs b/src/elements/heading.rs deleted file mode 100644 index 6970c77..0000000 --- a/src/elements/heading.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::sync::Arc; - -use figlet_rs::FIGfont; - -use crate::{ - widget::{Element, Widget}, - NoRender, NoRenderRoot, -}; - -pub struct Heading { - pub font: FIGfont, - pub smooth: bool, - children: Vec>, -} - -impl Heading { - pub fn new() -> Heading { - Heading { - font: FIGfont::standard().unwrap(), - smooth: false, - children: Vec::new(), - } - } -} - -impl Element for Heading { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - let mut s = String::new(); - for element in &self.children { - if element.get::().is_some() { - continue; - } - - if let Some(e) = element.get_elem().as_any().downcast_ref::() { - s += e; - } - } - if let Some(t) = self.font.convert(&s) { - scope.draw_text( - 0, - 0, - &if self.smooth { - format!("{t}").replace("-", "─").replace("|", "│") - } else { - format!("{t}") - }, - ); - } - } - - fn draw_child(&mut self, element: &Arc) { - element.inject(|w| w.component(NoRenderRoot)); - self.children.push(element.clone()); - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} diff --git a/src/elements/input.rs b/src/elements/input.rs deleted file mode 100644 index 9502ba8..0000000 --- a/src/elements/input.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -use crate::{ - state::{use_state, State}, - widget::Element, -}; - -pub struct Input { - pub state: State, - cursor: usize, -} - -impl Element for Input { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let s = self.state.get(); - scope.draw_text(0, 0, &s); - if render_context.is_focused() { - if let Some(c) = s.chars().nth(self.cursor) { - scope.draw_text_inverted(self.cursor as u16, 0, &c.to_string()); - } else { - scope.draw_text_inverted(s.len() as u16, 0, " "); - } - } - } - - fn event(&mut self, event: &dyn crate::prelude::Event) { - if let Some(crossterm::event::Event::Key(KeyEvent { - code, modifiers, .. - })) = event.get() - { - if !modifiers.is_empty() && !modifiers.contains(KeyModifiers::SHIFT) { - return; - } - - match code { - KeyCode::Char(c) => { - self.state.get().insert(self.cursor, *c); - self.cursor += 1; - } - KeyCode::Backspace => { - if self.cursor > 0 { - self.state.get().remove(self.cursor - 1); - self.cursor -= 1; - } - } - KeyCode::Delete => { - if self.state.get().len() > self.cursor { - self.state.get().remove(self.cursor); - } - } - KeyCode::Left => { - if self.cursor > 0 { - self.cursor -= 1; - } - } - KeyCode::Right => { - if self.cursor < self.state.get().len() { - self.cursor += 1; - } - } - _ => {} - } - } - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl Input { - pub fn new() -> Self { - Self { - state: use_state(String::new()), - cursor: 0, - } - } -} diff --git a/src/elements/mod.rs b/src/elements/mod.rs deleted file mode 100644 index 4a580a1..0000000 --- a/src/elements/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -pub mod div; -pub mod flex; -pub mod heading; -pub mod input; -pub mod paginator; -pub mod scroll; -pub mod velocity_handler; - -pub use div::*; -pub use flex::*; -pub use heading::*; -pub use input::*; -pub use paginator::*; -pub use scroll::*; -pub use velocity_handler::*; - -use crate::Element; - -impl Element for String { - fn render( - &mut self, - scope: &mut crate::render_scope::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.draw_text(0, 0, self); - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} diff --git a/src/elements/paginator.rs b/src/elements/paginator.rs deleted file mode 100644 index 7ff6270..0000000 --- a/src/elements/paginator.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::sync::Arc; - -use crossterm::event::{KeyCode, KeyEvent}; - -use crate::{ - prelude::DivRenderer, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct Paginator { - children: Vec>, - index: usize, - render: (u16, u16), -} - -impl Paginator { - pub fn new() -> Self { - Self { - children: Vec::new(), - index: 0, - render: (0, 0), - } - } -} - -impl Element for Paginator { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - if let Some(widget) = self.children.get(self.index) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.height = 0; - let mut renderer = DivRenderer(&mut transform); - scope.render_widget(&mut renderer, render_context.get_context(), widget); - - self.render = (transform.width, transform.height); - } - } - - fn event(&mut self, event: &dyn crate::prelude::Event) { - if let Some(crossterm::event::Event::Key(KeyEvent { code, .. })) = event.get() { - let cl = self.children.len(); - match code { - KeyCode::Tab => { - if self.index + 1 < cl { - self.index += 1; - } else { - self.index = 0; - } - } - KeyCode::BackTab => { - if self.index > 0 { - self.index -= 1; - } else if cl > 0 { - self.index = cl - 1; - } - } - _ => {} - } - } - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn draw_child(&mut self, element: &Arc) { - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} diff --git a/src/elements/scroll.rs b/src/elements/scroll.rs deleted file mode 100644 index 3d760b7..0000000 --- a/src/elements/scroll.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::sync::Arc; - -use crossterm::event::Event; - -use crate::{ - prelude::ElementRenderer, - style::RawTransform, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct ScrollRenderer<'a>(&'a mut RawTransform); - -pub struct Scroll { - pub scroll_offset: u16, - children: Vec>, - render: (u16, u16), -} - -impl Scroll { - pub fn new() -> Self { - Self { - scroll_offset: 0, - children: Vec::new(), - render: (0, 0), - } - } -} - -impl Element for Scroll { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - transform.offset_y = self.scroll_offset; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.width = 0; - transform.height = 0; - let mut renderer = ScrollRenderer(&mut transform); - for widget in &self.children { - scope.render_widget(&mut renderer, render_context.get_context(), widget); - } - - self.render = (transform.width, transform.height); - } - - fn draw_child(&mut self, element: &Arc) { - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn undraw_child(&mut self, element: &Arc) { - if let Some(i) = self.children.iter().position(|v| Arc::ptr_eq(v, element)) { - self.children.remove(i); - } - } - - fn event(&mut self, event: &dyn crate::prelude::Event) { - if let Some(e) = event.get::() { - match e { - Event::Key(k) => match k.code { - crossterm::event::KeyCode::PageUp => { - self.scroll_offset = self.scroll_offset.saturating_sub(1) - } - crossterm::event::KeyCode::PageDown => { - if self.scroll_offset + 1 < self.render.1 { - self.scroll_offset += 1; - } - } - _ => {} - }, - - Event::Mouse(m) => match m.kind { - crossterm::event::MouseEventKind::ScrollDown => { - if self.scroll_offset + 1 < self.render.1 { - self.scroll_offset += 1; - } - } - crossterm::event::MouseEventKind::ScrollUp => { - self.scroll_offset = self.scroll_offset.saturating_sub(1) - } - _ => {} - }, - _ => {} - } - } - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl ElementRenderer for ScrollRenderer<'_> { - fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, _widget: &Arc) { - let t = scope.get_transform_mut(); - self.0.width = self.0.width.max(t.width + (t.px * 2)); - self.0.height = self.0.height.max(t.height + (t.py * 2)); - t.x += self.0.x + self.0.px; - t.y += self.0.y + self.0.py; - t.offset_y = self.0.offset_y; - } -} diff --git a/src/elements/velocity_handler.rs b/src/elements/velocity_handler.rs deleted file mode 100644 index e70d953..0000000 --- a/src/elements/velocity_handler.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::sync::Arc; - -use crate::{ - component, - prelude::ElementRenderer, - style::RawTransform, - widget::{Element, Widget}, - NoRenderRoot, -}; - -pub struct VelocityRenderer<'a>(&'a mut RawTransform, &'a mut Vec<(i32, i32)>, usize, u16); - -pub struct VelocityHandler { - children: Vec>, - progress: Vec<(i32, i32)>, - render: (u16, u16), - tick: u16, -} - -impl VelocityHandler { - pub fn new() -> Self { - Self { - children: Vec::new(), - progress: Vec::new(), - render: (0, 0), - tick: 0, - } - } -} - -impl Element for VelocityHandler { - fn render( - &mut self, - scope: &mut crate::prelude::RenderScope, - _: &crate::render_scope::RenderContext, - ) { - scope.use_area(self.render.0, self.render.1); - } - - fn after_render( - &mut self, - parent_scope: &mut crate::render_scope::RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - let mut transform = { - let (width, height) = parent_scope.get_size_or_parent(); - let mut t = parent_scope.get_transform().clone(); - t.width = width; - t.height = height; - t.transform_parent(parent_scope.get_parent_transform()); - t - }; - - let mut scope = crate::render_scope::RenderScope::new(); - scope.set_parent_transform(transform.clone()); - - transform.width = 0; - transform.height = 0; - let mut renderer = VelocityRenderer(&mut transform, &mut self.progress, 0, self.tick); - for widget in &self.children { - scope.render_widget(&mut renderer, render_context.get_context(), widget); - } - - self.render = (transform.width, transform.height); - - if self.tick == 1000 { - self.tick = 0; - } else { - self.tick += 1; - } - } - - fn draw_child(&mut self, element: &Arc) { - self.progress.push((0, 0)); - self.children.push(element.clone()); - element.inject(|w| w.component(NoRenderRoot)); - } - - fn undraw_child(&mut self, element: &Arc) { - if let Some(i) = self.children.iter().position(|v| Arc::ptr_eq(v, element)) { - self.children.remove(i); - self.progress.remove(i); - } - } - - fn is_ghost(&mut self) -> bool { - true - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl ElementRenderer for VelocityRenderer<'_> { - fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, widget: &Arc) { - let t = scope.get_transform_mut(); - - if let Some(Velocity(vx, vy)) = widget.get() { - if let Some((ax, ay)) = self.1.get_mut(self.2) { - add_i32_to_u16(&mut t.x, *ax); - add_i32_to_u16(&mut t.y, *ay); - - if apply_velocity(self.3, vx, &mut t.x) { - apply_value(ax, vx); - } - if apply_velocity(self.3, vy, &mut t.y) { - apply_value(ay, vy); - } - } else { - self.1.push((0, 0)); - } - } - - self.2 += 1; - - self.0.width = self.0.width.max(t.x + t.width + (t.px * 2)); - self.0.height = self.0.height.max(t.y + t.height + (t.py * 2)); - - t.x += self.0.x + self.0.px; - t.y += self.0.y + self.0.py; - t.offset_y = self.0.offset_y; - } -} - -fn apply_velocity(ticks: u16, velocity: i32, x: &mut u16) -> bool { - let abs_v = velocity.abs(); - if abs_v == 0 { - return false; - } - - let interval = (1000 / abs_v).max(1); // prevent divide-by-zero - if ticks as i32 % interval == 0 { - if velocity > 0 { - *x = x.saturating_add(1); - } else { - *x = x.saturating_sub(1); - } - return true; - } - false -} - -fn add_i32_to_u16(value: &mut u16, delta: i32) { - if delta >= 0 { - *value = value.saturating_add(delta as u16); - } else { - *value = value.saturating_sub((-delta) as u16); - } -} - -fn apply_value(x: &mut i32, velocity: i32) { - *x = if velocity > 0 { - x.saturating_add(1) - } else { - x.saturating_sub(1) - }; -} - -component!(Velocity(pub i32, pub i32)); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..0efb8d8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,12 @@ +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum OsuiError { + Io(std::io::Error), +} + +impl From for OsuiError { + fn from(e: std::io::Error) -> Self { + OsuiError::Io(e) + } +} diff --git a/src/extensions/focus.rs b/src/extensions/focus.rs deleted file mode 100644 index 22d8806..0000000 --- a/src/extensions/focus.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::component; - -use super::Extension; - -use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyModifiers}; - -component!(AlwaysFocused); -component!(Focused); - -enum Direction { - Left, - Right, - Up, - Down, -} - -pub struct RelativeFocusExtension { - cursor: usize, - rendered: Arc>>, -} - -impl Extension for RelativeFocusExtension { - fn init(&mut self, _ctx: &super::Context) { - for widget in _ctx.get_widgets().iter() { - if let Some(Focused) = widget.get() { - widget.set_focused(true); - } - } - } - - fn event(&mut self, ctx: &super::Context, event: &dyn super::Event) { - if let Some(e) = event.get::() { - match e { - CrosstermEvent::Key(k) => { - if k.modifiers.contains(KeyModifiers::SHIFT) { - match k.code { - KeyCode::Right => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Right, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - KeyCode::Left => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Left, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - KeyCode::Up => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Up, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - KeyCode::Down => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = *rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Down, - current_x, - current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - - for (i, w) in ctx.get_widgets().iter().enumerate() { - if let Some(AlwaysFocused) = w.get() { - w.set_focused(true); - } else if !w.is_ghost() { - w.set_focused(i == self.cursor); - } - } - } - } - - _ => {} - } - } - } - _ => {} - } - } - } - - fn render(&mut self, _ctx: &super::Context, _scope: &mut crate::prelude::RenderScope) { - self.rendered.lock().unwrap().clear(); - } - - fn after_render_widget( - &mut self, - ctx: &super::Context, - scope: &mut crate::prelude::RenderScope, - widget: &std::sync::Arc, - ) { - let t = scope.get_transform().clone(); - let ctx = ctx.clone(); - let widget = widget.clone(); - - let rendered = self.rendered.clone(); - - std::thread::spawn(move || { - if let Some(p) = ctx - .get_widgets() - .iter() - .position(|w| Arc::ptr_eq(w, &widget) && !w.is_ghost()) - { - rendered.lock().unwrap().push((p, t.x, t.y)); - } - }); - } -} - -impl RelativeFocusExtension { - pub fn new() -> Self { - Self { - cursor: 0, - rendered: Arc::new(Mutex::new(Vec::new())), - } - } -} - -fn find_closest_in_direction( - direction: Direction, - current_x: u16, - current_y: u16, - rendered: &[(usize, u16, u16)], - cursor: usize, -) -> Option { - let mut closest_index: Option = None; - let mut closest_distance = u16::MAX; - let mut same_line_or_col_found = false; - - for &(i, x, y) in rendered { - if i == cursor { - continue; - } - - match direction { - Direction::Right if x > current_x => { - if y == current_y { - let dx = x - current_x; - if !same_line_or_col_found || dx < closest_distance { - closest_index = Some(i); - closest_distance = dx; - same_line_or_col_found = true; - } - } else if !same_line_or_col_found { - let dx = x - current_x; - let dy = (y as isize - current_y as isize).abs() as u16; - let dist = dx * dx + dy * dy; - if dist < closest_distance - || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) - { - closest_index = Some(i); - closest_distance = dist; - } - } - } - - Direction::Left if x < current_x => { - if y == current_y { - let dx = current_x - x; - if !same_line_or_col_found || dx < closest_distance { - closest_index = Some(i); - closest_distance = dx; - same_line_or_col_found = true; - } - } else if !same_line_or_col_found { - let dx = current_x - x; - let dy = (y as isize - current_y as isize).abs() as u16; - let dist = dx * dx + dy * dy; - if dist < closest_distance - || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) - { - closest_index = Some(i); - closest_distance = dist; - } - } - } - - Direction::Down if y > current_y => { - if x == current_x { - let dy = y - current_y; - if !same_line_or_col_found || dy < closest_distance { - closest_index = Some(i); - closest_distance = dy; - same_line_or_col_found = true; - } - } else if !same_line_or_col_found { - let dx = (x as isize - current_x as isize).abs() as u16; - let dy = y - current_y; - let dist = dx * dx + dy * dy; - if dist < closest_distance - || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) - { - closest_index = Some(i); - closest_distance = dist; - } - } - } - - Direction::Up if y < current_y => { - if x == current_x { - let dy = current_y - y; - if !same_line_or_col_found || dy < closest_distance { - closest_index = Some(i); - closest_distance = dy; - same_line_or_col_found = true; - } - } else if !same_line_or_col_found { - let dx = (x as isize - current_x as isize).abs() as u16; - let dy = current_y - y; - let dist = dx * dx + dy * dy; - if dist < closest_distance - || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) - { - closest_index = Some(i); - closest_distance = dist; - } - } - } - - _ => {} - } - } - - closest_index -} diff --git a/src/extensions/id.rs b/src/extensions/id.rs deleted file mode 100644 index d2be182..0000000 --- a/src/extensions/id.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::sync::Arc; - -use crate::{component, extensions::Extension, widget::Widget, Screen}; - -pub struct IdExtension(pub Arc); - -component!(Id(pub usize)); - -impl Extension for Arc {} - -impl IdExtension { - pub fn new(screen: Arc) -> Arc { - Arc::new(IdExtension(screen)) - } - - pub fn get_element(self: &Arc, id: usize) -> Option> { - for elem in self.0.widgets.lock().unwrap().iter() { - if let Some(current_id) = elem.get::() { - if current_id.0 == id { - return Some(elem.clone()); - } - } - } - - None - } -} diff --git a/src/extensions/input_handling.rs b/src/extensions/input_handling.rs deleted file mode 100644 index 8d80f0c..0000000 --- a/src/extensions/input_handling.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::io::stdout; - -use crossterm::{ - event::{ - DisableMouseCapture, EnableMouseCapture, KeyboardEnhancementFlags, - PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, - }, - execute, -}; - -use crate::extensions::{Context, Extension}; - -pub struct InputExtension; - -impl Extension for InputExtension { - fn init(&mut self, ctx: &Context) { - let ctx = ctx.clone(); - crossterm::terminal::enable_raw_mode().unwrap(); - execute!( - stdout(), - PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES) - ) - .unwrap(); - execute!(stdout(), EnableMouseCapture).unwrap(); - std::thread::spawn(move || loop { - if let Ok(e) = crossterm::event::read() { - ctx.event(&e); - } - }); - } - - fn on_close(&mut self) { - execute!(stdout(), PopKeyboardEnhancementFlags).unwrap(); - execute!(stdout(), DisableMouseCapture).unwrap(); - crossterm::terminal::disable_raw_mode().unwrap(); - } -} - -impl crate::extensions::Event for crossterm::event::Event { - fn as_any(&self) -> &dyn std::any::Any { - self - } -} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs deleted file mode 100644 index 69a4f3f..0000000 --- a/src/extensions/mod.rs +++ /dev/null @@ -1,137 +0,0 @@ -pub mod focus; -pub mod id; -pub mod input_handling; -pub mod tick; - -pub use focus::*; -pub use id::*; -pub use input_handling::*; -pub use tick::*; - -use std::{ - any::{type_name, Any}, - fmt::Debug, - sync::{Arc, Mutex, MutexGuard}, -}; - -use crate::{ - render_scope::RenderScope, - widget::{Component, Widget}, - Screen, -}; - -pub trait Extension { - fn init(&mut self, _ctx: &Context) {} - fn event(&mut self, _ctx: &Context, _event: &dyn Event) {} - fn on_close(&mut self) {} - fn render(&mut self, _ctx: &Context, _scope: &mut RenderScope) {} - fn render_widget(&mut self, _ctx: &Context, _scope: &mut RenderScope, _widget: &Arc) {} - fn after_render_widget( - &mut self, - _ctx: &Context, - _scope: &mut RenderScope, - _widget: &Arc, - ) { - } -} - -pub trait Event: Send + Sync { - fn as_any(&self) -> &dyn Any; -} - -#[derive(Clone)] -pub struct Context { - screen: Arc, -} - -#[derive(Clone)] -pub struct Handler(Arc, &E) + Send + Sync>>); - -impl Component for Handler { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -impl Handler { - pub fn new, &E) + Send + Sync + 'static>(f: F) -> Handler { - Handler(Arc::new(Mutex::new(f))) - } - - pub fn call(&self, w: &Arc, e: &E) { - (self.0.lock().unwrap())(w, e) - } -} - -impl Debug for Handler { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Handler({})", type_name::()) - } -} - -impl<'a> dyn Event + 'a { - pub fn get(&self) -> Option<&T> { - self.as_any().downcast_ref() - } -} - -impl Context { - pub fn new(screen: Arc) -> Self { - Self { screen } - } - - pub fn event(&self, e: &E) { - for widget in self.screen.widgets.lock().unwrap().iter() { - widget.event(e); - } - - for ext in self.screen.extensions.lock().unwrap().iter() { - ext.lock().unwrap().event(self, e); - } - } - - pub fn get_widgets(&'_ self) -> MutexGuard<'_, Vec>> { - self.screen.widgets.lock().unwrap() - } - - pub fn iter_components, Option)>( - &self, - mut iterator: F, - ) { - for widget in self.screen.widgets.lock().unwrap().iter() { - iterator(widget, widget.get()); - } - } - - pub fn get_components(&self) -> Vec { - let mut components = Vec::new(); - for widget in self.screen.widgets.lock().unwrap().iter() { - if let Some(c) = widget.get() { - components.push(c); - } - } - components - } - - pub fn render_root(&self, scope: &mut RenderScope) { - for ext in self.screen.extensions.lock().unwrap().iter() { - ext.lock().unwrap().render(self, scope); - } - } - - pub fn render(&self, w: &Arc, scope: &mut RenderScope) { - for ext in self.screen.extensions.lock().unwrap().iter() { - ext.lock().unwrap().render_widget(self, scope, w); - } - } - - pub fn after_render(&self, w: &Arc, scope: &mut RenderScope) { - for ext in self.screen.extensions.lock().unwrap().iter() { - ext.lock().unwrap().after_render_widget(self, scope, w); - } - } -} diff --git a/src/extensions/tick.rs b/src/extensions/tick.rs deleted file mode 100644 index 9f6701e..0000000 --- a/src/extensions/tick.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::fmt::Debug; - -use crate::event; -use crate::extensions::{Context, Extension}; - -pub struct TickExtension(pub u16); - -event!(TickEvent(pub u32)); - -impl Extension for TickExtension { - fn init(&mut self, ctx: &Context) { - let ctx = ctx.clone(); - let rate_dur = 1000 / self.0 as u64; - std::thread::spawn({ - move || { - let mut tick = 0; - loop { - ctx.event(&TickEvent(tick)); - tick += 1; - std::thread::sleep(std::time::Duration::from_millis(rate_dur)); - } - } - }); - } -} diff --git a/src/frontend.rs b/src/frontend.rs deleted file mode 100644 index d53f78c..0000000 --- a/src/frontend.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! The frontend binder for OSUI. -//! -//! This module defines the internal representation of RSX elements — the tree-like structure used -//! by the `rsx!` macro in OSUI to declaratively build terminal user interfaces. -//! -//! RSX elements can be either static widgets or dynamically loaded ones with dependencies. -//! This structure enables recursive rendering, parent-child composition, and runtime dependency handling. - -use std::sync::Arc; - -use crate::{ - state::DependencyHandler, - widget::{StaticWidget, Widget, WidgetLoad}, - Screen, -}; - -/// Represents a single element in the RSX tree. -pub enum RsxElement { - /// A static widget with children. - Element(StaticWidget, Rsx), - - /// A dynamically generated widget (e.g., with state) with associated dependencies and children. - DynElement( - Box WidgetLoad + Send + Sync>, - Vec>, - Rsx, - ), - - /// A dynamic iteration of widgets, keeping track of their state. - Iter( - Box Rsx + Send + Sync>, - Box, - ), -} - -/// A container representing a group of RSX elements. -/// This is typically created via the `rsx!` macro and rendered using a `Screen`. -pub struct Rsx(pub Vec); - -impl Rsx { - /// Draws the RSX tree onto the given screen without a parent widget. - /// - /// This is the entry point for rendering the UI. - pub fn draw(self, screen: &Arc) { - self.draw_parent(screen, None); - } - - /// Recursively draws the RSX tree with an optional parent widget. - /// - /// Used internally to establish parent-child widget relationships. - pub fn draw_parent( - self, - screen: &Arc, - parent: Option>, - ) -> Option> { - let mut first_widget = None; - - for rsx_elem in self.0 { - match rsx_elem { - RsxElement::DynElement(f, dep, child) => { - let w = screen.draw_box_dyn(f); - - if let Some(parent) = &parent { - parent.get_elem().draw_child(&w); - } - - for d in dep { - w.dependency_box(d); - } - - child.draw_parent(screen, Some(w.clone())); - if first_widget.is_none() { - first_widget = Some(w) - } - } - - RsxElement::Element(ws, child) => { - let w = Arc::new(Widget::Static(ws)); - screen.draw_widget(w.clone()); - - if let Some(parent) = &parent { - parent.get_elem().draw_child(&w); - } - - child.draw_parent(screen, Some(w.clone())); - if first_widget.is_none() { - first_widget = Some(w) - } - } - - RsxElement::Iter(mut rsx, dep) => { - #[allow(unused)] - let mut rsx_len = 0; - let mut fw = { - let r = rsx(); - rsx_len = r.0.len(); - r.draw_parent(screen, parent.clone()) - }; - let parent = parent.clone(); - let s = screen.clone(); - - screen.add_dependency(vec![dep], move || { - if let Some(fww) = &fw { - { - let mut widgets = s.widgets.lock().unwrap(); - if let Some(i) = widgets.iter().position(|v| Arc::ptr_eq(v, fww)) { - for v in (i..(i + rsx_len)).rev() { - if let Some(w) = widgets.get(v) { - if let Some(p) = &parent { - p.get_elem().undraw_child(w); - } - widgets.remove(v); - } - } - } - } - fw = { - let r = rsx(); - rsx_len = r.0.len(); - r.draw_parent(&s, parent.clone()) - }; - } - }); - } - } - } - - first_widget - } - - /// Adds a dynamically constructed element to the RSX tree. - /// - /// - `load`: A closure returning a `WidgetLoad` used to generate the widget. - /// - `dependencies`: A list of dependency handlers for state or event updates. - /// - `children`: Child RSX elements. - pub fn create_element WidgetLoad + Send + Sync + 'static>( - &mut self, - load: F, - dependencies: Vec>, - children: Rsx, - ) { - self.0.push(RsxElement::DynElement( - Box::new(load), - dependencies, - children, - )); - } - - /// Adds a statically defined element to the RSX tree with its children. - pub fn create_element_static(&mut self, element: StaticWidget, children: Rsx) { - self.0.push(RsxElement::Element(element, children)); - } - - /// Adds a statically defined element to the RSX tree with its children. - pub fn iter Rsx + Send + Sync + 'static>( - &mut self, - rsx: F, - dependency: Box, - ) { - self.0.push(RsxElement::Iter(Box::new(rsx), dependency)); - } - - /// Appends the elements from another `Rsx` tree into this one. - pub fn expand(&mut self, other: &mut Rsx) { - self.0.append(&mut other.0); - } - - /// Appends the elements from another `Rsx` tree into this one. nr stands for no reference - pub fn expand_nr(&mut self, mut other: Rsx) { - self.0.append(&mut other.0); - } -} diff --git a/src/lib.rs b/src/lib.rs index caaaf6d..d404186 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,249 +1,38 @@ -//! **OSUI** – A Rust Terminal User Interface Library -//! -//! OSUI is a library for building interactive and customizable terminal user interfaces in Rust. -//! It provides a component system, real-time keyboard input handling, and a `rsx!` macro -//! for defining UI elements in a declarative way, however that is optional. -//! -//! ✅ **Features** -//! - 🧱 RSX-like syntax with `rsx!` macro -//! - 🖥️ Virtual screen abstraction -//! - 🎹 Keyboard input handling -//! - 🎯 Component-based design -//! - ⚡ Real-time rendering -//! -//! 🚀 **Quick Example** -//! ```rust -//! use osui::prelude::*; -//! -//! fn main() -> std::io::Result<()> { -//! let screen = Screen::new(); -//! rsx! { -//! "👋 Hello, World!" -//! }.draw(&screen); -//! screen.run() -//! } -//! ``` -//! -//! --- -//! 🧰 For full documentation, visit: [osui.netlify.app/docs](https://osui.netlify.app/docs) -//! -//! 🧪 Examples and demos: [github.com/osui-rs/osui/demos](https://github.com/osui-rs/osui/tree/master/src/demos) +use crate::renderer::{OsuiRenderer, Renderer}; -use std::sync::{Arc, Mutex}; +pub mod error; +pub mod renderer; -use crate::{ - extensions::Extension, - prelude::{Context, ElementRenderer}, - render_scope::RenderScope, - state::DependencyHandler, - style::RawTransform, - widget::{BoxedElement, DynWidget, Element, StaticWidget, Widget, WidgetLoad}, -}; +pub use error::Result; -pub mod elements; -pub mod extensions; -pub mod frontend; -pub mod macros; -pub mod render_scope; -pub mod state; -pub mod style; -pub mod utils; -pub mod widget; - -pub mod prelude { - pub use crate::{ - elements::*, extensions::*, frontend::*, render_scope::*, state::*, style::*, utils::*, - widget::*, *, - }; - - pub use crate::style::{ - Dimension::{Content, Full}, - Position::{Center, End}, - }; +pub trait Component { + fn render(&mut self, renderer: &mut dyn Renderer); } -/// The main screen abstraction for rendering and managing widgets and extensions. -/// -/// `Screen` holds the root widget list and registered extensions. It provides methods -/// for drawing elements, adding extensions, and running the main rendering loop. -/// -/// # Examples -/// ```rust -/// let screen = Screen::new(); -/// rsx! { "Hello" }.draw(&screen); -/// screen.run()?; -/// ``` -pub struct Screen { - /// The list of widgets currently managed by the screen. - pub widgets: Mutex>>, - /// Registered extensions for the screen. - extensions: Mutex>>>>, - /// If it's still running - running: Mutex, - /// If it's still running - dependencies: Mutex< - Vec<( - Vec>, - Box, - )>, - >, +pub struct Osui { + component: Box, + renderer: Option>, } -event!(RenderWrapperEvent(*mut RenderScope)); -component!(NoRender); -component!(NoRenderRoot); - -impl RenderWrapperEvent { - /// Returns a mutable reference to the underlying `RenderScope`. - /// - /// # Safety - /// The caller must ensure the pointer is valid for the lifetime of the event. - pub fn get_scope(&self) -> &mut RenderScope { - unsafe { &mut *self.0 } +impl Osui { + pub fn new(component: Box) -> Self { + Osui { + component, + renderer: None, + } } -} - -unsafe impl Send for RenderWrapperEvent {} -unsafe impl Sync for RenderWrapperEvent {} - -impl Screen { - /// Creates a new screen instance wrapped in an `Arc`. - pub fn new() -> Arc { - Arc::new(Self { - widgets: Mutex::new(Vec::new()), - extensions: Mutex::new(Vec::new()), - running: Mutex::new(true), - dependencies: Mutex::new(Vec::new()), - }) + pub fn with_renderer(component: Box, renderer: Box) -> Self { + Osui { + component, + renderer: Some(renderer), + } } - /// Draws a static element and returns its widget handle. - pub fn draw(self: &Arc, element: E) -> Arc { - self.draw_box(Box::new(element)) - } - - /// Draws a boxed element and returns its widget handle. - pub fn draw_box(self: &Arc, element: BoxedElement) -> Arc { - let w = Arc::new(Widget::Static(StaticWidget::new(element))); - self.draw_widget(w.clone()); - w - } - - /// Draws a dynamic element using a closure and returns its widget handle. - pub fn draw_dyn WidgetLoad + 'static + Send + Sync>( - self: &Arc, - element: F, - ) -> Arc { - self.draw_box_dyn(Box::new(element)) - } - - /// Draws a dynamic element from a boxed closure and returns its widget handle. - pub fn draw_box_dyn( - self: &Arc, - element: Box WidgetLoad + Send + Sync>, - ) -> Arc { - let w = Arc::new(Widget::Dynamic(DynWidget::new(element))); - self.draw_widget(w.clone()); - w - } - - /// Adds an existing widget to the screen. - pub fn draw_widget(self: &Arc, widget: Arc) { - if self.widgets.lock().unwrap().len() == 0 { - widget.set_focused(true); // first widget is focused - self.widgets.lock().unwrap().push(widget); + pub fn render(&mut self) { + if let Some(renderer) = &mut self.renderer { + self.component.render(renderer.as_mut()); } else { - self.widgets.lock().unwrap().push(widget); - } - } - - /// Registers an extension with the screen. - pub fn extension(self: &Arc, ext: E) { - self.extensions - .lock() - .unwrap() - .push(Arc::new(Mutex::new(Box::new(ext)))); - } - - /// Runs the main rendering loop, calling extensions and rendering widgets. - /// - /// This method blocks and repeatedly renders the screen at a fixed interval. - pub fn run(self: &Arc) -> std::io::Result<()> { - let ctx = Context::new(self.clone()); - - for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().init(&ctx); - } - - utils::hide_cursor()?; - - while *self.running.lock().unwrap() { - self.render(&ctx)?; - std::thread::sleep(std::time::Duration::from_millis(28)); - } - - Ok(()) - } - - /// Renders all widgets and applies extensions. - /// - /// This method is called internally by `run`. - pub fn render(self: &Arc, ctx: &Context) -> std::io::Result<()> { - let mut scope = RenderScope::new(); - let mut renderer = ScreenRenderer; - let mut transform = RawTransform::new(); - (transform.width, transform.height) = crossterm::terminal::size().unwrap(); - scope.set_parent_transform(transform); - ctx.render_root(&mut scope); - - utils::clear()?; - for widget in self.widgets.lock().unwrap().iter() { - if widget.get::().is_some() { - widget.auto_refresh(); - continue; - } - - scope.render_widget(&mut renderer, ctx, widget); - } - for d in self.dependencies.lock().unwrap().iter_mut() { - for dh in &d.0 { - if dh.check() { - (d.1)(); - break; - } - } - } - Ok(()) - } - - /// Closes the loop and calls `on_close` in the extensions - pub fn close(self: &Arc) { - *self.running.lock().unwrap() = false; - utils::show_cursor().unwrap(); - utils::clear().unwrap(); - for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().on_close(); + self.component.render(&mut OsuiRenderer); } } } - -impl Screen { - pub fn add_dependency( - self: &Arc, - dependencies: Vec>, - handler: F, - ) { - for d in &dependencies { - d.add(); - } - - self.dependencies - .lock() - .unwrap() - .push((dependencies, Box::new(handler))); - } -} - -struct ScreenRenderer; - -impl ElementRenderer for ScreenRenderer {} diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index b07ec9b..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,363 +0,0 @@ -/// Declares a struct that implements the `Event` trait. -/// -/// This macro simplifies the creation of event types used within OSUI's reactive system. -/// -/// # Variants -/// -/// - `event!(Name)` -/// Defines a unit struct named `Name`. -/// -/// - `event!(Name { ... })` -/// Defines a named struct with fields. -/// -/// - `event!(Name (...))` -/// Defines a tuple struct. -/// -/// # Examples -/// ```rust -/// event!(Clicked); -/// event!(Resized { width: u32, height: u32 }); -/// event!(Moved(u32, u32)); -/// ``` -#[macro_export] -macro_rules! event { - ($name:ident) => { - #[derive(Debug, Clone)] - pub struct $name; - - impl $crate::extensions::Event for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - } - }; - - ($name:ident {$($inner:tt)*}) => { - #[derive(Debug, Clone)] - pub struct $name { - $($inner)* - } - - impl $crate::extensions::Event for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - } - }; - - ($name:ident ($($inner:tt)*)) => { - #[derive(Debug, Clone)] - pub struct $name ($($inner)*); - - impl $crate::extensions::Event for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - } - }; -} - -/// Declares a struct that implements the `Component` trait. -/// -/// Components allow widgets to extend their behavior or contain additional data. -/// This macro helps avoid boilerplate when defining new components. -/// -/// # Variants -/// -/// - `component!(Name)` -/// Defines a unit struct. -/// -/// - `component!(Name { ... })` -/// Defines a named struct with fields. -/// -/// - `component!(Name (...))` -/// Defines a tuple struct. -/// -/// # Examples -/// ```rust -/// component!(Focusable); -/// component!(Tooltip { text: String }); -/// component!(Size(u32, u32)); -/// ``` -#[macro_export] -macro_rules! component { - ($name:ident) => { - #[derive(Debug, Clone)] - pub struct $name; - - impl $crate::widget::Component for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - } - }; - - ($name:ident {$($inner:tt)*}) => { - #[derive(Debug, Clone)] - pub struct $name { - $($inner)* - } - - impl $crate::widget::Component for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - } - }; - - ($name:ident ($($inner:tt)*)) => { - #[derive(Debug, Clone)] - pub struct $name ($($inner)*); - - impl $crate::widget::Component for $name { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - } - }; -} - -/// Creates an event handler closure that calls a method on `self`. -/// -/// Useful when you need to register `'static` event handlers that interact with the current instance. -/// -/// # Arguments -/// - `$self_ty`: The type of `self`. -/// - `$self`: The instance variable (usually `self`) being used. -/// - `$events`: The event source object with an `.on` method. -/// - `$method`: The method to call when an event is received. -/// -/// # Example -/// ```rust -/// event_handler!(Self, self, events, on_event); -/// ``` -/// Expands roughly to: -/// ```rust -/// let self_ref = self as *mut Self; -/// events.on(move |es, e| unsafe { (*self_ref).on_event(es, e) }); -/// ``` -/// -/// # Safety -/// This macro uses `unsafe` code to cast `self` to a raw pointer and dereference it. -/// Make sure the reference is valid for the closure’s lifetime. -#[macro_export] -macro_rules! event_handler { - ($self_ty:ty, $self:ident, $events:ident, $method:ident) => {{ - let self_ref = $self as *mut $self_ty; - $events.on(move |es, e| unsafe { (*self_ref).$method(es, e) }); - }}; -} - -/// Creates a `Transform` with property overrides. -/// -/// Each key-value pair sets a field on a `Transform` struct. -/// -/// # Example -/// ```rust -/// let t = transform!( -/// x: 10, -/// y: 20, -/// scale: 1.5, -/// ); -/// ``` -/// -/// This expands to: -/// ```rust -/// let mut t = Transform::new(); -/// t.x = 10.into(); -/// t.y = 20.into(); -/// t.scale = 1.5.into(); -/// ``` -#[macro_export] -macro_rules! transform { - ($($f:ident: $v:expr),* $(,)?) => {{ - let mut t = Transform::new(); - $(t.$f = $v.into();)* - t - }}; -} - -/// Spawns a thread with referencing dependencies. -/// -/// This macro spawns a thread and clones states and either references them or uses them as a dependency. -/// -/// # Example -/// ```rust -/// let count = use_state(0); -/// run! { -/// use count // implement count as a dependency, running this again if it updates -/// ref count // ref means that it will only reference the count (clone) -/// { -/// // do something with the count -/// } -/// } -/// ``` -#[macro_export] -macro_rules! run { - (use $($d:ident),+ $(ref $($r:ident),+)? $code:block) => { - std::thread::spawn({ - $(let $d = $d.clone();)+ - $($(let $r = $r.clone();)+)? - move || { - $($d.add();)+ - $code - loop { - $( - if $d.check() $code - )+ - std::thread::sleep(std::time::Duration::from_millis(50)); - } - } - }); - }; - - (ref $($r:ident),+ $code:block) => { - std::thread::spawn({ - $(let $r = $r.clone();)+ - move || $code - }); - }; - - ($($code:tt)*) => { - std::thread::spawn({ - move || {$($code)*} - }); - }; -} - -/// Constructs an `Rsx` tree using declarative syntax. -/// -/// This macro is similar to JSX in UI frameworks, allowing you to nest widgets and assign components or dependencies. -/// It expands into a tree of `RsxElement` objects. -/// -/// Internally calls the recursive `rsx_inner!` macro. -/// -/// # Example -/// ```rust -/// rsx! { -/// "Hello" -/// static Label { } ("Text") -/// %state Label { } -/// @Velocity(20, 0); Transform::new(); "Moving Text" -/// } -/// ``` -#[macro_export] -macro_rules! rsx { - ($($inner:tt)*) => {{ - let mut r = $crate::frontend::Rsx(Vec::new()); - - $crate::rsx_inner! { r, $($inner)* }; - - r - }}; -} - -/// Internal macro used by `rsx!` to recursively build `Rsx` trees. -/// -/// This macro handles various syntactic forms used in the declarative layout system: -/// -/// - Static text or components -/// - Dynamic widgets with dependencies (`%dep`) -/// - Component annotations (`@comp`) -/// - Argument passing to constructors -/// - Nesting and expansion from other `Rsx` blocks -/// -/// **This macro is not intended for direct use**; use `rsx!` instead. -/// -/// # Example expansion -/// ```rust -/// rsx! { -/// static Label { } ("Title") -/// %state Label { inner } -/// "Text" -/// } -/// ``` -/// -/// Would produce a nested `Rsx` tree of static and dynamic widgets. -#[macro_export] -macro_rules! rsx_inner { - // for loop (iter) - ($r:expr, for $n:ident in $state:ident { $($inner:tt)* } $($rest:tt)*) => { - $r.iter({ - let $state = $state.clone(); - move || { - let mut r = $crate::frontend::Rsx(Vec::new()); - for $n in $state.get().iter() { - r.expand_nr($crate::rsx!{ $($inner)* }); - } - r - }}, Box::new($state)); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - // static - ($r:expr, $(@$comp:expr;)* static $s:literal $($rest:tt)*) => { - let w = $crate::widget::StaticWidget::new(Box::new(format!($s))); - $(w.component($comp);)* - $r.create_element_static(w, $crate::frontend::Rsx(Vec::new())); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr, $(@$comp:expr;)* $(%$dep:ident)* $s:literal $($rest:tt)*) => { - $r.create_element({ $(let $dep = $dep.clone();)* move || { - $crate::widget::WidgetLoad::new(format!($s)) $(.component($comp))* - } }, vec![$(Box::new($dep.clone())),*], $crate::frontend::Rsx(Vec::new())); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - // static - ($r:expr, $(@$comp:expr;)* static $name:path, $($f:ident $(: $v:expr)?),+, { $($inner:tt)* } $($rest:tt)*) => { - let w = $crate::widget::StaticWidget::new(Box::new({ - let mut elem = <$name>::new(); - $(elem.$f = $($v)?;)+ - elem - })); - $(w.component($comp);)* - $r.create_element_static(w, $crate::rsx!{ $($inner)* }); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr, $(@$comp:expr;)* static $name:path { $($inner:tt)* } $($rest:tt)*) => { - let w = $crate::widget::StaticWidget::new(Box::new(<$name>::new())); - $(w.component($comp);)* - $r.create_element_static(w, $crate::rsx!{ $($inner)* }); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr, $(@$comp:expr;)* $(%$dep:ident)* $name:path, $($f:ident $(: $v:expr)?),+, { $($inner:tt)* } $($rest:tt)*) => { - $r.create_element({ $(let $dep = $dep.clone();)* move || { - $crate::widget::WidgetLoad::new({ - let mut elem = <$name>::new(); - $(elem.$f = $($v)?;)+ - elem - }) $(.component($comp))* - } }, vec![$(Box::new($dep.clone())),*], $crate::rsx!{ $($inner)* }); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr, $(@$comp:expr;)* $(%$dep:ident)* $name:path { $($inner:tt)* } $($rest:tt)*) => { - $r.create_element({ $(let $dep = $dep.clone();)* move || { - $crate::widget::WidgetLoad::new(<$name>::new()) $(.component($comp))* - } }, vec![$(Box::new($dep.clone())),*], $crate::rsx!{ $($inner)* }); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr, $expand:ident => ($($inner:tt)*) $($rest:tt)*) => { - $r.expand(&mut $expand($($inner)*)); - $crate::rsx_inner! { $r, $($rest)* }; - }; - - ($r:expr,) => {}; -} diff --git a/src/render_scope.rs b/src/render_scope.rs deleted file mode 100644 index 9bade78..0000000 --- a/src/render_scope.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! Rendering logic and layout scope management for OSUI. -//! -//! `RenderScope` is responsible for handling transformations, parent-child dimensions, -//! stacking draw operations, and rendering styled output to the terminal. -//! -//! This module allows widgets to accumulate rendering commands (text, shapes, colors), -//! and then flush them to the screen with correct styling and positioning. -//! -//! Used internally by OSUI's layout and rendering system. - -use std::{fmt::Debug, sync::Arc}; - -use crate::{ - prelude::{Context, Handler}, - style::{RawTransform, Style, Transform}, - utils::{self, hex_ansi_bg}, - widget::Widget, - NoRender, RenderWrapperEvent, -}; - -pub trait ElementRenderer { - /// Called right after the `after_render` function is called - #[allow(unused)] - fn before_draw(&mut self, scope: &mut RenderScope, widget: &Arc) {} -} - -/// Represents a single render instruction. -#[derive(Clone)] -enum RenderMethod { - /// Plain text rendering at current transform. - Text(u16, u16, String), - /// Plain text rendering at current transform with the bg and fg swapped. - TextInverted(u16, u16, String), - /// Text rendered with a specific 24-bit color. - TextColored(u16, u16, String, u32), - /// A filled rectangle of a given size and background color. - Rectangle(u16, u16, u16, u16, u32), -} - -/// Stores renderable state and transformation data for a single UI widget. -/// -/// `RenderScope` tracks dimensions, styles, and draw commands such as text or -/// background rectangles. It accumulates instructions that are later executed in the `draw` method. -#[derive(Clone)] -pub struct RenderScope { - transform: RawTransform, - parent_transform: RawTransform, - render_stack: Vec, - style: Style, -} - -pub struct RenderContext(Context, bool); - -impl RenderContext { - pub fn new(c: &Context, focused: bool) -> Self { - Self(c.clone(), focused) - } - - pub fn is_focused(&self) -> bool { - self.1 - } - - pub fn render(&self, w: &Arc, scope: &mut RenderScope) { - self.0.render(w, scope); - } - - pub fn after_render(&self, w: &Arc, scope: &mut RenderScope) { - self.0.after_render(w, scope); - } - - pub fn get_context(&self) -> &Context { - &self.0 - } -} - -impl RenderScope { - /// Creates a new, empty `RenderScope`. - pub fn new() -> RenderScope { - RenderScope { - transform: RawTransform::new(), - parent_transform: RawTransform::new(), - render_stack: Vec::new(), - style: Style::new(), - } - } - - /// Applies a `Transform` to this scope, factoring in parent dimensions. - pub fn apply_transform(&mut self, transform: &Transform) { - transform.use_dimensions(&self.parent_transform, &mut self.transform); - transform.use_position(&self.parent_transform, &mut self.transform); - self.transform.px = transform.px; - self.transform.py = transform.py; - } - - /// Adds a text draw instruction. - pub fn draw_text(&mut self, x: u16, y: u16, text: &str) { - self.render_stack - .push(RenderMethod::Text(x, y, text.to_string())); - let (w, h) = utils::str_size(text); - self.transform.width = self.transform.width.max(w); - self.transform.height = self.transform.height.max(h); - } - - /// Adds a text draw instruction (inverted colors). - pub fn draw_text_inverted(&mut self, x: u16, y: u16, text: &str) { - self.render_stack - .push(RenderMethod::TextInverted(x, y, text.to_string())); - let (w, h) = utils::str_size(text); - self.transform.width = self.transform.width.max(w); - self.transform.height = self.transform.height.max(h); - } - - /// Adds a colored text draw instruction. - pub fn draw_text_colored(&mut self, x: u16, y: u16, text: &str, color: u32) { - self.render_stack - .push(RenderMethod::TextColored(x, y, text.to_string(), color)); - let (w, h) = utils::str_size(text); - self.transform.width = self.transform.width.max(w); - self.transform.height = self.transform.height.max(h); - } - - /// Adds a background rectangle draw instruction. - pub fn draw_rect(&mut self, x: u16, y: u16, width: u16, height: u16, color: u32) { - self.render_stack - .push(RenderMethod::Rectangle(x, y, width, height, color)); - self.transform.width = self.transform.width.max(width); - self.transform.height = self.transform.height.max(height); - } - - /// Manually ensures a minimum area is allocated. - pub fn use_area(&mut self, width: u16, height: u16) { - self.transform.width = self.transform.width.max(width); - self.transform.height = self.transform.height.max(height); - } - - /// Renders the current stack to the terminal. - /// - /// This will draw background styles (e.g. solid fill or outline) first, - /// followed by each draw instruction in the stack. - pub fn draw(&self) { - let width = self.transform.width; - let height = self.transform.height; - - match self.style.background { - crate::style::Background::NoBackground => {} - crate::style::Background::Outline(c) => { - let width = width + self.transform.px * 2; - let height = height + self.transform.py * 2; - if width > 1 && height > 1 { - let d = "─".repeat(width as usize - 2); - utils::print_liner_nopad( - &self.transform, - &self.parent_transform, - &utils::hex_ansi(c), - &format!( - "┌{d}┐{}\n└{d}┘", - format!("\n│{}│", " ".repeat(width as usize - 2)) - .repeat(height as usize - 2), - ), - ); - } - } - crate::style::Background::RoundedOutline(c) => { - let width = width + self.transform.px * 2; - let height = height + self.transform.py * 2; - if width > 1 && height > 1 { - let d = "─".repeat(width as usize - 2); - utils::print_liner_nopad( - &self.transform, - &self.parent_transform, - &utils::hex_ansi(c), - &format!( - "╭{d}╮{}\n╰{d}╯", - format!("\n│{}│", " ".repeat(width as usize - 2)) - .repeat(height as usize - 2), - ), - ); - } - } - crate::style::Background::Solid(c) => utils::print_liner_nopad( - &self.transform, - &self.parent_transform, - &hex_ansi_bg(c), - &std::iter::repeat(" ".repeat(width as usize)) - .take(height as usize) - .collect::>() - .join("\n"), - ), - } - - for m in &self.render_stack { - match m { - RenderMethod::Text(x, y, t) => { - if let Some(c) = self.style.foreground { - utils::print_liner( - *x, - *y, - &self.transform, - &self.parent_transform, - &utils::hex_ansi(c), - t, - ); - } else { - utils::print(*x, *y, &self.transform, &self.parent_transform, t); - } - } - RenderMethod::TextInverted(x, y, t) => { - if let Some(c) = self.style.foreground { - utils::print_liner( - *x, - *y, - &self.transform, - &self.parent_transform, - &utils::hex_ansi_bg(c), - t, - ); - } else { - utils::print(*x, *y, &self.transform, &self.parent_transform, t); - } - } - RenderMethod::TextColored(x, y, t, c) => utils::print_liner( - *x, - *y, - &self.transform, - &self.parent_transform, - &utils::hex_ansi(*c), - t, - ), - RenderMethod::Rectangle(x, y, width, height, c) => utils::print_liner( - *x, - *y, - &self.transform, - &self.parent_transform, - &utils::hex_ansi_bg(*c), - &std::iter::repeat(" ".repeat(*width as usize)) - .take(*height as usize) - .collect::>() - .join("\n"), - ), - } - } - } - - /// Clears all render instructions and resets the internal state. - pub fn clear(&mut self) { - self.render_stack.clear(); - self.transform = RawTransform::new(); - self.style = Style::new(); - } - - /// Gets the currently used width and height. - pub fn get_size(&self) -> (u16, u16) { - (self.transform.width, self.transform.height) - } - - /// Returns current size, or defaults if size is zero. - pub fn get_size_or(&self, width: u16, height: u16) -> (u16, u16) { - ( - if self.transform.width == 0 { - width - } else { - self.transform.width - }, - if self.transform.height == 0 { - height - } else { - self.transform.height - }, - ) - } - - /// Returns size, falling back to parent size if unset. - pub fn get_size_or_parent(&self) -> (u16, u16) { - ( - if self.transform.width == 0 { - self.parent_transform.width - } else { - self.transform.width - }, - if self.transform.height == 0 { - self.parent_transform.height - } else { - self.transform.height - }, - ) - } - /// Returns parent size. - pub fn get_parent_size(&self) -> (u16, u16) { - (self.parent_transform.width, self.parent_transform.height) - } - - /// Gets the dimensions of the parent container. - pub fn get_parent_transform(&self) -> &RawTransform { - &self.parent_transform - } - - /// Sets the transform of the parent container. - pub fn set_parent_transform(&mut self, transform: RawTransform) { - self.parent_transform = transform; - } - - /// Sets the transform of the parent container. - pub fn set_parent_size(&mut self, width: u16, height: u16) { - self.parent_transform.width = width; - self.parent_transform.height = height; - } - - /// Returns a mutable reference to the internal raw transform. - pub fn get_transform_mut(&mut self) -> &mut RawTransform { - &mut self.transform - } - - /// Returns a reference to the internal raw transform. - pub fn get_transform(&self) -> &RawTransform { - &self.transform - } - - /// Sets the style for the current render scope. - pub fn set_style(&mut self, style: Style) { - self.style = style; - } - - /// Gets a mutable reference to the style. - pub fn get_style(&mut self) -> &mut Style { - &mut self.style - } - - pub fn render_widget( - &mut self, - renderer: &mut dyn ElementRenderer, - ctx: &crate::extensions::Context, - widget: &std::sync::Arc, - ) -> bool { - if widget.get::().is_some() { - widget.auto_refresh(); - return false; - } - - if let Some(wrapper) = widget.get::>() { - wrapper.call(widget, &RenderWrapperEvent(self)); - } else { - self.clear(); - - let render_context = RenderContext::new(ctx, widget.is_focused()); - - if let Some(style) = widget.get() { - self.set_style(style); - } - if let Some(t) = widget.get() { - self.apply_transform(&t); - } - - widget.get_elem().render(self, &render_context); - ctx.render(widget, self); - - if let Some(t) = widget.get() { - self.apply_transform(&t); - } - - renderer.before_draw(self, widget); - - self.draw(); - - widget.get_elem().after_render(self, &render_context); - ctx.after_render(widget, self); - } - - widget.auto_refresh(); - - true - } -} - -impl Debug for RenderScope { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "RenderScope") - } -} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..98cc09b --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,91 @@ +pub(crate) mod utils { + //! The `utils` module provides utility functions for terminal manipulation + //! and string operations, designed to enhance terminal-based applications. + + use std::io::{self, Write}; + + /// Clears the terminal screen and moves the cursor to the top-left corner. + /// + /// # Returns + /// A `io::Result<()>` indicating whether the operation succeeded. + /// + /// # Example + /// ``` + /// utils::clear().unwrap(); + /// ``` + pub fn clear() -> io::Result<()> { + print!("\x1B[2J\x1B[H"); + std::io::stdout().flush() + } + + /// Hides the terminal cursor. + /// + /// # Returns + /// A `io::Result<()>` indicating whether the operation succeeded. + /// + /// # Example + /// ``` + /// utils::hide_cursor().unwrap(); + /// ``` + pub fn hide_cursor() -> io::Result<()> { + print!("\x1b[?25l"); + std::io::stdout().flush() + } + + /// Shows the terminal cursor. + /// + /// # Returns + /// A `io::Result<()>` indicating whether the operation succeeded. + /// + /// # Example + /// ``` + /// utils::show_cursor().unwrap(); + /// ``` + pub fn show_cursor() -> io::Result<()> { + print!("\x1B[?25h"); + std::io::stdout().flush() + } + + /// Flushes the terminal's stdout buffer. + /// + /// # Returns + /// A `io::Result<()>` indicating whether the operation succeeded. + /// + /// # Example + /// ``` + /// utils::flush().unwrap(); + /// ``` + pub fn flush() -> io::Result<()> { + std::io::stdout().flush() + } + + pub fn hex_ansi(hex: u32) -> String { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + format!("\x1b[38;2;{r};{g};{b}m") + } + + pub fn hex_ansi_bg(hex: u32) -> String { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + format!("\x1b[48;2;{r};{g};{b}m") + } +} + +pub trait Renderer { + fn draw_text(&mut self, text: &str, x: u32, y: u32) -> crate::Result<()>; +} + +pub struct OsuiRenderer; + +impl Renderer for OsuiRenderer { + fn draw_text(&mut self, text: &str, x: u32, y: u32) -> crate::Result<()> { + for line in text.lines() { + print!("\x1b[{};{}H{line}\x1b[0m", y + 1, x + 1); + } + utils::flush()?; + Ok(()) + } +} diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index b3b9ec0..0000000 --- a/src/state.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, - sync::{Arc, Mutex, MutexGuard}, -}; - -/// Trait for tracking dependencies and reactivity. -pub trait DependencyHandler: std::fmt::Debug + Send + Sync { - /// Called when a dependent is registered. - fn add(&self); - /// Returns `true` if the state has changed since the last check. - fn check(&self) -> bool; -} - -#[derive(Debug, Clone)] -pub struct State { - inner: Arc>>, -} - -#[derive(Debug)] -pub struct Inner { - value: T, - dependencies: usize, - changed: usize, -} - -pub fn use_state(v: T) -> State { - State { - inner: Arc::new(Mutex::new(Inner { - value: v, - dependencies: 0, - changed: 0, - })), - } -} - -impl State { - /// Gets the cloned value, recommended for preventing deadlocks - pub fn get_dl(&self) -> T { - self.inner.lock().unwrap().value.clone() - } -} - -impl State { - /// Gets a lock on the state for read/write access. - pub fn get(&self) -> MutexGuard<'_, Inner> { - self.inner.lock().unwrap() - } - - /// Sets the value and marks it as changed. - pub fn set(&self, v: T) { - let mut inner = self.inner.lock().unwrap(); - inner.value = v; - inner.changed = inner.dependencies; - } - - /// Marks the state as updated. - pub fn update(&self) { - let mut inner = self.inner.lock().unwrap(); - inner.changed = inner.dependencies; - } -} - -impl DependencyHandler for State { - fn check(&self) -> bool { - let mut inner = self.inner.lock().unwrap(); - let i = inner.changed > 0; - if i { - inner.changed -= 1; - } - i - } - - fn add(&self) { - let mut inner = self.inner.lock().unwrap(); - inner.dependencies += 1; - } -} - -impl Display for State { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let inner = self.inner.lock().unwrap(); - write!(f, "{}", inner.value) - } -} - -impl Deref for Inner { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl DerefMut for Inner { - fn deref_mut(&mut self) -> &mut Self::Target { - self.changed = self.dependencies; - &mut self.value - } -} diff --git a/src/style.rs b/src/style.rs deleted file mode 100644 index d1e1015..0000000 --- a/src/style.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Layout and style definitions for OSUI widgets. -//! -//! This module defines the structures used to manage rendering geometry (`Transform`, `RawTransform`), -//! position and size enums (`Position`, `Dimension`), and styling (`Style`, `Background`, etc.). -//! -//! These types are used internally by components to calculate layout and control their appearance. - -use crate::component; - -/// Resolved layout information for a widget after layout calculations. -/// -/// This struct holds concrete values for position (`x`, `y`), dimensions -/// (`width`, `height`), and padding (`px`, `py`). -#[derive(Debug, Clone)] -pub struct RawTransform { - pub x: u16, - pub y: u16, - pub width: u16, - pub height: u16, - pub px: u16, - pub py: u16, - pub offset_y: u16, -} - -/// Horizontal or vertical position relative to a parent container. -#[derive(Debug, Clone)] -pub enum Position { - /// Fixed position in cells from the origin. - Const(u16), - /// Centered in the parent. - Center, - /// Aligned to the end (right or bottom) of the parent. - End, -} - -/// Sizing rule for width or height. -#[derive(Debug, Clone)] -pub enum Dimension { - /// Fills the available space from the parent. - Full, - /// Automatically sized to fit content. - Content, - /// Fixed size in cells. - Const(u16), -} - -/// Background appearance for a widget. -#[derive(Debug, Clone)] -pub enum Background { - /// Transparent / no background. - NoBackground, - /// Draws a basic outline using the given color. - Outline(u32), - /// Draws a rounded outline using the given color. - RoundedOutline(u32), - /// Fills the background with the specified color. - Solid(u32), -} - -component!(Transform { - pub x: Position, - pub y: Position, - pub mx: i32, - pub my: i32, - pub px: u16, - pub py: u16, - pub width: Dimension, - pub height: Dimension, -}); - -component!(Style { - pub background: Background, - pub foreground: Option, -}); - -impl Style { - /// Creates a style with no background or foreground. - pub fn new() -> Self { - Self { - background: Background::NoBackground, - foreground: None, - } - } -} - -impl Transform { - /// Creates a default transform with top-left alignment and content sizing. - pub fn new() -> Transform { - Transform { - x: Position::Const(0), - y: Position::Const(0), - mx: 0, - my: 0, - px: 0, - py: 0, - width: Dimension::Content, - height: Dimension::Content, - } - } - - /// Shortcut for centering both horizontally and vertically. - pub fn center() -> Transform { - Transform { - x: Position::Center, - y: Position::Center, - mx: 0, - my: 0, - px: 0, - py: 0, - width: Dimension::Content, - height: Dimension::Content, - } - } - - /// Aligns the widget to the bottom of its parent. - pub fn bottom(mut self) -> Self { - self.y = Position::End; - self - } - - /// Aligns the widget to the right of its parent. - pub fn right(mut self) -> Self { - self.x = Position::End; - self - } - - /// Adds margin (offset) from parent edge. - pub fn margin(mut self, x: i32, y: i32) -> Self { - self.mx = x; - self.my = y; - self - } - - /// Adds internal spacing (padding) around content. - pub fn padding(mut self, x: u16, y: u16) -> Self { - self.px = x; - self.py = y; - self - } - - /// Sets constant dimensions. - pub fn dimensions_const(mut self, width: u16, height: u16) -> Self { - self.width = Dimension::Const(width); - self.height = Dimension::Const(height); - self - } - - /// Sets constant dimensions. - pub fn dimensions(mut self, width: Dimension, height: Dimension) -> Self { - self.width = width; - self.height = height; - self - } - - /// Resolves `Dimension` rules into absolute values for the given parent size. - pub fn use_dimensions(&self, parent_transform: &RawTransform, raw: &mut RawTransform) { - self.width - .use_dimension(parent_transform.width, &mut raw.width); - self.height - .use_dimension(parent_transform.height, &mut raw.height); - } - - /// Resolves `Position` rules into absolute positions for the given parent size. - pub fn use_position(&self, parent_transform: &RawTransform, raw: &mut RawTransform) { - self.x - .use_position(raw.width, parent_transform.width, self.mx, &mut raw.x); - self.y - .use_position(raw.height, parent_transform.height, self.my, &mut raw.y); - } -} - -impl RawTransform { - /// Creates a new transform with all fields set to 0. - pub fn new() -> RawTransform { - RawTransform { - x: 0, - y: 0, - width: 0, - height: 0, - px: 0, - py: 0, - offset_y: 0, - } - } - - pub fn transform_parent(&mut self, parent: &Self) { - let child_bottom = self.y + self.height; - let parent_bottom = parent.y + parent.height; - - let child_right = self.x + self.width; - let parent_right = parent.x + parent.width; - - if child_bottom > parent_bottom { - if parent_bottom > self.y { - self.height = parent_bottom - self.y; - } else { - self.height = 0; - } - } - - if child_right > parent_right { - if parent_right > self.x { - self.width = parent_right - self.x; - } else { - self.width = 0; - } - } - } -} - -impl Dimension { - /// Applies a dimension rule to determine a final size. - pub fn use_dimension(&self, parent: u16, r: &mut u16) { - match self { - Self::Full => *r = parent, - Self::Content => {} - Self::Const(n) => *r = *n, - } - } -} - -impl Position { - /// Applies a position rule to determine a final coordinate, based on size and parent. - pub fn use_position(&self, size: u16, parent: u16, m: i32, r: &mut u16) { - let base = match self { - Self::Center => { - if parent < size { - return; - } - (parent - size) / 2 - } - Self::Const(n) => *n, - Self::End => { - if parent < size { - return; - } - parent - size - } - }; - - let adjusted = if m >= 0 { - base.checked_add(m as u16) - } else { - base.checked_sub((-m) as u16) - }; - - if let Some(val) = adjusted { - *r = val; - } - } -} - -impl From for Position { - fn from(value: u16) -> Self { - Self::Const(value) - } -} - -impl From for Dimension { - fn from(value: u16) -> Self { - Self::Const(value) - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index ae93413..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! The `utils` module provides utility functions for terminal manipulation -//! and string operations, designed to enhance terminal-based applications. - -use std::io::{self, Write}; - -use crate::style::RawTransform; - -/// Clears the terminal screen and moves the cursor to the top-left corner. -/// -/// # Returns -/// A `io::Result<()>` indicating whether the operation succeeded. -/// -/// # Example -/// ``` -/// utils::clear().unwrap(); -/// ``` -pub fn clear() -> io::Result<()> { - print!("\x1B[2J\x1B[H"); - std::io::stdout().flush() -} - -/// Hides the terminal cursor. -/// -/// # Returns -/// A `io::Result<()>` indicating whether the operation succeeded. -/// -/// # Example -/// ``` -/// utils::hide_cursor().unwrap(); -/// ``` -pub fn hide_cursor() -> io::Result<()> { - print!("\x1b[?25l"); - std::io::stdout().flush() -} - -/// Shows the terminal cursor. -/// -/// # Returns -/// A `io::Result<()>` indicating whether the operation succeeded. -/// -/// # Example -/// ``` -/// utils::show_cursor().unwrap(); -/// ``` -pub fn show_cursor() -> io::Result<()> { - print!("\x1B[?25h"); - std::io::stdout().flush() -} - -/// Flushes the terminal's stdout buffer. -/// -/// # Returns -/// A `io::Result<()>` indicating whether the operation succeeded. -/// -/// # Example -/// ``` -/// utils::flush().unwrap(); -/// ``` -pub fn flush() -> io::Result<()> { - std::io::stdout().flush() -} - -/// Calculates the width and height of a string when rendered in a terminal. -/// -/// # Arguments -/// - `s`: The input string. -/// -/// # Returns -/// A tuple `(u16, u16)` where: -/// - The first value is the maximum width of the string in characters. -/// - The second value is the height of the string in lines. -/// -/// # Example -/// ``` -/// let (width, height) = utils::str_size("Hello\nWorld!"); -/// assert_eq!((5, 2), (width, height)); -/// ``` -pub fn str_size(s: &str) -> (u16, u16) { - let mut height = 1; - let mut max_width = 0; - let mut current_width = 0; - - for b in s.bytes() { - if b == b'\n' { - height += 1; - max_width = max_width.max(current_width); - current_width = 0; - } else { - current_width += 1; - } - } - - max_width = max_width.max(current_width); - - (max_width, height) -} - -pub fn hex_ansi(hex: u32) -> String { - let r = ((hex >> 16) & 0xFF) as u8; - let g = ((hex >> 8) & 0xFF) as u8; - let b = (hex & 0xFF) as u8; - format!("\x1b[38;2;{r};{g};{b}m") -} - -pub fn hex_ansi_bg(hex: u32) -> String { - let r = ((hex >> 16) & 0xFF) as u8; - let g = ((hex >> 8) & 0xFF) as u8; - let b = (hex & 0xFF) as u8; - format!("\x1b[48;2;{r};{g};{b}m") -} - -pub(crate) fn print( - x: u16, - y: u16, - transform: &RawTransform, - parent_transform: &RawTransform, - text: &str, -) { - let x = transform.x + transform.px + x; - let y = transform.y + transform.py + y; - - for (i, line) in text.lines().enumerate() { - if parent_transform.offset_y > y + i as u16 { - continue; - } - - let y = y + i as u16 - parent_transform.offset_y; - - if y >= parent_transform.height + parent_transform.y + parent_transform.py { - break; - } - print!("\x1b[{};{}H{line}\x1b[0m", y + 1, x + 1); - flush().unwrap(); - } -} - -pub(crate) fn print_liner( - x: u16, - y: u16, - transform: &RawTransform, - parent_transform: &RawTransform, - liner: &str, - text: &str, -) { - let x = transform.x + transform.px + x; - let y = transform.y + transform.py + y; - - for (i, line) in text.lines().enumerate() { - if parent_transform.offset_y > y + i as u16 { - continue; - } - - let y = y + i as u16 - parent_transform.offset_y; - - if y >= parent_transform.height + parent_transform.y + parent_transform.py { - break; - } - print!("\x1b[{};{}H{liner}{line}\x1b[0m", y + 1, x + 1); - flush().unwrap(); - } -} - -pub(crate) fn print_liner_nopad( - transform: &RawTransform, - parent_transform: &RawTransform, - liner: &str, - text: &str, -) { - for (i, line) in text.lines().enumerate() { - if parent_transform.offset_y > transform.y + i as u16 { - continue; - } - - let y = transform.y + i as u16 - parent_transform.offset_y; - - if y >= parent_transform.height + parent_transform.y + parent_transform.py { - break; - } - print!("\x1b[{};{}H{liner}{line}\x1b[0m", y + 1, transform.x + 1); - flush().unwrap(); - } -} diff --git a/src/widget.rs b/src/widget.rs deleted file mode 100644 index 1a28e6e..0000000 --- a/src/widget.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! Core widget infrastructure for OSUI. -//! -//! This module defines the traits and types that power the widget system, including: -//! - `Element` and `Component`: building blocks for renderable and state-carrying objects -//! - `Widget`: container type for wrapping elements and components -//! - `StaticWidget` and `DynWidget`: concrete widget implementations -//! - Dependency tracking and reactive updates for dynamic widgets - -use std::{ - any::{Any, TypeId}, - collections::HashMap, - sync::{Arc, Mutex, MutexGuard}, -}; - -use crate::{ - prelude::{Event, Handler}, - render_scope::RenderScope, - state::DependencyHandler, -}; - -/// A trait object for any renderable UI element. -pub type BoxedElement = Box; - -/// A trait object for any component attached to a widget. -pub type BoxedComponent = Box; - -/// Core trait for anything that can be rendered in the UI. -/// -/// Elements are responsible for their own rendering logic and can define hooks -/// for lifecycle events and child rendering. -pub trait Element: Send + Sync { - /// Called to perform rendering for the element. - #[allow(unused)] - fn render( - &mut self, - scope: &mut RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - } - - /// Called after rendering, for follow-up logic or cleanup. - #[allow(unused)] - fn after_render( - &mut self, - scope: &mut RenderScope, - render_context: &crate::render_scope::RenderContext, - ) { - } - - /// Called to draw child widgets, if any. - #[allow(unused)] - fn draw_child(&mut self, element: &Arc) {} - - /// Called to undraw child widgets, if any. - #[allow(unused)] - fn undraw_child(&mut self, element: &Arc) {} - - #[allow(unused)] - fn event(&mut self, event: &dyn Event) {} - - fn is_ghost(&mut self) -> bool { - false - } - - /// Returns a type-erased reference to this object. - fn as_any(&self) -> &dyn Any; - - /// Returns a mutable type-erased reference to this object. - fn as_any_mut(&mut self) -> &mut dyn Any; -} - -/// Optional trait for state or metadata attached to widgets. -/// -/// Components can be used to store data such as layout style, -/// animation state, bindings, or other logic. -pub trait Component: Send + Sync { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; -} - -/// Container for a widget during initial construction. -/// -/// Holds the root element and any associated components. -pub struct WidgetLoad(BoxedElement, HashMap); - -/// A widget with fixed content and no dynamic behavior. -pub struct StaticWidget { - element: Mutex, - components: Mutex>, - focused: Mutex, -} - -/// A widget with dynamic content and dependency tracking. -/// -/// This widget supports reactive updates and can be rebuilt using -/// a provided `FnMut()` function when dependencies change. -pub struct DynWidget { - element: Mutex, - components: Mutex>, - load: Mutex WidgetLoad + Send + Sync>>, - dependencies: Mutex>>, - injection: Mutex WidgetLoad + Send + Sync>>>, - focused: Mutex, -} - -/// A reference-counted wrapper around either a static or dynamic widget. -/// -/// Use `Arc` as the standard way to store and pass around widgets in the UI tree. -pub enum Widget { - Static(StaticWidget), - Dynamic(DynWidget), -} - -impl WidgetLoad { - /// Creates a new `WidgetLoad` with a given root element. - pub fn new(e: E) -> Self { - Self(Box::new(e), HashMap::new()) - } - - /// Attaches a component if one of its type doesn't already exist. - pub fn component(mut self, c: C) -> Self { - self.1.entry(c.type_id()).or_insert_with(|| Box::new(c)); - self - } - - /// Replaces any existing component of the same type. - pub fn set_component(mut self, c: C) -> Self { - self.1.insert(c.type_id(), Box::new(c)); - self - } - - /// Attempts to retrieve a component of the given type. - pub fn get(&self) -> Option { - self.1 - .get(&TypeId::of::()) - .and_then(|c| c.as_any().downcast_ref::()) - .map(|c| c.clone()) - } -} - -impl Widget { - pub fn new_static(e: BoxedElement) -> Self { - Self::Static(StaticWidget::new(e)) - } - - pub fn new_dyn WidgetLoad + 'static + Send + Sync>(e: F) -> Self { - Self::Dynamic(DynWidget::new(e)) - } -} - -impl Widget { - pub fn is_focused(&self) -> bool { - match self { - Self::Dynamic(w) => *w.focused.lock().unwrap(), - Self::Static(w) => *w.focused.lock().unwrap(), - } - } - - pub fn is_ghost(&self) -> bool { - match self { - Self::Dynamic(w) => w.element.lock().unwrap().is_ghost(), - Self::Static(w) => w.element.lock().unwrap().is_ghost(), - } - } - - pub fn set_focused(&self, f: bool) { - match self { - Self::Dynamic(w) => *w.focused.lock().unwrap() = f, - Self::Static(w) => *w.focused.lock().unwrap() = f, - } - } - - pub fn get_elem(&'_ self) -> MutexGuard<'_, BoxedElement> { - match self { - Widget::Static(w) => w.get_elem(), - Widget::Dynamic(w) => w.get_elem(), - } - } - - pub fn after_render(&self) { - match self { - Widget::Static(w) => w.after_render(), - Widget::Dynamic(w) => w.after_render(), - } - } - - pub fn component(self: &Arc, c: C) -> &Arc { - match &**self { - Widget::Static(w) => { - w.component(c); - } - Widget::Dynamic(w) => { - w.component(c); - } - } - self - } - - pub fn set_component(self: &Arc, c: C) -> &Arc { - match &**self { - Widget::Static(w) => { - w.set_component(c); - } - Widget::Dynamic(w) => { - w.set_component(c); - } - } - self - } - - pub fn get(&self) -> Option { - match self { - Widget::Static(w) => w.get(), - Widget::Dynamic(w) => w.get(), - } - } - - pub fn inject WidgetLoad + 'static + Send + Sync>( - self: &Arc, - mut f: F, - ) { - match &**self { - Widget::Dynamic(w) => { - w.inject(f); - } - Widget::Static(w) => { - let wl = WidgetLoad::new(String::new()); - w.components.lock().unwrap().extend(f(wl).1); - } - } - } - - pub fn refresh(self: &Arc) { - match &**self { - Widget::Dynamic(w) => { - w.refresh(); - } - Widget::Static(_) => {} - } - } - - pub fn auto_refresh(self: &Arc) { - match &**self { - Widget::Dynamic(w) => { - w.auto_refresh(); - } - Widget::Static(_) => {} - } - } - - pub fn dependency(self: &Arc, d: D) -> &Arc { - match &**self { - Widget::Dynamic(w) => { - w.dependency(d); - } - Widget::Static(_) => {} - } - self - } - - pub fn dependency_box(self: &Arc, d: Box) -> &Arc { - match &**self { - Widget::Dynamic(w) => { - w.dependency_box(d); - } - Widget::Static(_) => {} - } - self - } - - pub fn event(self: &Arc, e: &E) { - if let Some(wrapper) = self.get::>() { - wrapper.call(self, e); - } - - if self.is_focused() { - match &**self { - Widget::Dynamic(w) => { - w.get_elem().event(e); - } - Widget::Static(w) => { - w.get_elem().event(e); - } - } - } - } -} - -impl StaticWidget { - fn after_render(&self) {} - fn get_elem(&'_ self) -> MutexGuard<'_, BoxedElement> { - self.element.lock().unwrap() - } -} - -impl StaticWidget { - pub fn new(e: BoxedElement) -> Self { - Self { - element: Mutex::new(e), - components: Mutex::new(HashMap::new()), - focused: Mutex::new(false), - } - } - - pub fn component(&self, c: C) { - self.components - .lock() - .unwrap() - .entry(c.type_id()) - .or_insert_with(|| Box::new(c)); - } - - pub fn set_component(&self, c: C) { - self.components - .lock() - .unwrap() - .insert(c.type_id(), Box::new(c)); - } - - pub fn get(&self) -> Option { - self.components - .lock() - .unwrap() - .get(&TypeId::of::()) - .and_then(|c| c.as_any().downcast_ref::()) - .map(Clone::clone) - } -} - -impl DynWidget { - fn after_render(&self) {} - fn get_elem(&'_ self) -> MutexGuard<'_, BoxedElement> { - self.element.lock().unwrap() - } -} - -impl DynWidget { - pub fn new WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self { - let wl = e(); - Self { - element: Mutex::new(wl.0), - components: Mutex::new(wl.1), - load: Mutex::new(Box::new(e)), - dependencies: Mutex::new(Vec::new()), - injection: Mutex::new(None), - focused: Mutex::new(false), - } - } - - /// Replace or modify the widget's structure on reload and init. - pub fn inject WidgetLoad + 'static + Send + Sync>(&self, f: F) { - *self.injection.lock().unwrap() = Some(Box::new(f)); - self.refresh(); - } - - /// Rebuild the widget's content by re-evaluating the original function. - pub fn refresh(&self) { - let mut w = (self.load.lock().unwrap())(); - - if let Some(we) = &mut *self.injection.lock().unwrap() { - w = (we)(w); - } - - *self.element.lock().unwrap() = w.0; - *self.components.lock().unwrap() = w.1; - } - - /// Re-evaluates the widget if any dependency has changed. - pub fn auto_refresh(&self) { - for d in self.dependencies.lock().unwrap().iter() { - if d.check() { - self.refresh(); - } - } - } - - /// Adds a dependency to this widget. - pub fn dependency(&self, d: D) { - d.add(); - self.dependencies.lock().unwrap().push(Box::new(d)); - } - - /// Adds a boxed dependency. - pub fn dependency_box(&self, d: Box) { - d.add(); - self.dependencies.lock().unwrap().push(d); - } - - pub fn component(&self, c: C) { - self.components - .lock() - .unwrap() - .entry(c.type_id()) - .or_insert_with(|| Box::new(c)); - } - - pub fn set_component(&self, c: C) { - self.components - .lock() - .unwrap() - .insert(c.type_id(), Box::new(c)); - } - - pub fn get(&self) -> Option { - self.components - .lock() - .unwrap() - .get(&TypeId::of::()) - .and_then(|c| c.as_any().downcast_ref::()) - .map(Clone::clone) - } -}