Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/core/view/context.rs
StratoSDK / crates / strato-ui-core / src / core / view / context.rs
1use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};
2 
3use futures::future::{AbortHandle, Abortable};
4use futures::{Future, FutureExt};
5use pathfinder_geometry::rect::RectF;
6use thiserror::Error;
7 
8use crate::modals::{AlertDialogWithCallbacks, ModalButton, ViewModalCallback};
9use crate::platform::{
10 file_picker::{FilePickerConfiguration, FilePickerError},
11 Cursor, SaveFilePickerConfiguration, TerminationMode,
12};
13use crate::r#async::SpawnableOutput;
14use crate::windowing::WindowManager;
15use crate::{
16 accessibility::AccessibilityContent,
17 core::{Observation, Subscription, SubscriptionKey, TaskCallback},
18 fonts::Cache as FontCache,
19 notification::{NotificationSendError, RequestPermissionsOutcome, UserNotification},
20 r#async::{
21 executor::{Background, Foreground},
22 SpawnedFutureHandle, SpawnedLocalStream,
23 },
24 Action, AppContext, Effect, Entity, EntityId, ModelAsRef, ModelContext, ModelHandle,
25 UpdateModel, WindowId,
26};
27use crate::{GetSingletonModelHandle, ReadModel};
28 
29use super::{
30 handle::{AnyViewHandle, ReadView, UpdateView, ViewAsRef, ViewHandle, WeakViewHandle},
31 TypedActionView, View,
32};
33 
34/// Structure that combines view identifiers and a handle to the application
35/// context/application state.
36pub struct ViewContext<'a, T: ?Sized> {
37 app: &'a mut AppContext,
38 window_id: WindowId,
39 view_id: EntityId,
40 view_type: PhantomData<T>,
41}
42 
43impl<'a, T: View> ViewContext<'a, T> {
44 pub(in crate::core) fn new(
45 app: &'a mut AppContext,
46 window_id: WindowId,
47 view_id: EntityId,
48 ) -> Self {
49 Self {
50 app,
51 window_id,
52 view_id,
53 view_type: PhantomData,
54 }
55 }
56 
57 /// Adds a callback that will be invoked immediately after the next frame is drawn.
58 /// Note that the callback is only invoked once and is discarded after it is called.
59 pub fn on_next_frame_drawn<F: 'static + Fn()>(&mut self, callback: F) {
60 self.app.on_next_frame_drawn(self.window_id, callback);
61 }
62 
63 pub fn handle(&self) -> WeakViewHandle<T> {
64 WeakViewHandle::new(self.view_id)
65 }
66 
67 pub fn window_id(&self) -> WindowId {
68 self.window_id
69 }
70 
71 pub fn view_id(&self) -> EntityId {
72 self.view_id
73 }
74 
75 pub fn font_cache(&self) -> &FontCache {
76 self.app.font_cache()
77 }
78 
79 pub fn windows(&self) -> &WindowManager {
80 self.app.windows()
81 }
82 
83 pub fn disable_key_bindings_dispatching(&mut self) {
84 let window_id = self.window_id;
85 log::info!("disabling actions for window id {window_id}");
86 self.disable_key_bindings(window_id)
87 }
88 
89 pub fn enable_key_bindings_dispatching(&mut self) {
90 let window_id = self.window_id;
91 log::info!("enabling actions for window id {window_id}");
92 self.enable_key_bindings(window_id)
93 }
94 
95 // Function to check if the parent view is focused.
96 pub fn is_self_focused(&self) -> bool {
97 self.app.check_view_focused(self.window_id, &self.view_id)
98 }
99 
100 pub fn is_self_or_child_focused(&self) -> bool {
101 self.app
102 .check_view_or_child_focused(self.window_id, &self.view_id)
103 }
104 
105 pub fn element_position_by_id<S>(&self, id: S) -> Option<RectF>
106 where
107 S: AsRef<str>,
108 {
109 let presenter = self.app.presenter(self.window_id);
110 
111 if let Some(presenter) = presenter {
112 let borrowed_presenter = presenter.borrow();
113 borrowed_presenter.position_cache().get_position(id)
114 } else {
115 None
116 }
117 }
118 
119 pub fn focus<S: View>(&mut self, handle: &ViewHandle<S>) {
120 let handle: AnyViewHandle = handle.into();
121 self.app.pending_effects.push_back(Effect::Focus {
122 window_id: handle.window_id(self.app),
123 view_id: handle.id(),
124 });
125 }
126 
127 pub fn focus_self(&mut self) {
128 self.app.pending_effects.push_back(Effect::Focus {
129 window_id: self.window_id,
130 view_id: self.view_id,
131 });
132 }
133 
134 pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
135 where
136 S: Entity,
137 F: FnOnce(&mut ModelContext<S>) -> S,
138 {
139 self.app.add_model(build_model)
140 }
141 
142 pub fn add_view<S, F>(&mut self, build_view: F) -> ViewHandle<S>
143 where
144 S: View,
145 F: FnOnce(&mut ViewContext<S>) -> S,
146 {
147 self.app.add_view(self.window_id, build_view)
148 }
149 
150 pub fn add_typed_action_view<V, F>(&mut self, build_view: F) -> ViewHandle<V>
151 where
152 V: TypedActionView + View,
153 F: FnOnce(&mut ViewContext<V>) -> V,
154 {
155 // Add a new view, and set the parent view as the current context's view.
156 self.app
157 .add_typed_action_view_with_parent(self.window_id, build_view, self.view_id)
158 }
159 
160 pub fn add_option_view<S, F>(&mut self, build_view: F) -> Option<ViewHandle<S>>
161 where
162 S: View,
163 F: FnOnce(&mut ViewContext<S>) -> Option<S>,
164 {
165 self.app.add_option_view(self.window_id, build_view)
166 }
167 
168 pub fn subscribe_to_model<E, F>(&mut self, handle: &ModelHandle<E>, mut callback: F)
169 where
170 E: Entity,
171 E::Event: 'static,
172 F: 'static + FnMut(&mut T, ModelHandle<E>, &E::Event, &mut ViewContext<T>),
173 {
174 let emitter_handle = handle.downgrade();
175 self.app
176 .subscriptions
177 .entry(handle.id())
178 .or_default()
179 .push(Subscription::FromView {
180 window_id: self.window_id,
181 view_id: self.view_id,
182 callback: Box::new(move |view, payload, app, window_id, view_id| {
183 if let Some(emitter_handle) = emitter_handle.upgrade(app) {
184 let model = view.downcast_mut().expect("downcast is type safe");
185 let payload = payload.downcast_ref().expect("downcast is type safe");
186 let mut ctx = ViewContext::new(app, window_id, view_id);
187 callback(model, emitter_handle, payload, &mut ctx);
188 }
189 }),
190 });
191 }
192 
193 pub fn subscribe_to_view<V, F>(&mut self, handle: &ViewHandle<V>, mut callback: F)
194 where
195 V: View,
196 V::Event: 'static,
197 F: 'static + FnMut(&mut T, ViewHandle<V>, &V::Event, &mut ViewContext<T>),
198 {
199 let emitter_handle = handle.downgrade();
200 
201 self.app
202 .subscriptions
203 .entry(handle.id())
204 .or_default()
205 .push(Subscription::FromView {
206 window_id: self.window_id,
207 view_id: self.view_id,
208 callback: Box::new(move |view, payload, app, window_id, view_id| {
209 if let Some(emitter_handle) = emitter_handle.upgrade(app) {
210 let model = view.downcast_mut().expect("downcast is type safe");
211 let payload = payload.downcast_ref().expect("downcast is type safe");
212 let mut ctx = ViewContext::new(app, window_id, view_id);
213 callback(model, emitter_handle, payload, &mut ctx);
214 }
215 }),
216 });
217 }
218 
219 pub fn unsubscribe_to_view<V>(&mut self, handle: &ViewHandle<V>)
220 where
221 V: View,
222 V::Event: 'static,
223 {
224 let target_entity = handle.id();
225 
226 // If we're currently emitting events for this entity, defer the unsubscribe.
227 if let Some(ref mut pending) = self.app.pending_unsubscribes {
228 if pending.entity_id == target_entity {
229 pending
230 .keys
231 .insert(SubscriptionKey::View(self.window_id, self.view_id));
232 
233 // Remove subscriptions created earlier in this emission so subscribe-then-unsubscribe ordering is preserved.
234 if let std::collections::hash_map::Entry::Occupied(mut entry) =
235 self.app.subscriptions.entry(target_entity)
236 {
237 entry.get_mut().retain(|subscription| match subscription {
238 Subscription::FromModel { .. } | Subscription::FromApp { .. } => true,
239 Subscription::FromView {
240 window_id, view_id, ..
241 } => *window_id != self.window_id || *view_id != self.view_id,
242 });
243 
244 if entry.get().is_empty() {
245 entry.remove();
246 }
247 }
248 
249 return;
250 }
251 }
252 
253 // Otherwise process immediately.
254 self.app
255 .subscriptions
256 .entry(target_entity)
257 .or_default()
258 .retain(|subscription| match subscription {
259 Subscription::FromModel { .. } | Subscription::FromApp { .. } => true,
260 Subscription::FromView {
261 window_id, view_id, ..
262 } => *window_id != self.window_id || *view_id != self.view_id,
263 });
264 }
265 
266 pub fn unsubscribe_to_model<E>(&mut self, handle: &ModelHandle<E>)
267 where
268 E: Entity,
269 E::Event: 'static,
270 {
271 let target_entity = handle.id();
272 
273 // If we're currently emitting events for this entity, defer the unsubscribe.
274 if let Some(ref mut pending) = self.app.pending_unsubscribes {
275 if pending.entity_id == target_entity {
276 pending
277 .keys
278 .insert(SubscriptionKey::View(self.window_id, self.view_id));
279 
280 // Remove subscriptions created earlier in this emission so subscribe-then-unsubscribe ordering is preserved.
281 if let std::collections::hash_map::Entry::Occupied(mut entry) =
282 self.app.subscriptions.entry(target_entity)
283 {
284 entry.get_mut().retain(|subscription| match subscription {
285 Subscription::FromModel { .. } | Subscription::FromApp { .. } => true,
286 Subscription::FromView {
287 window_id, view_id, ..
288 } => *window_id != self.window_id || *view_id != self.view_id,
289 });
290 
291 if entry.get().is_empty() {
292 entry.remove();
293 }
294 }
295 
296 return;
297 }
298 }
299 
300 // Otherwise process immediately.
301 self.app
302 .subscriptions
303 .entry(target_entity)
304 .or_default()
305 .retain(|subscription| match subscription {
306 Subscription::FromModel { .. } | Subscription::FromApp { .. } => true,
307 Subscription::FromView {
308 window_id, view_id, ..
309 } => *window_id != self.window_id || *view_id != self.view_id,
310 })
311 }
312 
313 /// Prompt the user to pick file path(s) in the OS native file picker.
314 pub fn open_file_picker(
315 &mut self,
316 callback: impl FnOnce(Result<Vec<String>, FilePickerError>, &mut ViewContext<T>)
317 + Send
318 + Sync
319 + 'static,
320 config: FilePickerConfiguration,
321 ) {
322 let window_id = self.window_id;
323 let view_id = self.view_id;
324 self.app.open_file_picker(
325 move |result, app| {
326 let mut view_context = ViewContext::new(app, window_id, view_id);
327 callback(result, &mut view_context)
328 },
329 config,
330 )
331 }
332 
333 /// Prompt the user to save a file with the OS native save file dialog.
334 ///
335 /// The callback receives the chosen path (or `None` if cancelled), a mutable
336 /// reference to the owning view, and its `ViewContext`.
337 pub fn open_save_file_picker(
338 &mut self,
339 callback: impl FnOnce(Option<String>, &mut T, &mut ViewContext<T>) + Send + Sync + 'static,
340 config: SaveFilePickerConfiguration,
341 ) {
342 let view_id = self.view_id;
343 self.app.open_save_file_picker(
344 move |path, app| {
345 let weak = WeakViewHandle::<T>::new(view_id);
346 if let Some(handle) = weak.upgrade(app) {
347 app.update_view(&handle, |view, ctx| {
348 callback(path, view, ctx);
349 });
350 }
351 },
352 config,
353 )
354 }
355 
356 /// Emits the provided event on this `View`.
357 ///
358 /// Unlike DOM events, these events don't bubble or otherwise automatically
359 /// propagate themselves up the view hierarchy. In order for another view
360 /// to receive any events emitted by this view, the receiver will need to
361 /// explicitly subscribe to this view's events by calling
362 /// [`Self::subscribe_to_view()`][^note].
363 ///
364 /// [^note]: This subscription is on a per-instance basis, not on a per-type
365 /// basis.
366 pub fn emit(&mut self, payload: T::Event) {
367 self.app.pending_effects.push_back(Effect::Event {
368 entity_id: self.view_id,
369 payload: Box::new(payload),
370 });
371 }
372 
373 /// When all else fails, `emit_a11y_content` comes to the rescue! In our UI framework, some stuff is just an Event, and not an Action. Sometimes a different View emits the event, while another one handles it… So instead of solving this, we simply use `emit_a11y_content(&mut self, content: AccessibilityContent)` on demand, meaning, whenever something meaningful happens and it’s not related to Action or View focusing, we should emit a11y content. A good example for this is announcing that a new update is available.
374 ///
375 /// ### When and how to use it?
376 /// Whenever we feel like it’s important to make an announcement about events in the app. Note that this requires a ViewContext.
377 pub fn emit_a11y_content(&mut self, content: AccessibilityContent) {
378 let verbosity = self.a11y_verbosity;
379 self.platform_delegate
380 .set_accessibility_contents(content.with_verbosity(verbosity));
381 }
382 
383 /// Delegates to the OS to request the user attention for the given window. For mac this bounces the
384 /// icon in the dock. If the window is already focused this is a noop.
385 pub fn request_user_attention(&mut self) {
386 let window_id = self.window_id;
387 self.app.request_user_attention(window_id);
388 }
389 
390 /// Global actions are being phased out. Prefer dispatching typed actions instead of global actions.
391 /// Dispatch a global action to be handled by the registered handler
392 ///
393 /// Note: The dispatch of the global action will be registered as an effect and flushed after
394 /// the current view update is complete. This will ensure that the view has been re-inserted
395 /// into the `AppContext`, so it will be accessible to the global action, if necessary
396 #[track_caller]
397 pub fn dispatch_global_action<A: Any>(&mut self, name: &'static str, arg: A) {
398 let location = std::panic::Location::caller();
399 self.app.pending_effects.push_back(Effect::GlobalAction {
400 name,
401 location,
402 arg: Box::new(arg),
403 });
404 }
405 
406 pub fn dispatch_typed_action(&mut self, action: &dyn Action) {
407 let window_id = self.window_id;
408 let view_id = self.view_id;
409 self.dispatch_typed_action_for_view(window_id, view_id, action);
410 }
411 
412 /// Defers dispatching a typed action until effects are flushed.
413 ///
414 /// This is useful to avoid re-entrant view updates (e.g. triggering UI updates
415 /// while a view in the responder chain is still mid-update).
416 pub fn dispatch_typed_action_deferred<A: Action + 'static>(&mut self, action: A) {
417 self.app.pending_effects.push_back(Effect::TypedAction {
418 window_id: self.window_id,
419 view_id: self.view_id,
420 action: Box::new(action),
421 });
422 }
423 
424 pub fn observe<S, F>(&mut self, handle: &ModelHandle<S>, mut callback: F)
425 where
426 S: Entity,
427 F: 'static + FnMut(&mut T, ModelHandle<S>, &mut ViewContext<T>),
428 {
429 self.app
430 .observations
431 .entry(handle.id())
432 .or_default()
433 .push(Observation::FromView {
434 window_id: self.window_id,
435 view_id: self.view_id,
436 callback: Box::new(move |view, observed_id, app, window_id, view_id| {
437 let view = view.downcast_mut().expect("downcast is type safe");
438 let observed = ModelHandle::new(observed_id, &app.ref_counts);
439 let mut ctx = ViewContext::new(app, window_id, view_id);
440 callback(view, observed, &mut ctx);
441 }),
442 });
443 }
444 
445 /// Notifies the framework that this view is dirty and needs to be
446 /// re-rendered.
447 ///
448 /// "Dirtiness" only applies to this specific instance, and not the entire
449 /// view hierarchy rooted at this view. Each dirty child view also needs to
450 /// have `ctx.notify()` called on the child's `ViewContext` in order for the
451 /// child to be re-rendered.
452 pub fn notify(&mut self) {
453 self.app
454 .pending_effects
455 .push_back(Effect::ViewNotification {
456 window_id: self.window_id,
457 view_id: self.view_id,
458 });
459 }
460 
461 /// Requests permissions to send desktop notifications. The `on_completion callback` can be invoked to
462 /// propagate the outcome of the request (accepted/denied/other) back to the app.
463 ///
464 /// ## Platform-Specific
465 /// * Linux: Always calls the `on_completion_callback` with a value of [`RequestPermissionsOutcome::Accepted`].
466 pub fn request_desktop_notification_permissions<F>(&mut self, on_completion_callback: F)
467 where
468 F: 'static + Send + Sync + FnOnce(&mut T, RequestPermissionsOutcome, &mut ViewContext<T>),
469 {
470 let view_id = self.view_id;
471 let window_id = self.window_id;
472 self.app.request_desktop_notification_permissions(
473 view_id,
474 window_id,
475 on_completion_callback,
476 );
477 }
478 
479 /// Sends a desktop notification. The `on_error_callback` can be invoked to
480 /// propagate an error to the view that initiated the notification send.
481 pub fn send_desktop_notification<F>(&mut self, content: UserNotification, on_error_callback: F)
482 where
483 F: 'static + Send + Sync + FnOnce(&mut T, NotificationSendError, &mut ViewContext<T>),
484 {
485 let view_id = self.view_id;
486 let window_id = self.window_id;
487 self.app
488 .send_desktop_notification(content, view_id, window_id, on_error_callback);
489 }
490 
491 /// Schedules a future to run on the main thread, invoking a callback on the
492 /// main thread upon completion.
493 ///
494 /// The callback receives the output of the future, if any, in addition to
495 /// mutable references to the spawning view and its context, allowing for
496 /// dirtying of the view (via [`Self::notify()`]) if appropriate.
497 ///
498 /// This is private to [`ViewContext`] because we shouldn't ever need to
499 /// poll a future on the main thread. Currently, the only use is by
500 /// [`Self::spawn()`] in order to pass the results of the background task to
501 /// a callback executed on the main thread.
502 ///
503 /// TODO(vorporeal): Determine how best to eliminate this function and move
504 /// the relevant logic into `spawn()`.
505 fn spawn_local<S, F, U>(&mut self, future: S, callback: F) -> impl Future<Output = ()>
506 where
507 S: 'static + Future,
508 F: 'static + FnOnce(&mut T, S::Output, &mut ViewContext<T>) -> U,
509 U: 'static,
510 {
511 let (tx, rx) = futures::channel::oneshot::channel();
512 
513 let task_id = self.app.spawn_local(future);
514 
515 self.app.task_callbacks.insert(
516 task_id,
517 TaskCallback::ViewFromFuture {
518 window_id: self.window_id,
519 view_id: self.view_id,
520 callback: Box::new(move |view, output, app, window_id, view_id| {
521 let view = view.as_any_mut().downcast_mut().expect("this downcast should never fail, as correct typing is statically enforced via the generic parameters on spawn_local");
522 let output = *output.downcast().expect("this downcast should never fail, as correct typing is statically enforced via the generic parameters on spawn_local");
523 let result =
524 callback(view, output, &mut ViewContext::new(app, window_id, view_id));
525 let _ = tx.send(result);
526 }),
527 },
528 );
529 
530 async move {
531 if rx.await.is_err() {
532 log::error!("sender unexpectedly dropped before receiver");
533 }
534 }
535 }
536 
537 /// Schedules a future to run on a background thread, invoking a callback on
538 /// the _main_ thread upon completion.
539 ///
540 /// This function is useful in situations where a long-running process needs
541 /// to occur (e.g.: a network request), after which the view needs to be
542 /// updated based on the result.
543 ///
544 /// The callback receives the output of the future, if any, in addition to
545 /// mutable references to the spawning view and its context, allowing for
546 /// dirtying of the view (via [`Self::notify`]) if appropriate.
547 ///
548 /// The future can be aborted by calling `abort` on the returned `SpawnedFutureHandle`. Note the
549 /// future will only be aborted the _next_ time the future is polled.
550 ///
551 /// See [`Self::spawn_abortable`] for an alternative version of this function that accepts an
552 /// `on_abort` function that is called when the future is aborted.
553 pub fn spawn<S, F, U>(&mut self, future: S, callback: F) -> SpawnedFutureHandle
554 where
555 S: crate::r#async::Spawnable,
556 <S as Future>::Output: crate::r#async::SpawnableOutput,
557 F: 'static + FnOnce(&mut T, <S as Future>::Output, &mut ViewContext<T>) -> U,
558 U: 'static,
559 {
560 self.spawn_abortable::<S, _, _>(
561 future,
562 |view, output, ctx| {
563 callback(view, output, ctx);
564 },
565 |_, _| {},
566 )
567 }
568 
569 /// Schedules a future to run on a background thread, invoking the `on_resolve`
570 /// callback on the _main_ thread upon completion. If the future is aborted, the
571 /// `on_abort` function is called.
572 ///
573 /// This function is useful in situations where a long-running process needs
574 /// to occur (e.g.: a network request), after which the view needs to be
575 /// updated based on the result.
576 ///
577 /// The `on_resolve` callback receives the output of the future, if any, in addition to
578 /// mutable references to the spawning view and its context, allowing for
579 /// dirtying of the model (via [`Self::notify`]) if appropriate.
580 ///
581 /// The future can be aborted by calling `abort` on the returned `SpawnedFutureHandle`. Note, a
582 /// future is not immediately killed on `abort`--it will only be aborted once the future's
583 /// `poll` method returns.
584 ///
585 /// See [`Self::spawn`] for an alternative version of this function that doesn't
586 /// require a callback if/when the future is aborted.
587 pub fn spawn_abortable<S, F, A>(
588 &mut self,
589 future: S,
590 on_resolve: F,
591 on_abort: A,
592 ) -> SpawnedFutureHandle
593 where
594 S: crate::r#async::Spawnable,
595 <S as Future>::Output: crate::r#async::SpawnableOutput,
596 F: 'static + FnOnce(&mut T, <S as Future>::Output, &mut ViewContext<T>),
597 A: 'static + FnOnce(&mut T, &mut ViewContext<T>),
598 {
599 let (tx, rx) = futures::channel::oneshot::channel();
600 
601 let (abort_handle, abort_registration) = AbortHandle::new_pair();
602 self.app
603 .background_executor()
604 .spawn_boxed(Box::pin(async move {
605 let abortable = Abortable::new(future, abort_registration);
606 if tx.send(abortable.await).is_err() {
607 log::error!("Error sending background task result to main thread",);
608 }
609 }))
610 .detach();
611 
612 let future = self.spawn_local(rx, |view, rx_result, ctx| {
613 let output = match rx_result {
614 Ok(output) => output,
615 Err(_) => {
616 log::error!("sender unexpectedly dropped before receiver");
617 on_abort(view, ctx);
618 return;
619 }
620 };
621 
622 // Call the appropriate callback based on the output of resolving the future. If the
623 // future returned `Ok`, the future was not aborted so we can call `on_resolve`. If
624 // the future returned `Err`--the future was aborted.
625 match output {
626 Ok(output) => on_resolve(view, output, ctx),
627 Err(_) => on_abort(view, ctx),
628 }
629 });
630 
631 let future_id = self.app.register_spawned_future(future.boxed());
632 SpawnedFutureHandle::new(abort_handle, future_id)
633 }
634 
635 /// Schedules a stream to be polled on the main thread, invoking callbacks
636 /// upon the production of each item and upon the completion of the stream.
637 ///
638 /// This function is useful in situations where a view wants to process a
639 /// stream of events (say, a debounced stream of mouse movements) and update
640 /// itself in response to each.
641 ///
642 /// The callbacks receive mutable references to the spawning view and its
643 /// context, allowing for updating of the view's internal state and dirtying
644 /// it (via [`Self::notify`]) if appropriate.
645 pub fn spawn_stream_local<S, F, G>(
646 &mut self,
647 stream: S,
648 mut on_item: F,
649 mut on_done: G,
650 ) -> SpawnedLocalStream
651 where
652 S: 'static + crate::r#async::Stream,
653 S::Item: SpawnableOutput,
654 F: 'static + FnMut(&mut T, S::Item, &mut ViewContext<T>),
655 G: 'static + FnMut(&mut T, &mut ViewContext<T>),
656 {
657 let (tx, rx) = futures::channel::oneshot::channel();
658 
659 let task_id = self.app.spawn_stream_local(stream, tx);
660 self.app.task_callbacks.insert(
661 task_id,
662 TaskCallback::ViewFromStream {
663 window_id: self.window_id,
664 view_id: self.view_id,
665 on_item: Box::new(move |view, output, app, window_id, view_id| {
666 let view = view.as_any_mut().downcast_mut().expect("this downcast should never fail, as correct typing is statically enforced via the generic parameters on spawn_local");
667 let output = *output.downcast().expect("this downcast should never fail, as correct typing is statically enforced via the generic parameters on spawn_local");
668 let mut ctx = ViewContext::new(app, window_id, view_id);
669 on_item(view, output, &mut ctx);
670 }),
671 on_done: Box::new(move |view, app, window_id, view_id| {
672 let view = view.as_any_mut().downcast_mut().expect("this downcast should never fail, as correct typing is statically enforced via the generic parameters on spawn_local");
673 let mut ctx = ViewContext::new(app, window_id, view_id);
674 on_done(view, &mut ctx);
675 }),
676 },
677 );
678 
679 SpawnedLocalStream::new(
680 async move {
681 if rx.await.is_err() {
682 log::error!("sender unexpectedly dropped before receiver");
683 }
684 }
685 .boxed_local(),
686 )
687 }
688 
689 pub fn close_window(&mut self) {
690 self.app
691 .windows()
692 .close_window_async(self.window_id, TerminationMode::Cancellable);
693 }
694 
695 /// Minimizes the window which this View is in.
696 pub fn minimize_window(&mut self) {
697 if let Some(window) = self.app.windows().platform_window(self.window_id) {
698 window.minimize();
699 }
700 }
701 
702 /// Maximizes the window which this View is in, unless that window is already maximized, then it
703 /// restores it, i.e. "un-maximizes" it.
704 pub fn toggle_maximized_window(&mut self) {
705 if let Some(window) = self.app.windows().platform_window(self.window_id) {
706 window.toggle_maximized();
707 }
708 }
709 
710 pub fn toggle_fullscreen(&mut self) {
711 if let Some(window) = self.app.windows().platform_window(self.window_id) {
712 window.toggle_fullscreen();
713 }
714 }
715 
716 pub fn foreground_executor(&self) -> &Rc<Foreground> {
717 self.app.foreground_executor()
718 }
719 
720 pub fn background_executor(&self) -> &Arc<Background> {
721 self.app.background_executor()
722 }
723 
724 /// Create a window showing a modal dialog native to the platform. The modal will synchronously
725 /// block all other interactions with the app until dismissed. Each button can have a callback
726 /// attached to it in the [`crate::modals::ModalButton`] struct.
727 pub fn show_native_platform_modal(
728 &mut self,
729 view_alert: AlertDialogWithCallbacks<ViewModalCallback<T>>,
730 ) {
731 let weak_handle = self.handle();
732 let app_alert = AlertDialogWithCallbacks::for_app(
733 view_alert.message_text,
734 view_alert.info_text,
735 view_alert
736 .button_data
737 .into_iter()
738 .map(|button| {
739 let weak_handle = self.handle();
740 ModalButton::for_app(button.title, move |app| {
741 if let Some(handle) = weak_handle.upgrade(app) {
742 app.update_view(&handle, |view, ctx| {
743 (button.on_click)(view, ctx);
744 });
745 }
746 })
747 })
748 .collect(),
749 move |app| {
750 if let Some(handle) = weak_handle.upgrade(app) {
751 app.update_view(&handle, |view, ctx| {
752 (view_alert.on_disable)(view, ctx);
753 });
754 }
755 },
756 );
757 self.app.show_native_platform_modal(app_alert);
758 }
759 
760 pub fn set_cursor_shape(&mut self, cursor: Cursor) {
761 self.app
762 .set_cursor_shape(cursor, self.window_id, self.view_id)
763 }
764 
765 pub fn reset_cursor(&mut self) {
766 self.app.reset_cursor()
767 }
768 
769 /// Creates a handle which background tasks can use to spawn work for this view. Spawned tasks
770 /// are executed on the main thread in the context of the view, and results are sent back to
771 /// the background task.
772 ///
773 /// Note that the spawner *does not* keep a strong reference to the view. If the view is
774 /// dropped, any pending or future tasks will be discarded.
775 pub fn spawner(&mut self) -> ViewSpawner<T> {
776 let (task_tx, task_rx) = async_channel::unbounded();
777 let (completion_tx, _completion_rx) = futures::channel::oneshot::channel();
778 
779 let task_id = self.app.spawn_stream_local(task_rx, completion_tx);
780 self.app.task_callbacks.insert(
781 task_id,
782 TaskCallback::ViewFromStream {
783 window_id: self.window_id,
784 view_id: self.view_id,
785 on_item: Box::new(move |view, task, app, window_id, view_id| {
786 let view = view
787 .as_any_mut()
788 .downcast_mut()
789 .expect("unexpected view type");
790 let task: ViewTask<T> = *task
791 .downcast()
792 .expect("task from spawner should be ViewTask<T>");
793 let mut ctx = ViewContext::new(app, window_id, view_id);
794 task(view, &mut ctx);
795 }),
796 on_done: Box::new(move |_view, _app, _window_id, _view_id| {}),
797 },
798 );
799 
800 ViewSpawner {
801 task_sender: task_tx,
802 }
803 }
804}
805 
806impl<T> std::ops::Deref for ViewContext<'_, T> {
807 type Target = AppContext;
808 
809 fn deref(&self) -> &Self::Target {
810 self.app
811 }
812}
813 
814impl<T> std::ops::DerefMut for ViewContext<'_, T> {
815 fn deref_mut(&mut self) -> &mut Self::Target {
816 self.app
817 }
818}
819 
820/// A task which must run in the context of a view of type `V`.
821type ViewTask<V> = Box<dyn FnOnce(&mut V, &mut ViewContext<V>) + Send + 'static>;
822 
823/// A handle for spawning view tasks from background threads.
824pub struct ViewSpawner<V> {
825 task_sender: async_channel::Sender<ViewTask<V>>,
826}
827 
828impl<V> ViewSpawner<V> {
829 /// Spawn a task that will execute on the main thread, in the context of a view.
830 pub async fn spawn<R: Send + 'static>(
831 &self,
832 work: impl FnOnce(&mut V, &mut ViewContext<V>) -> R + Send + 'static,
833 ) -> Result<R, ViewDropped> {
834 let (tx, rx) = futures::channel::oneshot::channel();
835 
836 self.task_sender
837 .send(Box::new(move |me, ctx| {
838 let result = work(me, ctx);
839 // If the background task has dropped the receiver, then we don't need to send
840 // the result, and there's no one to inform regardless.
841 let _ = tx.send(result);
842 }))
843 .await
844 .map_err(|_| ViewDropped)?;
845 
846 rx.await.map_err(|_| ViewDropped)
847 }
848}
849 
850/// Error returned when a view has been dropped, and so references to it are invalid.
851#[derive(Debug, Error, PartialEq, Eq)]
852#[error("View has been dropped")]
853pub struct ViewDropped;
854 
855impl<V> ModelAsRef for ViewContext<'_, V> {
856 fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
857 self.app.model(handle)
858 }
859}
860 
861impl<V> ReadModel for ViewContext<'_, V> {
862 fn read_model<T, F, S>(&self, handle: &ModelHandle<T>, read: F) -> S
863 where
864 T: Entity,
865 F: FnOnce(&T, &AppContext) -> S,
866 {
867 self.app.read_model(handle, read)
868 }
869}
870 
871impl<V: View> UpdateModel for ViewContext<'_, V> {
872 fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
873 where
874 T: Entity,
875 F: FnOnce(&mut T, &mut ModelContext<T>) -> S,
876 {
877 self.app.update_model(handle, update)
878 }
879}
880 
881impl<V: View> ViewAsRef for ViewContext<'_, V> {
882 fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
883 self.app.view(handle)
884 }
885 
886 fn try_view<T: View>(&self, handle: &ViewHandle<T>) -> Option<&T> {
887 self.app.try_view(handle)
888 }
889}
890 
891impl<V: View> UpdateView for ViewContext<'_, V> {
892 fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
893 where
894 T: View,
895 F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
896 {
897 self.app.update_view(handle, update)
898 }
899}
900 
901impl<V: View> ReadView for ViewContext<'_, V> {
902 fn read_view<T, F, S>(&self, handle: &ViewHandle<T>, read: F) -> S
903 where
904 T: View,
905 F: FnOnce(&T, &AppContext) -> S,
906 {
907 self.app.read_view(handle, read)
908 }
909}
910 
911impl<V: View> GetSingletonModelHandle for ViewContext<'_, V> {
912 fn get_singleton_model_handle<T: crate::SingletonEntity>(&self) -> ModelHandle<T> {
913 self.app.get_singleton_model_handle()
914 }
915}
916 
917#[cfg(test)]
918#[path = "context_test.rs"]
919mod tests;
920