tui_dispatch_core/component.rs
1//! Component trait for pure UI elements
2
3use ratatui::{layout::Rect, Frame};
4
5use crate::event::EventKind;
6
7/// A pure UI component that renders based on props and emits actions
8///
9/// Components follow these rules:
10/// 1. Props contain ALL read-only data needed for rendering
11/// 2. `handle_event` returns actions, never mutates external state
12/// 3. `render` is a pure function of props (plus internal UI state like scroll position)
13///
14/// Internal UI state (scroll position, selection highlight) can be stored in `&mut self`,
15/// but data mutations must go through actions.
16///
17/// # Focus and Context
18///
19/// Components receive `EventKind` (the raw event) rather than the full `Event` with context.
20/// Focus information and other context should be passed through `Props`. This keeps components
21/// decoupled from the specific ComponentId type used by the application.
22///
23/// # Example
24///
25/// ```ignore
26/// use tui_dispatch::{Component, EventKind, Frame, Rect};
27///
28/// struct Counter;
29///
30/// struct CounterProps {
31/// count: i32,
32/// is_focused: bool,
33/// }
34///
35/// impl Component<AppAction> for Counter {
36/// type Props<'a> = CounterProps;
37///
38/// fn handle_event(&mut self, event: &EventKind, props: Self::Props<'_>) -> impl IntoIterator<Item = AppAction> {
39/// if !props.is_focused {
40/// return None;
41/// }
42/// if let EventKind::Key(key) = event {
43/// match key.code {
44/// KeyCode::Up => return Some(AppAction::Increment),
45/// KeyCode::Down => return Some(AppAction::Decrement),
46/// _ => {}
47/// }
48/// }
49/// None
50/// }
51///
52/// fn render(&mut self, frame: &mut Frame, area: Rect, props: Self::Props<'_>) {
53/// let text = format!("Count: {}", props.count);
54/// frame.render_widget(Paragraph::new(text), area);
55/// }
56/// }
57/// ```
58pub trait Component<A> {
59 /// Data required to render the component (read-only)
60 type Props<'a>;
61
62 /// Handle an event and return actions to dispatch
63 ///
64 /// Components receive the raw `EventKind` (key press, mouse event, etc.).
65 /// Focus state and other context should be passed through `Props`.
66 ///
67 /// Returns any type implementing `IntoIterator<Item = A>`:
68 /// - `None` - no actions (most common)
69 /// - `Some(action)` - single action
70 /// - `[a, b]` or `vec![...]` - multiple actions
71 ///
72 /// Default implementation returns no actions (render-only components).
73 #[allow(unused_variables)]
74 fn handle_event(
75 &mut self,
76 event: &EventKind,
77 props: Self::Props<'_>,
78 ) -> impl IntoIterator<Item = A> {
79 None::<A>
80 }
81
82 /// Render the component to the frame
83 fn render(&mut self, frame: &mut Frame, area: Rect, props: Self::Props<'_>);
84}