egui_cha/component.rs
1//! Reusable Component trait
2
3use crate::{Cmd, ViewCtx};
4
5/// A reusable UI component with its own state and messages
6///
7/// Components follow a "parent owns state" model (Elm-style).
8/// The parent allocates and stores the component's State,
9/// and passes Props when rendering.
10///
11/// # Example
12/// ```ignore
13/// struct Counter;
14///
15/// impl Component for Counter {
16/// type Props = i32; // initial value
17/// type State = i32; // current count
18/// type Msg = CounterMsg;
19///
20/// fn init(props: &Self::Props) -> Self::State {
21/// *props
22/// }
23///
24/// fn update(state: &mut Self::State, msg: Self::Msg) -> Cmd<Self::Msg> {
25/// match msg {
26/// CounterMsg::Increment => *state += 1,
27/// CounterMsg::Decrement => *state -= 1,
28/// }
29/// Cmd::none()
30/// }
31///
32/// fn view(props: &Self::Props, state: &Self::State, ctx: &mut ViewCtx<Self::Msg>) {
33/// ctx.horizontal(|ctx| {
34/// ctx.button("-", CounterMsg::Decrement);
35/// ctx.ui.label(format!("{}", state));
36/// ctx.button("+", CounterMsg::Increment);
37/// });
38/// }
39/// }
40/// ```
41pub trait Component: Sized {
42 /// Props passed from parent (immutable, for display/config)
43 type Props;
44
45 /// Internal state (owned by parent, mutated via update)
46 type State;
47
48 /// Messages for this component
49 type Msg: Clone + Send + 'static;
50
51 /// Initialize state from props
52 fn init(props: &Self::Props) -> Self::State;
53
54 /// Update state based on message
55 fn update(state: &mut Self::State, msg: Self::Msg) -> Cmd<Self::Msg>;
56
57 /// Render the component
58 fn view(props: &Self::Props, state: &Self::State, ctx: &mut ViewCtx<Self::Msg>);
59}
60
61/// Extension trait for ViewCtx to mount components
62impl<'a, ParentMsg> ViewCtx<'a, ParentMsg> {
63 /// Mount a child component, mapping its messages to parent messages
64 ///
65 /// # Example
66 /// ```ignore
67 /// ctx.mount::<Counter>(
68 /// &counter_props,
69 /// &counter_state,
70 /// |counter_msg| AppMsg::Counter(counter_msg),
71 /// );
72 /// ```
73 pub fn mount<C>(
74 &mut self,
75 props: &C::Props,
76 state: &C::State,
77 map_msg: impl Fn(C::Msg) -> ParentMsg,
78 ) where
79 C: Component,
80 {
81 let mut child_msgs: Vec<C::Msg> = Vec::new();
82
83 // Create child context with child's message type
84 {
85 let mut child_ctx = ViewCtx::new(self.ui, &mut child_msgs);
86 C::view(props, state, &mut child_ctx);
87 }
88
89 // Map child messages to parent messages
90 for child_msg in child_msgs {
91 self.emit(map_msg(child_msg));
92 }
93 }
94
95 /// Mount a child component with mutable state access
96 /// Use this when you need to update child state immediately
97 pub fn mount_mut<C>(
98 &mut self,
99 props: &C::Props,
100 state: &mut C::State,
101 map_msg: impl Fn(C::Msg) -> ParentMsg,
102 ) where
103 C: Component,
104 {
105 let mut child_msgs: Vec<C::Msg> = Vec::new();
106
107 {
108 let mut child_ctx = ViewCtx::new(self.ui, &mut child_msgs);
109 C::view(props, state, &mut child_ctx);
110 }
111
112 // Process child messages immediately on child state
113 for child_msg in child_msgs {
114 let _cmd = C::update(state, child_msg.clone());
115 // Note: We could process Cmd here, but for now we just emit to parent
116 self.emit(map_msg(child_msg));
117 }
118 }
119}