Skip to main content

tilo_app/framework/
opening.rs

1//! A window shown before its record ([`ResourceApp::opening`](super::ResourceApp::opening)).
2//!
3//! The workspace's record opens off the frame
4//! ([`RecordStore::begin_opening`](ui_core::record::RecordStore::begin_opening)); meanwhile the
5//! window's component is an [`OpeningRoot`] over what the app shows before it. Once the record
6//! is decoded, the window, about to sync, asks the root for the component to show in its
7//! place ([`AppWidget::take_component_replacement`]): the root takes the record in and builds
8//! the component any launch builds over it, whose view takes over what was shown ([`Opened`]),
9//! so nothing the person saw or did is built again or lost.
10//!
11//! Until then nothing of the root reaches the window's door: what was shown hands its edits to
12//! the door once the view has taken it over, in the order they were made.
13
14use std::cell::RefCell;
15
16use ui_core::application::ComponentReplacement;
17use ui_core::launch::LaunchError;
18use ui_core::record::RecordOpening;
19use ui_core::widget::{AppKind, AppWidget, IconDescriptor, Widget};
20
21use super::app::OpeningView;
22
23/// What a window showed before its record ([`ResourceApp::opening`](super::ResourceApp::opening)),
24/// lent to the view built once the record is had
25/// ([`RecordEnv::take_lent`](super::RecordEnv::take_lent)).
26pub struct Opened(RefCell<Option<Box<dyn OpeningView>>>);
27
28impl Opened {
29    pub(crate) fn new(shown: Box<dyn OpeningView>) -> Self {
30        Self(RefCell::new(Some(shown)))
31    }
32
33    /// What was shown, as the type the app built it as; once.
34    #[must_use]
35    pub fn take<W: 'static>(&self) -> Option<Box<W>> {
36        let shown = self.0.borrow_mut().take()?;
37        shown.into_lent().downcast::<W>().ok()
38    }
39}
40
41/// Builds the component of the record, which takes over what was shown.
42pub(crate) type Build =
43    Box<dyn FnOnce(Box<dyn OpeningView>) -> Result<ComponentReplacement, LaunchError>>;
44
45/// The root of a window's component while its record is read off the frame.
46pub(crate) struct OpeningRoot {
47    shown: Box<dyn OpeningView>,
48    record: RecordOpening,
49    build: Option<Build>,
50    title: &'static str,
51    icon: IconDescriptor,
52}
53
54impl OpeningRoot {
55    pub(crate) fn new(
56        shown: Box<dyn OpeningView>,
57        record: RecordOpening,
58        build: Build,
59        (title, icon): (&'static str, IconDescriptor),
60    ) -> Self {
61        // The window wakes when the record is decoded, to take it in.
62        record.wake_when_decoded(ui_core::wake::WakeHandle::new());
63        Self {
64            shown,
65            record,
66            build: Some(build),
67            title,
68            icon,
69        }
70    }
71}
72
73impl ui_core::widget::Decorator for OpeningRoot {
74    type Inner = dyn Widget;
75
76    fn inner(&self) -> &(dyn Widget + 'static) {
77        &*self.shown
78    }
79
80    fn inner_mut(&mut self) -> &mut (dyn Widget + 'static) {
81        &mut *self.shown
82    }
83
84    // What is shown keeps its controls, undo and presence to itself until the record's view
85    // takes it over: its edits wait for the door that view hands them to.
86    fn provide<'a>(&'a self, _request: &mut ui_core::widget::capability::Request<'a, '_>) {}
87
88    fn provide_mut<'a>(
89        &'a mut self,
90        _request: &mut ui_core::widget::capability::RequestMut<'a, '_>,
91    ) {
92    }
93}
94
95impl AppWidget for OpeningRoot {
96    fn app_kind(&self) -> AppKind {
97        AppKind::Editor
98    }
99
100    fn app_title(&self) -> &str {
101        self.title
102    }
103
104    fn app_icon(&self) -> IconDescriptor {
105        self.icon.clone()
106    }
107
108    /// Once the record is decoded: take it in and build its component over what was shown. A
109    /// record that does not decode is built too: opening it shows why.
110    fn take_component_replacement(&mut self) -> Option<ComponentReplacement> {
111        self.build.as_ref()?;
112        match self.record.take_in(false) {
113            Ok(false) => return None,
114            Ok(true) => {}
115            Err(error) => tracing::warn!(%error, "the workspace's record did not open"),
116        }
117        let build = self.build.take()?;
118        let shown = std::mem::replace(&mut self.shown, Box::new(ui_core::widget::Absent::new()));
119        match build(shown) {
120            Ok(replacement) => Some(replacement),
121            Err(error) => {
122                tracing::error!(%error, "the workspace's record opened, and its window did not");
123                None
124            }
125        }
126    }
127}