1use crate::application_shell::ApplicationShellService;
2use crate::application_types::WindowError;
3use crate::environment::{BUILD_SCOPE, EnvironmentFields, install_focus_scope_watch};
4use crate::file_dialogs::FileDialogService;
5use crate::frame::Runtime;
6use crate::global_shortcuts::GlobalShortcutService;
7use crate::restoration::{self, Restorable, RestorationHandle};
8#[cfg(not(target_arch = "wasm32"))]
9use crate::tasks::TokioHandle;
10use crate::tasks::{RuntimeSpawner, Task, TaskFailure, TaskHandle, TaskScope};
11use crate::window_commands::{WindowHandle, WindowOpener};
12use crate::window_state::WindowManager;
13use incular_config::RuntimeEnvironment;
14use incular_core::{RestorationKey, RestorationScope};
15use incular_platform::WindowOptions;
16use incular_widgets::{FocusScopeNode, Widget};
17use std::{
18 cell::{Cell, RefCell},
19 rc::Rc,
20};
21
22pub struct BuildContext {
27 spawner: RuntimeSpawner,
28 owner_scope: TaskScope,
29 environment: Rc<RefCell<RuntimeEnvironment>>,
30 environment_dependencies: Rc<Cell<EnvironmentFields>>,
31 window_manager: Option<WindowManager>,
32 restoration_scope: Option<RestorationScope>,
33 restoration: Option<restoration::RestorationManager>,
34}
35impl BuildContext {
36 pub(crate) fn new(
37 spawner: RuntimeSpawner,
38 owner_scope: TaskScope,
39 environment: Rc<RefCell<RuntimeEnvironment>>,
40 environment_dependencies: Rc<Cell<EnvironmentFields>>,
41 window_manager: Option<WindowManager>,
42 restoration_scope: Option<RestorationScope>,
43 ) -> Self {
44 Self {
45 spawner,
46 owner_scope,
47 environment,
48 environment_dependencies,
49 restoration_scope,
50 restoration: window_manager
51 .as_ref()
52 .and_then(|manager| manager.restoration.clone()),
53 window_manager,
54 }
55 }
56 pub fn spawn<F, T>(&self, future: F) -> Task<T>
59 where
60 F: std::future::Future<Output = T> + Send + 'static,
61 T: Send + 'static,
62 {
63 self.spawner.spawn_in(&self.owner_scope, future)
64 }
65 pub fn spawn_in<F, T>(&self, scope: &TaskScope, future: F) -> Task<T>
66 where
67 F: std::future::Future<Output = T> + Send + 'static,
68 T: Send + 'static,
69 {
70 self.spawner.spawn_in(scope, future)
71 }
72 pub fn spawn_into<F, T>(
75 &self,
76 future: F,
77 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
78 ) -> TaskHandle
79 where
80 F: std::future::Future<Output = T> + Send + 'static,
81 T: Send + 'static,
82 {
83 self.spawner
84 .spawn_into_in(&self.owner_scope, future, complete)
85 }
86 pub fn dispatch(&self, callback: impl FnOnce(&mut Runtime) + Send + 'static) {
89 self.spawner.dispatcher().dispatch(callback);
90 }
91 pub fn spawn_blocking<T>(
92 &self,
93 work: impl FnOnce() -> T + Send + 'static,
94 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
95 ) -> TaskHandle
96 where
97 T: Send + 'static,
98 {
99 self.spawner
100 .spawn_blocking_in(&self.owner_scope, work, complete)
101 }
102 #[must_use]
103 pub fn task_scope(&self) -> TaskScope {
104 self.owner_scope.child()
105 }
106
107 pub fn watch_focus_scope(&self, scope: &FocusScopeNode) {
114 let target = BUILD_SCOPE.with(|current| {
115 let current = current.borrow();
116 current.as_ref().map(|build| {
117 (
118 build.element,
119 build.queue.clone(),
120 build.initial_dependencies.clone(),
121 )
122 })
123 });
124 let Some((element, queue, initial_dependencies)) = target else {
125 return;
126 };
127 if let Some(element) = element {
128 if let Some(queue) = queue.upgrade() {
129 install_focus_scope_watch(&queue, element, scope);
130 }
131 } else if let Some(initial_dependencies) = initial_dependencies {
132 initial_dependencies
133 .borrow_mut()
134 .focus_scopes
135 .push(scope.clone());
136 }
137 }
138
139 #[must_use]
143 pub fn restoration_scope(&self) -> Option<RestorationScope> {
144 self.restoration_scope.clone()
145 }
146
147 #[must_use]
151 pub fn restoration(&self) -> Option<RestorationHandle> {
152 self.restoration.clone().map(RestorationHandle::new)
153 }
154
155 #[must_use]
159 pub fn restorable<T>(&self, key: RestorationKey, default: T) -> Option<Restorable<T>>
160 where
161 T: Clone + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
162 {
163 self.restoration_scope
164 .clone()
165 .map(|scope| Restorable::from_scope(scope, key, default))
166 }
167
168 #[must_use]
172 pub fn restored_signal<T>(&self, key: RestorationKey, default: T) -> Option<Restorable<T>>
173 where
174 T: Clone + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
175 {
176 self.restorable(key, default)
177 }
178
179 pub fn open_window(
184 &self,
185 options: WindowOptions,
186 root: Widget,
187 ) -> Result<WindowHandle, WindowError> {
188 self.window_manager
189 .as_ref()
190 .ok_or(WindowError::ApplicationStopped)?
191 .open_window(options, root)
192 }
193
194 #[must_use]
197 pub fn window_opener(&self) -> Option<WindowOpener> {
198 self.window_manager
199 .as_ref()
200 .cloned()
201 .map(|manager| WindowOpener { manager })
202 }
203
204 #[must_use]
207 pub fn window_handle(&self) -> Option<WindowHandle> {
208 let window_id = self.spawner.window_id()?;
209 self.window_manager.as_ref()?.handle(window_id)
210 }
211
212 #[must_use]
215 pub fn file_dialogs(&self) -> Option<FileDialogService> {
216 self.window_handle().map(|window| window.file_dialogs())
217 }
218
219 #[must_use]
223 pub fn global_shortcuts(&self) -> Option<GlobalShortcutService> {
224 let manager = self.window_manager.as_ref()?;
225 Some(GlobalShortcutService::new(
226 manager.global_shortcut_bridge.clone(),
227 manager.application_capabilities.clone(),
228 ))
229 }
230
231 #[must_use]
234 pub fn application_shell(&self) -> Option<ApplicationShellService> {
235 self.window_manager
236 .as_ref()
237 .map(|manager| manager.application_shell.clone())
238 }
239
240 #[must_use]
244 #[cfg(not(target_arch = "wasm32"))]
245 pub fn tokio_handle(&self) -> TokioHandle {
246 self.spawner.tokio_handle()
247 }
248 #[must_use]
251 pub fn environment(&self) -> RuntimeEnvironment {
252 self.environment_dependencies
253 .set(self.environment_dependencies.get() | EnvironmentFields::all());
254 self.environment.borrow().clone()
255 }
256 #[must_use]
257 pub fn viewport(&self) -> incular_core::Size {
258 self.record_environment(EnvironmentFields::VIEWPORT);
259 self.environment.borrow().viewport
260 }
261 #[must_use]
262 pub fn scale_factor(&self) -> f64 {
263 self.record_environment(EnvironmentFields::SCALE);
264 self.environment.borrow().scale_factor
265 }
266 #[must_use]
267 pub fn text_scale(&self) -> f32 {
268 self.record_environment(EnvironmentFields::TEXT_SCALE);
269 self.environment.borrow().text_scale
270 }
271 #[must_use]
272 pub fn safe_insets(&self) -> incular_config::EdgeInsets {
273 self.record_environment(EnvironmentFields::SAFE_INSETS);
274 self.environment.borrow().safe_insets
275 }
276 #[must_use]
277 pub fn view_insets(&self) -> incular_config::EdgeInsets {
278 self.record_environment(EnvironmentFields::VIEW_INSETS);
279 self.environment.borrow().view_insets
280 }
281 #[must_use]
285 pub fn view_padding(&self) -> incular_config::EdgeInsets {
286 self.record_environment(EnvironmentFields::VIEW_PADDING);
287 self.environment.borrow().view_padding
288 }
289 #[must_use]
295 pub fn safe_area(&self, safe_area: incular_widgets::SafeArea) -> Widget {
296 let safe = self.safe_insets();
297 if safe_area.maintains_bottom_view_padding() {
298 safe_area.resolve_with_padding(safe, self.view_padding())
299 } else {
300 safe_area.resolve(safe)
301 }
302 }
303 #[must_use]
304 pub fn brightness(&self) -> incular_config::Brightness {
305 self.record_environment(EnvironmentFields::BRIGHTNESS);
306 self.environment.borrow().brightness
307 }
308 #[must_use]
309 pub fn locales(&self) -> Vec<incular_config::Locale> {
310 self.record_environment(EnvironmentFields::LOCALE);
311 self.environment.borrow().locales.clone()
312 }
313 #[must_use]
316 pub fn resolve_locale(
317 &self,
318 supported: &[incular_config::Locale],
319 ) -> Option<incular_config::Locale> {
320 self.record_environment(EnvironmentFields::LOCALE);
321 self.environment.borrow().resolve_locale(supported)
322 }
323 #[must_use]
324 pub fn text_direction(&self) -> incular_config::TextDirection {
325 self.record_environment(EnvironmentFields::DIRECTION);
326 self.environment.borrow().text_direction
327 }
328 #[must_use]
329 pub fn window_focused(&self) -> bool {
330 self.record_environment(EnvironmentFields::WINDOW_FOCUS);
331 self.environment.borrow().window_focused
332 }
333 #[must_use]
334 pub fn reduced_motion(&self) -> bool {
335 self.record_environment(EnvironmentFields::REDUCED_MOTION);
336 self.environment.borrow().reduced_motion
337 }
338 #[must_use]
339 pub fn high_contrast(&self) -> bool {
340 self.record_environment(EnvironmentFields::HIGH_CONTRAST);
341 self.environment.borrow().high_contrast
342 }
343 #[must_use]
344 pub fn input_capabilities(&self) -> incular_config::InputCapabilities {
345 self.record_environment(EnvironmentFields::INPUT);
346 self.environment.borrow().input
347 }
348 #[must_use]
349 pub fn window_occluded(&self) -> bool {
350 self.record_environment(EnvironmentFields::WINDOW_OCCLUSION);
351 self.environment.borrow().window_occluded
352 }
353 #[must_use]
354 pub fn system_gesture_insets(&self) -> incular_config::EdgeInsets {
355 self.record_environment(EnvironmentFields::SYSTEM_GESTURE_INSETS);
356 self.environment.borrow().system_gesture_insets
357 }
358 #[must_use]
360 pub fn display_features(&self) -> Vec<incular_config::DisplayFeature> {
361 self.record_environment(EnvironmentFields::VIEWPORT);
362 self.environment.borrow().display_features.clone()
363 }
364 #[must_use]
365 pub fn always_use_24_hour_format(&self) -> bool {
366 self.record_environment(EnvironmentFields::ACCESSIBILITY);
367 self.environment.borrow().always_use_24_hour_format
368 }
369 #[must_use]
370 pub fn accessible_navigation(&self) -> bool {
371 self.record_environment(EnvironmentFields::ACCESSIBILITY);
372 self.environment.borrow().accessible_navigation
373 }
374 #[must_use]
375 pub fn invert_colors(&self) -> bool {
376 self.record_environment(EnvironmentFields::ACCESSIBILITY);
377 self.environment.borrow().invert_colors
378 }
379 #[must_use]
380 pub fn on_off_switch_labels(&self) -> bool {
381 self.record_environment(EnvironmentFields::ACCESSIBILITY);
382 self.environment.borrow().on_off_switch_labels
383 }
384 #[must_use]
385 pub fn bold_text(&self) -> bool {
386 self.record_environment(EnvironmentFields::ACCESSIBILITY);
387 self.environment.borrow().bold_text
388 }
389 #[must_use]
390 pub fn supports_announce(&self) -> bool {
391 self.record_environment(EnvironmentFields::ACCESSIBILITY);
392 self.environment.borrow().supports_announce
393 }
394 #[must_use]
395 pub fn navigation_mode(&self) -> incular_config::NavigationMode {
396 self.record_environment(EnvironmentFields::ACCESSIBILITY);
397 self.environment.borrow().navigation_mode
398 }
399 #[must_use]
400 pub fn supports_showing_system_context_menu(&self) -> bool {
401 self.record_environment(EnvironmentFields::ACCESSIBILITY);
402 self.environment
403 .borrow()
404 .supports_showing_system_context_menu
405 }
406 #[must_use]
407 pub fn line_height_scale_factor_override(&self) -> Option<f32> {
408 self.record_environment(EnvironmentFields::ACCESSIBILITY);
409 self.environment.borrow().line_height_scale_factor_override
410 }
411 #[must_use]
412 pub fn letter_spacing_override(&self) -> Option<f32> {
413 self.record_environment(EnvironmentFields::ACCESSIBILITY);
414 self.environment.borrow().letter_spacing_override
415 }
416 #[must_use]
417 pub fn word_spacing_override(&self) -> Option<f32> {
418 self.record_environment(EnvironmentFields::ACCESSIBILITY);
419 self.environment.borrow().word_spacing_override
420 }
421 #[must_use]
422 pub fn paragraph_spacing_override(&self) -> Option<f32> {
423 self.record_environment(EnvironmentFields::ACCESSIBILITY);
424 self.environment.borrow().paragraph_spacing_override
425 }
426 #[must_use]
429 pub fn disable_animations(&self) -> bool {
430 self.reduced_motion()
431 }
432 fn record_environment(&self, field: EnvironmentFields) {
433 self.environment_dependencies
434 .set(self.environment_dependencies.get() | field);
435 }
436}