Skip to main content

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}