From 0515087f0d4bbdbd0260c62ac610e463f9e1183b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 10:44:30 +0100 Subject: [PATCH 01/16] Renderer --- examples/hello_world.rs | 2 +- src/lib.rs | 46 +++++------------------------------------ src/render.rs | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 src/render.rs diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 7b6fdd5..2535b11 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -34,6 +34,6 @@ fn app(cx: &Arc) -> View { }); Arc::new(move |ctx| { - println!("Count: {count}, size: {:?}", ctx.available()); + println!("Count: {count}, size: {:?}", ctx.available); }) } diff --git a/src/lib.rs b/src/lib.rs index b07b882..f70bd95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,54 +1,18 @@ 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; - } -} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..f08e96d --- /dev/null +++ b/src/render.rs @@ -0,0 +1,41 @@ +use std::{fmt::Debug, sync::Arc}; + +pub trait DrawInstruction: Debug {} + +#[derive(Debug, Clone)] +pub struct Size { + pub width: u16, + pub height: u16, +} + +#[derive(Debug, Clone)] +pub struct DrawContext { + pub available: Size, + pub used: Size, + pub drawing: Vec>, +} + +#[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, + }, + drawing: Vec::new(), + } + } + + pub fn allocate(&mut self, width: u16, height: u16) { + self.used.width += width; + self.used.height += height; + } + + pub fn draw(&mut self, inst: I) { + self.drawing.push(Arc::new(inst)); + } +} From 979af999e42a8e1b92ebd5ba6c00a5f8030e1381 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 14:17:05 +0100 Subject: [PATCH 02/16] Simple renderer --- examples/hello_world.rs | 2 +- src/engine.rs | 28 ++++++++++++++++------------ src/render.rs | 38 ++++++++++++++++++++++++++------------ 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 2535b11..7e30dd4 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -34,6 +34,6 @@ fn app(cx: &Arc) -> View { }); 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/engine.rs b/src/engine.rs index 22dfc45..7f4f5e4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,8 +3,8 @@ use std::sync::{Arc, Mutex}; use crate::{prelude::Context, 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, ctx: &DrawContext, view: View); + fn draw_context(&self, ctx: &DrawContext); } pub struct Console { @@ -35,24 +35,28 @@ 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(); + let mut ctx = DrawContext::new(width, height); + self.render_view(&mut ctx, 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, ctx: &DrawContext, view: View) { + let mut context = DrawContext::new(ctx.allocated.width, ctx.allocated.height); + view(&mut context); + self.draw_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) => println!("{text}"), + crate::render::DrawInstruction::Child(point, child) => self.draw_context(child), + } + } } } diff --git a/src/render.rs b/src/render.rs index f08e96d..01c5353 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,6 +1,10 @@ -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; -pub trait DrawInstruction: Debug {} +#[derive(Debug, Clone)] +pub enum DrawInstruction { + Text(Point, String), + Child(Point, DrawContext), +} #[derive(Debug, Clone)] pub struct Size { @@ -9,33 +13,43 @@ pub struct Size { } #[derive(Debug, Clone)] -pub struct DrawContext { - pub available: Size, - pub used: Size, - pub drawing: Vec>, +pub struct Point { + pub x: u16, + pub y: u16, } #[derive(Debug, Clone)] -pub struct RenderContext {} +pub struct DrawContext { + pub available: Size, + pub allocated: Size, + pub parent: Point, + pub drawing: Vec, +} impl DrawContext { pub fn new(width: u16, height: u16) -> Self { Self { available: Size { width, height }, - used: Size { + allocated: Size { width: 0, height: 0, }, + parent: Point { x: 0, y: 0 }, drawing: Vec::new(), } } pub fn allocate(&mut self, width: u16, height: u16) { - self.used.width += width; - self.used.height += height; + self.allocated.width += width; + self.allocated.height += height; } - pub fn draw(&mut self, inst: I) { - self.drawing.push(Arc::new(inst)); + 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())); } } From 49bb99fedb30bc585965b50a9e85fa614236f864 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 14:26:04 +0100 Subject: [PATCH 03/16] Simple test --- src/engine.rs | 11 +++++++---- src/render.rs | 16 +++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7f4f5e4..4582e92 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use crate::{prelude::Context, DrawContext, View}; pub trait Engine { - fn render_view(&self, ctx: &DrawContext, view: View); + fn render_view(&self, ctx: &DrawContext, view: &View); fn draw_context(&self, ctx: &DrawContext); } @@ -38,17 +38,19 @@ impl Console { loop { let (width, height) = crossterm::terminal::size().unwrap(); let mut ctx = DrawContext::new(width, height); - self.render_view(&mut ctx, cx.get_view()); + + self.render_view(&mut ctx, &cx.get_view()); + self.draw_context(&ctx); + std::thread::sleep(std::time::Duration::from_millis(16)); } } } impl Engine for Console { - fn render_view(&self, ctx: &DrawContext, view: View) { + fn render_view(&self, ctx: &DrawContext, view: &View) { let mut context = DrawContext::new(ctx.allocated.width, ctx.allocated.height); view(&mut context); - self.draw_context(&context); } fn draw_context(&self, ctx: &DrawContext) { @@ -56,6 +58,7 @@ impl Engine for Console { match inst { crate::render::DrawInstruction::Text(point, text) => println!("{text}"), crate::render::DrawInstruction::Child(point, child) => self.draw_context(child), + crate::render::DrawInstruction::View(point, view) => self.render_view(ctx, view), } } } diff --git a/src/render.rs b/src/render.rs index 01c5353..2502427 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,24 +1,25 @@ -use std::fmt::Debug; +use crate::View; -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum DrawInstruction { Text(Point, String), + View(Point, View), Child(Point, DrawContext), } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Size { pub width: u16, pub height: u16, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Point { pub x: u16, pub y: u16, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct DrawContext { pub available: Size, pub allocated: Size, @@ -52,4 +53,9 @@ impl DrawContext { self.drawing .push(DrawInstruction::Text(point, text.to_string())); } + + pub fn draw_view(&mut self, point: Point, view: View) { + let ctx = DrawContext::new(self.allocated.width, self.allocated.height); + self.drawing.push(DrawInstruction::Child(point, ctx)); + } } From 1f9bf48bb4bff5b078deaa772849163b55083234 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 14:37:19 +0100 Subject: [PATCH 04/16] View render --- src/engine.rs | 30 +++++++++++++++++++----------- src/render.rs | 23 ++++++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 4582e92..092148f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,9 +1,9 @@ use std::sync::{Arc, Mutex}; -use crate::{prelude::Context, DrawContext, View}; +use crate::{prelude::Context, render::Area, DrawContext, View}; pub trait Engine { - fn render_view(&self, ctx: &DrawContext, view: &View); + fn render_view(&self, area: &Area, view: &View) -> DrawContext; fn draw_context(&self, ctx: &DrawContext); } @@ -37,10 +37,15 @@ impl Console { loop { let (width, height) = crossterm::terminal::size().unwrap(); - let mut ctx = DrawContext::new(width, height); - - self.render_view(&mut ctx, &cx.get_view()); - self.draw_context(&ctx); + 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)); } @@ -48,17 +53,20 @@ impl Console { } impl Engine for Console { - fn render_view(&self, ctx: &DrawContext, view: &View) { - let mut context = DrawContext::new(ctx.allocated.width, ctx.allocated.height); + 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) => println!("{text}"), - crate::render::DrawInstruction::Child(point, child) => self.draw_context(child), - crate::render::DrawInstruction::View(point, view) => self.render_view(ctx, view), + crate::render::DrawInstruction::Text(_point, text) => println!("{text}"), + 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/render.rs b/src/render.rs index 2502427..efed6ec 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,7 +3,7 @@ use crate::View; #[derive(Clone)] pub enum DrawInstruction { Text(Point, String), - View(Point, View), + View(Area, View), Child(Point, DrawContext), } @@ -19,23 +19,29 @@ pub struct Point { 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 available: Size, + pub area: Area, pub allocated: Size, - pub parent: Point, pub drawing: Vec, } impl DrawContext { - pub fn new(width: u16, height: u16) -> Self { + pub fn new(area: Area) -> Self { Self { - available: Size { width, height }, + area, allocated: Size { width: 0, height: 0, }, - parent: Point { x: 0, y: 0 }, drawing: Vec::new(), } } @@ -54,8 +60,7 @@ impl DrawContext { .push(DrawInstruction::Text(point, text.to_string())); } - pub fn draw_view(&mut self, point: Point, view: View) { - let ctx = DrawContext::new(self.allocated.width, self.allocated.height); - self.drawing.push(DrawInstruction::Child(point, ctx)); + pub fn draw_view(&mut self, area: Area, view: View) { + self.drawing.push(DrawInstruction::View(area, view)); } } From 0f12a371e5a694f2499feda5122360f0700281ea Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 14:47:53 +0100 Subject: [PATCH 05/16] Renderer --- examples/hello_world.rs | 8 ++++++++ src/render.rs | 21 ++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 7e30dd4..8e8f787 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -35,5 +35,13 @@ fn app(cx: &Arc) -> View { Arc::new(move |ctx| { ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); + let area = ctx.allocate(0, 0, 10, 10); + ctx.draw_view(area, Arc::new(my_view)); }) } + +fn my_view(ctx: &mut DrawContext) { + let count = 67; + + ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); +} diff --git a/src/render.rs b/src/render.rs index efed6ec..fadbe84 100644 --- a/src/render.rs +++ b/src/render.rs @@ -30,7 +30,7 @@ pub struct Area { #[derive(Clone)] pub struct DrawContext { pub area: Area, - pub allocated: Size, + pub allocated: Area, pub drawing: Vec, } @@ -38,7 +38,9 @@ impl DrawContext { pub fn new(area: Area) -> Self { Self { area, - allocated: Size { + allocated: Area { + x: u16::MAX, + y: u16::MAX, width: 0, height: 0, }, @@ -46,9 +48,18 @@ impl DrawContext { } } - pub fn allocate(&mut self, width: u16, height: u16) { - self.allocated.width += width; - self.allocated.height += height; + 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) { From 33370287b2cbf51dd9b3191ce9990b2bff6235f8 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 14:49:47 +0100 Subject: [PATCH 06/16] Renderer --- examples/hello_world.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 8e8f787..bc9d0dc 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -30,6 +30,7 @@ fn app(cx: &Arc) -> View { move |_cx, _event: &KeyPress| { let mut count = count.get(); *count += 1; + cx.refresh(); } }); From 02f3aea4e384ef1ad1476d8360ed271a4aeded35 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 16:01:22 +0100 Subject: [PATCH 07/16] Fixed refresh --- examples/hello_world.rs | 5 ++++- src/component.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index bc9d0dc..d50f1cf 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -29,8 +29,11 @@ fn app(cx: &Arc) -> View { let count = count.clone(); move |_cx, _event: &KeyPress| { let mut count = count.get(); + if *count > 5 { + _cx.refresh(); + return; + } *count += 1; - cx.refresh(); } }); diff --git a/src/component.rs b/src/component.rs index 2d8bccf..6da4dc2 100644 --- a/src/component.rs +++ b/src/component.rs @@ -35,6 +35,8 @@ impl Context { } pub fn refresh(self: &Arc) { + self.event_handlers.lock().unwrap().clear(); + let c = self.component.lock().unwrap().clone(); *self.view.lock().unwrap() = (c)(self); } From abe37686d8cf8e539c485d9ebb7100de80894e91 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 16:40:03 +0100 Subject: [PATCH 08/16] Refresh on children --- examples/hello_world.rs | 26 ++++++++++++++++++-------- src/component.rs | 28 +++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index d50f1cf..cd7dcd5 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -23,6 +23,24 @@ fn main() { } fn app(cx: &Arc) -> View { + let mc_cx = Context::new(my_component); + + cx.on_event({ + let mc_cx = mc_cx.clone(); + move |_, &KeyPress| { + mc_cx.emit_event(&KeyPress); + } + }); + + mc_cx.refresh(); + + Arc::new(move |ctx| { + let area = ctx.allocate(0, 0, 10, 10); + ctx.draw_view(area, mc_cx.get_view()); + }) +} + +fn my_component(cx: &Arc) -> View { let count = use_state(0); cx.on_event({ @@ -39,13 +57,5 @@ fn app(cx: &Arc) -> View { Arc::new(move |ctx| { ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); - let area = ctx.allocate(0, 0, 10, 10); - ctx.draw_view(area, Arc::new(my_view)); }) } - -fn my_view(ctx: &mut DrawContext) { - let count = 67; - - ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {count}")); -} diff --git a/src/component.rs b/src/component.rs index 6da4dc2..9404930 100644 --- a/src/component.rs +++ b/src/component.rs @@ -35,10 +35,32 @@ impl Context { } pub fn refresh(self: &Arc) { - self.event_handlers.lock().unwrap().clear(); + let s = self.clone(); - let c = self.component.lock().unwrap().clone(); - *self.view.lock().unwrap() = (c)(self); + 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 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 { From 9d55dacde275731df66810edb05849f3f6ff5ae4 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 17:22:59 +0100 Subject: [PATCH 09/16] Context children --- examples/hello_world.rs | 18 ++++++------------ src/component.rs | 28 +++++++++++++++++++++++++++- src/lib.rs | 1 + 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index cd7dcd5..387c314 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -23,20 +23,14 @@ fn main() { } fn app(cx: &Arc) -> View { - let mc_cx = Context::new(my_component); + cx.child(my_component, None); - cx.on_event({ - let mc_cx = mc_cx.clone(); - move |_, &KeyPress| { - mc_cx.emit_event(&KeyPress); + Arc::new({ + let cx = cx.clone(); + + move |ctx| { + cx.draw_children(ctx); } - }); - - mc_cx.refresh(); - - Arc::new(move |ctx| { - let area = ctx.allocate(0, 0, 10, 10); - ctx.draw_view(area, mc_cx.get_view()); }) } diff --git a/src/component.rs b/src/component.rs index 9404930..b9ea6e5 100644 --- a/src/component.rs +++ b/src/component.rs @@ -4,7 +4,7 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::View; +use crate::{render::DrawContext, View, ViewWrapper}; pub trait Event { fn as_any(&self) -> &dyn Any; @@ -23,6 +23,7 @@ pub struct Context { component: Mutex, event_handlers: Mutex>>, view: Mutex, + children: Mutex, Option)>>, } impl Context { @@ -31,6 +32,7 @@ impl Context { component: Mutex::new(Arc::new(component)), event_handlers: Mutex::new(HashMap::new()), view: Mutex::new(Arc::new(|_| {})), + children: Mutex::new(Vec::new()), }) } @@ -108,4 +110,28 @@ impl Context { } } } + + 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 draw_children(self: &Arc, ctx: &mut DrawContext) { + for (child, view_wrapper) in self.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.allocated.clone(), view); + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index f70bd95..16af55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,3 +16,4 @@ pub mod prelude { } pub type View = Arc; +pub type ViewWrapper = Arc; From 8f0712d4f82d81f90dc033551f7ebade90501e6f Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 17:31:58 +0100 Subject: [PATCH 10/16] Event passing --- src/component.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/component.rs b/src/component.rs index b9ea6e5..60758b5 100644 --- a/src/component.rs +++ b/src/component.rs @@ -93,6 +93,10 @@ impl Context { (i.lock().unwrap())(self, event); } } + + for (child, _) in self.children.lock().unwrap().iter() { + child.emit_event(event); + } } pub fn emit_event_threaded( @@ -109,6 +113,10 @@ impl Context { }); } } + + for (child, _) in self.children.lock().unwrap().iter() { + child.emit_event_threaded(event); + } } pub fn child) -> View + Send + Sync + 'static>( From 9953e4a0bc674f23d12e611b7ebaf5d86a95ab14 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 17:46:33 +0100 Subject: [PATCH 11/16] Render position --- examples/hello_world.rs | 16 +++++++++++++++- src/component.rs | 2 +- src/engine.rs | 18 ++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 387c314..72dc920 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -23,7 +23,21 @@ fn main() { } fn app(cx: &Arc) -> View { - cx.child(my_component, None); + cx.child( + my_component, + Some(Arc::new(|ctx, view| { + let area = ctx.allocate(0, 0, 0, 0); + ctx.draw_view(area, view) + })), + ); + + cx.child( + my_component, + Some(Arc::new(|ctx, view| { + let area = ctx.allocate(0, 1, 0, 0); + ctx.draw_view(area, view) + })), + ); Arc::new({ let cx = cx.clone(); diff --git a/src/component.rs b/src/component.rs index 60758b5..1081e73 100644 --- a/src/component.rs +++ b/src/component.rs @@ -138,7 +138,7 @@ impl Context { if let Some(view_wrapper) = view_wrapper { view_wrapper(ctx, view) } else { - ctx.draw_view(ctx.allocated.clone(), view); + ctx.draw_view(ctx.area.clone(), view); } } } diff --git a/src/engine.rs b/src/engine.rs index 092148f..ac02d84 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,4 +1,9 @@ -use std::sync::{Arc, Mutex}; +use std::{ + io::{stdout, Write}, + sync::{Arc, Mutex}, +}; + +use crossterm::{cursor::MoveTo, execute, terminal::Clear}; use crate::{prelude::Context, render::Area, DrawContext, View}; @@ -37,6 +42,10 @@ impl Console { 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, @@ -62,7 +71,12 @@ impl Engine for Console { fn draw_context(&self, ctx: &DrawContext) { for inst in &ctx.drawing { match inst { - crate::render::DrawInstruction::Text(_point, text) => println!("{text}"), + 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)) From 926405a8633e5c59859967e9abde7010500841c2 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 24 Jan 2026 21:38:45 +0100 Subject: [PATCH 12/16] Dynamic scoping --- examples/hello_world.rs | 32 +++++++++-------- src/component.rs | 79 ++++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 72dc920..1d45b1a 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -23,21 +23,25 @@ fn main() { } fn app(cx: &Arc) -> View { - cx.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 0, 0, 0); - ctx.draw_view(area, view) - })), - ); + { + let scope = cx.scope(); - cx.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 1, 0, 0); - ctx.draw_view(area, view) - })), - ); + scope.child( + my_component, + Some(Arc::new(|ctx, view| { + let area = ctx.allocate(0, 0, 0, 0); + ctx.draw_view(area, view) + })), + ); + + scope.child( + my_component, + Some(Arc::new(|ctx, view| { + let area = ctx.allocate(0, 1, 0, 0); + ctx.draw_view(area, view) + })), + ); + } Arc::new({ let cx = cx.clone(); diff --git a/src/component.rs b/src/component.rs index 1081e73..4c6a585 100644 --- a/src/component.rs +++ b/src/component.rs @@ -19,24 +19,29 @@ 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 scope: Mutex, +} + pub struct Context { component: Mutex, event_handlers: Mutex>>, view: Mutex, - children: Mutex, Option)>>, + 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(|_| {})), - children: Mutex::new(Vec::new()), + scopes: Mutex::new(Vec::new()), }) } - pub fn refresh(self: &Arc) { + pub fn refresh(self: &Arc) { let s = self.clone(); std::thread::spawn({ @@ -48,7 +53,7 @@ impl Context { }); } - pub fn refresh_atomic(self: &Arc) -> Arc> { + pub fn refresh_atomic(self: &Arc) -> Arc> { let s = self.clone(); let done = Arc::new(Mutex::new(false)); @@ -65,12 +70,12 @@ impl Context { done } - pub fn get_view(self: &Arc) -> View { + 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 @@ -79,7 +84,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); } @@ -87,20 +92,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 (child, _) in self.children.lock().unwrap().iter() { - child.emit_event(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::()) { @@ -114,13 +121,41 @@ impl Context { } } - for (child, _) in self.children.lock().unwrap().iter() { - child.emit_event_threaded(event); + 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()), + scope: Mutex::new(0), + }); + self.scopes.lock().unwrap().push(scope.clone()); + + 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, + self: &Arc, child: F, view_wrapper: Option, ) { @@ -130,16 +165,4 @@ impl Context { self.children.lock().unwrap().push((ctx, view_wrapper)); } - - pub fn draw_children(self: &Arc, ctx: &mut DrawContext) { - for (child, view_wrapper) in self.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); - } - } - } } From 05a390b10feaf8427d6fb7caa9e8970d7f44c85c Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 00:30:53 +0100 Subject: [PATCH 13/16] Dynamic scoping --- examples/hello_world.rs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 1d45b1a..23aeeab 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -23,6 +23,8 @@ fn main() { } fn app(cx: &Arc) -> View { + let count = use_state(0); + { let scope = cx.scope(); @@ -34,13 +36,28 @@ fn app(cx: &Arc) -> View { })), ); - scope.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 1, 0, 0); - ctx.draw_view(area, view) - })), - ); + cx.on_event({ + let count = count.clone(); + move |_, _: &KeyPress| { + let mut count = count.get(); + + if *count > 5 { + if scope.children.lock().unwrap().len() == 1 { + scope.child( + my_component, + Some(Arc::new(|ctx, view| { + let area = ctx.allocate(0, 1, 0, 0); + ctx.draw_view(area, view) + })), + ); + } + + *count = 0; + } + + *count += 1; + } + }); } Arc::new({ @@ -53,7 +70,7 @@ fn app(cx: &Arc) -> View { } fn my_component(cx: &Arc) -> View { - let count = use_state(0); + let count = use_state(1); cx.on_event({ let count = count.clone(); From 6e481862ec2997622a6ed63b97431c84770eec9b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 02:17:08 +0100 Subject: [PATCH 14/16] Dynamic scoping with if statement --- examples/hello_world.rs | 56 ++++++++++++++++++++++++++--------------- src/component.rs | 31 ++++++++++++++++++++--- src/state.rs | 9 ++++++- 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 23aeeab..dc65101 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -25,6 +25,21 @@ 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(); @@ -35,29 +50,30 @@ fn app(cx: &Arc) -> View { ctx.draw_view(area, view) })), ); + } - cx.on_event({ - let count = count.clone(); - move |_, _: &KeyPress| { - let mut count = count.get(); - - if *count > 5 { - if scope.children.lock().unwrap().len() == 1 { - scope.child( - my_component, - Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 1, 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(0, 2, 0, 0); + ctx.draw_view(area, view) + })), + ); + } + } else { + scope.children.lock().unwrap().clear(); } - - *count = 0; } - - *count += 1; - } - }); + }, + &[&count], + ); } Arc::new({ diff --git a/src/component.rs b/src/component.rs index 4c6a585..18a4d9b 100644 --- a/src/component.rs +++ b/src/component.rs @@ -4,7 +4,11 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::{render::DrawContext, View, ViewWrapper}; +use crate::{ + render::DrawContext, + state::{use_effect, HookDependency}, + View, ViewWrapper, +}; pub trait Event { fn as_any(&self) -> &dyn Any; @@ -21,7 +25,6 @@ pub type EventHandler = Arc, &dyn Event) + Send + pub struct Scope { pub children: Mutex, Option)>>, - pub scope: Mutex, } pub struct Context { @@ -131,13 +134,35 @@ impl Context { pub fn scope(self: &Arc) -> Arc { let scope = Arc::new(Scope { children: Mutex::new(Vec::new()), - scope: Mutex::new(0), }); 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() { 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()); From 470512a5429f6f63db5063e8ba2041969cd03d03 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 02:22:16 +0100 Subject: [PATCH 15/16] Dynamic scoping with for loop --- examples/hello_world.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index dc65101..fc998cd 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -46,7 +46,7 @@ fn app(cx: &Arc) -> View { scope.child( my_component, Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 0, 0, 0); + let area = ctx.allocate(5, 0, 0, 0); ctx.draw_view(area, view) })), ); @@ -62,7 +62,7 @@ fn app(cx: &Arc) -> View { scope.child( my_component, Some(Arc::new(|ctx, view| { - let area = ctx.allocate(0, 2, 0, 0); + let area = ctx.allocate(5, 2, 0, 0); ctx.draw_view(area, view) })), ); @@ -76,6 +76,27 @@ fn app(cx: &Arc) -> View { ); } + { + cx.dyn_scope( + { + let count = count.clone(); + move |scope| { + scope.children.lock().unwrap().clear(); + + for i in 0..count.get_dl() { + scope.child( + my_component, + Some(Arc::new(move |ctx, _| { + ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); + })), + ); + } + } + }, + &[&count], + ); + } + Arc::new({ let cx = cx.clone(); From 6b3e9239c94d408f0aa429559975dcc5235a9fe0 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 25 Jan 2026 02:27:36 +0100 Subject: [PATCH 16/16] Scope view --- examples/hello_world.rs | 9 +++------ src/component.rs | 8 ++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index fc998cd..87702ef 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -84,12 +84,9 @@ fn app(cx: &Arc) -> View { scope.children.lock().unwrap().clear(); for i in 0..count.get_dl() { - scope.child( - my_component, - Some(Arc::new(move |ctx, _| { - ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); - })), - ); + scope.view(Arc::new(move |ctx| { + ctx.draw_text(Point { x: 0, y: i }, &format!("{i}")); + })); } } }, diff --git a/src/component.rs b/src/component.rs index 18a4d9b..a65bde0 100644 --- a/src/component.rs +++ b/src/component.rs @@ -190,4 +190,12 @@ impl Scope { 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)); + } }