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}