Merge pull request #12 from osui-rs/better-state-management

Better state management
This commit is contained in:
Leo Codes
2025-08-01 16:34:26 -05:00
committed by GitHub
8 changed files with 287 additions and 98 deletions
+5 -4
View File
@@ -21,14 +21,15 @@
</p> </p>
```rust ```rust
use osui::{style::Transform, Screen}; use osui::prelude::*;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let screen = Screen::new(); let screen = Screen::new();
screen rsx! {
.draw(format!("Hello, World")) "Hello, World"
.component(Transform::center()); }
.draw(&screen);
screen.run() screen.run()
} }
+2 -2
View File
@@ -29,7 +29,7 @@ impl Element for Div {
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
elem.0.lock().unwrap().render(scope); elem.get_elem().render(scope);
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
@@ -40,7 +40,7 @@ impl Element for Div {
t.y += transform.y + transform.py; t.y += transform.y + transform.py;
scope.draw(); scope.draw();
elem.0.lock().unwrap().after_render(scope); elem.get_elem().after_render(scope);
} }
scope.set_parent_size(w, h); scope.set_parent_size(w, h);
self.size = (transform.width, transform.height); self.size = (transform.width, transform.height);
+4 -4
View File
@@ -38,7 +38,7 @@ impl Element for FlexRow {
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
elem.0.lock().unwrap().render(scope); elem.get_elem().render(scope);
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
@@ -53,7 +53,7 @@ impl Element for FlexRow {
scope.draw(); scope.draw();
elem.0.lock().unwrap().after_render(scope); elem.get_elem().after_render(scope);
} }
scope.set_parent_size(w, h); scope.set_parent_size(w, h);
self.size = (transform.width, transform.height); self.size = (transform.width, transform.height);
@@ -104,7 +104,7 @@ impl Element for FlexCol {
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
elem.0.lock().unwrap().render(scope); elem.get_elem().render(scope);
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
@@ -119,7 +119,7 @@ impl Element for FlexCol {
scope.draw(); scope.draw();
elem.0.lock().unwrap().after_render(scope); elem.get_elem().after_render(scope);
} }
scope.set_parent_size(w, h); scope.set_parent_size(w, h);
self.size = (transform.width, transform.height); self.size = (transform.width, transform.height);
+28 -8
View File
@@ -2,12 +2,14 @@ use std::sync::Arc;
use crate::{ use crate::{
state::DependencyHandler, state::DependencyHandler,
widget::{Widget, WidgetLoad}, widget::{StaticWidget, Widget, WidgetLoad},
Screen, Screen,
}; };
pub enum RsxElement { pub enum RsxElement {
Element( Element(Arc<StaticWidget>, Rsx),
DynElement(
Box<dyn FnMut() -> WidgetLoad + Send + Sync>, Box<dyn FnMut() -> WidgetLoad + Send + Sync>,
Vec<Box<dyn DependencyHandler>>, Vec<Box<dyn DependencyHandler>>,
Rsx, Rsx,
@@ -24,13 +26,13 @@ impl Rsx {
pub fn draw_parent(self, screen: &Arc<Screen>, parent: Option<Arc<Widget>>) { pub fn draw_parent(self, screen: &Arc<Screen>, parent: Option<Arc<Widget>>) {
for rsx_elem in self.0 { for rsx_elem in self.0 {
match rsx_elem { match rsx_elem {
RsxElement::Element(f, dep, child) => { RsxElement::DynElement(f, dep, child) => {
let w = if let Some(parent) = &parent { let w = if let Some(parent) = &parent {
let w = screen.draw_box(f); let w = screen.draw_box_dyn(f);
parent.0.lock().unwrap().draw_child(&w); parent.get_elem().draw_child(&w);
w w
} else { } else {
screen.draw_box(f) screen.draw_box_dyn(f)
}; };
for d in dep { for d in dep {
@@ -38,6 +40,17 @@ impl Rsx {
} }
child.draw_parent(screen, Some(w.clone())); child.draw_parent(screen, Some(w.clone()));
} }
RsxElement::Element(ws, child) => {
let w = Arc::new(Widget::Static(ws));
screen.draw_widget(w.clone());
if let Some(parent) = &parent {
parent.get_elem().draw_child(&w);
}
child.draw_parent(screen, Some(w.clone()));
}
} }
} }
} }
@@ -48,8 +61,15 @@ impl Rsx {
dependencies: Vec<Box<dyn DependencyHandler>>, dependencies: Vec<Box<dyn DependencyHandler>>,
children: Rsx, children: Rsx,
) { ) {
self.0 self.0.push(RsxElement::DynElement(
.push(RsxElement::Element(Box::new(load), dependencies, children)); Box::new(load),
dependencies,
children,
));
}
pub fn create_element_static(&mut self, element: Arc<StaticWidget>, children: Rsx) {
self.0.push(RsxElement::Element(element, children));
} }
pub fn expand(&mut self, other: &mut Rsx) { pub fn expand(&mut self, other: &mut Rsx) {
+28 -10
View File
@@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
use crate::{ use crate::{
extensions::{Extension, Handler}, extensions::{Extension, Handler},
render_scope::RenderScope, render_scope::RenderScope,
widget::{Element, Widget, WidgetLoad}, widget::{BoxedElement, DynWidget, Element, StaticWidget, Widget, WidgetLoad},
}; };
pub mod elements; pub mod elements;
@@ -53,20 +53,38 @@ impl Screen {
}) })
} }
pub fn draw<F: FnMut() -> WidgetLoad + 'static + Send + Sync>( pub fn draw<E: Element + 'static + Send + Sync>(self: &Arc<Self>, element: E) -> Arc<Widget> {
self: &Arc<Self>, let w = Arc::new(Widget::Static(Arc::new(StaticWidget::new(Box::new(
element: F, element,
) -> Arc<Widget> { )))));
let w = Arc::new(Widget::new(Box::new(element)));
self.widgets.lock().unwrap().push(w.clone()); self.widgets.lock().unwrap().push(w.clone());
w w
} }
pub fn draw_box( pub fn draw_box(self: &Arc<Self>, element: BoxedElement) -> Arc<Widget> {
let w = Arc::new(Widget::Static(Arc::new(StaticWidget::new(element))));
self.widgets.lock().unwrap().push(w.clone());
w
}
pub fn draw_widget(self: &Arc<Self>, widget: Arc<Widget>) {
self.widgets.lock().unwrap().push(widget);
}
pub fn draw_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(
self: &Arc<Self>,
element: F,
) -> Arc<Widget> {
let w = Arc::new(Widget::Dynamic(Arc::new(DynWidget::new(element))));
self.widgets.lock().unwrap().push(w.clone());
w
}
pub fn draw_box_dyn(
self: &Arc<Self>, self: &Arc<Self>,
element: Box<dyn FnMut() -> WidgetLoad + Send + Sync>, element: Box<dyn FnMut() -> WidgetLoad + Send + Sync>,
) -> Arc<Widget> { ) -> Arc<Widget> {
let w = Arc::new(Widget::new(Box::new(element))); let w = Arc::new(Widget::Dynamic(Arc::new(DynWidget::new(element))));
self.widgets.lock().unwrap().push(w.clone()); self.widgets.lock().unwrap().push(w.clone());
w w
} }
@@ -122,14 +140,14 @@ impl Screen {
ext.lock().unwrap().render_widget(&mut scope, elem); ext.lock().unwrap().render_widget(&mut scope, elem);
} }
elem.0.lock().unwrap().render(&mut scope); elem.get_elem().render(&mut scope);
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
} }
scope.draw(); scope.draw();
elem.0.lock().unwrap().after_render(&mut scope); elem.get_elem().after_render(&mut scope);
} }
elem.auto_refresh(); elem.auto_refresh();
+18
View File
@@ -145,6 +145,12 @@ macro_rules! rsx {
#[macro_export] #[macro_export]
macro_rules! rsx_inner { macro_rules! rsx_inner {
// static
($r:expr, $(@$comp:expr;)* static $s:literal $($rest:tt)*) => {
$r.create_element_static(std::sync::Arc::new($crate::widget::StaticWidget::new(Box::new(format!($s)))) $(.component($comp))* .clone(), $crate::frontend::Rsx(Vec::new()));
$crate::rsx_inner! { $r, $($rest)* };
};
($r:expr, $(%$dep:ident)* $(@$comp:expr;)* $s:literal $($rest:tt)*) => { ($r:expr, $(%$dep:ident)* $(@$comp:expr;)* $s:literal $($rest:tt)*) => {
$r.create_element({ $(let $dep = $dep.clone();)* move || { $r.create_element({ $(let $dep = $dep.clone();)* move || {
$crate::widget::WidgetLoad::new(format!($s)) $(.component($comp))* $crate::widget::WidgetLoad::new(format!($s)) $(.component($comp))*
@@ -152,6 +158,18 @@ macro_rules! rsx_inner {
$crate::rsx_inner! { $r, $($rest)* }; $crate::rsx_inner! { $r, $($rest)* };
}; };
// static
($r:expr, $(%$dep:ident)* $(@$comp:expr;)* static $name:path { $($inner:tt)* } ($($e:expr),*) $($rest:tt)*) => {
$r.create_element_static(std::sync::Arc::new($crate::widget::StaticWidget::new(Box::new(<$name>::new($($e),*)))) $(.component($comp))* .clone(), $crate::frontend::Rsx(Vec::new()));
$crate::rsx_inner! { $r, $($rest)* };
};
// static
($r:expr, $(@$comp:expr;)* static $name:path { $($inner:tt)* } $($rest:tt)*) => {
$r.create_element_static(std::sync::Arc::new($crate::widget::StaticWidget::new(Box::new(<$name>::new()))) $(.component($comp))* .clone(), $crate::frontend::Rsx(Vec::new()));
$crate::rsx_inner! { $r, $($rest)* };
};
($r:expr, $(%$dep:ident)* $(@$comp:expr;)* $name:path { $($inner:tt)* } ($($e:expr),*) $($rest:tt)*) => { ($r:expr, $(%$dep:ident)* $(@$comp:expr;)* $name:path { $($inner:tt)* } ($($e:expr),*) $($rest:tt)*) => {
$r.create_element({ $(let $dep = $dep.clone();)* move || { $r.create_element({ $(let $dep = $dep.clone();)* move || {
$crate::widget::WidgetLoad::new(<$name>::new($($e),*)) $(.component($comp))* $crate::widget::WidgetLoad::new(<$name>::new($($e),*)) $(.component($comp))*
+14 -26
View File
@@ -2,33 +2,21 @@ use osui::prelude::*;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let screen = Screen::new(); let screen = Screen::new();
screen.extension(VelocityExtension);
app().draw(&screen); let count = use_state(0);
rsx! {
@Velocity(20, 0);
@Transform::new();
static "Count: {count}"
}
.draw(&screen);
std::thread::spawn(move || loop {
**count.get() += 1;
std::thread::sleep(std::time::Duration::from_millis(100));
});
screen.run() screen.run()
} }
fn app() -> Rsx {
rsx! {
@Transform::new().padding(3, 3);
@Style { background: Background::RoundedOutline(0xff0000), foreground: None };
Div {
@Transform::new().padding(1, 1);
@Style { background: Background::RoundedOutline(0x00ff00), foreground: None };
FlexCol {
@Transform::new().padding(1, 1);
"Hello, World!"
@Transform::new().padding(1, 1);
"Hello, World!"
@Transform::new().padding(1, 1);
"Hello, World!"
@Transform::new().padding(1, 1);
"Hello, World!"
@Transform::new().padding(1, 1);
"Hello, World!"
@Transform::new().padding(1, 1);
"Hello, World!"
} (2)
}
}
}
+188 -44
View File
@@ -1,7 +1,7 @@
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
collections::HashMap, collections::HashMap,
sync::{Arc, Mutex}, sync::{Arc, Mutex, MutexGuard},
}; };
use crate::{render_scope::RenderScope, state::DependencyHandler}; use crate::{render_scope::RenderScope, state::DependencyHandler};
@@ -25,15 +25,190 @@ pub trait Component: Send + Sync {
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
} }
pub struct Widget( pub struct WidgetLoad(BoxedElement, HashMap<TypeId, BoxedComponent>);
pub Mutex<BoxedElement>,
impl WidgetLoad {
pub fn new<E: Element + 'static>(e: E) -> Self {
Self(Box::new(e), HashMap::new())
}
pub fn component<C: Component + 'static>(mut self, c: C) -> Self {
self.1.entry(c.type_id()).or_insert_with(|| Box::new(c));
self
}
pub fn set_component<C: Component + 'static>(mut self, c: C) -> Self {
self.1.insert(c.type_id(), Box::new(c));
self
}
pub fn get<C: Component + 'static + Clone>(&self) -> Option<C> {
self.1
.get(&TypeId::of::<C>())
.and_then(|c| c.as_any().downcast_ref::<C>())
.map(|c| c.clone())
}
}
pub enum Widget {
Static(Arc<StaticWidget>),
Dynamic(Arc<DynWidget>),
}
// === Trait Implementation ===
impl Widget {
pub fn get_elem(&self) -> MutexGuard<BoxedElement> {
match self {
Widget::Static(w) => w.get_elem(),
Widget::Dynamic(w) => w.get_elem(),
}
}
pub fn after_render(&self) {
match self {
Widget::Static(w) => w.after_render(),
Widget::Dynamic(w) => w.after_render(),
}
}
pub fn component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self> {
match &**self {
Widget::Static(w) => {
w.component(c);
}
Widget::Dynamic(w) => {
w.component(c);
}
}
self
}
pub fn set_component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self> {
match &**self {
Widget::Static(w) => {
w.set_component(c);
}
Widget::Dynamic(w) => {
w.set_component(c);
}
}
self
}
pub fn get<C: Component + 'static + Clone>(&self) -> Option<C> {
match self {
Widget::Static(w) => w.get(),
Widget::Dynamic(w) => w.get(),
}
}
pub fn inject<F: FnMut(WidgetLoad) -> WidgetLoad + 'static + Send + Sync>(
self: &Arc<Self>,
f: F,
) {
match &**self {
Widget::Dynamic(w) => {
w.inject(f);
}
Widget::Static(_) => {}
}
}
pub fn refresh(self: &Arc<Self>) {
match &**self {
Widget::Dynamic(w) => {
w.refresh();
}
Widget::Static(_) => {}
}
}
pub fn auto_refresh(self: &Arc<Self>) {
match &**self {
Widget::Dynamic(w) => {
w.auto_refresh();
}
Widget::Static(_) => {}
}
}
pub fn dependency<D: DependencyHandler + 'static>(self: &Arc<Self>, d: D) -> &Arc<Self> {
match &**self {
Widget::Dynamic(w) => {
w.dependency(d);
}
Widget::Static(_) => {}
}
self
}
pub fn dependency_box(self: &Arc<Self>, d: Box<dyn DependencyHandler>) -> &Arc<Self> {
match &**self {
Widget::Dynamic(w) => {
w.dependency_box(d);
}
Widget::Static(_) => {}
}
self
}
}
// === Static Widget ===
pub struct StaticWidget(Mutex<BoxedElement>, Mutex<HashMap<TypeId, BoxedComponent>>);
impl StaticWidget {
fn after_render(&self) {}
fn get_elem(&self) -> MutexGuard<BoxedElement> {
self.0.lock().unwrap()
}
}
impl StaticWidget {
pub fn new(e: BoxedElement) -> Self {
Self(Mutex::new(e), Mutex::new(HashMap::new()))
}
pub fn component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self> {
self.1
.lock()
.unwrap()
.entry(c.type_id())
.or_insert_with(|| Box::new(c));
self
}
pub fn set_component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self> {
self.1.lock().unwrap().insert(c.type_id(), Box::new(c));
self
}
pub fn get<C: Component + 'static + Clone>(&self) -> Option<C> {
self.1
.lock()
.unwrap()
.get(&TypeId::of::<C>())
.and_then(|c| c.as_any().downcast_ref::<C>())
.map(Clone::clone)
}
}
// === Dynamic Widget ===
pub struct DynWidget(
Mutex<BoxedElement>,
Mutex<HashMap<TypeId, BoxedComponent>>, Mutex<HashMap<TypeId, BoxedComponent>>,
Mutex<Box<dyn FnMut() -> WidgetLoad + Send + Sync>>, Mutex<Box<dyn FnMut() -> WidgetLoad + Send + Sync>>,
Mutex<Vec<Box<dyn DependencyHandler>>>, Mutex<Vec<Box<dyn DependencyHandler>>>,
Mutex<Option<Box<dyn FnMut(WidgetLoad) -> WidgetLoad + Send + Sync>>>, Mutex<Option<Box<dyn FnMut(WidgetLoad) -> WidgetLoad + Send + Sync>>>,
); );
impl Widget { impl DynWidget {
fn after_render(&self) {}
fn get_elem(&self) -> MutexGuard<BoxedElement> {
self.0.lock().unwrap()
}
}
impl DynWidget {
pub fn new<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self { pub fn new<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self {
let wl = e(); let wl = e();
Self( Self(
@@ -49,25 +224,19 @@ impl Widget {
self: &Arc<Self>, self: &Arc<Self>,
f: F, f: F,
) { ) {
{ *self.4.lock().unwrap() = Some(Box::new(f));
*self.4.lock().unwrap() = Some(Box::new(f));
}
self.refresh(); self.refresh();
} }
pub fn refresh(self: &Arc<Self>) { pub fn refresh(self: &Arc<Self>) {
let w = (self.2.lock().unwrap())(); let mut w = (self.2.lock().unwrap())();
match &mut *self.4.lock().unwrap() {
Some(inject) => { if let Some(we) = &mut *self.4.lock().unwrap() {
let w = inject(w); w = (we)(w);
*self.0.lock().unwrap() = w.0;
*self.1.lock().unwrap() = w.1;
}
None => {
*self.0.lock().unwrap() = w.0;
*self.1.lock().unwrap() = w.1;
}
} }
*self.0.lock().unwrap() = w.0;
*self.1.lock().unwrap() = w.1;
} }
pub fn auto_refresh(self: &Arc<Self>) { pub fn auto_refresh(self: &Arc<Self>) {
@@ -110,31 +279,6 @@ impl Widget {
.unwrap() .unwrap()
.get(&TypeId::of::<C>()) .get(&TypeId::of::<C>())
.and_then(|c| c.as_any().downcast_ref::<C>()) .and_then(|c| c.as_any().downcast_ref::<C>())
.map(|c| c.clone()) .map(Clone::clone)
}
}
pub struct WidgetLoad(BoxedElement, HashMap<TypeId, BoxedComponent>);
impl WidgetLoad {
pub fn new<E: Element + 'static>(e: E) -> Self {
Self(Box::new(e), HashMap::new())
}
pub fn component<C: Component + 'static>(mut self, c: C) -> Self {
self.1.entry(c.type_id()).or_insert_with(|| Box::new(c));
self
}
pub fn set_component<C: Component + 'static>(mut self, c: C) -> Self {
self.1.insert(c.type_id(), Box::new(c));
self
}
pub fn get<C: Component + 'static + Clone>(&self) -> Option<C> {
self.1
.get(&TypeId::of::<C>())
.and_then(|c| c.as_any().downcast_ref::<C>())
.map(|c| c.clone())
} }
} }