yew_state/
handle.rs

1//! Ergonomic interface with shared state.
2use std::rc::Rc;
3
4use yew::{Callback, Properties};
5
6use crate::handler::{
7    HandlerLink, Reduction, ReductionOnce, SharedHandler, StateHandler, StorageHandler,
8};
9
10/// Handle for basic shared state.
11pub type SharedHandle<T> = StateHandle<SharedHandler<T>>;
12/// Handle for shared state with persistent storage.
13
14pub type StorageHandle<T> = StateHandle<StorageHandler<T>>;
15
16type Model<T> = <T as StateHandler>::Model;
17
18pub trait Handle {
19    type Handler: StateHandler;
20}
21
22impl<HANDLER> Handle for StateHandle<HANDLER>
23where
24    HANDLER: StateHandler,
25    <HANDLER as StateHandler>::Message: Clone,
26    <HANDLER as StateHandler>::Output: Clone,
27    <HANDLER as StateHandler>::Input: Clone,
28    Model<HANDLER>: Clone + 'static,
29{
30    type Handler = HANDLER;
31}
32
33pub trait SharedState {
34    type Handle: Handle;
35
36    fn handle(&mut self) -> &mut Self::Handle;
37}
38
39/// Provides mutable access for wrapper component to update
40pub trait WrapperHandle: Handle {
41    fn set_state(&mut self, state: Rc<Model<Self::Handler>>);
42    fn set_callbacks(
43        &mut self,
44        callback: Callback<Reduction<Model<Self::Handler>>>,
45        callback_once: Callback<ReductionOnce<Model<Self::Handler>>>,
46    );
47    fn set_link(&mut self, _link: HandlerLink<Self::Handler>) {}
48}
49
50impl<HANDLER> WrapperHandle for StateHandle<HANDLER>
51where
52    HANDLER: StateHandler,
53    <HANDLER as StateHandler>::Message: Clone,
54    <HANDLER as StateHandler>::Output: Clone,
55    <HANDLER as StateHandler>::Input: Clone,
56    Model<HANDLER>: Clone + 'static,
57{
58    fn set_state(&mut self, state: Rc<Model<Self::Handler>>) {
59        self.state = Some(state);
60    }
61
62    fn set_link(&mut self, link: HandlerLink<Self::Handler>) {
63        self.link = Some(link);
64    }
65
66    fn set_callbacks(
67        &mut self,
68        callback: Callback<Reduction<Model<Self::Handler>>>,
69        callback_once: Callback<ReductionOnce<Model<Self::Handler>>>,
70    ) {
71        self.callback = callback;
72        self.callback_once = callback_once;
73    }
74}
75
76/// Interface to shared state
77#[derive(Properties)]
78pub struct StateHandle<HANDLER>
79where
80    HANDLER: StateHandler,
81    <HANDLER as StateHandler>::Message: Clone,
82    <HANDLER as StateHandler>::Output: Clone,
83    <HANDLER as StateHandler>::Input: Clone,
84    Model<HANDLER>: Clone + 'static,
85{
86    #[prop_or_default]
87    state: Option<Rc<Model<HANDLER>>>,
88    #[prop_or_default]
89    callback: Callback<Reduction<Model<HANDLER>>>,
90    #[prop_or_default]
91    callback_once: Callback<ReductionOnce<Model<HANDLER>>>,
92    #[prop_or_default]
93    link: Option<HandlerLink<HANDLER>>,
94}
95
96impl<HANDLER> StateHandle<HANDLER>
97where
98    HANDLER: StateHandler,
99    <HANDLER as StateHandler>::Message: Clone,
100    <HANDLER as StateHandler>::Output: Clone,
101    <HANDLER as StateHandler>::Input: Clone,
102    Model<HANDLER>: Clone + 'static,
103{
104    pub fn link(&self) -> &HandlerLink<HANDLER> {
105        self.link.as_ref().expect(
106            "Link accessed prematurely. Is your component wrapped in a SharedStateComponent?",
107        )
108    }
109
110    pub fn state(&self) -> &Model<HANDLER> {
111        self.state.as_ref().expect(
112            "State accessed prematurely. Is your component wrapped in a SharedStateComponent?",
113        )
114    }
115
116    /// Apply a function that may mutate shared state.
117    /// Changes are not immediate, and must be handled in `Component::change`.
118    pub fn reduce(&self, f: impl FnOnce(&mut Model<HANDLER>) + 'static) {
119        self.callback_once.emit(Box::new(f))
120    }
121
122    /// Convenience method for modifying shared state directly from a `Callback`.
123    /// The callback event is ignored here, see `reduce_callback_with` for the alternative.
124    pub fn reduce_callback<E: 'static>(
125        &self,
126        f: impl Fn(&mut Model<HANDLER>) + 'static,
127    ) -> Callback<E>
128    where
129        Model<HANDLER>: 'static,
130    {
131        let f = Rc::new(f);
132        self.callback.reform(move |_| f.clone())
133    }
134
135    /// Convenience method for modifying shared state directly from a `CallbackOnce`.
136    /// The callback event is ignored here, see `reduce_callback_once_with` for the alternative.
137    pub fn reduce_callback_once<E: 'static>(
138        &self,
139        f: impl FnOnce(&mut Model<HANDLER>) + 'static,
140    ) -> Callback<E>
141    where
142        Model<HANDLER>: 'static,
143    {
144        let f = Box::new(f);
145        let cb = self.callback_once.clone();
146        Callback::once(move |_| cb.emit(f))
147    }
148
149    /// Convenience method for modifying shared state directly from a `Callback`.
150    /// Similar to `reduce_callback` but it also accepts the fired event.
151    pub fn reduce_callback_with<E: 'static>(
152        &self,
153        f: impl Fn(&mut Model<HANDLER>, E) + 'static,
154    ) -> Callback<E>
155    where
156        Model<HANDLER>: 'static,
157        E: Clone,
158    {
159        let f = Rc::new(f);
160        self.callback.reform(move |e: E| {
161            let f = f.clone();
162            Rc::new(move |state| f.clone()(state, e.clone()))
163        })
164    }
165
166    /// Convenience method for modifying shared state directly from a `CallbackOnce`.
167    /// Similar to `reduce_callback` but it also accepts the fired event.
168    pub fn reduce_callback_once_with<E: 'static>(
169        &self,
170        f: impl FnOnce(&mut Model<HANDLER>, E) + 'static,
171    ) -> Callback<E>
172    where
173        Model<HANDLER>: 'static,
174    {
175        let cb = self.callback_once.clone();
176        Callback::once(move |e| cb.emit(Box::new(move |state| f(state, e))))
177    }
178}
179
180impl<HANDLER> SharedState for StateHandle<HANDLER>
181where
182    HANDLER: StateHandler,
183    <HANDLER as StateHandler>::Message: Clone,
184    <HANDLER as StateHandler>::Output: Clone,
185    <HANDLER as StateHandler>::Input: Clone,
186    Model<HANDLER>: Clone,
187{
188    type Handle = Self;
189
190    fn handle(&mut self) -> &mut Self::Handle {
191        self
192    }
193}
194
195impl<HANDLER> Default for StateHandle<HANDLER>
196where
197    HANDLER: StateHandler,
198    <HANDLER as StateHandler>::Message: Clone,
199    <HANDLER as StateHandler>::Output: Clone,
200    <HANDLER as StateHandler>::Input: Clone,
201    Model<HANDLER>: Clone,
202{
203    fn default() -> Self {
204        Self {
205            state: Default::default(),
206            callback: Default::default(),
207            callback_once: Default::default(),
208            link: Default::default(),
209        }
210    }
211}
212
213impl<HANDLER> Clone for StateHandle<HANDLER>
214where
215    HANDLER: StateHandler,
216    HandlerLink<HANDLER>: Clone,
217    <HANDLER as StateHandler>::Message: Clone,
218    <HANDLER as StateHandler>::Output: Clone,
219    <HANDLER as StateHandler>::Input: Clone,
220    Model<HANDLER>: Clone,
221{
222    fn clone(&self) -> Self {
223        Self {
224            state: self.state.clone(),
225            callback: self.callback.clone(),
226            callback_once: self.callback_once.clone(),
227            link: self.link.clone(),
228        }
229    }
230}
231
232impl<HANDLER> PartialEq for StateHandle<HANDLER>
233where
234    HANDLER: StateHandler,
235    <HANDLER as StateHandler>::Message: Clone,
236    <HANDLER as StateHandler>::Output: Clone,
237    <HANDLER as StateHandler>::Input: Clone,
238    Model<HANDLER>: Clone,
239{
240    fn eq(&self, other: &Self) -> bool {
241        self.state
242            .as_ref()
243            .zip(other.state.as_ref())
244            .map(|(a, b)| Rc::ptr_eq(a, b))
245            .unwrap_or(false)
246            && self.callback == other.callback
247            && self.callback_once == other.callback_once
248    }
249}