diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 7b6fdd5..87702ef 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -25,15 +25,100 @@ fn main() { fn app(cx: &Arc) -> View { let count = use_state(0); + cx.on_event({ + let count = count.clone(); + move |_, _: &KeyPress| { + let mut count = count.get(); + + if *count > 5 { + *count = 0; + + return; + } + + *count += 1; + } + }); + + { + let scope = cx.scope(); + + 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| { + 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], + ); + } + + Arc::new({ + let cx = cx.clone(); + + move |ctx| { + cx.draw_children(ctx); + } + }) +} + +fn my_component(cx: &Arc) -> View { + let count = use_state(1); + cx.on_event({ let count = count.clone(); move |_cx, _event: &KeyPress| { let mut count = count.get(); + if *count > 5 { + _cx.refresh(); + return; + } *count += 1; } }); Arc::new(move |ctx| { - println!("Count: {count}, size: {:?}", ctx.available()); + ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); }) } diff --git a/src/component.rs b/src/component.rs index 2d8bccf..a65bde0 100644 --- a/src/component.rs +++ b/src/component.rs @@ -4,7 +4,11 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::View; +use crate::{ + render::DrawContext, + state::{use_effect, HookDependency}, + View, ViewWrapper, +}; pub trait Event { fn as_any(&self) -> &dyn Any; @@ -19,32 +23,62 @@ impl<'a> dyn Event + 'a { pub type Component = Arc) -> View + Send + Sync>; pub type EventHandler = Arc, &dyn Event) + Send + Sync>>; +pub struct Scope { + pub children: Mutex, Option)>>, +} + pub struct Context { component: Mutex, event_handlers: Mutex>>, view: Mutex, + scopes: Mutex>>, } impl Context { - pub fn new) -> View + Send + Sync + 'static>(component: F) -> Arc { + pub fn new) -> View + Send + Sync + 'static>(component: F) -> 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()), }) } - pub fn refresh(self: &Arc) { - let c = self.component.lock().unwrap().clone(); - *self.view.lock().unwrap() = (c)(self); + pub fn refresh(self: &Arc) { + let s = self.clone(); + + std::thread::spawn({ + move || { + s.event_handlers.lock().unwrap().clear(); + let c = s.component.lock().unwrap().clone(); + *s.view.lock().unwrap() = (c)(&s); + } + }); } - pub fn get_view(self: &Arc) -> View { + pub fn refresh_atomic(self: &Arc) -> Arc> { + let s = self.clone(); + let done = Arc::new(Mutex::new(false)); + + std::thread::spawn({ + let done = done.clone(); + move || { + s.event_handlers.lock().unwrap().clear(); + let c = s.component.lock().unwrap().clone(); + *s.view.lock().unwrap() = (c)(&s); + *done.lock().unwrap() = true; + } + }); + + done + } + + pub fn get_view(self: &Arc) -> View { self.view.lock().unwrap().clone() } - pub fn on_event, &T) + Send + Sync + 'static>( - self: &Arc, + pub fn on_event, &T) + Send + Sync + 'static>( + self: &Arc, handler: F, ) { self.event_handlers @@ -53,7 +87,7 @@ impl Context { .entry(TypeId::of::()) .or_insert_with(|| Vec::new()) .push(Arc::new(Mutex::new( - move |ctx: &Arc, event: &dyn Event| { + move |ctx: &Arc, event: &dyn Event| { if let Some(e) = event.get::() { (handler)(ctx, e); } @@ -61,16 +95,22 @@ impl Context { ))); } - 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); } } + + for scope in self.scopes.lock().unwrap().iter() { + for (child, _) in scope.children.lock().unwrap().iter() { + child.emit_event(event); + } + } } pub fn emit_event_threaded( - self: &Arc, + self: &Arc, event: &E, ) { if let Some(v) = self.event_handlers.lock().unwrap().get(&TypeId::of::()) { @@ -83,5 +123,79 @@ impl Context { }); } } + + for scope in self.scopes.lock().unwrap().iter() { + for (child, _) in scope.children.lock().unwrap().iter() { + child.emit_event_threaded(event); + } + } + } + + pub fn scope(self: &Arc) -> Arc { + let scope = Arc::new(Scope { + children: Mutex::new(Vec::new()), + }); + self.scopes.lock().unwrap().push(scope.clone()); + + scope + } + + pub fn dyn_scope) + Send + Sync + 'static>( + self: &Arc, + drawer: F, + dependencies: &[&dyn HookDependency], + ) -> Arc { + let scope = Arc::new(Scope { + children: Mutex::new(Vec::new()), + }); + self.scopes.lock().unwrap().push(scope.clone()); + + use_effect( + { + let scope = scope.clone(); + move || { + drawer(&scope); + } + }, + dependencies, + ); + + scope + } + + pub fn draw_children(self: &Arc, ctx: &mut DrawContext) { + for scope in self.scopes.lock().unwrap().iter() { + for (child, view_wrapper) in scope.children.lock().unwrap().iter() { + let view = child.get_view(); + + if let Some(view_wrapper) = view_wrapper { + view_wrapper(ctx, view) + } else { + ctx.draw_view(ctx.area.clone(), view); + } + } + } + } +} + +impl Scope { + pub fn child) -> View + Send + Sync + 'static>( + self: &Arc, + child: F, + view_wrapper: Option, + ) { + let ctx = Context::new(child); + + ctx.refresh(); + + self.children.lock().unwrap().push((ctx, view_wrapper)); + } + + pub fn view(self: &Arc, view: View) { + let ctx = Context::new(move |_| view.clone()); + + ctx.refresh(); + + self.children.lock().unwrap().push((ctx, None)); } } diff --git a/src/engine.rs b/src/engine.rs index 22dfc45..ac02d84 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,10 +1,15 @@ -use std::sync::{Arc, Mutex}; +use std::{ + io::{stdout, Write}, + sync::{Arc, Mutex}, +}; -use crate::{prelude::Context, DrawContext, View}; +use crossterm::{cursor::MoveTo, execute, terminal::Clear}; + +use crate::{prelude::Context, render::Area, DrawContext, View}; pub trait Engine { - fn render(&self, ctx: &mut DrawContext, view: View); - fn draw_text(&self, ctx: &mut DrawContext, text: &str); + fn render_view(&self, area: &Area, view: &View) -> DrawContext; + fn draw_context(&self, ctx: &DrawContext); } pub struct Console { @@ -35,24 +40,48 @@ impl Console { }); } - let (width, height) = crossterm::terminal::size().unwrap(); - loop { - cx.get_view()(&mut DrawContext::new(width, height)); + 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(&self, ctx: &mut DrawContext, view: View) { - view(&mut DrawContext::new( - ctx.available.width, - ctx.available.height, - )); + fn render_view(&self, area: &Area, view: &View) -> DrawContext { + let mut context = DrawContext::new(area.clone()); + view(&mut context); + context } - fn draw_text(&self, _ctx: &mut DrawContext, text: &str) { - println!("{}", text); + 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/lib.rs b/src/lib.rs index b07b882..16af55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,54 +1,19 @@ use std::sync::Arc; +use crate::render::DrawContext; + pub mod component; pub mod engine; +pub mod render; pub mod state; pub mod prelude { pub use crate::component::*; pub use crate::engine::*; + pub use crate::render::*; pub use crate::state::*; - pub use crate::{DrawContext, View}; + pub use crate::View; } pub type View = Arc; - -#[derive(Debug, Clone)] -pub struct Size { - pub width: u16, - pub height: u16, -} - -#[derive(Debug, Clone)] -pub struct DrawContext { - available: Size, - used: Size, -} - -#[derive(Debug, Clone)] -pub struct RenderContext {} - -impl DrawContext { - pub fn new(width: u16, height: u16) -> Self { - Self { - available: Size { width, height }, - used: Size { - width: 0, - height: 0, - }, - } - } - - pub fn available(&self) -> &Size { - &self.available - } - - pub fn used(&self) -> &Size { - &self.used - } - - pub fn allocate(&mut self, width: u16, height: u16) { - self.used.width += width; - self.used.height += height; - } -} +pub type ViewWrapper = Arc; diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..fadbe84 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,77 @@ +use crate::View; + +#[derive(Clone)] +pub enum DrawInstruction { + Text(Point, String), + View(Area, View), + Child(Point, DrawContext), +} + +#[derive(Clone)] +pub struct Size { + pub width: u16, + pub height: u16, +} + +#[derive(Clone)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +#[derive(Clone)] +pub struct Area { + pub x: u16, + pub y: u16, + pub width: u16, + pub height: u16, +} + +#[derive(Clone)] +pub struct DrawContext { + pub area: Area, + pub allocated: Area, + pub drawing: Vec, +} + +impl DrawContext { + pub fn new(area: Area) -> Self { + Self { + area, + allocated: Area { + x: u16::MAX, + y: u16::MAX, + width: 0, + height: 0, + }, + drawing: Vec::new(), + } + } + + pub fn allocate(&mut self, x: u16, y: u16, width: u16, height: u16) -> Area { + self.allocated.x = self.allocated.x.min(x); + self.allocated.y = self.allocated.y.min(y); + self.allocated.width = self.allocated.width.max(width); + self.allocated.height = self.allocated.height.max(height); + + Area { + x, + y, + width, + height, + } + } + + pub fn draw(&mut self, inst: DrawInstruction) { + self.drawing.push(inst); + } + + pub fn draw_text(&mut self, point: Point, text: &str) { + self.drawing + .push(DrawInstruction::Text(point, text.to_string())); + } + + pub fn draw_view(&mut self, area: Area, view: View) { + self.drawing.push(DrawInstruction::View(area, view)); + } +} diff --git a/src/state.rs b/src/state.rs index c851b68..669ecca 100644 --- a/src/state.rs +++ b/src/state.rs @@ -113,7 +113,14 @@ impl HookDependency for State { } pub fn use_effect(f: F, dependencies: &[&dyn HookDependency]) { - let hook = HookEffect(Arc::new(Mutex::new(f))); + let f = Arc::new(Mutex::new(f)); + let hook = HookEffect(Arc::new(Mutex::new({ + let f = f.clone(); + move || { + let f = f.clone(); + std::thread::spawn(move || (f.lock().unwrap())()); + } + }))); for d in dependencies { d.on_update(hook.clone());