crux_core/capabilities/
render.rs

1//! Built-in capability used to notify the Shell that a UI update is necessary.
2
3use std::future::Future;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    capability::{CapabilityContext, Operation},
9    command::NotificationBuilder,
10    Capability, Command, Request,
11};
12
13/// Use an instance of `Render` to notify the Shell that it should update the user
14/// interface. This assumes a declarative UI framework is used in the Shell, which will
15/// take the ViewModel provided by [`Core::view`](crate::Core::view) and reconcile the new UI state based
16/// on the view model with the previous one.
17///
18/// For imperative UIs, the Shell will need to understand the difference between the two
19/// view models and update the user interface accordingly.
20pub struct Render<Ev> {
21    context: CapabilityContext<RenderOperation, Ev>,
22}
23
24impl<Ev> Clone for Render<Ev> {
25    fn clone(&self) -> Self {
26        Self {
27            context: self.context.clone(),
28        }
29    }
30}
31
32/// The single operation `Render` implements.
33#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
34pub struct RenderOperation;
35
36impl Operation for RenderOperation {
37    type Output = ();
38}
39
40/// Public API of the capability, called by App::update.
41impl<Ev> Render<Ev>
42where
43    Ev: 'static,
44{
45    pub fn new(context: CapabilityContext<RenderOperation, Ev>) -> Self {
46        Self { context }
47    }
48
49    /// Call `render` from [`App::update`](crate::App::update) to signal to the Shell that
50    /// UI should be re-drawn.
51    pub fn render(&self) {
52        let ctx = self.context.clone();
53        self.context.spawn(async move {
54            ctx.notify_shell(RenderOperation).await;
55        });
56    }
57}
58
59impl<Ev> Capability<Ev> for Render<Ev> {
60    type Operation = RenderOperation;
61    type MappedSelf<MappedEv> = Render<MappedEv>;
62
63    fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
64    where
65        F: Fn(NewEv) -> Ev + Send + Sync + 'static,
66        Ev: 'static,
67        NewEv: 'static,
68    {
69        Render::new(self.context.map_event(f))
70    }
71}
72
73/// Signal to the shell that the UI should be redrawn.
74/// Returns a [`NotificationBuilder`].
75///
76/// ### Examples:
77/// To use in a sync context:
78/// ```
79///# use crux_core::{Command, render::{render_builder, Render, RenderOperation}};
80///# #[crux_core::macros::effect]pub enum Effect {Render(RenderOperation)}
81///# enum Event {None}
82/// let command: Command<Effect, Event> =
83///     render_builder().into(); // or use `render_command()`
84/// ```
85/// To use in an async context:
86/// ```
87///# use crux_core::{Command, render::{render_builder, Render, RenderOperation}};
88///# #[crux_core::macros::effect]pub enum Effect {Render(RenderOperation)}
89///# enum Event {None}
90///# let command: Command<Effect, Event> = Command::new(|ctx| async move {
91/// render_builder().into_future(ctx).await;
92///# });
93/// ```
94pub fn render_builder<Effect, Event>(
95) -> NotificationBuilder<Effect, Event, impl Future<Output = ()>>
96where
97    Effect: From<Request<RenderOperation>> + Send + 'static,
98    Event: Send + 'static,
99{
100    Command::notify_shell(RenderOperation)
101}
102
103/// Signal to the shell that the UI should be redrawn.
104/// Returns a [`Command`].
105pub fn render<Effect, Event>() -> Command<Effect, Event>
106where
107    Effect: From<Request<RenderOperation>> + Send + 'static,
108    Event: Send + 'static,
109{
110    render_builder().into()
111}