Skip to main content

tui_dispatch_core/
lib.rs

1//! Core traits and types for tui-dispatch
2//!
3//! This crate provides the foundational abstractions for building TUI applications
4//! with centralized state management, following a Redux/Elm-inspired architecture.
5//!
6//! # Core Concepts
7//!
8//! - **Action**: Events that describe state changes
9//! - **Store**: Centralized state container with reducer pattern
10//! - **Component**: Pure UI elements that render based on props
11//! - **EventBus**: Pub/sub system for event routing
12//! - **Keybindings**: Context-aware key mapping
13//!
14//! # Basic Example
15//!
16//! ```
17//! use tui_dispatch_core::{Action, Store};
18//!
19//! #[derive(Clone, Debug)]
20//! enum MyAction { Increment, Decrement }
21//!
22//! impl Action for MyAction {
23//!     fn name(&self) -> &'static str {
24//!         match self {
25//!             MyAction::Increment => "Increment",
26//!             MyAction::Decrement => "Decrement",
27//!         }
28//!     }
29//! }
30//!
31//! #[derive(Default)]
32//! struct AppState { counter: i32 }
33//!
34//! fn reducer(state: &mut AppState, action: MyAction) -> bool {
35//!     match action {
36//!         MyAction::Increment => { state.counter += 1; true }
37//!         MyAction::Decrement => { state.counter -= 1; true }
38//!     }
39//! }
40//!
41//! let mut store = Store::new(AppState::default(), reducer);
42//! store.dispatch(MyAction::Increment);
43//! assert_eq!(store.state().counter, 1);
44//! ```
45//!
46//! # Async Handler Pattern
47//!
48//! For applications with async operations (API calls, file I/O, etc.), use a two-phase
49//! action pattern:
50//!
51//! 1. **Intent actions** trigger async work (e.g., `FetchData`)
52//! 2. **Result actions** carry the outcome back (e.g., `DidFetchData`, `DidFetchError`)
53//!
54//! ```ignore
55//! use tokio::sync::mpsc;
56//!
57//! #[derive(Action, Clone, Debug)]
58//! #[action(infer_categories)]
59//! enum Action {
60//!     // Intent: triggers async fetch
61//!     DataFetch { id: String },
62//!     // Result: async operation completed
63//!     DataDidLoad { id: String, payload: Vec<u8> },
64//!     DataDidError { id: String, error: String },
65//! }
66//!
67//! // Async handler spawns a task and sends result back via channel
68//! fn handle_async(action: &Action, tx: mpsc::UnboundedSender<Action>) {
69//!     match action {
70//!         Action::DataFetch { id } => {
71//!             let id = id.clone();
72//!             let tx = tx.clone();
73//!             tokio::spawn(async move {
74//!                 match fetch_from_api(&id).await {
75//!                     Ok(payload) => tx.send(Action::DataDidLoad { id, payload }),
76//!                     Err(e) => tx.send(Action::DataDidError { id, error: e.to_string() }),
77//!                 }
78//!             });
79//!         }
80//!         _ => {}
81//!     }
82//! }
83//!
84//! // Main loop receives actions from both events and async completions
85//! loop {
86//!     tokio::select! {
87//!         Some(action) = action_rx.recv() => {
88//!             handle_async(&action, action_tx.clone());
89//!             store.dispatch(action);
90//!         }
91//!         // ... event handling
92//!     }
93//! }
94//! ```
95//!
96//! The `Did*` naming convention clearly identifies result actions. With `#[action(infer_categories)]`,
97//! these are automatically grouped (e.g., `DataFetch` and `DataDidLoad` both get category `"data"`).
98
99#[cfg(target_arch = "wasm32")]
100extern crate tinycrossterm as crossterm;
101
102pub mod action;
103pub mod bus;
104pub mod component;
105pub mod effect;
106pub mod event;
107pub mod features;
108pub mod keybindings;
109pub mod resource;
110pub mod runtime;
111pub mod store;
112#[cfg(feature = "subscriptions")]
113pub mod subscriptions;
114#[cfg(feature = "tasks")]
115pub mod tasks;
116pub mod testing;
117
118// Core trait exports
119#[allow(deprecated)]
120pub use action::{Action, ActionCategory, ActionParams, ActionSummary};
121pub use component::Component;
122pub use features::{DynamicFeatures, FeatureFlags};
123
124// Event system exports
125pub use bus::{
126    process_raw_event, spawn_event_poller, DefaultBindingContext, EventBus, EventHandler,
127    EventOutcome, EventRoutingState, HandlerResponse, RawEvent, RouteTarget, RoutedEvent,
128    SimpleEventBus,
129};
130pub use event::{
131    ComponentId, Event, EventContext, EventKind, EventType, GlobalKeyPolicy, NumericComponentId,
132};
133
134// Keybindings exports
135pub use keybindings::{format_key_for_display, parse_key_string, BindingContext, Keybindings};
136
137// Store exports
138#[cfg(feature = "tracing")]
139pub use store::LoggingMiddleware;
140pub use store::{
141    ComposedMiddleware, DispatchError, DispatchLimits, Middleware, NoopMiddleware, Reducer, Store,
142    StoreWithMiddleware,
143};
144
145// Runtime exports
146pub use runtime::{
147    DispatchErrorPolicy, DispatchRuntime, DispatchStore, EffectContext, EffectRuntime,
148    EffectStoreLike, PollerConfig, RenderContext,
149};
150
151// Effect exports
152pub use effect::{EffectReducer, EffectStore, EffectStoreWithMiddleware, ReducerResult};
153
154// Resource exports
155pub use resource::DataResource;
156
157// Task exports (requires "tasks" feature)
158#[cfg(feature = "tasks")]
159pub use tasks::{TaskKey, TaskManager, TaskPauseHandle};
160
161// Subscription exports (requires "subscriptions" feature)
162#[cfg(feature = "subscriptions")]
163pub use subscriptions::{SubKey, SubPauseHandle, Subscriptions};
164
165// Re-export ratatui types for convenience
166pub use ratatui::{
167    layout::Rect,
168    style::{Color, Modifier, Style},
169    text::{Line, Span, Text},
170    Frame,
171};
172
173// Testing exports
174pub use testing::{
175    alt_key, buffer_rect_to_string_plain, buffer_to_string, buffer_to_string_plain, char_key,
176    ctrl_key, into_event, key, key_event, key_events, keys, ActionAssertions, ActionAssertionsEq,
177    EffectAssertions, EffectAssertionsEq, EffectStoreTestHarness, RenderHarness, StoreTestHarness,
178    TestHarness,
179};
180
181#[cfg(feature = "testing-time")]
182pub use testing::{advance_time, pause_time, resume_time};
183
184/// Prelude module for convenient imports
185pub mod prelude {
186    pub use crate::action::{Action, ActionCategory, ActionParams};
187    pub use crate::bus::{
188        process_raw_event, spawn_event_poller, DefaultBindingContext, EventBus, EventHandler,
189        EventOutcome, EventRoutingState, HandlerResponse, RawEvent, RouteTarget, RoutedEvent,
190        SimpleEventBus,
191    };
192    pub use crate::component::Component;
193    pub use crate::effect::{EffectReducer, EffectStore, EffectStoreWithMiddleware, ReducerResult};
194    pub use crate::event::{
195        ComponentId, Event, EventContext, EventKind, EventType, GlobalKeyPolicy, NumericComponentId,
196    };
197    pub use crate::features::{DynamicFeatures, FeatureFlags};
198    pub use crate::keybindings::{
199        format_key_for_display, parse_key_string, BindingContext, Keybindings,
200    };
201    pub use crate::reducer_compose;
202    pub use crate::resource::DataResource;
203    #[cfg(feature = "tracing")]
204    pub use crate::store::LoggingMiddleware;
205    pub use crate::store::{
206        ComposedMiddleware, DispatchError, DispatchLimits, Middleware, NoopMiddleware, Reducer,
207        Store, StoreWithMiddleware,
208    };
209
210    // Runtime helpers
211    pub use crate::runtime::{
212        DispatchErrorPolicy, DispatchRuntime, DispatchStore, EffectContext, EffectRuntime,
213        EffectStoreLike, PollerConfig, RenderContext,
214    };
215    #[cfg(feature = "subscriptions")]
216    pub use crate::subscriptions::{SubKey, SubPauseHandle, Subscriptions};
217    #[cfg(feature = "tasks")]
218    pub use crate::tasks::{TaskKey, TaskManager, TaskPauseHandle};
219
220    // Re-export ratatui types
221    pub use ratatui::{
222        layout::Rect,
223        style::{Color, Modifier, Style},
224        text::{Line, Span, Text},
225        Frame,
226    };
227}