Documented
This commit is contained in:
@@ -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::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
@@ -16,15 +22,28 @@ use crate::{
|
||||
|
||||
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 {
|
||||
/// The component implementation
|
||||
component: AccessCell<Component>,
|
||||
/// The current rendered view
|
||||
view: AccessCell<View>,
|
||||
/// Event handlers grouped by event type
|
||||
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
|
||||
/// Child scopes (component hierarchies)
|
||||
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
|
||||
/// Command executor for this context's command handling
|
||||
executor: Arc<dyn CommandExecutor>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Creates a new context for the given component
|
||||
pub fn new<F: ComponentImpl + 'static>(
|
||||
component: F,
|
||||
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>) {
|
||||
self.event_handlers
|
||||
.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>) {
|
||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||
|
||||
@@ -79,10 +104,15 @@ impl Context {
|
||||
let _ = rx.recv();
|
||||
}
|
||||
|
||||
/// Gets the current view
|
||||
pub fn get_view(self: &Arc<Self>) -> View {
|
||||
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>(
|
||||
self: &Arc<Self>,
|
||||
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) {
|
||||
let event = Arc::new(event);
|
||||
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>(
|
||||
self: &Arc<Self>,
|
||||
event: &E,
|
||||
@@ -141,6 +179,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new child scope
|
||||
pub fn scope(self: &Arc<Self>) -> Arc<Scope> {
|
||||
let scope = Scope::new(self.executor.clone());
|
||||
self.scopes.lock().unwrap().push(scope.clone());
|
||||
@@ -148,6 +187,7 @@ impl Context {
|
||||
scope
|
||||
}
|
||||
|
||||
/// Creates a dynamic child scope that re-renders when dependencies change
|
||||
pub fn dyn_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
||||
self: &Arc<Self>,
|
||||
drawer: F,
|
||||
@@ -171,10 +211,12 @@ impl Context {
|
||||
scope
|
||||
}
|
||||
|
||||
/// Adds a pre-constructed scope as a child
|
||||
pub fn add_scope(self: &Arc<Self>, scope: Arc<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) {
|
||||
for scope in self.scopes.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> {
|
||||
self.executor.clone()
|
||||
}
|
||||
|
||||
/// Executes a command
|
||||
pub fn execute<T: Command + 'static>(self: &Arc<Self>, command: T) -> crate::Result<()> {
|
||||
self.executor
|
||||
.execute_command(&(Arc::new(command) as Arc<dyn Command>))
|
||||
}
|
||||
|
||||
/// Stops the application
|
||||
pub fn stop(self: &Arc<Self>) -> crate::Result<()> {
|
||||
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 scope;
|
||||
|
||||
@@ -10,10 +16,15 @@ use crate::View;
|
||||
|
||||
use context::Context;
|
||||
|
||||
/// A Component is an implementor of the ComponentImpl trait, wrapped in Arc
|
||||
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>>;
|
||||
|
||||
/// Trait implemented by components to render themselves
|
||||
pub trait ComponentImpl: Send + Sync {
|
||||
/// Renders the component within the given context, returning a 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 crate::{engine::CommandExecutor, View, ViewWrapper};
|
||||
|
||||
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 {
|
||||
/// Child components with optional view wrappers
|
||||
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
||||
/// Command executor for this scope's children
|
||||
executor: Arc<dyn CommandExecutor>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
/// Creates a new scope with the given command executor
|
||||
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
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>(
|
||||
self: &Arc<Self>,
|
||||
child: F,
|
||||
@@ -29,6 +45,7 @@ impl Scope {
|
||||
self.children.lock().unwrap().push((ctx, view_wrapper));
|
||||
}
|
||||
|
||||
/// Adds a view directly to this scope
|
||||
pub fn view(self: &Arc<Self>, view: View) {
|
||||
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 crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
||||
@@ -7,18 +11,26 @@ use crate::{render::Area, DrawContext, View};
|
||||
|
||||
use super::Engine;
|
||||
|
||||
/// Results of a benchmark run
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BenchmarkResult {
|
||||
/// Average render time in microseconds
|
||||
pub average: u128,
|
||||
/// Minimum render time in microseconds
|
||||
pub min: u128,
|
||||
/// Maximum render time in microseconds
|
||||
pub max: u128,
|
||||
/// Total time spent rendering in microseconds
|
||||
pub total_render: u128,
|
||||
/// Total benchmark time including setup in microseconds
|
||||
pub total: u128,
|
||||
}
|
||||
|
||||
/// Wraps an engine to benchmark its rendering performance
|
||||
pub struct Benchmark<T: Engine>(T);
|
||||
|
||||
impl<T: Engine> Benchmark<T> {
|
||||
/// Creates a new benchmark wrapper around the given engine
|
||||
pub fn new(engine: T) -> Self {
|
||||
Self(engine)
|
||||
}
|
||||
@@ -31,6 +43,7 @@ impl<T: Engine> Engine<BenchmarkResult> for Benchmark<T> {
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
// Run 40 render cycles and measure each
|
||||
for _ in 0..40 {
|
||||
let start = Instant::now();
|
||||
self.render(&cx);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
//! # Commands Module
|
||||
//!
|
||||
//! Defines built-in commands for controlling the engine.
|
||||
|
||||
use crate::engine::Command;
|
||||
|
||||
/// Command to stop the engine and terminate the application
|
||||
pub struct 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::{
|
||||
io::{stdout, Write},
|
||||
sync::{Arc, Mutex},
|
||||
@@ -14,16 +19,24 @@ use crate::{
|
||||
|
||||
use super::Engine;
|
||||
|
||||
/// Executes commands for the console engine
|
||||
pub struct ConsoleExecutor {
|
||||
/// Flag indicating whether the application is running
|
||||
running: Mutex<bool>,
|
||||
}
|
||||
|
||||
/// Console-based rendering engine
|
||||
///
|
||||
/// Renders components to the terminal using crossterm for cross-platform support.
|
||||
pub struct Console {
|
||||
/// Thread functions to execute
|
||||
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
|
||||
/// The executor for this console
|
||||
executor: Arc<ConsoleExecutor>,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Creates a new console engine
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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) {
|
||||
self.threads.lock().unwrap().push(Arc::new(run));
|
||||
}
|
||||
@@ -114,10 +128,12 @@ impl Engine for Console {
|
||||
}
|
||||
|
||||
impl ConsoleExecutor {
|
||||
/// Checks if the engine is still running
|
||||
pub fn is_running(self: &Arc<ConsoleExecutor>) -> bool {
|
||||
*self.running.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Stops the engine
|
||||
pub fn stop(&self) -> crate::Result<()> {
|
||||
*self.running.lock()? = false;
|
||||
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 commands;
|
||||
pub mod console;
|
||||
@@ -10,23 +16,40 @@ use std::{any::Any, sync::Arc};
|
||||
use crate::component::{context::Context, ComponentImpl};
|
||||
use crate::{render::Area, DrawContext, View};
|
||||
|
||||
/// Main engine trait for rendering and running components
|
||||
pub trait Engine<Output = ()> {
|
||||
/// Runs a component to completion
|
||||
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>;
|
||||
|
||||
/// Renders the current state of a component
|
||||
fn render(&self, cx: &Arc<Context>);
|
||||
|
||||
/// Sleeps between render frames (default 16ms for ~60fps)
|
||||
fn render_delay(&self) {
|
||||
crate::sleep(16);
|
||||
}
|
||||
|
||||
/// Renders a view within an area and returns the draw context
|
||||
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
|
||||
|
||||
/// Executes the drawing instructions in a draw context
|
||||
fn draw_context(&self, ctx: &DrawContext);
|
||||
|
||||
/// Returns the command executor for this engine
|
||||
fn executor(&self) -> Arc<dyn CommandExecutor>;
|
||||
}
|
||||
|
||||
/// Trait for commands that can be executed by the engine
|
||||
pub trait Command {
|
||||
/// Returns the command as Any for downcasting
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
/// Executes commands during the application lifecycle
|
||||
pub trait CommandExecutor: Send + Sync {
|
||||
/// Executes the given command
|
||||
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 crate::component::{context::Context, scope::Scope};
|
||||
use crate::{render::Point, state::HookDependency, View};
|
||||
|
||||
/// Trait for converting values to RSX
|
||||
pub trait ToRsx {
|
||||
/// Convert to RSX representation
|
||||
fn to_rsx(&self) -> Rsx;
|
||||
}
|
||||
|
||||
/// Scope types for RSX components
|
||||
#[derive(Clone)]
|
||||
pub enum RsxScope {
|
||||
/// Static scope - executed once and never updated
|
||||
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
|
||||
/// Dynamic scope - re-executed when dependencies change
|
||||
Dynamic(
|
||||
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
|
||||
Vec<Arc<dyn HookDependency>>,
|
||||
),
|
||||
/// Child RSX scope for composition
|
||||
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)]
|
||||
pub struct Rsx(Vec<RsxScope>);
|
||||
|
||||
impl Rsx {
|
||||
/// Creates a new empty RSX
|
||||
pub fn new() -> Self {
|
||||
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) {
|
||||
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>(
|
||||
&mut self,
|
||||
drawer: F,
|
||||
@@ -38,10 +63,12 @@ impl Rsx {
|
||||
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
|
||||
}
|
||||
|
||||
/// Adds a child RSX
|
||||
pub fn child<R: ToRsx>(&mut self, child: R) {
|
||||
self.0.push(RsxScope::Child(child.to_rsx()));
|
||||
}
|
||||
|
||||
/// Generates child components within the given context
|
||||
pub fn generate_children(&self, context: &Arc<Context>) {
|
||||
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 {
|
||||
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 crate::render::DrawContext;
|
||||
@@ -9,6 +47,7 @@ pub mod render;
|
||||
pub mod state;
|
||||
|
||||
pub mod prelude {
|
||||
//! Prelude module - Re-exports commonly used items for convenience
|
||||
pub use crate::component::{context::*, scope::*, *};
|
||||
pub use crate::engine::*;
|
||||
pub use crate::frontend::*;
|
||||
@@ -20,12 +59,21 @@ pub mod prelude {
|
||||
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>;
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// Result type for OSUI operations
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Error type for OSUI operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
/// Error that occurs when a mutex is poisoned
|
||||
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) {
|
||||
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;
|
||||
|
||||
/// Represents a drawing instruction that can be executed by the rendering engine
|
||||
#[derive(Clone)]
|
||||
pub enum DrawInstruction {
|
||||
/// Draw text at a specific point
|
||||
Text(Point, String),
|
||||
/// Render a view within a specified area
|
||||
View(Area, View),
|
||||
/// Render a child drawing context at an offset
|
||||
Child(Point, DrawContext),
|
||||
}
|
||||
|
||||
/// Represents the dimensions of a drawable area
|
||||
#[derive(Clone)]
|
||||
pub struct Size {
|
||||
/// Width in terminal columns
|
||||
pub width: u16,
|
||||
/// Height in terminal rows
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
/// Represents a position in 2D space
|
||||
#[derive(Clone)]
|
||||
pub struct Point {
|
||||
/// X coordinate (column)
|
||||
pub x: u16,
|
||||
/// Y coordinate (row)
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
/// Represents a rectangular area with position and dimensions
|
||||
#[derive(Clone)]
|
||||
pub struct Area {
|
||||
/// X coordinate (column) of the top-left corner
|
||||
pub x: u16,
|
||||
/// Y coordinate (row) of the top-left corner
|
||||
pub y: u16,
|
||||
/// Width in terminal columns
|
||||
pub width: u16,
|
||||
/// Height in terminal rows
|
||||
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)]
|
||||
pub struct DrawContext {
|
||||
/// The total area available for drawing
|
||||
pub area: Area,
|
||||
/// The area that has been allocated for drawing (union of all allocations)
|
||||
pub allocated: Area,
|
||||
/// List of drawing instructions to execute
|
||||
pub drawing: Vec<DrawInstruction>,
|
||||
}
|
||||
|
||||
impl DrawContext {
|
||||
/// Creates a new DrawContext with the specified area
|
||||
pub fn new(area: Area) -> Self {
|
||||
Self {
|
||||
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 {
|
||||
self.allocated.x = self.allocated.x.min(x);
|
||||
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) {
|
||||
self.drawing.push(inst);
|
||||
}
|
||||
|
||||
/// Draws text at the specified point
|
||||
pub fn draw_text(&mut self, point: Point, text: &str) {
|
||||
self.drawing
|
||||
.push(DrawInstruction::Text(point, text.to_string()));
|
||||
}
|
||||
|
||||
/// Draws a view within the specified area
|
||||
pub fn draw_view(&mut self, area: Area, view: 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::{
|
||||
any::Any,
|
||||
fmt::{Debug, Display, Formatter, Result as FmtResult},
|
||||
@@ -7,24 +12,42 @@ use std::{
|
||||
|
||||
use crate::component::context::Context;
|
||||
|
||||
/// Effect callback that can be triggered by state changes
|
||||
#[derive(Clone)]
|
||||
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)]
|
||||
pub struct State<T> {
|
||||
/// The actual state value
|
||||
value: Arc<Mutex<T>>,
|
||||
/// Functions to call when state is updated
|
||||
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> {
|
||||
value: MutexGuard<'a, T>,
|
||||
dependents: Arc<Mutex<Vec<HookEffect>>>,
|
||||
updated: bool,
|
||||
}
|
||||
|
||||
/// Mount lifecycle hook
|
||||
///
|
||||
/// Tracks whether a component has been mounted and executes
|
||||
/// any pending mount effects.
|
||||
#[derive(Debug, Clone)]
|
||||
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> {
|
||||
State {
|
||||
value: Arc::new(Mutex::new(v)),
|
||||
@@ -33,14 +56,20 @@ pub fn use_state<T>(v: T) -> 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 {
|
||||
self.value.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
Inner {
|
||||
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) {
|
||||
*self.value.lock().unwrap() = v;
|
||||
self.update();
|
||||
}
|
||||
|
||||
/// Notifies all dependents of an update
|
||||
pub fn update(&self) {
|
||||
for d in self.dependents.lock().unwrap().iter() {
|
||||
d.call();
|
||||
}
|
||||
}
|
||||
|
||||
/// Clones the State handle (not the value)
|
||||
pub fn clone(&self) -> Self {
|
||||
Self {
|
||||
dependents: self.dependents.clone(),
|
||||
@@ -82,15 +113,19 @@ impl Debug for HookEffect {
|
||||
}
|
||||
|
||||
impl HookEffect {
|
||||
/// Creates a new effect from a function
|
||||
pub fn new<F: Fn() + Send + Sync + 'static>(f: F) -> Self {
|
||||
Self(Arc::new(Mutex::new(f)))
|
||||
}
|
||||
|
||||
/// Executes the effect function
|
||||
pub fn call(&self) {
|
||||
(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> {
|
||||
fn drop(&mut self) {
|
||||
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 {
|
||||
/// Register an effect to be triggered on updates
|
||||
fn on_update(&self, hook: HookEffect);
|
||||
}
|
||||
|
||||
@@ -136,6 +173,7 @@ impl HookDependency for Mount {
|
||||
}
|
||||
|
||||
impl Mount {
|
||||
/// Mark the component as mounted and execute pending effects
|
||||
pub fn mount(&self) {
|
||||
*self.0.lock().unwrap() = true;
|
||||
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]) {
|
||||
let f = Arc::new(Mutex::new(f));
|
||||
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 {
|
||||
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 {
|
||||
Mount(
|
||||
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<
|
||||
T: Send + Sync + 'static,
|
||||
E: Any + 'static,
|
||||
@@ -190,6 +244,10 @@ pub fn use_sync_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<
|
||||
T: Send + Sync + 'static,
|
||||
Ev: Send + Sync + 'static,
|
||||
|
||||
Reference in New Issue
Block a user