diff --git a/Cargo.toml b/Cargo.toml index a40db29..6aec650 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cc = "1.0" [dependencies] crossterm = "0.28.1" figlet-rs = "0.1.5" +osui-macros = { path = "macros" } [profile.dev] opt-level = 1 @@ -42,3 +43,6 @@ overflow-checks = false lto = true panic = 'abort' codegen-units = 1 + +[workspace] +members = [".", "macros"] diff --git a/examples/benchmark.rs b/examples/benchmark.rs new file mode 100644 index 0000000..4fc8e72 --- /dev/null +++ b/examples/benchmark.rs @@ -0,0 +1,15 @@ +use std::sync::Arc; + +use osui::prelude::*; + +pub fn main() { + let engine = Benchmark::new(Console::new()); + engine.run(app); +} + +fn app(cx: &Arc) -> View { + rsx! { + "Hello, world!" + } + .view(cx.clone()) +} diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 09c9dd3..9ba9489 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,68 +1,15 @@ use std::sync::Arc; -use osui::{prelude::*, rsx}; +use osui::prelude::*; -pub struct Count(pub State); - -fn main() { +pub fn main() { let console = Console::new(); console.run(app); } 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(); - 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! { - // 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(18, 0, 10, 10); - ctx.draw_view(area, view); - } - } - - // Dynamic scope - 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 { - // Sync state with parent - let count = use_sync_state(cx, 0, |Count(v)| v.get_dl()); - - rsx! { - "Count: {count}" + "Hello, world!" } .view(cx.clone()) } diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..3337994 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "osui-macros" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/macros/src/emit.rs b/macros/src/emit.rs new file mode 100644 index 0000000..c4683c9 --- /dev/null +++ b/macros/src/emit.rs @@ -0,0 +1,92 @@ +use crate::parse::*; +use proc_macro2::TokenStream; +use quote::quote; + +pub fn emit_rsx(root: RsxRoot) -> TokenStream { + let nodes = root.nodes.iter().map(emit_node); + + quote! {{ + let mut r = osui::frontend::Rsx::new(); + #(#nodes)* + r + }} +} + +fn emit_deps(deps: &[Dep]) -> TokenStream { + deps.iter() + .map(|d| { + let ident = &d.ident; + if let Some(pat) = &d.pat { + quote!( let #pat = #ident.clone(); ) + } else { + quote!( let #ident = #ident.clone(); ) + } + }) + .collect() +} + +fn emit_node(node: &RsxNode) -> TokenStream { + match node { + RsxNode::Text(text) => quote! { + r.static_scope(|scope| scope.view(Arc::new(move |ctx| { + ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text)) + }))); + }, + + RsxNode::Component(name) => quote! { + r.static_scope(|scope| scope.view(Arc::new(move |ctx| { + scope.child(#name, None) + }))); + }, + + RsxNode::Mount(m) => quote! { + #m.mount(); + }, + + RsxNode::If { + deps, + cond, + children, + } => { + let deps_emit = emit_deps(deps); + let kids = children.iter().map(emit_node); + + quote! { + { + #deps_emit + r.dynamic_scope(move |scope| { + if #cond { + if scope.children.lock().unwrap().is_empty() { + #(#kids)* + } + } else { + scope.children.lock().unwrap().clear(); + } + }); + } + } + } + + RsxNode::For { + deps, + pat, + expr, + children, + } => { + let deps_emit = emit_deps(deps); + let kids = children.iter().map(emit_node); + + quote! { + { + #deps_emit + r.dynamic_scope(move |scope| { + scope.children.lock().unwrap().clear(); + for #pat in #expr { + #(#kids)* + } + }); + } + } + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..bb4e672 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,10 @@ +use proc_macro::TokenStream; + +mod emit; +mod parse; + +#[proc_macro] +pub fn rsx(input: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(input as parse::RsxRoot); + emit::emit_rsx(ast).into() +} diff --git a/macros/src/parse.rs b/macros/src/parse.rs new file mode 100644 index 0000000..fc4ee19 --- /dev/null +++ b/macros/src/parse.rs @@ -0,0 +1,129 @@ +use syn::braced; +use syn::{ + Expr, Ident, LitStr, Pat, Result, Token, + parse::{Parse, ParseStream}, +}; + +pub struct RsxRoot { + pub nodes: Vec, +} + +impl Parse for RsxRoot { + fn parse(input: ParseStream) -> Result { + let mut nodes = Vec::new(); + while !input.is_empty() { + nodes.push(input.parse()?); + } + Ok(Self { nodes }) + } +} + +pub enum RsxNode { + Text(LitStr), + Component(Ident), + Mount(Ident), + If { + deps: Vec, + cond: Expr, + children: Vec, + }, + For { + deps: Vec, + pat: Pat, + expr: Expr, + children: Vec, + }, +} + +pub struct Dep { + pub ident: Ident, + pub pat: Option, +} + +fn parse_deps(input: ParseStream) -> Result> { + let mut deps = Vec::new(); + + if input.peek(Token![%]) { + input.parse::()?; + loop { + let ident: Ident = input.parse()?; + let pat = if input.peek(Token![as]) { + input.parse::()?; + // Patterns in syn 2.0 are parsed via `Pat::parse_multi` + // instead of implementing the `Parse` trait directly. + Some(Pat::parse_multi(input)?) + } else { + None + }; + + deps.push(Dep { ident, pat }); + + if !input.peek(Token![,]) { + break; + } + input.parse::()?; + } + } + + Ok(deps) +} + +impl Parse for RsxNode { + fn parse(input: ParseStream) -> Result { + if input.peek(Token![!]) { + input.parse::()?; + let mount: Ident = input.parse()?; + return Ok(RsxNode::Mount(mount)); + } + + let deps = parse_deps(input)?; + + if input.peek(Token![if]) { + input.parse::()?; + let cond: Expr = input.parse()?; + + let content; + braced!(content in input); + + let children = parse_children(&content)?; + return Ok(RsxNode::If { + deps, + cond, + children, + }); + } + + if input.peek(Token![for]) { + input.parse::()?; + // Parse a possibly multi-pattern (e.g. with `|`) for the `for` binding. + let pat: Pat = Pat::parse_multi(input)?; + input.parse::()?; + let expr: Expr = input.parse()?; + + let content; + braced!(content in input); + + let children = parse_children(&content)?; + return Ok(RsxNode::For { + deps, + pat, + expr, + children, + }); + } + + if input.peek(LitStr) { + return Ok(RsxNode::Text(input.parse()?)); + } + + Ok(RsxNode::Component(input.parse()?)) + } +} + +fn parse_children(input: ParseStream) -> Result> { + let mut nodes = Vec::new(); + while !input.is_empty() { + nodes.push(input.parse()?); + } + Ok(nodes) +} diff --git a/src/component.rs b/src/component.rs index 40e63c4..90c910a 100644 --- a/src/component.rs +++ b/src/component.rs @@ -5,6 +5,7 @@ use std::{ }; use crate::{ + engine::{Command, CommandExecutor}, render::DrawContext, state::{use_effect, HookDependency}, View, ViewWrapper, @@ -15,6 +16,7 @@ pub type EventHandler = Arc, &dyn Any) + Send + Sy pub struct Scope { pub children: Mutex, Option)>>, + executor: Arc, } pub struct Context { @@ -22,15 +24,20 @@ pub struct Context { event_handlers: Mutex>>, view: Mutex, pub(crate) scopes: Mutex>>, + executor: Arc, } impl Context { - pub fn new) -> View + Send + Sync + 'static>(component: F) -> Arc { + pub fn new) -> View + Send + Sync + 'static>( + component: F, + executor: Arc, + ) -> Arc { Arc::new(Self { component: Mutex::new(Arc::new(component)), event_handlers: Mutex::new(HashMap::new()), view: Mutex::new(Arc::new(|_| {})), scopes: Mutex::new(Vec::new()), + executor, }) } @@ -122,9 +129,7 @@ impl Context { } pub fn scope(self: &Arc) -> Arc { - let scope = Arc::new(Scope { - children: Mutex::new(Vec::new()), - }); + let scope = Scope::new(self.executor.clone()); self.scopes.lock().unwrap().push(scope.clone()); scope @@ -135,9 +140,7 @@ impl Context { drawer: F, dependencies: &[&dyn HookDependency], ) -> Arc { - let scope = Arc::new(Scope { - children: Mutex::new(Vec::new()), - }); + let scope = Scope::new(self.executor.clone()); self.scopes.lock().unwrap().push(scope.clone()); drawer(&scope); @@ -168,12 +171,26 @@ impl Context { } } } + + pub fn get_executor(self: &Arc) -> Arc { + self.executor.clone() + } + + pub fn execute(self: &Arc, command: T) -> crate::Result<()> { + self.executor + .execute_command(&(Arc::new(command) as Arc)) + } + + pub fn stop(self: &Arc) -> crate::Result<()> { + self.execute(crate::engine::commands::Stop) + } } impl Scope { - pub fn new() -> Arc { + pub fn new(executor: Arc) -> Arc { Arc::new(Self { children: Mutex::new(Vec::new()), + executor, }) } @@ -182,7 +199,7 @@ impl Scope { child: F, view_wrapper: Option, ) { - let ctx = Context::new(child); + let ctx = Context::new(child, self.executor.clone()); ctx.refresh(); @@ -190,7 +207,7 @@ impl Scope { } pub fn view(self: &Arc, view: View) { - let ctx = Context::new(move |_| view.clone()); + let ctx = Context::new(move |_| view.clone(), self.executor.clone()); ctx.refresh(); diff --git a/src/engine.rs b/src/engine.rs deleted file mode 100644 index ac02d84..0000000 --- a/src/engine.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::{ - io::{stdout, Write}, - sync::{Arc, Mutex}, -}; - -use crossterm::{cursor::MoveTo, execute, terminal::Clear}; - -use crate::{prelude::Context, render::Area, DrawContext, View}; - -pub trait Engine { - fn render_view(&self, area: &Area, view: &View) -> DrawContext; - fn draw_context(&self, ctx: &DrawContext); -} - -pub struct Console { - threads: Mutex) + Send + Sync>>>, -} - -impl Console { - pub fn new() -> Self { - Self { - threads: Mutex::new(Vec::new()), - } - } - - pub fn thread) + Send + Sync + 'static>(&self, run: F) { - self.threads.lock().unwrap().push(Arc::new(run)); - } - - pub fn run) -> View + Send + Sync + 'static>(&self, component: F) { - let cx = Context::new(component); - cx.refresh(); - - for thread in self.threads.lock().unwrap().iter() { - let thread = thread.clone(); - - std::thread::spawn({ - let cx = cx.clone(); - move || thread(cx) - }); - } - - loop { - let (width, height) = crossterm::terminal::size().unwrap(); - - execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); - execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); - - self.draw_context(&self.render_view( - &Area { - x: 0, - y: 0, - width, - height, - }, - &cx.get_view(), - )); - - std::thread::sleep(std::time::Duration::from_millis(16)); - } - } -} - -impl Engine for Console { - fn render_view(&self, area: &Area, view: &View) -> DrawContext { - let mut context = DrawContext::new(area.clone()); - view(&mut context); - context - } - - fn draw_context(&self, ctx: &DrawContext) { - for inst in &ctx.drawing { - match inst { - crate::render::DrawInstruction::Text(point, text) => { - let (x, y) = (ctx.area.x + point.x, ctx.area.y + point.y); - execute!(stdout(), MoveTo(x, y),).unwrap(); - print!("{text}"); - stdout().flush().unwrap(); - } - crate::render::DrawInstruction::Child(_point, child) => self.draw_context(child), - crate::render::DrawInstruction::View(area, view) => { - self.draw_context(&self.render_view(area, view)) - } - } - } - } -} diff --git a/src/engine/benchmark.rs b/src/engine/benchmark.rs new file mode 100644 index 0000000..df8c700 --- /dev/null +++ b/src/engine/benchmark.rs @@ -0,0 +1,94 @@ +use std::{io::stdout, sync::Arc, time::Instant}; + +use crossterm::{cursor::MoveTo, execute, terminal::Clear}; + +use crate::{prelude::Context, render::Area, DrawContext, View}; + +use super::Engine; + +pub struct Benchmark(T); + +impl Benchmark { + pub fn new(engine: T) -> Self { + Self(engine) + } +} + +impl Engine for Benchmark { + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let mut times: Vec = Vec::new(); + let cx = self.init(component); + + let start = Instant::now(); + + for _ in 0..100 { + let start = Instant::now(); + self.render(&cx); + let end = Instant::now(); + times.push(end.duration_since(start).as_millis() as usize); + self.0.render_delay(); + } + + let end = Instant::now(); + + execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); + execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); + execute!(stdout(), MoveTo(0, 0)).unwrap(); + + println!( + "Average time: {}ms", + if times.len() > 0 { + times.iter().sum::() / times.len() + } else { + 0 + } + ); + println!("Min time: {}ms", times.iter().min().unwrap_or(&0)); + println!("Max time: {}ms", times.iter().max().unwrap_or(&0)); + println!( + "Median time: {}ms", + times.iter().nth(times.len() / 2).unwrap_or(&0) + ); + println!( + "90th percentile: {}ms", + times.iter().nth(times.len() * 90 / 100).unwrap_or(&0) + ); + println!( + "95th percentile: {}ms", + times.iter().nth(times.len() * 95 / 100).unwrap_or(&0) + ); + println!( + "99th percentile: {}ms", + times.iter().nth(times.len() * 99 / 100).unwrap_or(&0) + ); + + println!("Total time: {}ms", end.duration_since(start).as_millis()); + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc { + self.0.init(component) + } + + fn draw_context(&self, ctx: &DrawContext) { + self.0.draw_context(ctx) + } + + fn render(&self, cx: &Arc) { + self.0.render(cx) + } + + fn render_view(&self, area: &Area, view: &View) -> DrawContext { + self.0.render_view(area, view) + } + + fn render_delay(&self) { + self.0.render_delay(); + } + + fn executor(&self) -> Arc { + self.0.executor() + } +} diff --git a/src/engine/commands.rs b/src/engine/commands.rs new file mode 100644 index 0000000..8cda616 --- /dev/null +++ b/src/engine/commands.rs @@ -0,0 +1,9 @@ +use crate::engine::Command; + +pub struct Stop; + +impl Command for Stop { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/src/engine/console.rs b/src/engine/console.rs new file mode 100644 index 0000000..b8553a3 --- /dev/null +++ b/src/engine/console.rs @@ -0,0 +1,136 @@ +use std::{ + io::{stdout, Write}, + sync::{Arc, Mutex}, +}; + +use crossterm::{cursor::MoveTo, execute, terminal::Clear}; + +use crate::{ + engine::{commands, CommandExecutor}, + prelude::Context, + render::Area, + DrawContext, View, +}; + +use super::Engine; + +pub struct ConsoleExecutor { + running: Mutex, +} + +pub struct Console { + threads: Mutex) + Send + Sync>>>, + executor: Arc, +} + +impl Console { + pub fn new() -> Self { + Self { + threads: Mutex::new(Vec::new()), + executor: Arc::new(ConsoleExecutor { + running: Mutex::new(true), + }), + } + } + + pub fn thread) + Send + Sync + 'static>(&self, run: F) { + self.threads.lock().unwrap().push(Arc::new(run)); + } +} + +impl Engine for Console { + fn render_view(&self, area: &Area, view: &View) -> DrawContext { + let mut context = DrawContext::new(area.clone()); + view(&mut context); + context + } + + fn draw_context(&self, ctx: &DrawContext) { + for inst in &ctx.drawing { + match inst { + crate::render::DrawInstruction::Text(point, text) => { + let (x, y) = (ctx.area.x + point.x, ctx.area.y + point.y); + execute!(stdout(), MoveTo(x, y),).unwrap(); + print!("{text}"); + stdout().flush().unwrap(); + } + crate::render::DrawInstruction::Child(_point, child) => self.draw_context(child), + crate::render::DrawInstruction::View(area, view) => { + self.draw_context(&self.render_view(area, view)) + } + } + } + } + + fn render(&self, cx: &Arc) { + let (width, height) = crossterm::terminal::size().unwrap(); + + execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); + execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); + + self.draw_context(&self.render_view( + &Area { + x: 0, + y: 0, + width, + height, + }, + &cx.get_view(), + )); + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc { + let cx = Context::new(component, self.executor.clone()); + cx.refresh(); + + for thread in self.threads.lock().unwrap().iter() { + let thread = thread.clone(); + + std::thread::spawn({ + let cx = cx.clone(); + move || thread(cx) + }); + } + + cx + } + + fn executor(&self) -> Arc { + self.executor.clone() + } + + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let cx = self.init(component); + + while self.executor.is_running() { + self.render(&cx); + self.render_delay(); + } + } +} + +impl ConsoleExecutor { + pub fn is_running(self: &Arc) -> bool { + *self.running.lock().unwrap() + } + + pub fn stop(&self) -> crate::Result<()> { + *self.running.lock()? = false; + Ok(()) + } +} + +impl CommandExecutor for ConsoleExecutor { + fn execute_command(&self, command: &Arc) -> crate::Result<()> { + let command = command.as_any(); + + if let Some(commands::Stop) = command.downcast_ref() { + self.stop()?; + } + + Ok(()) + } +} diff --git a/src/engine/mod.rs b/src/engine/mod.rs new file mode 100644 index 0000000..bd28fb3 --- /dev/null +++ b/src/engine/mod.rs @@ -0,0 +1,42 @@ +pub mod benchmark; +pub mod commands; +pub mod console; + +pub use benchmark::*; +pub use console::*; + +use std::{any::Any, sync::Arc}; + +use crate::{prelude::Context, render::Area, DrawContext, View}; + +pub trait Engine { + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let cx = self.init(component); + + loop { + self.render(&cx); + self.render_delay(); + } + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc; + fn render(&self, cx: &Arc); + fn render_delay(&self) { + std::thread::sleep(std::time::Duration::from_millis(16)) + } + + fn render_view(&self, area: &Area, view: &View) -> DrawContext; + fn draw_context(&self, ctx: &DrawContext); + fn executor(&self) -> Arc; +} + +pub trait Command { + fn as_any(&self) -> &dyn Any; +} + +pub trait CommandExecutor: Send + Sync { + fn execute_command(&self, command: &Arc) -> crate::Result<()>; +} diff --git a/src/frontend.rs b/src/frontend.rs index e90ea99..bcabbb5 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -7,7 +7,7 @@ use crate::{ }; pub enum RsxScope { - Static(Arc), + Static(Box) + Send + Sync>), Dynamic( Arc) + Send + Sync>, Vec>, @@ -21,8 +21,8 @@ impl Rsx { Self(Vec::new()) } - pub fn static_scope(&mut self, scope: Arc) { - self.0.push(RsxScope::Static(scope)); + pub fn static_scope) + Send + Sync + 'static>(&mut self, scope: F) { + self.0.push(RsxScope::Static(Box::new(scope))); } pub fn dynamic_scope) + Send + Sync + 'static>( @@ -35,9 +35,15 @@ impl Rsx { } pub fn view(&self, context: Arc) -> View { + let executor = context.get_executor(); + for scope in &self.0 { match scope { - RsxScope::Static(scope) => context.scopes.lock().unwrap().push(scope.clone()), + RsxScope::Static(scope_fn) => { + let scope = Scope::new(executor.clone()); + (scope_fn)(&scope); + context.scopes.lock().unwrap().push(scope) + } RsxScope::Dynamic(drawer, dependencies) => { let drawer = drawer.clone(); context.dyn_scope( diff --git a/src/lib.rs b/src/lib.rs index 1462c02..7923bda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use crate::render::DrawContext; pub mod component; pub mod engine; pub mod frontend; -pub mod macros; pub mod render; pub mod state; @@ -15,7 +14,20 @@ pub mod prelude { pub use crate::render::*; pub use crate::state::*; pub use crate::View; + pub use osui_macros::rsx; } pub type View = Arc; pub type ViewWrapper = Arc; +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub enum Error { + PoisonError, +} + +impl From>> for Error { + fn from(_value: std::sync::PoisonError>) -> Self { + Error::PoisonError + } +} diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index 23c3e7a..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,165 +0,0 @@ -#[macro_export] -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, 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| { - scope.children.lock().unwrap().clear(); - for $p in $v { - $crate::rsx_child!(scope, $($inner)*); - } - } - }, vec![$($(Box::new($dep)),+)?]); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($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| { - 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 $(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(); - - $($($crate::rsx_dep!($dep $(as $dp)?);)+)? - - $crate::rsx_child!(scope, $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(); - - $crate::rsx_child!(scope, $component ($ctx, $view) $body); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, $component:ident $($rest:tt)*) => { - { - let scope = $crate::component::Scope::new(); - - $crate::rsx_child!(scope, $component); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, !mount $mount:ident $($rest:tt)*) => { - { - $mount.mount(); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr,) => {}; -} - -#[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)))); - } - - $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,) => {}; -} - -#[macro_export] -macro_rules! rsx_dep { - ($dep:ident as $dp:pat) => { - let $dp = $dep.clone(); - }; - - ($dep:ident) => { - let $dep = $dep.clone(); - }; -}