Skip to main content

lgui_core/core/component/
reactor.rs

1use std::{
2    any::type_name,
3    future::Future,
4    ops::Deref,
5    sync::{Arc, Mutex},
6};
7
8use super::{
9    ComponentId, ComponentState, DeclarativeView, HookId, HookSlotKind, Observable, UiElement,
10    UiId, UiRenderContext, UiScope,
11};
12use crate::{
13    command::{Command, CommandHandle},
14    events::{AsyncEventHandler, Event, EventKey},
15};
16
17pub struct RenderCx<'a, 'ctx> {
18    scope: UiScope,
19    context: &'ctx UiRenderContext<'a>,
20    component_id: ComponentId,
21    hook_index: usize,
22    force_children: bool,
23}
24
25#[derive(Clone)]
26pub struct State<T> {
27    value: Arc<Mutex<T>>,
28    invalidate: Arc<dyn Fn() + Send + Sync + 'static>,
29}
30
31#[derive(Clone)]
32pub struct StateSetter<T> {
33    set: Arc<dyn Fn(T) + Send + Sync + 'static>,
34    state: State<T>,
35}
36
37#[derive(Clone)]
38pub struct UiFocusHandle {
39    target: UiId,
40    updates: Arc<super::UiUpdateQueue>,
41}
42
43#[derive(Clone, Copy)]
44enum HookKind {
45    Stable,
46    State,
47    Effect,
48    Context,
49    Store,
50}
51
52impl<'a, 'ctx> RenderCx<'a, 'ctx> {
53    pub(crate) const fn component_id(&self) -> ComponentId {
54        self.component_id
55    }
56
57    pub fn new(scope: &UiScope, context: &'ctx UiRenderContext<'a>) -> Self {
58        let component_id = context
59            .component_tree()
60            .root(scope.node_id(), "RenderCx root");
61        context.contexts().begin_component(component_id);
62        let force_children = context
63            .component_tree()
64            .begin_component_execution(component_id);
65        Self {
66            scope: scope.clone(),
67            context,
68            component_id,
69            hook_index: 0,
70            force_children,
71        }
72    }
73
74    #[doc(hidden)]
75    pub fn use_stable_id(&mut self) -> UiId {
76        let index = self.next_hook(HookKind::Stable).index();
77        self.scope
78            .id(format!("h.{}.{index}", HookKind::Stable.code()))
79    }
80
81    #[doc(hidden)]
82    pub fn focus_handle(&self, target: UiId) -> UiFocusHandle {
83        UiFocusHandle {
84            target,
85            updates: self.context.hook_updates(),
86        }
87    }
88
89    pub(crate) fn for_component(
90        scope: &UiScope,
91        context: &'ctx UiRenderContext<'a>,
92        component_id: ComponentId,
93        force_children: bool,
94    ) -> Self {
95        Self {
96            scope: scope.clone(),
97            context,
98            component_id,
99            hook_index: 0,
100            force_children,
101        }
102    }
103
104    pub fn node_id(&self) -> UiId {
105        self.scope.node_id()
106    }
107
108    pub fn viewport(&self) -> super::UiRect {
109        self.context.viewport()
110    }
111
112    pub fn application(&mut self) -> crate::application::ApplicationContext {
113        self.use_context::<crate::application::ApplicationContext>()
114    }
115
116    pub fn command<C>(&mut self) -> CommandHandle<C>
117    where
118        C: Command,
119    {
120        self.application().command::<C>()
121    }
122
123    pub fn use_event<E>(
124        &mut self,
125        deps: impl Clone + PartialEq + 'static,
126        handler: impl Fn(E) + Send + Sync + 'static,
127    ) where
128        E: Event,
129    {
130        self.listen_with(EventKey::new(E::NAME), deps, handler);
131    }
132
133    pub fn use_event_once<E>(&mut self, handler: impl Fn(E) + Send + Sync + 'static)
134    where
135        E: Event,
136    {
137        self.use_event::<E>((), handler);
138    }
139
140    pub fn use_event_async<E>(
141        &mut self,
142        deps: impl Clone + PartialEq + 'static,
143        handler: impl AsyncEventHandler<E>,
144    ) where
145        E: Event,
146    {
147        let application = self.application();
148        let handler = Arc::new(handler);
149        let key = EventKey::new(E::NAME);
150        self.use_effect(deps, move || {
151            let task_application = application.clone();
152            let subscription = application.subscribe_keyed(key, move |event| {
153                let context = super::UiAsyncContext::application_only(task_application.clone());
154                let _ = task_application.spawn(handler.call(context, event));
155            });
156            move || drop(subscription)
157        });
158    }
159
160    pub fn use_event_async_once<E>(&mut self, handler: impl AsyncEventHandler<E>)
161    where
162        E: Event,
163    {
164        self.use_event_async::<E>((), handler);
165    }
166
167    /// Listens for a typed Event key for this component's mounted lifetime.
168    ///
169    /// The listener is installed only after a successful present and is
170    /// automatically removed when this component unmounts or the key changes.
171    pub fn listen<T>(&mut self, key: EventKey<T>, listener: impl Fn(T) + Send + Sync + 'static)
172    where
173        T: Clone + Send + Sync + 'static,
174    {
175        self.listen_with(key, (), listener);
176    }
177
178    /// Listens for a typed Event key and replaces the listener when `deps`
179    /// changes.
180    pub fn listen_with<T, D>(
181        &mut self,
182        key: EventKey<T>,
183        deps: D,
184        listener: impl Fn(T) + Send + Sync + 'static,
185    ) where
186        T: Clone + Send + Sync + 'static,
187        D: Clone + PartialEq + 'static,
188    {
189        let application = self.application();
190        let effect_deps = (key.clone(), deps);
191        self.use_effect(effect_deps, move || {
192            let subscription = application.subscribe_keyed(key, listener);
193            move || drop(subscription)
194        });
195    }
196
197    /// Listens for a typed Event key and schedules each callback Future on the
198    /// Application executor.
199    pub fn listen_async<T, F, Fut>(&mut self, key: EventKey<T>, listener: F)
200    where
201        T: Clone + Send + Sync + 'static,
202        F: Fn(T) -> Fut + Send + Sync + 'static,
203        Fut: Future<Output = ()> + Send + 'static,
204    {
205        self.listen_async_with(key, (), listener);
206    }
207
208    /// Asynchronously listens for a typed Event key and replaces the listener
209    /// when `deps` changes.
210    pub fn listen_async_with<T, D, F, Fut>(&mut self, key: EventKey<T>, deps: D, listener: F)
211    where
212        T: Clone + Send + Sync + 'static,
213        D: Clone + PartialEq + 'static,
214        F: Fn(T) -> Fut + Send + Sync + 'static,
215        Fut: Future<Output = ()> + Send + 'static,
216    {
217        let application = self.application();
218        let effect_deps = (key.clone(), deps);
219        let listener = Arc::new(listener);
220        self.use_effect(effect_deps, move || {
221            let task_application = application.clone();
222            let subscription = application.subscribe_keyed(key, move |payload| {
223                let _ = task_application.spawn(listener(payload));
224            });
225            move || drop(subscription)
226        });
227    }
228
229    pub(crate) fn compile<V>(&self, view: V) -> UiElement
230    where
231        V: DeclarativeView,
232    {
233        view.compile(
234            &self.scope,
235            self.context,
236            self.component_id,
237            self.force_children,
238        )
239    }
240
241    pub fn use_state<T>(&mut self, initial: impl FnOnce() -> T) -> (T, StateSetter<T>)
242    where
243        T: Clone + Send + 'static,
244    {
245        let state = self.create_state_hook(self.scope.node_id(), initial);
246        let value = state.get();
247        (value, StateSetter::new(state))
248    }
249
250    pub fn use_state_eq<T>(&mut self, initial: impl FnOnce() -> T) -> (T, StateSetter<T>)
251    where
252        T: Clone + PartialEq + Send + 'static,
253    {
254        let (value, setter) = self.use_state(initial);
255        (value, setter.with_equality())
256    }
257
258    pub fn state<T>(&mut self, initial: T) -> State<T>
259    where
260        T: Clone + Send + 'static,
261    {
262        self.state_with(|| initial)
263    }
264
265    pub fn state_with<T>(&mut self, initial: impl FnOnce() -> T) -> State<T>
266    where
267        T: Clone + Send + 'static,
268    {
269        self.create_state_hook(self.scope.node_id(), initial)
270    }
271
272    pub fn use_component_state<T, R>(&mut self, update: impl FnOnce(&mut T) -> R) -> R
273    where
274        T: ComponentState + Clone + Default + 'static,
275    {
276        let index = self.next_hook(HookKind::State).index();
277        let id = self
278            .scope
279            .id(format!("h.{}.{index}", HookKind::State.code()));
280        let (result, wants_frame) = self.context.component_state_mut_for_component(
281            &id,
282            self.component_id,
283            self.scope.node_id(),
284            |state: &mut T| {
285                let result = update(state);
286                (result, state.wants_frame())
287            },
288        );
289        if wants_frame {
290            self.context.hook_updates().request_frame();
291        }
292        result
293    }
294
295    fn create_state_hook<T>(&mut self, owner: UiId, initial: impl FnOnce() -> T) -> State<T>
296    where
297        T: Clone + Send + 'static,
298    {
299        let id = self.next_hook(HookKind::State);
300        let value = self
301            .context
302            .hook_state(id, || Arc::new(Mutex::new(initial())));
303        let updates = self.context.hook_updates();
304        let component_id = self.component_id;
305        State {
306            value,
307            invalidate: Arc::new(move || {
308                updates.invalidate(component_id, owner.clone());
309            }),
310        }
311    }
312
313    pub fn use_effect<D, F, R>(&mut self, deps: D, effect: F)
314    where
315        D: Clone + PartialEq + 'static,
316        F: FnOnce() -> R + 'static,
317        R: super::IntoEffectCleanup,
318    {
319        let id = self.next_hook(HookKind::Effect);
320        self.context.effect(id, deps, effect);
321    }
322
323    pub fn use_effect_once<F>(&mut self, effect: F)
324    where
325        F: FnOnce() + 'static,
326    {
327        self.use_effect((), effect);
328    }
329
330    #[cfg(feature = "async")]
331    pub fn use_async_effect<D, F, Fut>(&mut self, deps: D, effect: F)
332    where
333        D: Clone + PartialEq + 'static,
334        F: FnOnce() -> Fut + 'static,
335        Fut: Future<Output = ()> + Send + 'static,
336    {
337        let spawner = self
338            .context
339            .task_spawner()
340            .unwrap_or_else(super::noop_task_spawner);
341        self.use_effect(deps, move || {
342            let (cancel, task) = super::task::cancellable_task(Box::pin(effect()));
343            spawner.spawn(task);
344            move || cancel.cancel()
345        });
346    }
347
348    #[cfg(feature = "async")]
349    pub fn use_async_effect_once<F, Fut>(&mut self, effect: F)
350    where
351        F: FnOnce() -> Fut + 'static,
352        Fut: Future<Output = ()> + Send + 'static,
353    {
354        self.use_async_effect((), effect);
355    }
356
357    pub fn use_mount<F>(&mut self, effect: F)
358    where
359        F: FnOnce() + 'static,
360    {
361        self.use_effect_once(effect);
362    }
363
364    pub fn use_context<T>(&mut self) -> T
365    where
366        T: Clone + 'static,
367    {
368        self.try_use_context::<T>().unwrap_or_else(|| {
369            panic!(
370                "missing context provider for `{}` in component {}",
371                type_name::<T>(),
372                self.component_id
373            )
374        })
375    }
376
377    pub fn try_use_context<T>(&mut self) -> Option<T>
378    where
379        T: Clone + 'static,
380    {
381        self.next_hook(HookKind::Context);
382        self.context.contexts().read(self.component_id)
383    }
384
385    pub fn use_observable<T, S, F>(&mut self, source: Observable<T>, selector: F) -> S
386    where
387        T: Send + 'static,
388        S: Clone + PartialEq + Send + 'static,
389        F: Fn(&T) -> S + Copy + Send + Sync + 'static,
390    {
391        let id = self.next_hook(HookKind::Store);
392        let current = selector(&source.read());
393        let selected = self
394            .context
395            .hook_state(id, || Arc::new(Mutex::new(current.clone())));
396        *selected.lock().expect("store selector state poisoned") = current.clone();
397
398        let owner = self.component_id;
399        let invalidation_id = self.scope.node_id();
400        let updates = self.context.hook_updates();
401        let source_id = source.id();
402        self.use_effect((source_id, std::any::TypeId::of::<F>()), move || {
403            let observed_source = source.clone();
404            let observed_selected = Arc::clone(&selected);
405            let listener = Arc::new(move || {
406                let next = selector(&observed_source.read());
407                let mut current = observed_selected
408                    .lock()
409                    .expect("store selector state poisoned");
410                if *current == next {
411                    return;
412                }
413                *current = next;
414                updates.invalidate(owner, invalidation_id.clone());
415            });
416            let cleanup = source.subscribe(listener);
417            move || cleanup()
418        });
419        current
420    }
421
422    fn next_hook(&mut self, kind: HookKind) -> HookId {
423        let index = self.hook_index;
424        self.hook_index += 1;
425        let kind = kind.slot_kind();
426        self.context
427            .component_tree()
428            .record_hook(self.component_id, kind);
429        HookId::new(self.component_id, index, kind)
430    }
431}
432
433impl HookKind {
434    fn code(self) -> u8 {
435        match self {
436            Self::Stable => 0,
437            Self::State => 1,
438            Self::Effect => 2,
439            Self::Context => 3,
440            Self::Store => 4,
441        }
442    }
443
444    fn slot_kind(self) -> HookSlotKind {
445        match self {
446            Self::Stable => HookSlotKind::Stable,
447            Self::State => HookSlotKind::State,
448            Self::Effect => HookSlotKind::Effect,
449            Self::Context => HookSlotKind::Context,
450            Self::Store => HookSlotKind::Store,
451        }
452    }
453}
454
455impl Drop for RenderCx<'_, '_> {
456    fn drop(&mut self) {
457        if std::thread::panicking() {
458            self.context
459                .component_tree()
460                .abandon_component(self.component_id);
461        } else {
462            self.context
463                .component_tree()
464                .finish_component(self.component_id);
465        }
466    }
467}
468
469impl<T> Deref for StateSetter<T> {
470    type Target = dyn Fn(T) + Send + Sync + 'static;
471
472    fn deref(&self) -> &Self::Target {
473        self.set.as_ref()
474    }
475}
476
477impl<T> State<T>
478where
479    T: Clone + Send + 'static,
480{
481    pub fn get(&self) -> T {
482        self.value.lock().expect("state value poisoned").clone()
483    }
484
485    pub fn set(&self, next: T) {
486        *self.value.lock().expect("state value poisoned") = next;
487        (self.invalidate)();
488    }
489
490    pub fn update(&self, update: impl FnOnce(&mut T)) {
491        update(&mut self.value.lock().expect("state value poisoned"));
492        (self.invalidate)();
493    }
494
495    pub fn try_update(&self, update: impl FnOnce(&mut T) -> bool) -> bool {
496        let changed = update(&mut self.value.lock().expect("state value poisoned"));
497        if changed {
498            (self.invalidate)();
499        }
500        changed
501    }
502}
503
504impl<T> StateSetter<T>
505where
506    T: Clone + Send + 'static,
507{
508    fn new(state: State<T>) -> Self {
509        let set_state = state.clone();
510        Self {
511            set: Arc::new(move |next| set_state.set(next)),
512            state,
513        }
514    }
515
516    pub fn current(&self) -> T {
517        self.state.get()
518    }
519
520    pub fn update(&self, update: impl FnOnce(&mut T) + Send + 'static) {
521        self.state.update(update);
522    }
523
524    pub fn try_update(&self, update: impl FnOnce(&mut T) -> bool) -> bool {
525        self.state.try_update(update)
526    }
527
528    fn with_equality(self) -> Self
529    where
530        T: PartialEq,
531    {
532        let current = self.state.clone();
533        let set = Arc::clone(&self.set);
534        Self {
535            set: Arc::new(move |next| {
536                if current.get() != next {
537                    set(next);
538                }
539            }),
540            state: self.state,
541        }
542    }
543}
544
545impl UiFocusHandle {
546    pub fn focus(&self) {
547        self.updates.request_focus(self.target.clone());
548    }
549}
550
551#[cfg(test)]
552#[path = "reactor_test.rs"]
553mod tests;