Documented
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
|
//! # RSX Emission
|
||||||
|
//!
|
||||||
|
//! Converts parsed RSX AST into Rust code that constructs RSX objects.
|
||||||
|
|
||||||
use crate::parse::*;
|
use crate::parse::*;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
|
/// Emits code for the root RSX
|
||||||
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
||||||
emit_rsx_vec(&root.nodes)
|
emit_rsx_vec(&root.nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits code for a vector of RSX nodes
|
||||||
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
|
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
|
||||||
let nodes = nodes.iter().map(emit_node_scope);
|
let nodes = nodes.iter().map(emit_node_scope);
|
||||||
|
|
||||||
@@ -16,6 +22,7 @@ pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits variable bindings for dependencies
|
||||||
fn emit_deps(deps: &[Dep]) -> TokenStream {
|
fn emit_deps(deps: &[Dep]) -> TokenStream {
|
||||||
deps.iter()
|
deps.iter()
|
||||||
.map(|d| {
|
.map(|d| {
|
||||||
@@ -29,6 +36,7 @@ fn emit_deps(deps: &[Dep]) -> TokenStream {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a Vec of dependencies as HookDependency trait objects
|
||||||
fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
|
fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
|
||||||
let deps = deps.iter().map(|d| {
|
let deps = deps.iter().map(|d| {
|
||||||
let ident = &d.ident;
|
let ident = &d.ident;
|
||||||
@@ -43,6 +51,7 @@ fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits code for a single node within a scope
|
||||||
fn emit_node_scope(node: &RsxNode) -> TokenStream {
|
fn emit_node_scope(node: &RsxNode) -> TokenStream {
|
||||||
match node {
|
match node {
|
||||||
RsxNode::Text(_) => {
|
RsxNode::Text(_) => {
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
//! # OSUI Macros
|
||||||
|
//!
|
||||||
|
//! Procedural macros for OSUI that provide ergonomic syntax for defining components.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! - `#[component]` - Transforms a function into a reusable component with props
|
||||||
|
//! - `rsx!` - Creates RSX (React-like Syntax) for component hierarchies
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
|
use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
|
||||||
@@ -5,12 +14,41 @@ use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
|
|||||||
mod emit;
|
mod emit;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
|
||||||
|
/// RSX (React-like Syntax) macro for building component hierarchies
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// rsx! {
|
||||||
|
/// Component {
|
||||||
|
/// prop: value,
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn rsx(input: TokenStream) -> TokenStream {
|
pub fn rsx(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as parse::RsxRoot);
|
let ast = parse_macro_input!(input as parse::RsxRoot);
|
||||||
emit::emit_rsx(ast).into()
|
emit::emit_rsx(ast).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Component attribute macro for defining reusable components
|
||||||
|
///
|
||||||
|
/// Transforms a function into a component with automatic prop handling.
|
||||||
|
/// The first parameter must be `cx: &Arc<Context>`.
|
||||||
|
/// Remaining parameters become component props.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[component]
|
||||||
|
/// pub fn Counter(cx: &Arc<Context>, initial: &i32) -> View {
|
||||||
|
/// let count = use_state(*initial);
|
||||||
|
///
|
||||||
|
/// Arc::new(move |ctx| {
|
||||||
|
/// ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {}", count.get_dl()));
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(item as ItemFn);
|
let input = parse_macro_input!(item as ItemFn);
|
||||||
|
|||||||
+26
-1
@@ -1,3 +1,7 @@
|
|||||||
|
//! # RSX Parser
|
||||||
|
//!
|
||||||
|
//! Parses RSX syntax into an AST that can be emitted as Rust code.
|
||||||
|
|
||||||
use syn::braced;
|
use syn::braced;
|
||||||
use syn::parse::discouraged::Speculative;
|
use syn::parse::discouraged::Speculative;
|
||||||
use syn::{
|
use syn::{
|
||||||
@@ -6,7 +10,9 @@ use syn::{
|
|||||||
token::Brace,
|
token::Brace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Root of an RSX expression
|
||||||
pub struct RsxRoot {
|
pub struct RsxRoot {
|
||||||
|
/// Top-level nodes in the RSX
|
||||||
pub nodes: Vec<RsxNode>,
|
pub nodes: Vec<RsxNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,30 +26,49 @@ impl Parse for RsxRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single prop: `ident: expr`.
|
/// A single component prop: `name: value`
|
||||||
pub struct RsxProp {
|
pub struct RsxProp {
|
||||||
|
/// Property name
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
|
/// Property value expression
|
||||||
pub value: Expr,
|
pub value: Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// AST node representing different RSX constructs
|
||||||
pub enum RsxNode {
|
pub enum RsxNode {
|
||||||
|
/// String literal: `"text"`
|
||||||
Text(LitStr),
|
Text(LitStr),
|
||||||
|
/// Expression node: `{expr}`
|
||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
|
/// Component instantiation: `Component { prop: value, ... }`
|
||||||
Component {
|
Component {
|
||||||
|
/// Component path (e.g., `my_module::MyComponent`)
|
||||||
path: Path,
|
path: Path,
|
||||||
|
/// Component properties
|
||||||
props: Vec<RsxProp>,
|
props: Vec<RsxProp>,
|
||||||
|
/// Child nodes
|
||||||
children: Vec<RsxNode>,
|
children: Vec<RsxNode>,
|
||||||
},
|
},
|
||||||
|
/// Mount lifecycle: `@mount`
|
||||||
Mount(Ident),
|
Mount(Ident),
|
||||||
|
/// Conditional rendering: `@if condition { ... }`
|
||||||
If {
|
If {
|
||||||
|
/// Dependencies to track for reactivity
|
||||||
deps: Vec<Dep>,
|
deps: Vec<Dep>,
|
||||||
|
/// Condition expression
|
||||||
cond: Expr,
|
cond: Expr,
|
||||||
|
/// Child nodes to render if true
|
||||||
children: Vec<RsxNode>,
|
children: Vec<RsxNode>,
|
||||||
},
|
},
|
||||||
|
/// Loop rendering: `@for pattern in expr { ... }`
|
||||||
For {
|
For {
|
||||||
|
/// Dependencies to track for reactivity
|
||||||
deps: Vec<Dep>,
|
deps: Vec<Dep>,
|
||||||
|
/// Loop pattern (e.g., `(key, value)`)
|
||||||
pat: Pat,
|
pat: Pat,
|
||||||
|
/// Iterable expression
|
||||||
expr: Expr,
|
expr: Expr,
|
||||||
|
/// Child nodes to render for each iteration
|
||||||
children: Vec<RsxNode>,
|
children: Vec<RsxNode>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! # Context Module
|
||||||
|
//!
|
||||||
|
//! Provides the Context type which is central to component state management.
|
||||||
|
//! Context holds component state, manages event handlers, and coordinates
|
||||||
|
//! rendering and updates across the component tree.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -16,15 +22,28 @@ use crate::{
|
|||||||
|
|
||||||
use super::{scope::Scope, Component, ComponentImpl};
|
use super::{scope::Scope, Component, ComponentImpl};
|
||||||
|
|
||||||
|
/// Context represents the runtime state and behavior of a component
|
||||||
|
///
|
||||||
|
/// Each component instance has a Context that holds:
|
||||||
|
/// - The component implementation
|
||||||
|
/// - The current view (render result)
|
||||||
|
/// - Event handlers for responding to events
|
||||||
|
/// - Child scopes for managing child components
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
|
/// The component implementation
|
||||||
component: AccessCell<Component>,
|
component: AccessCell<Component>,
|
||||||
|
/// The current rendered view
|
||||||
view: AccessCell<View>,
|
view: AccessCell<View>,
|
||||||
|
/// Event handlers grouped by event type
|
||||||
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
|
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
|
||||||
|
/// Child scopes (component hierarchies)
|
||||||
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
|
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
|
||||||
|
/// Command executor for this context's command handling
|
||||||
executor: Arc<dyn CommandExecutor>,
|
executor: Arc<dyn CommandExecutor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Creates a new context for the given component
|
||||||
pub fn new<F: ComponentImpl + 'static>(
|
pub fn new<F: ComponentImpl + 'static>(
|
||||||
component: F,
|
component: F,
|
||||||
executor: Arc<dyn CommandExecutor>,
|
executor: Arc<dyn CommandExecutor>,
|
||||||
@@ -38,6 +57,9 @@ impl Context {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Refreshes the component by re-rendering it
|
||||||
|
///
|
||||||
|
/// Clears event handlers and calls the component to produce a new view.
|
||||||
pub fn refresh(self: &Arc<Self>) {
|
pub fn refresh(self: &Arc<Self>) {
|
||||||
self.event_handlers
|
self.event_handlers
|
||||||
.access(|event_handlers| event_handlers.clear());
|
.access(|event_handlers| event_handlers.clear());
|
||||||
@@ -53,6 +75,9 @@ impl Context {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Synchronously refreshes the component
|
||||||
|
///
|
||||||
|
/// Blocks until the component has finished rendering.
|
||||||
pub fn refresh_sync(self: &Arc<Self>) {
|
pub fn refresh_sync(self: &Arc<Self>) {
|
||||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||||
|
|
||||||
@@ -79,10 +104,15 @@ impl Context {
|
|||||||
let _ = rx.recv();
|
let _ = rx.recv();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the current view
|
||||||
pub fn get_view(self: &Arc<Self>) -> View {
|
pub fn get_view(self: &Arc<Self>) -> View {
|
||||||
self.view.access_ref().clone()
|
self.view.access_ref().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers an event handler for events of type T
|
||||||
|
///
|
||||||
|
/// When an event of type T is emitted, the handler is called with
|
||||||
|
/// the context and a reference to the event.
|
||||||
pub fn on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(
|
pub fn on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
handler: F,
|
handler: F,
|
||||||
@@ -101,6 +131,10 @@ impl Context {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits an event to this component and all descendants
|
||||||
|
///
|
||||||
|
/// Calls all registered handlers for this event type,
|
||||||
|
/// then propagates the event to child components.
|
||||||
pub fn emit_event<E: Send + Sync + Any + 'static>(self: &Arc<Self>, event: E) {
|
pub fn emit_event<E: Send + Sync + Any + 'static>(self: &Arc<Self>, event: E) {
|
||||||
let event = Arc::new(event);
|
let event = Arc::new(event);
|
||||||
let handlers_to_call: Vec<EventHandler> = {
|
let handlers_to_call: Vec<EventHandler> = {
|
||||||
@@ -118,6 +152,10 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits an event to this component in a spawned thread
|
||||||
|
///
|
||||||
|
/// Similar to emit_event but handlers are called in spawned threads
|
||||||
|
/// for concurrent execution.
|
||||||
pub fn emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(
|
pub fn emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
event: &E,
|
event: &E,
|
||||||
@@ -141,6 +179,7 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new child scope
|
||||||
pub fn scope(self: &Arc<Self>) -> Arc<Scope> {
|
pub fn scope(self: &Arc<Self>) -> Arc<Scope> {
|
||||||
let scope = Scope::new(self.executor.clone());
|
let scope = Scope::new(self.executor.clone());
|
||||||
self.scopes.lock().unwrap().push(scope.clone());
|
self.scopes.lock().unwrap().push(scope.clone());
|
||||||
@@ -148,6 +187,7 @@ impl Context {
|
|||||||
scope
|
scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a dynamic child scope that re-renders when dependencies change
|
||||||
pub fn dyn_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
pub fn dyn_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
drawer: F,
|
drawer: F,
|
||||||
@@ -171,10 +211,12 @@ impl Context {
|
|||||||
scope
|
scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a pre-constructed scope as a child
|
||||||
pub fn add_scope(self: &Arc<Self>, scope: Arc<Scope>) {
|
pub fn add_scope(self: &Arc<Self>, scope: Arc<Scope>) {
|
||||||
self.scopes.lock().unwrap().push(scope);
|
self.scopes.lock().unwrap().push(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders all child components to the draw context
|
||||||
pub fn draw_children(self: &Arc<Self>, ctx: &mut DrawContext) {
|
pub fn draw_children(self: &Arc<Self>, ctx: &mut DrawContext) {
|
||||||
for scope in self.scopes.lock().unwrap().iter() {
|
for scope in self.scopes.lock().unwrap().iter() {
|
||||||
for (child, view_wrapper) in scope.children.lock().unwrap().iter() {
|
for (child, view_wrapper) in scope.children.lock().unwrap().iter() {
|
||||||
@@ -189,15 +231,18 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the command executor for this context
|
||||||
pub fn get_executor(self: &Arc<Self>) -> Arc<dyn CommandExecutor> {
|
pub fn get_executor(self: &Arc<Self>) -> Arc<dyn CommandExecutor> {
|
||||||
self.executor.clone()
|
self.executor.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a command
|
||||||
pub fn execute<T: Command + 'static>(self: &Arc<Self>, command: T) -> crate::Result<()> {
|
pub fn execute<T: Command + 'static>(self: &Arc<Self>, command: T) -> crate::Result<()> {
|
||||||
self.executor
|
self.executor
|
||||||
.execute_command(&(Arc::new(command) as Arc<dyn Command>))
|
.execute_command(&(Arc::new(command) as Arc<dyn Command>))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stops the application
|
||||||
pub fn stop(self: &Arc<Self>) -> crate::Result<()> {
|
pub fn stop(self: &Arc<Self>) -> crate::Result<()> {
|
||||||
self.execute(crate::engine::commands::Stop)
|
self.execute(crate::engine::commands::Stop)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! # Component Module
|
||||||
|
//!
|
||||||
|
//! Provides the component system that forms the foundation of OSUI.
|
||||||
|
//! Components are reusable units of UI that can manage their own state
|
||||||
|
//! and respond to events.
|
||||||
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
|
|
||||||
@@ -10,10 +16,15 @@ use crate::View;
|
|||||||
|
|
||||||
use context::Context;
|
use context::Context;
|
||||||
|
|
||||||
|
/// A Component is an implementor of the ComponentImpl trait, wrapped in Arc
|
||||||
pub type Component = Arc<dyn ComponentImpl>;
|
pub type Component = Arc<dyn ComponentImpl>;
|
||||||
|
|
||||||
|
/// An event handler function stored in a mutex for thread-safe mutation
|
||||||
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
|
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
|
||||||
|
|
||||||
|
/// Trait implemented by components to render themselves
|
||||||
pub trait ComponentImpl: Send + Sync {
|
pub trait ComponentImpl: Send + Sync {
|
||||||
|
/// Renders the component within the given context, returning a View
|
||||||
fn call(&self, cx: &Arc<Context>) -> View;
|
fn call(&self, cx: &Arc<Context>) -> View;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
|
//! # Scope Module
|
||||||
|
//!
|
||||||
|
//! Provides the Scope type for managing component hierarchies.
|
||||||
|
//! Scopes group child components and manage their lifecycle.
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::{engine::CommandExecutor, View, ViewWrapper};
|
use crate::{engine::CommandExecutor, View, ViewWrapper};
|
||||||
|
|
||||||
use super::{context::Context, ComponentImpl};
|
use super::{context::Context, ComponentImpl};
|
||||||
|
|
||||||
|
/// A scope groups child components and manages their rendering
|
||||||
|
///
|
||||||
|
/// Scopes form the hierarchical structure of a component tree.
|
||||||
|
/// Each scope contains references to its child components and their
|
||||||
|
/// optional view wrappers (for layout/styling).
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
|
/// Child components with optional view wrappers
|
||||||
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
||||||
|
/// Command executor for this scope's children
|
||||||
executor: Arc<dyn CommandExecutor>,
|
executor: Arc<dyn CommandExecutor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
|
/// Creates a new scope with the given command executor
|
||||||
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
|
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
children: Mutex::new(Vec::new()),
|
children: Mutex::new(Vec::new()),
|
||||||
@@ -17,6 +30,9 @@ impl Scope {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a child component to this scope
|
||||||
|
///
|
||||||
|
/// The view_wrapper is optional and can be used for layout or styling.
|
||||||
pub fn child<F: ComponentImpl + 'static>(
|
pub fn child<F: ComponentImpl + 'static>(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
child: F,
|
child: F,
|
||||||
@@ -29,6 +45,7 @@ impl Scope {
|
|||||||
self.children.lock().unwrap().push((ctx, view_wrapper));
|
self.children.lock().unwrap().push((ctx, view_wrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a view directly to this scope
|
||||||
pub fn view(self: &Arc<Self>, view: View) {
|
pub fn view(self: &Arc<Self>, view: View) {
|
||||||
let ctx = Context::new(view, self.executor.clone());
|
let ctx = Context::new(view, self.executor.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
//! # Benchmark Module
|
||||||
|
//!
|
||||||
|
//! Provides performance benchmarking capabilities for rendering engines.
|
||||||
|
|
||||||
use std::{io::stdout, sync::Arc, time::Instant};
|
use std::{io::stdout, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
||||||
@@ -7,18 +11,26 @@ use crate::{render::Area, DrawContext, View};
|
|||||||
|
|
||||||
use super::Engine;
|
use super::Engine;
|
||||||
|
|
||||||
|
/// Results of a benchmark run
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BenchmarkResult {
|
pub struct BenchmarkResult {
|
||||||
|
/// Average render time in microseconds
|
||||||
pub average: u128,
|
pub average: u128,
|
||||||
|
/// Minimum render time in microseconds
|
||||||
pub min: u128,
|
pub min: u128,
|
||||||
|
/// Maximum render time in microseconds
|
||||||
pub max: u128,
|
pub max: u128,
|
||||||
|
/// Total time spent rendering in microseconds
|
||||||
pub total_render: u128,
|
pub total_render: u128,
|
||||||
|
/// Total benchmark time including setup in microseconds
|
||||||
pub total: u128,
|
pub total: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps an engine to benchmark its rendering performance
|
||||||
pub struct Benchmark<T: Engine>(T);
|
pub struct Benchmark<T: Engine>(T);
|
||||||
|
|
||||||
impl<T: Engine> Benchmark<T> {
|
impl<T: Engine> Benchmark<T> {
|
||||||
|
/// Creates a new benchmark wrapper around the given engine
|
||||||
pub fn new(engine: T) -> Self {
|
pub fn new(engine: T) -> Self {
|
||||||
Self(engine)
|
Self(engine)
|
||||||
}
|
}
|
||||||
@@ -31,6 +43,7 @@ impl<T: Engine> Engine<BenchmarkResult> for Benchmark<T> {
|
|||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
|
// Run 40 render cycles and measure each
|
||||||
for _ in 0..40 {
|
for _ in 0..40 {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
self.render(&cx);
|
self.render(&cx);
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
//! # Commands Module
|
||||||
|
//!
|
||||||
|
//! Defines built-in commands for controlling the engine.
|
||||||
|
|
||||||
use crate::engine::Command;
|
use crate::engine::Command;
|
||||||
|
|
||||||
|
/// Command to stop the engine and terminate the application
|
||||||
pub struct Stop;
|
pub struct Stop;
|
||||||
|
|
||||||
impl Command for Stop {
|
impl Command for Stop {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
//! # Console Engine Implementation
|
||||||
|
//!
|
||||||
|
//! Provides a Console implementation of the Engine trait for rendering
|
||||||
|
//! to the terminal using crossterm.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
@@ -14,16 +19,24 @@ use crate::{
|
|||||||
|
|
||||||
use super::Engine;
|
use super::Engine;
|
||||||
|
|
||||||
|
/// Executes commands for the console engine
|
||||||
pub struct ConsoleExecutor {
|
pub struct ConsoleExecutor {
|
||||||
|
/// Flag indicating whether the application is running
|
||||||
running: Mutex<bool>,
|
running: Mutex<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Console-based rendering engine
|
||||||
|
///
|
||||||
|
/// Renders components to the terminal using crossterm for cross-platform support.
|
||||||
pub struct Console {
|
pub struct Console {
|
||||||
|
/// Thread functions to execute
|
||||||
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
|
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
|
||||||
|
/// The executor for this console
|
||||||
executor: Arc<ConsoleExecutor>,
|
executor: Arc<ConsoleExecutor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Console {
|
impl Console {
|
||||||
|
/// Creates a new console engine
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
threads: Mutex::new(Vec::new()),
|
threads: Mutex::new(Vec::new()),
|
||||||
@@ -33,6 +46,7 @@ impl Console {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a thread function to run alongside the engine
|
||||||
pub fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F) {
|
pub fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F) {
|
||||||
self.threads.lock().unwrap().push(Arc::new(run));
|
self.threads.lock().unwrap().push(Arc::new(run));
|
||||||
}
|
}
|
||||||
@@ -114,10 +128,12 @@ impl Engine for Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ConsoleExecutor {
|
impl ConsoleExecutor {
|
||||||
|
/// Checks if the engine is still running
|
||||||
pub fn is_running(self: &Arc<ConsoleExecutor>) -> bool {
|
pub fn is_running(self: &Arc<ConsoleExecutor>) -> bool {
|
||||||
*self.running.lock().unwrap()
|
*self.running.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stops the engine
|
||||||
pub fn stop(&self) -> crate::Result<()> {
|
pub fn stop(&self) -> crate::Result<()> {
|
||||||
*self.running.lock()? = false;
|
*self.running.lock()? = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! # Engine Module
|
||||||
|
//!
|
||||||
|
//! Provides the rendering engine and command execution system.
|
||||||
|
//! The engine is responsible for initializing components, rendering frames,
|
||||||
|
//! and handling user commands.
|
||||||
|
|
||||||
pub mod benchmark;
|
pub mod benchmark;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod console;
|
pub mod console;
|
||||||
@@ -10,23 +16,40 @@ use std::{any::Any, sync::Arc};
|
|||||||
use crate::component::{context::Context, ComponentImpl};
|
use crate::component::{context::Context, ComponentImpl};
|
||||||
use crate::{render::Area, DrawContext, View};
|
use crate::{render::Area, DrawContext, View};
|
||||||
|
|
||||||
|
/// Main engine trait for rendering and running components
|
||||||
pub trait Engine<Output = ()> {
|
pub trait Engine<Output = ()> {
|
||||||
|
/// Runs a component to completion
|
||||||
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
|
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
|
||||||
|
|
||||||
|
/// Initializes a component and returns its context
|
||||||
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
|
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
|
||||||
|
|
||||||
|
/// Renders the current state of a component
|
||||||
fn render(&self, cx: &Arc<Context>);
|
fn render(&self, cx: &Arc<Context>);
|
||||||
|
|
||||||
|
/// Sleeps between render frames (default 16ms for ~60fps)
|
||||||
fn render_delay(&self) {
|
fn render_delay(&self) {
|
||||||
crate::sleep(16);
|
crate::sleep(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders a view within an area and returns the draw context
|
||||||
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
|
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
|
||||||
|
|
||||||
|
/// Executes the drawing instructions in a draw context
|
||||||
fn draw_context(&self, ctx: &DrawContext);
|
fn draw_context(&self, ctx: &DrawContext);
|
||||||
|
|
||||||
|
/// Returns the command executor for this engine
|
||||||
fn executor(&self) -> Arc<dyn CommandExecutor>;
|
fn executor(&self) -> Arc<dyn CommandExecutor>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for commands that can be executed by the engine
|
||||||
pub trait Command {
|
pub trait Command {
|
||||||
|
/// Returns the command as Any for downcasting
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes commands during the application lifecycle
|
||||||
pub trait CommandExecutor: Send + Sync {
|
pub trait CommandExecutor: Send + Sync {
|
||||||
|
/// Executes the given command
|
||||||
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>;
|
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,59 @@
|
|||||||
|
//! # Frontend Module
|
||||||
|
//!
|
||||||
|
//! Provides the RSX (React-like Syntax) system for composing components.
|
||||||
|
//! This module defines the structure for building component hierarchies
|
||||||
|
//! with static and dynamic scopes, similar to React's JSX.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::component::{context::Context, scope::Scope};
|
use crate::component::{context::Context, scope::Scope};
|
||||||
use crate::{render::Point, state::HookDependency, View};
|
use crate::{render::Point, state::HookDependency, View};
|
||||||
|
|
||||||
|
/// Trait for converting values to RSX
|
||||||
pub trait ToRsx {
|
pub trait ToRsx {
|
||||||
|
/// Convert to RSX representation
|
||||||
fn to_rsx(&self) -> Rsx;
|
fn to_rsx(&self) -> Rsx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scope types for RSX components
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum RsxScope {
|
pub enum RsxScope {
|
||||||
|
/// Static scope - executed once and never updated
|
||||||
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
|
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
|
||||||
|
/// Dynamic scope - re-executed when dependencies change
|
||||||
Dynamic(
|
Dynamic(
|
||||||
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
|
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
|
||||||
Vec<Arc<dyn HookDependency>>,
|
Vec<Arc<dyn HookDependency>>,
|
||||||
),
|
),
|
||||||
|
/// Child RSX scope for composition
|
||||||
Child(Rsx),
|
Child(Rsx),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RSX (React-like Syntax) builder for component hierarchies
|
||||||
|
///
|
||||||
|
/// Represents a collection of scopes that define component structure.
|
||||||
|
/// Scopes can be static (execute once) or dynamic (reactive to changes).
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Rsx(Vec<RsxScope>);
|
pub struct Rsx(Vec<RsxScope>);
|
||||||
|
|
||||||
impl Rsx {
|
impl Rsx {
|
||||||
|
/// Creates a new empty RSX
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Vec::new())
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a static scope to this RSX
|
||||||
|
///
|
||||||
|
/// The provided function is executed once during rendering
|
||||||
|
/// and will not be re-executed on dependency changes.
|
||||||
pub fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F) {
|
pub fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F) {
|
||||||
self.0.push(RsxScope::Static(Arc::new(scope)));
|
self.0.push(RsxScope::Static(Arc::new(scope)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a dynamic scope to this RSX
|
||||||
|
///
|
||||||
|
/// The provided function is executed when dependencies change,
|
||||||
|
/// allowing for reactive updates similar to React hooks.
|
||||||
pub fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
pub fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
drawer: F,
|
drawer: F,
|
||||||
@@ -38,10 +63,12 @@ impl Rsx {
|
|||||||
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
|
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a child RSX
|
||||||
pub fn child<R: ToRsx>(&mut self, child: R) {
|
pub fn child<R: ToRsx>(&mut self, child: R) {
|
||||||
self.0.push(RsxScope::Child(child.to_rsx()));
|
self.0.push(RsxScope::Child(child.to_rsx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates child components within the given context
|
||||||
pub fn generate_children(&self, context: &Arc<Context>) {
|
pub fn generate_children(&self, context: &Arc<Context>) {
|
||||||
let executor = context.get_executor();
|
let executor = context.get_executor();
|
||||||
|
|
||||||
@@ -64,6 +91,7 @@ impl Rsx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts this RSX to a View
|
||||||
pub fn view(&self, context: &Arc<Context>) -> View {
|
pub fn view(&self, context: &Arc<Context>) -> View {
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
|
|||||||
+50
@@ -1,3 +1,41 @@
|
|||||||
|
//! # OSUI - A TUI Library for Advanced UIs
|
||||||
|
//!
|
||||||
|
//! OSUI is a Rust library for building sophisticated Terminal User Interfaces (TUIs).
|
||||||
|
//! It provides a component-based architecture with state management, event handling,
|
||||||
|
//! and rendering capabilities for creating interactive console applications.
|
||||||
|
//!
|
||||||
|
//! ## Key Features
|
||||||
|
//!
|
||||||
|
//! - **Component System**: Build UIs using composable components
|
||||||
|
//! - **State Management**: React-like hooks for managing component state
|
||||||
|
//! - **Event Handling**: Type-safe event system with reactive updates
|
||||||
|
//! - **RSX Syntax**: Macro-based DSL for defining component hierarchies
|
||||||
|
//! - **Console Engine**: Terminal rendering with crossterm support
|
||||||
|
//!
|
||||||
|
//! ## Architecture
|
||||||
|
//!
|
||||||
|
//! - [`component`] - Component system and context management
|
||||||
|
//! - [`state`] - State management with hooks (useState, useEffect, etc.)
|
||||||
|
//! - [`engine`] - Rendering engine and command execution
|
||||||
|
//! - [`frontend`] - RSX (React-like Syntax) for component definitions
|
||||||
|
//! - [`render`] - Low-level rendering primitives
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use osui::prelude::*;
|
||||||
|
//! use std::sync::Arc;
|
||||||
|
//!
|
||||||
|
//! #[component]
|
||||||
|
//! pub fn Counter(cx: &Arc<Context>) -> View {
|
||||||
|
//! let count = use_state(0);
|
||||||
|
//!
|
||||||
|
//! Arc::new(move |ctx| {
|
||||||
|
//! ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {}", count.get_dl()));
|
||||||
|
//! })
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::render::DrawContext;
|
use crate::render::DrawContext;
|
||||||
@@ -9,6 +47,7 @@ pub mod render;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
//! Prelude module - Re-exports commonly used items for convenience
|
||||||
pub use crate::component::{context::*, scope::*, *};
|
pub use crate::component::{context::*, scope::*, *};
|
||||||
pub use crate::engine::*;
|
pub use crate::engine::*;
|
||||||
pub use crate::frontend::*;
|
pub use crate::frontend::*;
|
||||||
@@ -20,12 +59,21 @@ pub mod prelude {
|
|||||||
pub use std::sync::{Arc, Mutex};
|
pub use std::sync::{Arc, Mutex};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A View is an async function that renders content to a DrawContext.
|
||||||
|
/// It takes a mutable DrawContext and produces drawing instructions.
|
||||||
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;
|
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;
|
||||||
|
|
||||||
|
/// A ViewWrapper is a higher-order function that wraps views.
|
||||||
|
/// It can modify or enhance how a view is rendered.
|
||||||
pub type ViewWrapper = Arc<dyn Fn(&mut DrawContext, View) + Send + Sync>;
|
pub type ViewWrapper = Arc<dyn Fn(&mut DrawContext, View) + Send + Sync>;
|
||||||
|
|
||||||
|
/// Result type for OSUI operations
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Error type for OSUI operations
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Error that occurs when a mutex is poisoned
|
||||||
PoisonError,
|
PoisonError,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +83,8 @@ impl From<std::sync::PoisonError<std::sync::MutexGuard<'_, bool>>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sleep for the specified duration in milliseconds.
|
||||||
|
/// Useful for controlling render frame rate or delays.
|
||||||
pub fn sleep(delay_ms: u64) {
|
pub fn sleep(delay_ms: u64) {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
|
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,69 @@
|
|||||||
|
//! # Rendering Module
|
||||||
|
//!
|
||||||
|
//! This module provides low-level rendering primitives and data structures
|
||||||
|
//! for drawing content to the terminal. It includes geometric primitives
|
||||||
|
//! (Point, Area, Size) and drawing instructions.
|
||||||
|
|
||||||
use crate::View;
|
use crate::View;
|
||||||
|
|
||||||
|
/// Represents a drawing instruction that can be executed by the rendering engine
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum DrawInstruction {
|
pub enum DrawInstruction {
|
||||||
|
/// Draw text at a specific point
|
||||||
Text(Point, String),
|
Text(Point, String),
|
||||||
|
/// Render a view within a specified area
|
||||||
View(Area, View),
|
View(Area, View),
|
||||||
|
/// Render a child drawing context at an offset
|
||||||
Child(Point, DrawContext),
|
Child(Point, DrawContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the dimensions of a drawable area
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
|
/// Width in terminal columns
|
||||||
pub width: u16,
|
pub width: u16,
|
||||||
|
/// Height in terminal rows
|
||||||
pub height: u16,
|
pub height: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a position in 2D space
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
|
/// X coordinate (column)
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
|
/// Y coordinate (row)
|
||||||
pub y: u16,
|
pub y: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a rectangular area with position and dimensions
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Area {
|
pub struct Area {
|
||||||
|
/// X coordinate (column) of the top-left corner
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
|
/// Y coordinate (row) of the top-left corner
|
||||||
pub y: u16,
|
pub y: u16,
|
||||||
|
/// Width in terminal columns
|
||||||
pub width: u16,
|
pub width: u16,
|
||||||
|
/// Height in terminal rows
|
||||||
pub height: u16,
|
pub height: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Context for drawing operations
|
||||||
|
///
|
||||||
|
/// Accumulates drawing instructions that are executed by the rendering engine.
|
||||||
|
/// Tracks allocated space within the drawable area.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DrawContext {
|
pub struct DrawContext {
|
||||||
|
/// The total area available for drawing
|
||||||
pub area: Area,
|
pub area: Area,
|
||||||
|
/// The area that has been allocated for drawing (union of all allocations)
|
||||||
pub allocated: Area,
|
pub allocated: Area,
|
||||||
|
/// List of drawing instructions to execute
|
||||||
pub drawing: Vec<DrawInstruction>,
|
pub drawing: Vec<DrawInstruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawContext {
|
impl DrawContext {
|
||||||
|
/// Creates a new DrawContext with the specified area
|
||||||
pub fn new(area: Area) -> Self {
|
pub fn new(area: Area) -> Self {
|
||||||
Self {
|
Self {
|
||||||
area,
|
area,
|
||||||
@@ -48,6 +77,8 @@ impl DrawContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocates space within the drawable area and returns the allocated area
|
||||||
|
/// Updates the allocated bounds to include this allocation
|
||||||
pub fn allocate(&mut self, x: u16, y: u16, width: u16, height: u16) -> Area {
|
pub fn allocate(&mut self, x: u16, y: u16, width: u16, height: u16) -> Area {
|
||||||
self.allocated.x = self.allocated.x.min(x);
|
self.allocated.x = self.allocated.x.min(x);
|
||||||
self.allocated.y = self.allocated.y.min(y);
|
self.allocated.y = self.allocated.y.min(y);
|
||||||
@@ -62,15 +93,18 @@ impl DrawContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a drawing instruction to be executed
|
||||||
pub fn draw(&mut self, inst: DrawInstruction) {
|
pub fn draw(&mut self, inst: DrawInstruction) {
|
||||||
self.drawing.push(inst);
|
self.drawing.push(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws text at the specified point
|
||||||
pub fn draw_text(&mut self, point: Point, text: &str) {
|
pub fn draw_text(&mut self, point: Point, text: &str) {
|
||||||
self.drawing
|
self.drawing
|
||||||
.push(DrawInstruction::Text(point, text.to_string()));
|
.push(DrawInstruction::Text(point, text.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws a view within the specified area
|
||||||
pub fn draw_view(&mut self, area: Area, view: View) {
|
pub fn draw_view(&mut self, area: Area, view: View) {
|
||||||
self.drawing.push(DrawInstruction::View(area, view));
|
self.drawing.push(DrawInstruction::View(area, view));
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-3
@@ -1,3 +1,8 @@
|
|||||||
|
//! # State Management Module
|
||||||
|
//!
|
||||||
|
//! Provides React-like hooks for managing component state and side effects.
|
||||||
|
//! This module includes useState, useEffect, useMount, and state synchronization hooks.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
fmt::{Debug, Display, Formatter, Result as FmtResult},
|
fmt::{Debug, Display, Formatter, Result as FmtResult},
|
||||||
@@ -7,24 +12,42 @@ use std::{
|
|||||||
|
|
||||||
use crate::component::context::Context;
|
use crate::component::context::Context;
|
||||||
|
|
||||||
|
/// Effect callback that can be triggered by state changes
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
|
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
|
||||||
|
|
||||||
|
/// State holder for reactive values
|
||||||
|
///
|
||||||
|
/// Similar to React's useState hook. Holds a value and tracks dependents
|
||||||
|
/// that need to be notified when the value changes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State<T> {
|
pub struct State<T> {
|
||||||
|
/// The actual state value
|
||||||
value: Arc<Mutex<T>>,
|
value: Arc<Mutex<T>>,
|
||||||
|
/// Functions to call when state is updated
|
||||||
dependents: Arc<Mutex<Vec<HookEffect>>>,
|
dependents: Arc<Mutex<Vec<HookEffect>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Guard for accessing and potentially modifying state
|
||||||
|
///
|
||||||
|
/// Dereferences to the state value. When dropped after modification,
|
||||||
|
/// automatically triggers all dependent effects.
|
||||||
pub struct Inner<'a, T> {
|
pub struct Inner<'a, T> {
|
||||||
value: MutexGuard<'a, T>,
|
value: MutexGuard<'a, T>,
|
||||||
dependents: Arc<Mutex<Vec<HookEffect>>>,
|
dependents: Arc<Mutex<Vec<HookEffect>>>,
|
||||||
updated: bool,
|
updated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mount lifecycle hook
|
||||||
|
///
|
||||||
|
/// Tracks whether a component has been mounted and executes
|
||||||
|
/// any pending mount effects.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Mount(Arc<Mutex<bool>>, Arc<Mutex<Vec<HookEffect>>>);
|
pub struct Mount(Arc<Mutex<bool>>, Arc<Mutex<Vec<HookEffect>>>);
|
||||||
|
|
||||||
|
/// Creates a new state value
|
||||||
|
///
|
||||||
|
/// Returns a State that can be read and written from multiple threads.
|
||||||
pub fn use_state<T>(v: T) -> State<T> {
|
pub fn use_state<T>(v: T) -> State<T> {
|
||||||
State {
|
State {
|
||||||
value: Arc::new(Mutex::new(v)),
|
value: Arc::new(Mutex::new(v)),
|
||||||
@@ -33,14 +56,20 @@ pub fn use_state<T>(v: T) -> State<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> State<T> {
|
impl<T: Clone> State<T> {
|
||||||
/// Gets the cloned value, recommended for preventing deadlocks
|
/// Gets a cloned copy of the state value
|
||||||
|
///
|
||||||
|
/// Recommended over `get()` to prevent deadlocks when cloning is acceptable.
|
||||||
|
/// "dl" stands for "deadlock-less".
|
||||||
pub fn get_dl(&self) -> T {
|
pub fn get_dl(&self) -> T {
|
||||||
self.value.lock().unwrap().clone()
|
self.value.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> State<T> {
|
impl<T> State<T> {
|
||||||
/// Gets a lock on the state for read/write access.
|
/// Acquires a lock on the state for read/write access
|
||||||
|
///
|
||||||
|
/// Returns an Inner guard that implements Deref and DerefMut.
|
||||||
|
/// When dropped after modification, triggers dependent effects.
|
||||||
pub fn get(&self) -> Inner<'_, T> {
|
pub fn get(&self) -> Inner<'_, T> {
|
||||||
Inner {
|
Inner {
|
||||||
value: self.value.lock().unwrap(),
|
value: self.value.lock().unwrap(),
|
||||||
@@ -49,18 +78,20 @@ impl<T> State<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the value and marks it as changed.
|
/// Sets the state value and triggers dependents
|
||||||
pub fn set(&self, v: T) {
|
pub fn set(&self, v: T) {
|
||||||
*self.value.lock().unwrap() = v;
|
*self.value.lock().unwrap() = v;
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notifies all dependents of an update
|
||||||
pub fn update(&self) {
|
pub fn update(&self) {
|
||||||
for d in self.dependents.lock().unwrap().iter() {
|
for d in self.dependents.lock().unwrap().iter() {
|
||||||
d.call();
|
d.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clones the State handle (not the value)
|
||||||
pub fn clone(&self) -> Self {
|
pub fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dependents: self.dependents.clone(),
|
dependents: self.dependents.clone(),
|
||||||
@@ -82,15 +113,19 @@ impl Debug for HookEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HookEffect {
|
impl HookEffect {
|
||||||
|
/// Creates a new effect from a function
|
||||||
pub fn new<F: Fn() + Send + Sync + 'static>(f: F) -> Self {
|
pub fn new<F: Fn() + Send + Sync + 'static>(f: F) -> Self {
|
||||||
Self(Arc::new(Mutex::new(f)))
|
Self(Arc::new(Mutex::new(f)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the effect function
|
||||||
pub fn call(&self) {
|
pub fn call(&self) {
|
||||||
(self.0.lock().unwrap())()
|
(self.0.lock().unwrap())()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inner implements Deref for read access and DerefMut for write access
|
||||||
|
/// On drop after mutation, automatically triggers dependent effects
|
||||||
impl<T> Drop for Inner<'_, T> {
|
impl<T> Drop for Inner<'_, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.updated {
|
if self.updated {
|
||||||
@@ -115,7 +150,9 @@ impl<T> DerefMut for Inner<'_, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for values that can be tracked as dependencies in hooks
|
||||||
pub trait HookDependency: Send + Sync {
|
pub trait HookDependency: Send + Sync {
|
||||||
|
/// Register an effect to be triggered on updates
|
||||||
fn on_update(&self, hook: HookEffect);
|
fn on_update(&self, hook: HookEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +173,7 @@ impl HookDependency for Mount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mount {
|
impl Mount {
|
||||||
|
/// Mark the component as mounted and execute pending effects
|
||||||
pub fn mount(&self) {
|
pub fn mount(&self) {
|
||||||
*self.0.lock().unwrap() = true;
|
*self.0.lock().unwrap() = true;
|
||||||
for hook_effect in self.1.lock().unwrap().iter() {
|
for hook_effect in self.1.lock().unwrap().iter() {
|
||||||
@@ -145,6 +183,10 @@ impl Mount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a function when dependencies change
|
||||||
|
///
|
||||||
|
/// Similar to React's useEffect. The provided function is executed
|
||||||
|
/// when any of the dependencies change.
|
||||||
pub fn use_effect<F: FnMut() + Send + Sync + 'static>(f: F, dependencies: &[&dyn HookDependency]) {
|
pub fn use_effect<F: FnMut() + Send + Sync + 'static>(f: F, dependencies: &[&dyn HookDependency]) {
|
||||||
let f = Arc::new(Mutex::new(f));
|
let f = Arc::new(Mutex::new(f));
|
||||||
let hook = HookEffect(Arc::new(Mutex::new({
|
let hook = HookEffect(Arc::new(Mutex::new({
|
||||||
@@ -160,10 +202,18 @@ pub fn use_effect<F: FnMut() + Send + Sync + 'static>(f: F, dependencies: &[&dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a mount lifecycle hook
|
||||||
|
///
|
||||||
|
/// Returns a Mount that tracks component lifecycle and executes
|
||||||
|
/// effects after mounting.
|
||||||
pub fn use_mount() -> Mount {
|
pub fn use_mount() -> Mount {
|
||||||
Mount(Arc::new(Mutex::new(true)), Arc::new(Mutex::new(Vec::new())))
|
Mount(Arc::new(Mutex::new(true)), Arc::new(Mutex::new(Vec::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a manual mount lifecycle hook
|
||||||
|
///
|
||||||
|
/// Similar to use_mount but the component starts as unmounted.
|
||||||
|
/// Must call .mount() to trigger mounted effects.
|
||||||
pub fn use_mount_manual() -> Mount {
|
pub fn use_mount_manual() -> Mount {
|
||||||
Mount(
|
Mount(
|
||||||
Arc::new(Mutex::new(false)),
|
Arc::new(Mutex::new(false)),
|
||||||
@@ -171,6 +221,10 @@ pub fn use_mount_manual() -> Mount {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Synchronizes state with events from the context
|
||||||
|
///
|
||||||
|
/// Creates state that is automatically updated when events are emitted
|
||||||
|
/// to the context. The decoder function converts events to state values.
|
||||||
pub fn use_sync_state<
|
pub fn use_sync_state<
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
E: Any + 'static,
|
E: Any + 'static,
|
||||||
@@ -190,6 +244,10 @@ pub fn use_sync_state<
|
|||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Synchronizes state changes back to the context as events
|
||||||
|
///
|
||||||
|
/// Creates an effect that emits an event whenever the state changes.
|
||||||
|
/// The encoder function converts state values to events.
|
||||||
pub fn use_sync_effect<
|
pub fn use_sync_effect<
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
Ev: Send + Sync + 'static,
|
Ev: Send + Sync + 'static,
|
||||||
|
|||||||
Reference in New Issue
Block a user