Merge pull request #43 from osui-rs/developed-structure

Developed structure
This commit is contained in:
Klesti Selimaj
2026-01-31 00:30:43 +01:00
committed by GitHub
16 changed files with 576 additions and 157 deletions
+46 -7
View File
@@ -1,15 +1,54 @@
use std::sync::Arc;
use osui::prelude::*;
use std::{sync::mpsc, thread};
pub fn main() {
let engine = Benchmark::new(Console::new());
engine.run(app);
let engine = Arc::new(Benchmark::new(Console::new()));
let (tx, rx) = mpsc::channel();
let max_threads = 32;
let mut handles = vec![];
for chunk in (0..500).collect::<Vec<_>>().chunks(max_threads) {
for &items in chunk {
let engine = engine.clone();
let tx = tx.clone();
let handle = thread::spawn(move || {
let res = engine
.run(BenchmarkApp { items })
.expect("Failed to run engine");
tx.send((items, res)).expect("Failed to send result");
});
handles.push(handle);
}
for handle in handles.drain(..) {
handle.join().expect("Thread panicked");
}
}
drop(tx);
let mut results: Vec<(usize, BenchmarkResult)> = rx.iter().collect();
results.sort_by_key(|(items, _)| *items);
for (items, bench) in results {
println!("Results for {items} items:\n{bench}");
}
}
fn app(cx: &Arc<Context>) -> View {
#[component]
fn BenchmarkApp(cx: &Arc<Context>, items: &usize) -> View {
let items = items.clone();
rsx! {
"Hello, world!"
for _ in (0..items) {
"Hello, world!"
}
}
.view(cx.clone())
.view(&cx)
}
+6 -7
View File
@@ -1,15 +1,14 @@
use std::sync::Arc;
use osui::prelude::*;
pub fn main() {
let console = Console::new();
console.run(app);
let engine = Console::new();
engine.run(App {}).expect("Failed to run engine");
}
fn app(cx: &Arc<Context>) -> View {
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
"Hello, world!"
"Hello World"
}
.view(cx.clone())
.view(&cx)
}
+19
View File
@@ -0,0 +1,19 @@
use osui::prelude::*;
pub fn main() {
let engine = Benchmark::new(Console::new());
let benchmark_result = engine.run(App {}).expect("Failed to run engine");
println!("Avg: {} μs", benchmark_result.average);
println!("Min: {} μs", benchmark_result.min);
println!("Max: {} μs", benchmark_result.max);
println!("Tot: {} μs", benchmark_result.total);
println!("Tot Render: {} μs", benchmark_result.total_render);
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
"Hello, world!"
}
.view(&cx)
}
+23
View File
@@ -0,0 +1,23 @@
use osui::prelude::*;
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run engine");
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MyComponent { "------ example" }
}
.view(&cx)
}
#[component]
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
rsx! {
@{children}
"Simple"
}
.view(&cx)
}
+97 -15
View File
@@ -3,7 +3,11 @@ use proc_macro2::TokenStream;
use quote::quote;
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
let nodes = root.nodes.iter().map(emit_node);
emit_rsx_vec(&root.nodes)
}
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
let nodes = nodes.iter().map(emit_node_scope);
quote! {{
let mut r = osui::frontend::Rsx::new();
@@ -25,19 +29,35 @@ fn emit_deps(deps: &[Dep]) -> TokenStream {
.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))
})));
},
fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
let deps = deps.iter().map(|d| {
let ident = &d.ident;
RsxNode::Component(name) => quote! {
r.static_scope(|scope| scope.view(Arc::new(move |ctx| {
scope.child(#name, None)
})));
},
quote! {
std::sync::Arc::new(#ident) as std::sync::Arc<dyn HookDependency>
}
});
quote! {
vec![ #(#deps),* ]
}
}
fn emit_node_scope(node: &RsxNode) -> TokenStream {
match node {
RsxNode::Text(_) => {
let emit = emit_node(node);
quote! {
r.static_scope(move |scope| {#emit});
}
}
RsxNode::Component { .. } => {
let emit = emit_node(node);
quote! {
r.static_scope(move |scope| {#emit});
}
}
RsxNode::Mount(m) => quote! {
#m.mount();
@@ -49,6 +69,7 @@ fn emit_node(node: &RsxNode) -> TokenStream {
children,
} => {
let deps_emit = emit_deps(deps);
let deps_vec_emit = emit_deps_vec(deps);
let kids = children.iter().map(emit_node);
quote! {
@@ -62,7 +83,7 @@ fn emit_node(node: &RsxNode) -> TokenStream {
} else {
scope.children.lock().unwrap().clear();
}
});
}, #deps_vec_emit);
}
}
}
@@ -74,19 +95,80 @@ fn emit_node(node: &RsxNode) -> TokenStream {
children,
} => {
let deps_emit = emit_deps(deps);
let deps_vec_emit = emit_deps_vec(deps);
let kids = children.iter().map(emit_node);
quote! {
{
#deps_emit
#[allow(unused_parens)]
r.dynamic_scope(move |scope| {
scope.children.lock().unwrap().clear();
for #pat in #expr {
#(#kids)*
}
});
}, #deps_vec_emit);
}
}
}
RsxNode::Expr(expr) => quote! {
r.child(#expr);
},
}
}
fn emit_node(node: &RsxNode) -> TokenStream {
match node {
RsxNode::Text(text) => quote! {
scope.view(Arc::new(move |ctx| {
ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text))
}));
},
RsxNode::Component {
path,
props,
children,
} => {
let prop_inits = props.iter().map(|p| {
let name = &p.name;
let value = &p.value;
quote! { #name: #value }
});
let emit_children = emit_rsx_vec(children);
let component_expr = if children.len() > 0 {
quote! {
#path {
#(#prop_inits,)*
children: #emit_children
}
}
} else {
quote! {
#path {
#(#prop_inits,)*
}
}
};
quote! {
scope.child(#component_expr, None);
}
}
RsxNode::Mount(m) => quote! {
#m.mount();
},
RsxNode::If { .. } => panic!("Invalid if statement"),
RsxNode::For { .. } => panic!("Invalid for loop"),
RsxNode::Expr(expr) => quote! {
r.child(#expr);
},
}
}
+102 -1
View File
@@ -1,10 +1,111 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
mod emit;
mod parse;
#[proc_macro]
pub fn rsx(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as parse::RsxRoot);
let ast = parse_macro_input!(input as parse::RsxRoot);
emit::emit_rsx(ast).into()
}
#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let name = &input.sig.ident;
let vis = &input.vis;
let body = &input.block;
let return_ty = match &input.sig.output {
ReturnType::Type(_, ty) => ty,
_ => {
return syn::Error::new_spanned(&input.sig, "component must return View")
.to_compile_error()
.into();
}
};
let mut inputs = input.sig.inputs.iter();
// ---- First param must be cx ----
let cx = match inputs.next() {
Some(FnArg::Typed(pat)) => pat,
_ => {
return syn::Error::new_spanned(&input.sig, "first argument must be cx: &Arc<Context>")
.to_compile_error()
.into();
}
};
let cx_ident = match &*cx.pat {
Pat::Ident(id) => &id.ident,
_ => unreachable!(),
};
let cx_ty = &cx.ty;
// ---- Remaining params are props ----
let mut struct_fields = Vec::new();
let mut render_params = Vec::new();
let mut call_args = Vec::new();
for arg in inputs {
let FnArg::Typed(pat) = arg else { continue };
let ident = match &*pat.pat {
Pat::Ident(id) => &id.ident,
_ => {
return syn::Error::new_spanned(pat, "unsupported prop pattern")
.to_compile_error()
.into();
}
};
// Strip leading &
let owned_ty = match &*pat.ty {
Type::Reference(r) => &r.elem,
ty => ty,
};
struct_fields.push(quote! {
pub #ident: #owned_ty
});
render_params.push(quote! {
#ident: &#owned_ty
});
call_args.push(quote! {
&self.#ident
});
}
let expanded = quote! {
#vis struct #name {
#(#struct_fields,)*
}
impl #name {
pub fn component(
#cx_ident: #cx_ty,
#(#render_params,)*
) -> #return_ty {
#body
}
}
impl ComponentImpl for #name {
fn call(&self, cx: &std::sync::Arc<Context>) -> #return_ty {
Self::component(
cx,
#(#call_args,)*
)
}
}
};
expanded.into()
}
+95 -8
View File
@@ -1,7 +1,9 @@
use syn::braced;
use syn::parse::discouraged::Speculative;
use syn::{
Expr, Ident, LitStr, Pat, Result, Token,
Expr, Ident, LitStr, Pat, Path, Result, Token,
parse::{Parse, ParseStream},
token::Brace,
};
pub struct RsxRoot {
@@ -18,9 +20,20 @@ impl Parse for RsxRoot {
}
}
/// A single prop: `ident: expr`.
pub struct RsxProp {
pub name: Ident,
pub value: Expr,
}
pub enum RsxNode {
Text(LitStr),
Component(Ident),
Expr(Expr),
Component {
path: Path,
props: Vec<RsxProp>,
children: Vec<RsxNode>,
},
Mount(Ident),
If {
deps: Vec<Dep>,
@@ -70,6 +83,18 @@ fn parse_deps(input: ParseStream) -> Result<Vec<Dep>> {
impl Parse for RsxNode {
fn parse(input: ParseStream) -> Result<Self> {
// @{ $expr }
if input.peek(Token![@]) {
input.parse::<Token![@]>()?;
let content;
braced!(content in input);
let expr: Expr = content.parse()?;
return Ok(RsxNode::Expr(expr));
}
// !$ident
if input.peek(Token![!]) {
input.parse::<Token![!]>()?;
let mount: Ident = input.parse()?;
@@ -78,13 +103,12 @@ impl Parse for RsxNode {
let deps = parse_deps(input)?;
// %$dep if $expr { $rsx }
if input.peek(Token![if]) {
input.parse::<Token![if]>()?;
let cond: Expr = input.parse()?;
let content;
braced!(content in input);
let children = parse_children(&content)?;
return Ok(RsxNode::If {
deps,
@@ -93,16 +117,14 @@ impl Parse for RsxNode {
});
}
// %$dep for $pat in $expr { $rsx }
if input.peek(Token![for]) {
input.parse::<Token![for]>()?;
// Parse a possibly multi-pattern (e.g. with `|`) for the `for` binding.
let pat: Pat = Pat::parse_multi(input)?;
input.parse::<Token![in]>()?;
let expr: Expr = input.parse()?;
let content;
braced!(content in input);
let children = parse_children(&content)?;
return Ok(RsxNode::For {
deps,
@@ -112,14 +134,79 @@ impl Parse for RsxNode {
});
}
// "%$dep for $pat in $expr { $rsx }"
if input.peek(LitStr) {
return Ok(RsxNode::Text(input.parse()?));
}
Ok(RsxNode::Component(input.parse()?))
parse_component_invocation(input)
}
}
/// Parses Path or Path { ... } into RsxNode::Component.
fn parse_component_invocation(input: ParseStream) -> Result<RsxNode> {
let path: Path = input.parse()?;
if !input.peek(Brace) {
return Ok(RsxNode::Component {
path,
props: Vec::new(),
children: Vec::new(),
});
}
let content;
braced!(content in input);
let (props, children) = parse_props_and_children(&content)?;
Ok(RsxNode::Component {
path,
props,
children,
})
}
fn parse_props_and_children(input: ParseStream) -> Result<(Vec<RsxProp>, Vec<RsxNode>)> {
Ok((parse_props(input)?, RsxRoot::parse(input)?.nodes))
}
fn parse_props(input: ParseStream) -> Result<Vec<RsxProp>> {
let mut props = Vec::new();
while !input.is_empty() {
let fork = input.fork();
let path: Path = match fork.parse() {
Ok(p) => p,
Err(_) => break,
};
if fork.peek(Token![:]) {
let name = path
.get_ident()
.cloned()
.ok_or_else(|| fork.error("prop name must be a single identifier"))?;
fork.parse::<Token![:]>()?;
let value: Expr = fork.parse()?;
input.advance_to(&fork);
props.push(RsxProp { name, value });
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
} else {
// ❌ do not commit → nothing consumed
break;
}
}
Ok(props)
}
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
let mut nodes = Vec::new();
while !input.is_empty() {
+6 -40
View File
@@ -5,19 +5,14 @@ use std::{
};
use crate::{
component::EventHandler,
engine::{Command, CommandExecutor},
render::DrawContext,
state::{use_effect, HookDependency},
View, ViewWrapper,
View,
};
pub type Component = Arc<dyn Fn(&Arc<Context>) -> View + Send + Sync>;
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
use super::{scope::Scope, Component, ComponentImpl};
pub struct Context {
component: Mutex<Component>,
@@ -28,7 +23,7 @@ pub struct Context {
}
impl Context {
pub fn new<F: Fn(&Arc<Self>) -> View + Send + Sync + 'static>(
pub fn new<F: ComponentImpl + 'static>(
component: F,
executor: Arc<dyn CommandExecutor>,
) -> Arc<Self> {
@@ -48,7 +43,7 @@ impl Context {
move || {
s.event_handlers.lock().unwrap().clear();
let c = s.component.lock().unwrap().clone();
*s.view.lock().unwrap() = (c)(&s);
*s.view.lock().unwrap() = c.call(&s);
}
});
}
@@ -62,7 +57,7 @@ impl Context {
move || {
s.event_handlers.lock().unwrap().clear();
let c = s.component.lock().unwrap().clone();
*s.view.lock().unwrap() = (c)(&s);
*s.view.lock().unwrap() = c.call(&s);
*done.lock().unwrap() = true;
}
});
@@ -185,32 +180,3 @@ impl Context {
self.execute(crate::engine::commands::Stop)
}
}
impl Scope {
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
Arc::new(Self {
children: Mutex::new(Vec::new()),
executor,
})
}
pub fn child<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
self: &Arc<Self>,
child: F,
view_wrapper: Option<ViewWrapper>,
) {
let ctx = Context::new(child, self.executor.clone());
ctx.refresh();
self.children.lock().unwrap().push((ctx, view_wrapper));
}
pub fn view(self: &Arc<Self>, view: View) {
let ctx = Context::new(move |_| view.clone(), self.executor.clone());
ctx.refresh();
self.children.lock().unwrap().push((ctx, None));
}
}
+33
View File
@@ -0,0 +1,33 @@
pub mod context;
pub mod scope;
use std::{
any::Any,
sync::{Arc, Mutex},
};
use crate::View;
use context::Context;
pub type Component = Arc<dyn ComponentImpl>;
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
pub trait ComponentImpl: Send + Sync {
fn call(&self, cx: &Arc<Context>) -> View;
}
impl ComponentImpl for View {
fn call(&self, _: &Arc<Context>) -> View {
self.clone()
}
}
impl<F> ComponentImpl for F
where
F: Fn(&Arc<Context>) -> View + Send + Sync,
{
fn call(&self, ctx: &Arc<Context>) -> View {
self(ctx)
}
}
+39
View File
@@ -0,0 +1,39 @@
use std::sync::{Arc, Mutex};
use crate::{engine::CommandExecutor, View, ViewWrapper};
use super::{context::Context, ComponentImpl};
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
impl Scope {
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
Arc::new(Self {
children: Mutex::new(Vec::new()),
executor,
})
}
pub fn child<F: ComponentImpl + 'static>(
self: &Arc<Self>,
child: F,
view_wrapper: Option<ViewWrapper>,
) {
let ctx = Context::new(child, self.executor.clone());
ctx.refresh();
self.children.lock().unwrap().push((ctx, view_wrapper));
}
pub fn view(self: &Arc<Self>, view: View) {
let ctx = Context::new(view, self.executor.clone());
ctx.refresh();
self.children.lock().unwrap().push((ctx, None));
}
}
+40 -37
View File
@@ -2,10 +2,20 @@ use std::{io::stdout, sync::Arc, time::Instant};
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
use crate::{prelude::Context, render::Area, DrawContext, View};
use crate::component::{context::Context, ComponentImpl};
use crate::{render::Area, DrawContext, View};
use super::Engine;
#[derive(Debug, Clone)]
pub struct BenchmarkResult {
pub average: u128,
pub min: u128,
pub max: u128,
pub total_render: u128,
pub total: u128,
}
pub struct Benchmark<T: Engine>(T);
impl<T: Engine> Benchmark<T> {
@@ -14,19 +24,18 @@ impl<T: Engine> Benchmark<T> {
}
}
impl<T: Engine> Engine for Benchmark<T> {
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
let mut times: Vec<usize> = Vec::new();
impl<T: Engine> Engine<BenchmarkResult> for Benchmark<T> {
fn run<F: ComponentImpl + 'static>(&self, component: F) -> crate::Result<BenchmarkResult> {
let mut times: Vec<u128> = Vec::new();
let cx = self.init(component);
let start = Instant::now();
for _ in 0..100 {
for _ in 0..40 {
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();
times.push(end.duration_since(start).as_micros());
}
let end = Instant::now();
@@ -35,40 +44,20 @@ impl<T: Engine> Engine for Benchmark<T> {
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::<usize>() / times.len()
Ok(BenchmarkResult {
min: *times.iter().min().unwrap_or(&0),
max: *times.iter().max().unwrap_or(&0),
total: end.duration_since(start).as_micros(),
total_render: times.iter().sum::<u128>(),
average: if times.len() > 0 {
times.iter().sum::<u128>() / (times.len() as u128)
} 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<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context> {
fn init<F: ComponentImpl + 'static>(&self, component: F) -> Arc<Context> {
self.0.init(component)
}
@@ -92,3 +81,17 @@ impl<T: Engine> Engine for Benchmark<T> {
self.0.executor()
}
}
impl std::fmt::Display for BenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"- Average: {} µs\n\
- Min: {} µs\n\
- Max: {} µs\n\
- Total Render: {} µs\n\
- Total: {} µs",
self.average, self.min, self.max, self.total_render, self.total
)
}
}
+7 -6
View File
@@ -5,9 +5,9 @@ use std::{
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
use crate::component::{context::Context, ComponentImpl};
use crate::{
engine::{commands, CommandExecutor},
prelude::Context,
render::Area,
DrawContext, View,
};
@@ -79,10 +79,9 @@ impl Engine for Console {
));
}
fn init<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context> {
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
crossterm::execute!(std::io::stdout(), crossterm::cursor::Hide).unwrap();
let cx = Context::new(component, self.executor.clone());
cx.refresh();
@@ -102,13 +101,15 @@ impl Engine for Console {
self.executor.clone()
}
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
fn run<F: ComponentImpl + 'static>(&self, component: F) -> crate::Result<()> {
let cx = self.init(component);
while self.executor.is_running() {
self.render(&cx);
self.render_delay();
}
Ok(())
}
}
+6 -16
View File
@@ -7,25 +7,15 @@ pub use console::*;
use std::{any::Any, sync::Arc};
use crate::{prelude::Context, render::Area, DrawContext, View};
use crate::component::{context::Context, ComponentImpl};
use crate::{render::Area, DrawContext, View};
pub trait Engine {
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
let cx = self.init(component);
loop {
self.render(&cx);
self.render_delay();
}
}
fn init<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context>;
pub trait Engine<Output = ()> {
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
fn render(&self, cx: &Arc<Context>);
fn render_delay(&self) {
std::thread::sleep(std::time::Duration::from_millis(16))
crate::sleep(16);
}
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
+44 -14
View File
@@ -1,19 +1,23 @@
use std::sync::Arc;
use crate::{
component::{Context, Scope},
state::HookDependency,
View,
};
use crate::component::{context::Context, scope::Scope};
use crate::{render::Point, state::HookDependency, View};
pub enum RsxScope {
Static(Box<dyn Fn(&Arc<Scope>) + Send + Sync>),
Dynamic(
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
Vec<Box<dyn HookDependency>>,
),
pub trait ToRsx {
fn to_rsx(&self) -> Rsx;
}
#[derive(Clone)]
pub enum RsxScope {
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
Dynamic(
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
Vec<Arc<dyn HookDependency>>,
),
Child(Rsx),
}
#[derive(Clone)]
pub struct Rsx(Vec<RsxScope>);
impl Rsx {
@@ -22,19 +26,23 @@ impl Rsx {
}
pub fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F) {
self.0.push(RsxScope::Static(Box::new(scope)));
self.0.push(RsxScope::Static(Arc::new(scope)));
}
pub fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
&mut self,
drawer: F,
dependencies: Vec<Box<dyn HookDependency>>,
dependencies: Vec<Arc<dyn HookDependency>>,
) {
self.0
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
}
pub fn view(&self, context: Arc<Context>) -> View {
pub fn child<R: ToRsx>(&mut self, child: R) {
self.0.push(RsxScope::Child(child.to_rsx()));
}
pub fn generate_children(&self, context: &Arc<Context>) {
let executor = context.get_executor();
for scope in &self.0 {
@@ -51,10 +59,16 @@ impl Rsx {
&dependencies.iter().map(|d| d.as_ref()).collect::<Vec<_>>(),
);
}
RsxScope::Child(child) => child.generate_children(context),
}
}
}
pub fn view(&self, context: &Arc<Context>) -> View {
let context = context.clone();
self.generate_children(&context);
Arc::new({
move |ctx| {
context.draw_children(ctx);
@@ -62,3 +76,19 @@ impl Rsx {
})
}
}
impl ToRsx for &Rsx {
fn to_rsx(&self) -> Rsx {
Rsx(self.0.clone())
}
}
impl<T: std::fmt::Display> ToRsx for T {
fn to_rsx(&self) -> Rsx {
let s = self.to_string();
Rsx(vec![RsxScope::Static(Arc::new(move |scope| {
let s = s.clone();
scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &s)))
}))])
}
}
+10 -3
View File
@@ -9,12 +9,15 @@ pub mod render;
pub mod state;
pub mod prelude {
pub use crate::component::*;
pub use crate::component::{context::*, scope::*, *};
pub use crate::engine::*;
pub use crate::frontend::*;
pub use crate::render::*;
pub use crate::state::*;
pub use crate::View;
pub use osui_macros::rsx;
pub use crate::{sleep, Error, Result, View, ViewWrapper};
pub use crossterm;
pub use osui_macros::{component, rsx};
pub use std::sync::{Arc, Mutex};
}
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;
@@ -31,3 +34,7 @@ impl From<std::sync::PoisonError<std::sync::MutexGuard<'_, bool>>> for Error {
Error::PoisonError
}
}
pub fn sleep(delay_ms: u64) {
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
}
+3 -3
View File
@@ -5,7 +5,7 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
use crate::prelude::Context;
use crate::component::context::Context;
#[derive(Clone)]
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
@@ -115,11 +115,11 @@ impl<T> DerefMut for Inner<'_, T> {
}
}
pub trait HookDependency {
pub trait HookDependency: Send + Sync {
fn on_update(&self, hook: HookEffect);
}
impl<T> HookDependency for State<T> {
impl<T: Send + Sync> HookDependency for State<T> {
fn on_update(&self, hook: HookEffect) {
self.dependents.lock().unwrap().push(hook);
}