From 4afe1c24c2464d0fb8adf193bf252c8339b5549a Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 17:22:40 +0100 Subject: [PATCH 01/15] Added use_mount --- src/state.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/state.rs b/src/state.rs index 669ecca..ab67050 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,6 +19,8 @@ pub struct Inner<'a, T> { updated: bool, } +pub struct Mount; + pub fn use_state(v: T) -> State { State { value: Arc::new(Mutex::new(v)), @@ -112,6 +114,12 @@ impl HookDependency for State { } } +impl HookDependency for Mount { + fn on_update(&self, hook: HookEffect) { + hook.call(); + } +} + pub fn use_effect(f: F, dependencies: &[&dyn HookDependency]) { let f = Arc::new(Mutex::new(f)); let hook = HookEffect(Arc::new(Mutex::new({ @@ -126,3 +134,7 @@ pub fn use_effect(f: F, dependencies: &[&dyn d.on_update(hook.clone()); } } + +pub fn use_mount() -> Mount { + Mount +} From f6a4bbd8ac86e5e452b3af2a8655303553807cb4 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 18:38:07 +0100 Subject: [PATCH 02/15] Frontend structure --- Cargo.toml | 5 ++-- examples/hello_world.rs | 10 ++----- src/component.rs | 2 +- src/frontend.rs | 58 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/macros.rs | 11 ++++++++ 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 src/frontend.rs create mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index d9080c4..a40db29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,10 @@ license = "Apache-2.0" description = "A TUI library for advanced uis" repository = "https://github.com/osui-rs/osui" homepage = "https://osui.netlify.app" -documentation = "https://osui.netlify.app/docs/" +documentation = "https://osui.netlify.app/docs/" [features] -no_rsx = [] -no_elem = [] +rsx = [] [lib] name = "osui" diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 87702ef..948c67c 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use osui::prelude::*; +use osui::{prelude::*, rsx}; #[derive(Debug, Clone)] pub struct KeyPress; @@ -94,13 +94,7 @@ fn app(cx: &Arc) -> View { ); } - Arc::new({ - let cx = cx.clone(); - - move |ctx| { - cx.draw_children(ctx); - } - }) + rsx! {}.view(cx.clone()) } fn my_component(cx: &Arc) -> View { diff --git a/src/component.rs b/src/component.rs index a65bde0..27f6c14 100644 --- a/src/component.rs +++ b/src/component.rs @@ -31,7 +31,7 @@ pub struct Context { component: Mutex, event_handlers: Mutex>>, view: Mutex, - scopes: Mutex>>, + pub(crate) scopes: Mutex>>, } impl Context { diff --git a/src/frontend.rs b/src/frontend.rs new file mode 100644 index 0000000..e90ea99 --- /dev/null +++ b/src/frontend.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use crate::{ + component::{Context, Scope}, + state::HookDependency, + View, +}; + +pub enum RsxScope { + Static(Arc), + Dynamic( + Arc) + Send + Sync>, + Vec>, + ), +} + +pub struct Rsx(Vec); + +impl Rsx { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn static_scope(&mut self, scope: Arc) { + self.0.push(RsxScope::Static(scope)); + } + + pub fn dynamic_scope) + Send + Sync + 'static>( + &mut self, + drawer: F, + dependencies: Vec>, + ) { + self.0 + .push(RsxScope::Dynamic(Arc::new(drawer), dependencies)); + } + + pub fn view(&self, context: Arc) -> View { + for scope in &self.0 { + match scope { + RsxScope::Static(scope) => context.scopes.lock().unwrap().push(scope.clone()), + RsxScope::Dynamic(drawer, dependencies) => { + let drawer = drawer.clone(); + context.dyn_scope( + move |c| drawer(c), + &dependencies.iter().map(|d| d.as_ref()).collect::>(), + ); + } + } + } + + let context = context.clone(); + Arc::new({ + move |ctx| { + context.draw_children(ctx); + } + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 16af55e..1462c02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ use crate::render::DrawContext; pub mod component; pub mod engine; +pub mod frontend; +pub mod macros; pub mod render; pub mod state; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..331ef5c --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,11 @@ +#[macro_export] +macro_rules! rsx { + () => { + $crate::frontend::Rsx::new() + }; +} + +#[macro_export] +macro_rules! rsx_scope { + () => {}; +} From a9ecc8da8b1c26ac79b87c34c21a7e7822be5afb Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 18:58:24 +0100 Subject: [PATCH 03/15] working Rsx --- examples/hello_world.rs | 107 +++++++++++++++++++--------------------- src/component.rs | 6 +++ src/macros.rs | 35 ++++++++++++- 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 948c67c..dfbca25 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -40,61 +40,55 @@ fn app(cx: &Arc) -> View { } }); - { - let scope = cx.scope(); + // { + // cx.dyn_scope( + // { + // let count = count.clone(); + // move |scope| { + // if *count.get() != 0 { + // if scope.children.lock().unwrap().len() == 0 { + // scope.child( + // my_component, + // Some(Arc::new(|ctx, view| { + // let area = ctx.allocate(5, 2, 0, 0); + // ctx.draw_view(area, view) + // })), + // ); + // } + // } else { + // scope.children.lock().unwrap().clear(); + // } + // } + // }, + // &[&count], + // ); + // } - scope.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(5, 0, 0, 0); - ctx.draw_view(area, view) - })), - ); + // { + // cx.dyn_scope( + // { + // let count = count.clone(); + // move |scope| { + // scope.children.lock().unwrap().clear(); + + // for i in 0..count.get_dl() { + // scope.view(Arc::new(move |ctx| { + // ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); + // })); + // } + // } + // }, + // &[&count], + // ); + // } + + rsx! { + my_component (ctx, view) { + let area = ctx.allocate(0, 0, 10, 10); + ctx.draw_view(area, view); + } } - - { - cx.dyn_scope( - { - let count = count.clone(); - move |scope| { - if *count.get() != 0 { - if scope.children.lock().unwrap().len() == 0 { - scope.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(5, 2, 0, 0); - ctx.draw_view(area, view) - })), - ); - } - } else { - scope.children.lock().unwrap().clear(); - } - } - }, - &[&count], - ); - } - - { - cx.dyn_scope( - { - let count = count.clone(); - move |scope| { - scope.children.lock().unwrap().clear(); - - for i in 0..count.get_dl() { - scope.view(Arc::new(move |ctx| { - ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); - })); - } - } - }, - &[&count], - ); - } - - rsx! {}.view(cx.clone()) + .view(cx.clone()) } fn my_component(cx: &Arc) -> View { @@ -112,7 +106,8 @@ fn my_component(cx: &Arc) -> View { } }); - Arc::new(move |ctx| { - ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); - }) + rsx! { + "Count: {count}" + } + .view(cx.clone()) } diff --git a/src/component.rs b/src/component.rs index 27f6c14..0d43ed2 100644 --- a/src/component.rs +++ b/src/component.rs @@ -179,6 +179,12 @@ impl Context { } impl Scope { + pub fn new() -> Arc { + Arc::new(Self { + children: Mutex::new(Vec::new()), + }) + } + pub fn child) -> View + Send + Sync + 'static>( self: &Arc, child: F, diff --git a/src/macros.rs b/src/macros.rs index 331ef5c..fbb33c4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,9 +3,42 @@ macro_rules! rsx { () => { $crate::frontend::Rsx::new() }; + + ($($rsx:tt)+) => {{ + let mut r = $crate::frontend::Rsx::new(); + $crate::rsx_scope!(r, $($rsx)+); + r + }}; } #[macro_export] macro_rules! rsx_scope { - () => {}; + ($rsx:expr, $text:literal $($rest:tt)*) => { + let scope = $crate::component::Scope::new(); + + scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); + + $rsx.static_scope(scope); + $crate::rsx_scope!($rsx, $($rest)*); + }; + + ($rsx:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { + let scope = $crate::component::Scope::new(); + + scope.child($component, Some(Arc::new(|$ctx, $view| $body))); + + $rsx.static_scope(scope); + $crate::rsx_scope!($rsx, $($rest)*); + }; + + ($rsx:expr, $component:ident $($rest:tt)*) => { + let scope = $crate::component::Scope::new(); + + scope.child($component, None); + + $rsx.static_scope(scope); + $crate::rsx_scope!($rsx, $($rest)*); + }; + + ($rsx:expr,) => {}; } From ecc11727376666aa0ff5e9c925af36308be84a2a Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 19:22:36 +0100 Subject: [PATCH 04/15] Rsx scope child --- src/macros.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index fbb33c4..28afe93 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -13,10 +13,12 @@ macro_rules! rsx { #[macro_export] macro_rules! rsx_scope { - ($rsx:expr, $text:literal $($rest:tt)*) => { + ($rsx:expr, $(%$($dep:ident)+)? $text:literal $($rest:tt)*) => { let scope = $crate::component::Scope::new(); - scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); + $($(let $dep = $dep.clone();)+)? + + $crate::rsx_child!(scope, $text); $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); @@ -25,7 +27,7 @@ macro_rules! rsx_scope { ($rsx:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { let scope = $crate::component::Scope::new(); - scope.child($component, Some(Arc::new(|$ctx, $view| $body))); + $crate::rsx_child!(scope, $component ($ctx, $view) $body); $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); @@ -34,7 +36,7 @@ macro_rules! rsx_scope { ($rsx:expr, $component:ident $($rest:tt)*) => { let scope = $crate::component::Scope::new(); - scope.child($component, None); + $crate::rsx_child!(scope, $component $body); $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); @@ -42,3 +44,23 @@ macro_rules! rsx_scope { ($rsx:expr,) => {}; } + +#[macro_export] +macro_rules! rsx_child { + ($scope:expr, $text:literal $($rest:tt)*) => { + $scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); + $crate::rsx_child!($scope, $($rest)*); + }; + + ($scope:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { + $scope.child($component, Some(Arc::new(|$ctx, $view| $body))); + $crate::rsx_child!($scope, $($rest)*); + }; + + ($scope:expr, $component:ident $($rest:tt)*) => { + $scope.child($component, None); + $crate::rsx_child!($scope, $($rest)*); + }; + + ($rsx:expr,) => {}; +} From 993a790da72ab0cea6557964679679244f89b464 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 20:00:21 +0100 Subject: [PATCH 05/15] If statement rsx --- examples/hello_world.rs | 10 ++++-- src/macros.rs | 68 ++++++++++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index dfbca25..9f99450 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -83,10 +83,14 @@ fn app(cx: &Arc) -> View { // } rsx! { - my_component (ctx, view) { - let area = ctx.allocate(0, 0, 10, 10); - ctx.draw_view(area, view); + if %count (*count.get() > 0) { + my_component (ctx, view) { + let area = ctx.allocate(0, 1, 10, 10); + ctx.draw_view(area, view); + } } + + %count "{count}" } .view(cx.clone()) } diff --git a/src/macros.rs b/src/macros.rs index 28afe93..7dd86a0 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -6,39 +6,71 @@ macro_rules! rsx { ($($rsx:tt)+) => {{ let mut r = $crate::frontend::Rsx::new(); - $crate::rsx_scope!(r, $($rsx)+); + { + $crate::rsx_scope!(r, $($rsx)+); + } r }}; } #[macro_export] macro_rules! rsx_scope { + ($rsx:expr, if $(%$($dep:ident)+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { + { + $($(let $dep = $dep.clone();)+)? + + $rsx.dynamic_scope({ + $($(let $dep = $dep.clone();)+)? + move |scope| { + if $st { + if scope.children.lock().unwrap().len() == 0 { + $crate::rsx_child!(scope, $($inner)*); + } + } else { + scope.children.lock().unwrap().clear(); + } + } + }, vec![$($(Box::new($dep)),+)?]); + } + + $crate::rsx_scope!($rsx, $($rest)*); + }; + ($rsx:expr, $(%$($dep:ident)+)? $text:literal $($rest:tt)*) => { - let scope = $crate::component::Scope::new(); + { + let scope = $crate::component::Scope::new(); - $($(let $dep = $dep.clone();)+)? + $($(let $dep = $dep.clone();)+)? - $crate::rsx_child!(scope, $text); + $crate::rsx_child!(scope, $text); + + $rsx.static_scope(scope); + } - $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); }; ($rsx:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { - let scope = $crate::component::Scope::new(); + { + let scope = $crate::component::Scope::new(); - $crate::rsx_child!(scope, $component ($ctx, $view) $body); + $crate::rsx_child!(scope, $component ($ctx, $view) $body); + + $rsx.static_scope(scope); + } - $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); }; ($rsx:expr, $component:ident $($rest:tt)*) => { - let scope = $crate::component::Scope::new(); + { + let scope = $crate::component::Scope::new(); - $crate::rsx_child!(scope, $component $body); + $crate::rsx_child!(scope, $component $body); + + $rsx.static_scope(scope); + } - $rsx.static_scope(scope); $crate::rsx_scope!($rsx, $($rest)*); }; @@ -48,17 +80,25 @@ macro_rules! rsx_scope { #[macro_export] macro_rules! rsx_child { ($scope:expr, $text:literal $($rest:tt)*) => { - $scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); + { + $scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); + } + $crate::rsx_child!($scope, $($rest)*); }; ($scope:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { - $scope.child($component, Some(Arc::new(|$ctx, $view| $body))); + { + $scope.child($component, Some(Arc::new(|$ctx, $view| $body))); + } + $crate::rsx_child!($scope, $($rest)*); }; ($scope:expr, $component:ident $($rest:tt)*) => { - $scope.child($component, None); + { + $scope.child($component, None); + } $crate::rsx_child!($scope, $($rest)*); }; From 04a69b138a8112ff6fd761484fc7721cd453eeb8 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 20:12:47 +0100 Subject: [PATCH 06/15] RSX For loop --- examples/hello_world.rs | 48 ++++------------------------------------- src/macros.rs | 19 +++++++++++++++- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 9f99450..b00799d 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -40,57 +40,17 @@ fn app(cx: &Arc) -> View { } }); - // { - // cx.dyn_scope( - // { - // let count = count.clone(); - // move |scope| { - // if *count.get() != 0 { - // if scope.children.lock().unwrap().len() == 0 { - // scope.child( - // my_component, - // Some(Arc::new(|ctx, view| { - // let area = ctx.allocate(5, 2, 0, 0); - // ctx.draw_view(area, view) - // })), - // ); - // } - // } else { - // scope.children.lock().unwrap().clear(); - // } - // } - // }, - // &[&count], - // ); - // } - - // { - // cx.dyn_scope( - // { - // let count = count.clone(); - // move |scope| { - // scope.children.lock().unwrap().clear(); - - // for i in 0..count.get_dl() { - // scope.view(Arc::new(move |ctx| { - // ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); - // })); - // } - // } - // }, - // &[&count], - // ); - // } - rsx! { if %count (*count.get() > 0) { my_component (ctx, view) { - let area = ctx.allocate(0, 1, 10, 10); + let area = ctx.allocate(10, 0, 10, 10); ctx.draw_view(area, view); } } - %count "{count}" + for %count (i in 0..count.get_dl()) { + "{i}" + } } .view(cx.clone()) } diff --git a/src/macros.rs b/src/macros.rs index 7dd86a0..3724013 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,6 +15,23 @@ macro_rules! rsx { #[macro_export] macro_rules! rsx_scope { + ($rsx:expr, for $(%$($dep:ident)+)? ($p:pat in $v:expr) {$($inner:tt)*} $($rest:tt)*) => { + { + $($(let $dep = $dep.clone();)+)? + + $rsx.dynamic_scope({ + $($(let $dep = $dep.clone();)+)? + move |scope| { + for $p in $v { + $crate::rsx_child!(scope, $($inner)*); + } + } + }, vec![$($(Box::new($dep)),+)?]); + } + + $crate::rsx_scope!($rsx, $($rest)*); + }; + ($rsx:expr, if $(%$($dep:ident)+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { { $($(let $dep = $dep.clone();)+)? @@ -66,7 +83,7 @@ macro_rules! rsx_scope { { let scope = $crate::component::Scope::new(); - $crate::rsx_child!(scope, $component $body); + $crate::rsx_child!(scope, $component); $rsx.static_scope(scope); } From 229353f5f038c50bb244238267f481087e4c70a3 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 20:39:17 +0100 Subject: [PATCH 07/15] RSX dependency --- examples/hello_world.rs | 10 +++++----- src/macros.rs | 27 +++++++++++++++++---------- src/state.rs | 1 + 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index b00799d..c379a2c 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -24,6 +24,7 @@ fn main() { fn app(cx: &Arc) -> View { let count = use_state(0); + let mount = use_mount(); cx.on_event({ let count = count.clone(); @@ -32,7 +33,6 @@ fn app(cx: &Arc) -> View { if *count > 5 { *count = 0; - return; } @@ -41,16 +41,16 @@ fn app(cx: &Arc) -> View { }); rsx! { - if %count (*count.get() > 0) { + if %count, mount as _, (*count.get() > 0) { my_component (ctx, view) { let area = ctx.allocate(10, 0, 10, 10); ctx.draw_view(area, view); } } - for %count (i in 0..count.get_dl()) { - "{i}" - } + // for %count, mount as _, (i in 0..count.get_dl()) { + // "{i}" + // } } .view(cx.clone()) } diff --git a/src/macros.rs b/src/macros.rs index 3724013..a87d1b1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,12 +15,10 @@ macro_rules! rsx { #[macro_export] macro_rules! rsx_scope { - ($rsx:expr, for $(%$($dep:ident)+)? ($p:pat in $v:expr) {$($inner:tt)*} $($rest:tt)*) => { + ($rsx:expr, for $(%$($dep:ident $(as $dp:pat,)?),+)? ($p:pat in $v:expr) {$($inner:tt)*} $($rest:tt)*) => { { - $($(let $dep = $dep.clone();)+)? - $rsx.dynamic_scope({ - $($(let $dep = $dep.clone();)+)? + $($($crate::rsx_dep!($dep $(as $dp)?);)+)? move |scope| { for $p in $v { $crate::rsx_child!(scope, $($inner)*); @@ -32,12 +30,10 @@ macro_rules! rsx_scope { $crate::rsx_scope!($rsx, $($rest)*); }; - ($rsx:expr, if $(%$($dep:ident)+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { + ($rsx:expr, if $(%$($dep:ident $(as $dp:pat,)?),+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { { - $($(let $dep = $dep.clone();)+)? - $rsx.dynamic_scope({ - $($(let $dep = $dep.clone();)+)? + $($($crate::rsx_dep!($dep $(as $dp)?);)+)? move |scope| { if $st { if scope.children.lock().unwrap().len() == 0 { @@ -53,11 +49,11 @@ macro_rules! rsx_scope { $crate::rsx_scope!($rsx, $($rest)*); }; - ($rsx:expr, $(%$($dep:ident)+)? $text:literal $($rest:tt)*) => { + ($rsx:expr, $(%$($dep:ident $(as $dp:pat,)?),+)? $text:literal $($rest:tt)*) => { { let scope = $crate::component::Scope::new(); - $($(let $dep = $dep.clone();)+)? + $($($crate::rsx_dep!($dep $(as $dp)?);)+)? $crate::rsx_child!(scope, $text); @@ -121,3 +117,14 @@ macro_rules! rsx_child { ($rsx:expr,) => {}; } + +#[macro_export] +macro_rules! rsx_dep { + ($dep:ident as $dp:pat) => { + let $dp = $dep.clone(); + }; + + ($dep:ident) => { + let $dep = $dep.clone(); + }; +} diff --git a/src/state.rs b/src/state.rs index ab67050..d4c083b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,6 +19,7 @@ pub struct Inner<'a, T> { updated: bool, } +#[derive(Debug, Clone, Copy)] pub struct Mount; pub fn use_state(v: T) -> State { From eb702dce454f52ca48842452dfbb7e0af77cc307 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 20:40:24 +0100 Subject: [PATCH 08/15] RSX dependency --- examples/hello_world.rs | 6 +++--- src/macros.rs | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index c379a2c..2c877d7 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -48,9 +48,9 @@ fn app(cx: &Arc) -> View { } } - // for %count, mount as _, (i in 0..count.get_dl()) { - // "{i}" - // } + for %count, mount as _, (i in 0..count.get_dl()) { + "{i}" + } } .view(cx.clone()) } diff --git a/src/macros.rs b/src/macros.rs index a87d1b1..b1b1a57 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -17,6 +17,8 @@ macro_rules! rsx { macro_rules! rsx_scope { ($rsx:expr, for $(%$($dep:ident $(as $dp:pat,)?),+)? ($p:pat in $v:expr) {$($inner:tt)*} $($rest:tt)*) => { { + $($($crate::rsx_dep!($dep);)+)? + $rsx.dynamic_scope({ $($($crate::rsx_dep!($dep $(as $dp)?);)+)? move |scope| { @@ -32,6 +34,8 @@ macro_rules! rsx_scope { ($rsx:expr, if $(%$($dep:ident $(as $dp:pat,)?),+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { { + $($($crate::rsx_dep!($dep);)+)? + $rsx.dynamic_scope({ $($($crate::rsx_dep!($dep $(as $dp)?);)+)? move |scope| { From 98598bec15d6ee159f8851d5411a9e5532e6e99d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 20:46:26 +0100 Subject: [PATCH 09/15] Initial dynamic render --- examples/hello_world.rs | 15 +++++++++++---- src/component.rs | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 2c877d7..7dfb5da 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -24,7 +24,6 @@ fn main() { fn app(cx: &Arc) -> View { let count = use_state(0); - let mount = use_mount(); cx.on_event({ let count = count.clone(); @@ -41,14 +40,22 @@ fn app(cx: &Arc) -> View { }); rsx! { - if %count, mount as _, (*count.get() > 0) { + // Static scope + my_component (ctx, view) { + let area = ctx.allocate(5, 0, 10, 10); + ctx.draw_view(area, view); + } + + // Dynamic scope + if %count (*count.get() > 5) { my_component (ctx, view) { - let area = ctx.allocate(10, 0, 10, 10); + let area = ctx.allocate(18, 0, 10, 10); ctx.draw_view(area, view); } } - for %count, mount as _, (i in 0..count.get_dl()) { + // Dynamic scope + for %count (i in 0..count.get_dl()) { "{i}" } } diff --git a/src/component.rs b/src/component.rs index 0d43ed2..af20a4b 100644 --- a/src/component.rs +++ b/src/component.rs @@ -150,6 +150,8 @@ impl Context { }); self.scopes.lock().unwrap().push(scope.clone()); + drawer(&scope); + use_effect( { let scope = scope.clone(); From 4364177538eac75fd618b8eb0e31a93045f24ec0 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 21:11:42 +0100 Subject: [PATCH 10/15] Text @position --- examples/hello_world.rs | 2 +- src/macros.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 7dfb5da..4e5c77f 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -56,7 +56,7 @@ fn app(cx: &Arc) -> View { // Dynamic scope for %count (i in 0..count.get_dl()) { - "{i}" + "{i}" @Point { x: 0, y: i }; } } .view(cx.clone()) diff --git a/src/macros.rs b/src/macros.rs index b1b1a57..4c8b828 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -53,6 +53,20 @@ macro_rules! rsx_scope { $crate::rsx_scope!($rsx, $($rest)*); }; + ($rsx:expr, $(%$($dep:ident $(as $dp:pat,)?),+)? $text:literal @$d:expr; $($rest:tt)*) => { + { + let scope = $crate::component::Scope::new(); + + $($($crate::rsx_dep!($dep $(as $dp)?);)+)? + + $crate::rsx_child!(scope, $text @$d;); + + $rsx.static_scope(scope); + } + + $crate::rsx_scope!($rsx, $($rest)*); + }; + ($rsx:expr, $(%$($dep:ident $(as $dp:pat,)?),+)? $text:literal $($rest:tt)*) => { { let scope = $crate::component::Scope::new(); @@ -96,6 +110,14 @@ macro_rules! rsx_scope { #[macro_export] macro_rules! rsx_child { + ($scope:expr, $text:literal @$d:expr; $($rest:tt)*) => { + { + $scope.view(Arc::new(move |ctx| ctx.draw_text($d, &format!($text)))); + } + + $crate::rsx_child!($scope, $($rest)*); + }; + ($scope:expr, $text:literal $($rest:tt)*) => { { $scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); From 083812458b947465fe3ad6a404d95cbdee134baf Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 21:12:34 +0100 Subject: [PATCH 11/15] Children clear for loop --- src/macros.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/macros.rs b/src/macros.rs index 4c8b828..c5ca97f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -22,6 +22,7 @@ macro_rules! rsx_scope { $rsx.dynamic_scope({ $($($crate::rsx_dep!($dep $(as $dp)?);)+)? move |scope| { + scope.children.lock().unwrap().clear(); for $p in $v { $crate::rsx_child!(scope, $($inner)*); } From bd53e6450c52a30502ed15d2999785ac79f768ee Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 26 Jan 2026 09:34:23 +0100 Subject: [PATCH 12/15] use effect instead of event --- examples/hello_world.rs | 69 ++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 4e5c77f..75bd963 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -2,42 +2,30 @@ use std::sync::Arc; use osui::{prelude::*, rsx}; -#[derive(Debug, Clone)] -pub struct KeyPress; - -impl Event for KeyPress { - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - fn main() { let console = Console::new(); - - console.thread(move |ctx| loop { - crossterm::event::read().unwrap(); - ctx.emit_event_threaded(&KeyPress); - }); - console.run(app); } fn app(cx: &Arc) -> View { let count = use_state(0); + let mount = use_mount(); - cx.on_event({ - let count = count.clone(); - move |_, _: &KeyPress| { - let mut count = count.get(); - - if *count > 5 { - *count = 0; - return; + use_effect( + { + let count = count.clone(); + move || loop { + std::thread::sleep(std::time::Duration::from_millis(500)); + let mut count = count.get(); + if *count > 5 { + *count = 0; + continue; + } + *count += 1; } - - *count += 1; - } - }); + }, + &[&mount], + ); rsx! { // Static scope @@ -63,19 +51,24 @@ fn app(cx: &Arc) -> View { } fn my_component(cx: &Arc) -> View { - let count = use_state(1); + let count = use_state(0); + let mount = use_mount(); - cx.on_event({ - let count = count.clone(); - move |_cx, _event: &KeyPress| { - let mut count = count.get(); - if *count > 5 { - _cx.refresh(); - return; + use_effect( + { + let count = count.clone(); + move || loop { + std::thread::sleep(std::time::Duration::from_millis(500)); + let mut count = count.get(); + if *count > 5 { + *count = 0; + continue; + } + *count += 1; } - *count += 1; - } - }); + }, + &[&mount], + ); rsx! { "Count: {count}" From 89b460697fbec091c0ba612eeed1d90a8f8fab28 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 26 Jan 2026 09:38:36 +0100 Subject: [PATCH 13/15] Any event --- src/component.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/component.rs b/src/component.rs index af20a4b..40e63c4 100644 --- a/src/component.rs +++ b/src/component.rs @@ -10,18 +10,8 @@ use crate::{ View, ViewWrapper, }; -pub trait Event { - fn as_any(&self) -> &dyn Any; -} - -impl<'a> dyn Event + 'a { - pub fn get(&self) -> Option<&T> { - self.as_any().downcast_ref() - } -} - pub type Component = Arc) -> View + Send + Sync>; -pub type EventHandler = Arc, &dyn Event) + Send + Sync>>; +pub type EventHandler = Arc, &dyn Any) + Send + Sync>>; pub struct Scope { pub children: Mutex, Option)>>, @@ -77,7 +67,7 @@ impl Context { self.view.lock().unwrap().clone() } - pub fn on_event, &T) + Send + Sync + 'static>( + pub fn on_event, &T) + Send + Sync + 'static>( self: &Arc, handler: F, ) { @@ -87,15 +77,15 @@ impl Context { .entry(TypeId::of::()) .or_insert_with(|| Vec::new()) .push(Arc::new(Mutex::new( - move |ctx: &Arc, event: &dyn Event| { - if let Some(e) = event.get::() { + move |ctx: &Arc, event: &dyn Any| { + if let Some(e) = event.downcast_ref::() { (handler)(ctx, e); } }, ))); } - pub fn emit_event(self: &Arc, event: &E) { + pub fn emit_event(self: &Arc, event: &E) { if let Some(v) = self.event_handlers.lock().unwrap().get(&TypeId::of::()) { for i in v { (i.lock().unwrap())(self, event); @@ -109,7 +99,7 @@ impl Context { } } - pub fn emit_event_threaded( + pub fn emit_event_threaded( self: &Arc, event: &E, ) { From 86e3f4e6840edef37e4bb63a55b4441bce8a7c21 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 26 Jan 2026 16:58:44 +0100 Subject: [PATCH 14/15] Sync state (param passing) --- examples/hello_world.rs | 20 ++++++++++++-- src/macros.rs | 8 ++++++ src/state.rs | 60 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 75bd963..dc2ccc3 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -2,6 +2,8 @@ use std::sync::Arc; use osui::{prelude::*, rsx}; +pub struct Count(pub State); + fn main() { let console = Console::new(); console.run(app); @@ -9,7 +11,7 @@ fn main() { fn app(cx: &Arc) -> View { let count = use_state(0); - let mount = use_mount(); + let mount = use_mount_manual(); use_effect( { @@ -27,6 +29,18 @@ fn app(cx: &Arc) -> View { &[&mount], ); + // Pass the count to the children + use_effect( + { + let count = count.clone(); + let cx = cx.clone(); + move || { + cx.emit_event(&Count(count.clone())); + } + }, + &[&mount], + ); + rsx! { // Static scope my_component (ctx, view) { @@ -46,13 +60,15 @@ fn app(cx: &Arc) -> View { for %count (i in 0..count.get_dl()) { "{i}" @Point { x: 0, y: i }; } + + !mount mount } .view(cx.clone()) } fn my_component(cx: &Arc) -> View { - let count = use_state(0); let mount = use_mount(); + let count = use_sync_state(cx, 0, |Count(v)| v.get_dl()); use_effect( { diff --git a/src/macros.rs b/src/macros.rs index c5ca97f..23c3e7a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -106,6 +106,14 @@ macro_rules! rsx_scope { $crate::rsx_scope!($rsx, $($rest)*); }; + ($rsx:expr, !mount $mount:ident $($rest:tt)*) => { + { + $mount.mount(); + } + + $crate::rsx_scope!($rsx, $($rest)*); + }; + ($rsx:expr,) => {}; } diff --git a/src/state.rs b/src/state.rs index d4c083b..c8f2193 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,13 +1,16 @@ use std::{ + any::Any, fmt::{Debug, Display, Formatter, Result as FmtResult}, ops::{Deref, DerefMut}, sync::{Arc, Mutex, MutexGuard}, }; +use crate::prelude::Context; + #[derive(Clone)] pub struct HookEffect(Arc>); -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State { value: Arc>, dependents: Arc>>, @@ -19,8 +22,8 @@ pub struct Inner<'a, T> { updated: bool, } -#[derive(Debug, Clone, Copy)] -pub struct Mount; +#[derive(Debug, Clone)] +pub struct Mount(Arc>, Arc>>); pub fn use_state(v: T) -> State { State { @@ -57,6 +60,13 @@ impl State { d.call(); } } + + pub fn clone(&self) -> Self { + Self { + dependents: self.dependents.clone(), + value: self.value.clone(), + } + } } impl Display for State { @@ -117,7 +127,21 @@ impl HookDependency for State { impl HookDependency for Mount { fn on_update(&self, hook: HookEffect) { - hook.call(); + if *self.0.lock().unwrap() { + hook.call(); + } else { + self.1.lock().unwrap().push(hook); + } + } +} + +impl Mount { + pub fn mount(&self) { + *self.0.lock().unwrap() = true; + for hook_effect in self.1.lock().unwrap().iter() { + hook_effect.call(); + } + self.1.lock().unwrap().clear(); } } @@ -137,5 +161,31 @@ pub fn use_effect(f: F, dependencies: &[&dyn } pub fn use_mount() -> Mount { - Mount + Mount(Arc::new(Mutex::new(true)), Arc::new(Mutex::new(Vec::new()))) +} + +pub fn use_mount_manual() -> Mount { + Mount( + Arc::new(Mutex::new(false)), + Arc::new(Mutex::new(Vec::new())), + ) +} + +pub fn use_sync_state< + T: Send + Sync + 'static, + E: Any + 'static, + D: Fn(&E) -> T + Send + Sync + 'static, +>( + cx: &Arc, + v: T, + decoder: D, +) -> State { + let state = use_state(v); + + cx.on_event({ + let state = state.clone(); + move |_, v: &E| state.set(decoder(v)) + }); + + state } From 506ccae049d81649fdda30c1da19864ffcd844b5 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 26 Jan 2026 17:27:47 +0100 Subject: [PATCH 15/15] State syncing --- examples/hello_world.rs | 33 ++++----------------------------- src/state.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index dc2ccc3..09c9dd3 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -13,6 +13,9 @@ fn app(cx: &Arc) -> View { let count = use_state(0); let mount = use_mount_manual(); + // Sync state with children + use_sync_effect(cx, &count, |v| Count(v.clone()), &[&mount, &count]); + use_effect( { let count = count.clone(); @@ -29,18 +32,6 @@ fn app(cx: &Arc) -> View { &[&mount], ); - // Pass the count to the children - use_effect( - { - let count = count.clone(); - let cx = cx.clone(); - move || { - cx.emit_event(&Count(count.clone())); - } - }, - &[&mount], - ); - rsx! { // Static scope my_component (ctx, view) { @@ -67,25 +58,9 @@ fn app(cx: &Arc) -> View { } fn my_component(cx: &Arc) -> View { - let mount = use_mount(); + // Sync state with parent let count = use_sync_state(cx, 0, |Count(v)| v.get_dl()); - use_effect( - { - let count = count.clone(); - move || loop { - std::thread::sleep(std::time::Duration::from_millis(500)); - let mut count = count.get(); - if *count > 5 { - *count = 0; - continue; - } - *count += 1; - } - }, - &[&mount], - ); - rsx! { "Count: {count}" } diff --git a/src/state.rs b/src/state.rs index c8f2193..502e56c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -189,3 +189,25 @@ pub fn use_sync_state< state } + +pub fn use_sync_effect< + T: Send + Sync + 'static, + Ev: 'static, + E: Fn(&State) -> Ev + Send + Sync + 'static, +>( + cx: &Arc, + state: &State, + encoder: E, + deps: &[&dyn HookDependency], +) { + use_effect( + { + let state = state.clone(); + let cx = cx.clone(); + move || { + cx.emit_event(&encoder(&state)); + } + }, + deps, + ); +}