1use ui_core::application_view::{
12 ApplicationFocusRestore, ApplicationLocalView, ApplicationViewState,
13};
14use ui_core::collaboration::{CollabEvent, CollabRequest};
15use ui_core::presence::PresenceCtx;
16use ui_core::text_render::TextRenderer;
17use ui_core::theme::{AppearanceIntent, Theme};
18use ui_core::time::Instant;
19use ui_core::vello::Scene;
20use ui_core::vello::kurbo::Affine;
21use ui_core::view::ThawError;
22use ui_core::widget::{AppModel, Collaborative, Decorator, Element, ModelPath, Widget};
23
24type Hook = Box<dyn FnMut()>;
25type Ask = Box<dyn Fn() -> bool>;
26type Wake = Box<dyn Fn() -> Option<Instant>>;
27type Appearance = Box<dyn Fn() -> Option<AppearanceIntent>>;
28type TakeRequests = Box<dyn FnMut() -> Vec<CollabRequest>>;
29type HandleEvent = Box<dyn FnMut(&CollabEvent)>;
30type Restore = Box<dyn FnMut(&[u8]) -> Result<(), String>>;
31
32struct KeptView {
34 app_id: String,
35 version: u32,
36 capture: Box<dyn Fn() -> Vec<u8>>,
37 restore: Restore,
38}
39
40#[derive(Default)]
44pub struct Upkeep {
45 sync: Option<Hook>,
46 busy: Option<Ask>,
47 next_wake: Option<Wake>,
48 pending: Option<Ask>,
49 presented: Option<Hook>,
50 appearance: Option<Appearance>,
51 collaboration: Option<(TakeRequests, HandleEvent)>,
52 view: Option<KeptView>,
53}
54
55impl Upkeep {
56 #[must_use]
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 #[must_use]
65 pub fn syncing(mut self, sync: impl FnMut() + 'static) -> Self {
66 self.sync = Some(Box::new(sync));
67 self
68 }
69
70 #[must_use]
73 pub fn busy(mut self, busy: impl Fn() -> bool + 'static) -> Self {
74 self.busy = Some(Box::new(busy));
75 self
76 }
77
78 #[must_use]
81 pub fn next_wake(mut self, next: impl Fn() -> Option<Instant> + 'static) -> Self {
82 self.next_wake = Some(Box::new(next));
83 self
84 }
85
86 #[must_use]
90 pub fn pending_visual_work(mut self, pending: impl Fn() -> bool + 'static) -> Self {
91 self.pending = Some(Box::new(pending));
92 self
93 }
94
95 #[must_use]
97 pub fn presenting(mut self, presented: impl FnMut() + 'static) -> Self {
98 self.presented = Some(Box::new(presented));
99 self
100 }
101
102 #[must_use]
106 pub fn appearance(
107 mut self,
108 appearance: impl Fn() -> Option<AppearanceIntent> + 'static,
109 ) -> Self {
110 self.appearance = Some(Box::new(appearance));
111 self
112 }
113
114 #[must_use]
118 pub fn collaboration(
119 mut self,
120 take: impl FnMut() -> Vec<CollabRequest> + 'static,
121 handle: impl FnMut(&CollabEvent) + 'static,
122 ) -> Self {
123 self.collaboration = Some((Box::new(take), Box::new(handle)));
124 self
125 }
126
127 #[must_use]
131 pub fn kept_view(
132 mut self,
133 app_id: impl Into<String>,
134 version: u32,
135 capture: impl Fn() -> Vec<u8> + 'static,
136 restore: impl FnMut(&[u8]) -> Result<(), String> + 'static,
137 ) -> Self {
138 self.view = Some(KeptView {
139 app_id: app_id.into(),
140 version,
141 capture: Box::new(capture),
142 restore: Box::new(restore),
143 });
144 self
145 }
146}
147
148pub(crate) fn upkeep(upkeep: Upkeep, content: Element) -> Element {
150 Box::new(Kept { upkeep, content })
151}
152
153struct Kept {
154 upkeep: Upkeep,
155 content: Element,
156}
157
158impl Decorator for Kept {
159 type Inner = dyn Widget;
160
161 fn inner(&self) -> &(dyn Widget + 'static) {
162 self.content.as_ref()
163 }
164
165 fn inner_mut(&mut self) -> &mut (dyn Widget + 'static) {
166 self.content.as_mut()
167 }
168
169 fn provide<'a>(&'a self, request: &mut ui_core::widget::capability::Request<'a, '_>) {
170 if self.upkeep.view.is_some() && request.wants::<dyn ApplicationViewState>() {
171 request.provide::<dyn ApplicationViewState>(self);
172 return;
173 }
174 self.content.provide(request);
175 }
176
177 fn provide_mut<'a>(
178 &'a mut self,
179 request: &mut ui_core::widget::capability::RequestMut<'a, '_>,
180 ) {
181 if self.upkeep.collaboration.is_some() && request.wants::<dyn Collaborative>() {
182 request.provide::<dyn Collaborative>(self);
183 return;
184 }
185 if self.upkeep.view.is_some() && request.wants::<dyn ApplicationViewState>() {
186 request.provide::<dyn ApplicationViewState>(self);
187 return;
188 }
189 self.content.provide_mut(request);
190 }
191
192 fn as_any(&self) -> Option<&dyn std::any::Any> {
195 self.content.as_any()
196 }
197
198 fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
199 self.content.as_any_mut()
200 }
201
202 fn name(&self) -> &'static str {
203 "Upkeep"
204 }
205
206 fn take_structure_change(&mut self) -> ui_core::widget::StructureChange {
208 self.content.take_structure_change()
209 }
210
211 fn sync(&mut self, state: &AppModel, path: &ModelPath) {
212 if let Some(sync) = self.upkeep.sync.as_mut() {
213 sync();
214 }
215 self.content.sync(state, path);
216 }
217
218 fn draw(
219 &mut self,
220 scene: &mut Scene,
221 transform: Affine,
222 text_renderer: &mut TextRenderer,
223 theme: &Theme,
224 presence: &PresenceCtx,
225 ) {
226 self.content
227 .draw(scene, transform, text_renderer, theme, presence);
228 if let Some(presented) = self.upkeep.presented.as_mut() {
229 presented();
230 }
231 }
232
233 fn needs_animation_frame(&self) -> bool {
234 self.upkeep.busy.as_ref().is_some_and(|busy| busy()) || self.content.needs_animation_frame()
235 }
236
237 fn next_wake_deadline(&self) -> Option<Instant> {
238 let own = self.upkeep.next_wake.as_ref().and_then(|next| next());
239 match (own, self.content.next_wake_deadline()) {
240 (Some(own), Some(content)) => Some(own.min(content)),
241 (own, content) => own.or(content),
242 }
243 }
244
245 fn has_pending_visual_work(&self) -> bool {
246 self.upkeep
247 .pending
248 .as_ref()
249 .is_some_and(|pending| pending())
250 || self.content.has_pending_visual_work()
251 }
252
253 fn appearance_intent(&self) -> Option<AppearanceIntent> {
254 self.upkeep
255 .appearance
256 .as_ref()
257 .and_then(|appearance| appearance())
258 .or_else(|| self.content.appearance_intent())
259 }
260}
261
262impl Collaborative for Kept {
263 fn take_collab_requests(&mut self) -> Vec<CollabRequest> {
264 self.upkeep
265 .collaboration
266 .as_mut()
267 .map_or_else(Vec::new, |(take, _)| take())
268 }
269
270 fn handle_collab_event(&mut self, event: &CollabEvent) {
271 if let Some((_, handle)) = self.upkeep.collaboration.as_mut() {
272 handle(event);
273 }
274 }
275}
276
277impl ApplicationViewState for Kept {
278 fn capture_application_view(&self) -> Result<Option<ApplicationLocalView>, ThawError> {
279 Ok(self.upkeep.view.as_ref().map(|view| ApplicationLocalView {
280 app_id: view.app_id.clone(),
281 version: view.version,
282 payload: (view.capture)(),
283 }))
284 }
285
286 fn restore_application_view(
287 &mut self,
288 local: &ApplicationLocalView,
289 _doc: &ui_core::automerge::AutoCommit,
290 ) -> Result<ApplicationFocusRestore, ThawError> {
291 let view = self
292 .upkeep
293 .view
294 .as_mut()
295 .ok_or_else(|| ThawError::State("no kept view".into()))?;
296 if local.app_id != view.app_id || local.version != view.version {
297 return Err(ThawError::State(format!(
298 "a view of {} at version {}, not {} at {}",
299 local.app_id, local.version, view.app_id, view.version
300 )));
301 }
302 (view.restore)(&local.payload).map_err(ThawError::State)?;
303 Ok(ApplicationFocusRestore::Generic)
304 }
305}
306
307#[cfg(test)]
308#[path = "upkeep_tests.rs"]
309mod tests;