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
99pub mod action;
100pub mod bus;
101pub mod component;
102pub mod effect;
103pub mod event;
104pub mod features;
105pub mod keybindings;
106pub mod resource;
107pub mod runtime;
108pub mod store;
109#[cfg(feature = "subscriptions")]
110pub mod subscriptions;
111#[cfg(feature = "tasks")]
112pub mod tasks;
113pub mod testing;
114
115// Core trait exports
116#[allow(deprecated)]
117pub use action::{Action, ActionCategory, ActionParams, ActionSummary};
118pub use component::Component;
119pub use features::{DynamicFeatures, FeatureFlags};
120
121// Event system exports
122pub use bus::{
123 process_raw_event, spawn_event_poller, DefaultBindingContext, EventBus, EventHandler,
124 EventOutcome, EventRoutingState, HandlerResponse, RawEvent, RouteTarget, RoutedEvent,
125 SimpleEventBus,
126};
127pub use event::{
128 ComponentId, Event, EventContext, EventKind, EventType, GlobalKeyPolicy, NumericComponentId,
129};
130
131// Keybindings exports
132pub use keybindings::{format_key_for_display, parse_key_string, BindingContext, Keybindings};
133
134// Store exports
135#[cfg(feature = "tracing")]
136pub use store::LoggingMiddleware;
137pub use store::{
138 ComposedMiddleware, Middleware, NoopMiddleware, Reducer, Store, StoreWithMiddleware,
139};
140
141// Runtime exports
142pub use runtime::{
143 DispatchRuntime, DispatchStore, EffectContext, EffectRuntime, EffectStoreLike, PollerConfig,
144 RenderContext,
145};
146
147// Effect exports
148pub use effect::{EffectReducer, EffectStore, EffectStoreWithMiddleware, ReducerResult};
149
150// Resource exports
151pub use resource::DataResource;
152
153// Task exports (requires "tasks" feature)
154#[cfg(feature = "tasks")]
155pub use tasks::{TaskKey, TaskManager, TaskPauseHandle};
156
157// Subscription exports (requires "subscriptions" feature)
158#[cfg(feature = "subscriptions")]
159pub use subscriptions::{SubKey, SubPauseHandle, Subscriptions};
160
161// Re-export ratatui types for convenience
162pub use ratatui::{
163 layout::Rect,
164 style::{Color, Modifier, Style},
165 text::{Line, Span, Text},
166 Frame,
167};
168
169// Testing exports
170pub use testing::{
171 alt_key, buffer_rect_to_string_plain, buffer_to_string, buffer_to_string_plain, char_key,
172 ctrl_key, into_event, key, key_event, key_events, keys, ActionAssertions, ActionAssertionsEq,
173 EffectAssertions, EffectAssertionsEq, EffectStoreTestHarness, RenderHarness, StoreTestHarness,
174 TestHarness,
175};
176
177#[cfg(feature = "testing-time")]
178pub use testing::{advance_time, pause_time, resume_time};
179
180/// Prelude module for convenient imports
181pub mod prelude {
182 pub use crate::action::{Action, ActionCategory, ActionParams};
183 pub use crate::bus::{
184 process_raw_event, spawn_event_poller, DefaultBindingContext, EventBus, EventHandler,
185 EventOutcome, EventRoutingState, HandlerResponse, RawEvent, RouteTarget, RoutedEvent,
186 SimpleEventBus,
187 };
188 pub use crate::component::Component;
189 pub use crate::effect::{EffectReducer, EffectStore, EffectStoreWithMiddleware, ReducerResult};
190 pub use crate::event::{
191 ComponentId, Event, EventContext, EventKind, EventType, GlobalKeyPolicy, NumericComponentId,
192 };
193 pub use crate::features::{DynamicFeatures, FeatureFlags};
194 pub use crate::keybindings::{
195 format_key_for_display, parse_key_string, BindingContext, Keybindings,
196 };
197 pub use crate::reducer_compose;
198 pub use crate::resource::DataResource;
199 #[cfg(feature = "tracing")]
200 pub use crate::store::LoggingMiddleware;
201 pub use crate::store::{
202 ComposedMiddleware, Middleware, NoopMiddleware, Reducer, Store, StoreWithMiddleware,
203 };
204
205 // Runtime helpers
206 pub use crate::runtime::{
207 DispatchRuntime, DispatchStore, EffectContext, EffectRuntime, EffectStoreLike,
208 PollerConfig, RenderContext,
209 };
210 #[cfg(feature = "subscriptions")]
211 pub use crate::subscriptions::{SubKey, SubPauseHandle, Subscriptions};
212 #[cfg(feature = "tasks")]
213 pub use crate::tasks::{TaskKey, TaskManager, TaskPauseHandle};
214
215 // Re-export ratatui types
216 pub use ratatui::{
217 layout::Rect,
218 style::{Color, Modifier, Style},
219 text::{Line, Span, Text},
220 Frame,
221 };
222}