xilem_core/views/
map_message.rs

1// Copyright 2024 the Xilem Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use core::fmt::Debug;
5use core::marker::PhantomData;
6
7use crate::{MessageContext, MessageResult, Mut, View, ViewMarker, ViewPathTracker};
8
9/// View type for [`map_message`] and [`map_action`]. Most users will want to use `map_action` (the latter).
10///
11/// This view maps a child [`View<State,ChildAction,_>`] to [`View<State,ParentAction,_>`], whilst allowing the kind of [`MessageResult`] to be changed.
12#[must_use = "View values do nothing unless provided to Xilem."]
13pub struct MapMessage<
14    V,
15    State,
16    ParentAction,
17    ChildAction,
18    Context,
19    // This default only exists for documentation purposes.
20    F = fn(&mut State, ChildAction) -> ParentAction,
21> {
22    map_fn: F,
23    child: V,
24    phantom: PhantomData<fn() -> (State, ParentAction, ChildAction, Context)>,
25}
26
27impl<V, State, ParentAction, ChildAction, Context, F> Debug
28    for MapMessage<V, State, ParentAction, ChildAction, Context, F>
29where
30    V: Debug,
31{
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        f.debug_struct("MapAction")
34            .field("child", &self.child)
35            .finish_non_exhaustive()
36    }
37}
38
39/// A view that maps a child [`View<State,ChildAction,_>`] to [`View<State,ParentAction,_>`] while providing mutable access to `State` in the map function.
40///
41/// This is very similar to the Elm architecture, where the parent view can update state based on the action message from the child view.
42///
43/// # Examples
44///
45/// (From the Xilem implementation)
46///
47/// ```ignore
48/// enum CountMessage {
49///     Increment,
50///     Decrement,
51/// }
52///
53/// fn count_view<T>(count: i32) -> impl WidgetView<T, CountMessage> {
54///     flex((
55///         label(format!("count: {}", count)),
56///         button("+", |_| CountMessage::Increment),
57///         button("-", |_| CountMessage::Decrement),
58///     ))
59/// }
60///
61/// fn app_logic(count: &mut i32) -> impl WidgetView<i32> {
62///     map_action(count_view(*count), |count, message| match message {
63///         CountMessage::Increment => *count += 1,
64///         CountMessage::Decrement => *count -= 1,
65///     })
66/// }
67/// ```
68pub fn map_action<State, ParentAction, ChildAction, Context: ViewPathTracker, V, F>(
69    view: V,
70    map_fn: F,
71) -> MapMessage<
72    V,
73    State,
74    ParentAction,
75    ChildAction,
76    Context,
77    impl Fn(&mut State, MessageResult<ChildAction>) -> MessageResult<ParentAction> + 'static,
78>
79where
80    State: 'static,
81    ParentAction: 'static,
82    ChildAction: 'static,
83    V: View<State, ChildAction, Context>,
84    F: Fn(&mut State, ChildAction) -> ParentAction + 'static,
85{
86    MapMessage {
87        map_fn: move |app_state: &mut State, result: MessageResult<ChildAction>| {
88            result.map(|action| map_fn(app_state, action))
89        },
90        child: view,
91        phantom: PhantomData,
92    }
93}
94
95/// A view which maps a child [`View<State,ChildAction,_>`] to [`View<State,ParentAction,_>`], whilst allowing the kind of [`MessageResult`] to be changed.
96///
97/// This is the more general form of [`map_action`].
98/// In most cases, you probably want to use that function.
99pub fn map_message<State, ParentAction, ChildAction, Context: ViewPathTracker, V, F>(
100    view: V,
101    map_fn: F,
102) -> MapMessage<V, State, ParentAction, ChildAction, Context, F>
103where
104    State: 'static,
105    ParentAction: 'static,
106    ChildAction: 'static,
107    V: View<State, ChildAction, Context>,
108    F: Fn(&mut State, MessageResult<ChildAction>) -> MessageResult<ParentAction> + 'static,
109{
110    MapMessage {
111        map_fn,
112        child: view,
113        phantom: PhantomData,
114    }
115}
116
117impl<V, State, ParentAction, ChildAction, F, Context> ViewMarker
118    for MapMessage<V, State, ParentAction, ChildAction, Context, F>
119{
120}
121impl<V, State, ParentAction, ChildAction, Context, F> View<State, ParentAction, Context>
122    for MapMessage<V, State, ParentAction, ChildAction, Context, F>
123where
124    V: View<State, ChildAction, Context>,
125    State: 'static,
126    ParentAction: 'static,
127    ChildAction: 'static,
128    F: Fn(&mut State, MessageResult<ChildAction>) -> MessageResult<ParentAction> + 'static,
129    Context: ViewPathTracker + 'static,
130{
131    type ViewState = V::ViewState;
132    type Element = V::Element;
133
134    fn build(&self, ctx: &mut Context, app_state: &mut State) -> (Self::Element, Self::ViewState) {
135        self.child.build(ctx, app_state)
136    }
137
138    fn rebuild(
139        &self,
140        prev: &Self,
141        view_state: &mut Self::ViewState,
142        ctx: &mut Context,
143        element: Mut<'_, Self::Element>,
144        app_state: &mut State,
145    ) {
146        self.child
147            .rebuild(&prev.child, view_state, ctx, element, app_state);
148    }
149
150    fn teardown(
151        &self,
152        view_state: &mut Self::ViewState,
153        ctx: &mut Context,
154        element: Mut<'_, Self::Element>,
155    ) {
156        self.child.teardown(view_state, ctx, element);
157    }
158
159    fn message(
160        &self,
161        view_state: &mut Self::ViewState,
162        message: &mut MessageContext,
163        element: Mut<'_, Self::Element>,
164        app_state: &mut State,
165    ) -> MessageResult<ParentAction> {
166        let child_result = self.child.message(view_state, message, element, app_state);
167        (self.map_fn)(app_state, child_result)
168    }
169}