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}