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    #[must_use]
46    pub fn new(context: CapabilityContext<RenderOperation, Ev>) -> Self {
47        Self { context }
48    }
49
50    /// Call `render` from [`App::update`](crate::App::update) to signal to the Shell that
51    /// UI should be re-drawn.
52    pub fn render(&self) {
53        let ctx = self.context.clone();
54        self.context.spawn(async move {
55            ctx.notify_shell(RenderOperation).await;
56        });
57    }
58}
59
60impl<Ev> Capability<Ev> for Render<Ev> {
61    type Operation = RenderOperation;
62    type MappedSelf<MappedEv> = Render<MappedEv>;
63
64    fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
65    where
66        F: Fn(NewEv) -> Ev + Send + Sync + 'static,
67        Ev: 'static,
68        NewEv: 'static,
69    {
70        Render::new(self.context.map_event(f))
71    }
72}
73
74/// Signal to the shell that the UI should be redrawn.
75/// Returns a [`NotificationBuilder`].
76///
77/// ### Examples:
78/// To use in a sync context:
79/// ```
80///# use crux_core::{Command, render::{render_builder, Render, RenderOperation}};
81///# #[crux_core::macros::effect]pub enum Effect {Render(RenderOperation)}
82///# enum Event {None}
83/// let command: Command<Effect, Event> =
84///     render_builder().into(); // or use `render_command()`
85/// ```
86/// To use in an async context:
87/// ```
88///# use crux_core::{Command, render::{render_builder, Render, RenderOperation}};
89///# #[crux_core::macros::effect]pub enum Effect {Render(RenderOperation)}
90///# enum Event {None}
91///# let command: Command<Effect, Event> = Command::new(|ctx| async move {
92/// render_builder().into_future(ctx).await;
93///# });
94/// ```
95#[must_use]
96pub fn render_builder<Effect, Event>(
97) -> NotificationBuilder<Effect, Event, impl Future<Output = ()>>
98where
99    Effect: From<Request<RenderOperation>> + Send + 'static,
100    Event: Send + 'static,
101{
102    Command::notify_shell(RenderOperation)
103}
104
105/// Signal to the shell that the UI should be redrawn.
106/// Returns a [`Command`].
107pub fn render<Effect, Event>() -> Command<Effect, Event>
108where
109    Effect: From<Request<RenderOperation>> + Send + 'static,
110    Event: Send + 'static,
111{
112    render_builder().into()
113}