inset_embedder/platform.rs
1//! Host capabilities the framework calls at runtime.
2//!
3//! Unlike Dart's isolate-global `PlatformDispatcher`, this object is supplied
4//! by the embedder and held by `App`. Incoming host events travel through
5//! `EmbedderClient`, so this interface contains requests and state only.
6
7use std::any::Any;
8use std::rc::Rc;
9use std::sync::Arc;
10use std::time::Duration;
11
12use web_time::Instant;
13
14use crate::clipboard::Clipboard;
15use crate::file_dialogs::FileDialogs;
16use crate::fonts::FontSource;
17use crate::geometry::{Color, Offset, Rect};
18use crate::menus::{DockMenu, MenuBar, PopupMenus, TrayIcons};
19use crate::notifications::Notifications;
20use crate::opener::Opener;
21use crate::restoration::{RestorationMap, RestorationUpdate};
22use crate::system_context_menu::SystemContextMenuItem;
23use crate::{View, ViewId};
24
25pub type PlatformRef = Rc<dyn Platform>;
26
27pub type ViewRef = Rc<dyn View>;
28
29/// Specifies a description of the application that is pertinent to the
30/// embedder's application switcher (also known as "recent tasks") user
31/// interface.
32///
33/// Flutter counterpart: `ApplicationSwitcherDescription` (`services/system_chrome.dart`).
34/// It lives here because [`Platform`] names it.
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
36pub struct ApplicationSwitcherDescription {
37 /// A label and description of the current state of the application.
38 pub label: Option<String>,
39 /// The application's primary color, as a 32-bit ARGB value.
40 ///
41 /// This may influence the color that the operating system uses to represent
42 /// the application.
43 pub primary_color: Option<u32>,
44}
45
46/// Specifies a system overlay style for a portion of the app.
47///
48/// Flutter counterpart: `SystemUiOverlayStyle` (`services/system_chrome.dart`).
49/// It lives here because [`Platform`] names it.
50#[derive(Clone, Copy, Debug, Default, PartialEq)]
51pub struct SystemUiOverlayStyle {
52 /// The color of the system bottom navigation bar.
53 ///
54 /// Only honored in Android versions O and greater.
55 pub system_navigation_bar_color: Option<Color>,
56
57 /// The color of the divider between the system's bottom navigation bar and the app's
58 /// content.
59 ///
60 /// Only honored in Android versions P and greater.
61 pub system_navigation_bar_divider_color: Option<Color>,
62
63 /// The brightness of the system navigation bar icons.
64 ///
65 /// Only honored in Android versions O and greater.
66 /// When set to [`Brightness::Light`], the system navigation bar icons are light.
67 /// When set to [`Brightness::Dark`], the system navigation bar icons are dark.
68 pub system_navigation_bar_icon_brightness: Option<Brightness>,
69
70 /// Overrides the contrast enforcement when setting a transparent navigation bar.
71 ///
72 /// When setting a transparent navigation bar in SDK 29+, or Android 10 and up, a
73 /// translucent body scrim may be applied behind the button navigation bar to ensure
74 /// contrast with buttons and the background of the application.
75 ///
76 /// SDK 28-, or Android P and lower, will not apply this body scrim.
77 ///
78 /// Setting this to false overrides the default body scrim.
79 pub system_navigation_bar_contrast_enforced: Option<bool>,
80
81 /// The color of top status bar.
82 ///
83 /// Only honored in Android version M and greater.
84 pub status_bar_color: Option<Color>,
85
86 /// The brightness of top status bar.
87 ///
88 /// Only honored in iOS.
89 pub status_bar_brightness: Option<Brightness>,
90
91 /// The brightness of the top status bar icons.
92 ///
93 /// Only honored in Android version M and greater.
94 pub status_bar_icon_brightness: Option<Brightness>,
95
96 /// Overrides the contrast enforcement when setting a transparent status bar.
97 ///
98 /// When setting a transparent status bar in SDK 29+, or Android 10 and up, a translucent
99 /// body scrim may be applied to ensure contrast with icons and the background of the
100 /// application.
101 ///
102 /// SDK 28-, or Android P and lower, will not apply this body scrim.
103 ///
104 /// Setting this to false overrides the default body scrim.
105 pub system_status_bar_contrast_enforced: Option<bool>,
106}
107
108impl SystemUiOverlayStyle {
109 /// System overlays should be drawn with a light color. Intended for applications with a
110 /// dark background.
111 pub const LIGHT: SystemUiOverlayStyle = SystemUiOverlayStyle {
112 system_navigation_bar_color: Some(Color::new(0xFF000000)),
113 system_navigation_bar_icon_brightness: Some(Brightness::Light),
114 status_bar_icon_brightness: Some(Brightness::Light),
115 status_bar_brightness: Some(Brightness::Dark),
116 system_navigation_bar_divider_color: None,
117 system_navigation_bar_contrast_enforced: None,
118 status_bar_color: None,
119 system_status_bar_contrast_enforced: None,
120 };
121
122 /// System overlays should be drawn with a dark color. Intended for applications with a
123 /// light background.
124 pub const DARK: SystemUiOverlayStyle = SystemUiOverlayStyle {
125 system_navigation_bar_color: Some(Color::new(0xFF000000)),
126 system_navigation_bar_icon_brightness: Some(Brightness::Light),
127 status_bar_icon_brightness: Some(Brightness::Dark),
128 status_bar_brightness: Some(Brightness::Light),
129 system_navigation_bar_divider_color: None,
130 system_navigation_bar_contrast_enforced: None,
131 status_bar_color: None,
132 system_status_bar_contrast_enforced: None,
133 };
134
135 /// Creates a new [`SystemUiOverlayStyle`]; Dart's named arguments are the setters.
136 pub const fn new() -> SystemUiOverlayStyle {
137 SystemUiOverlayStyle {
138 system_navigation_bar_color: None,
139 system_navigation_bar_divider_color: None,
140 system_navigation_bar_icon_brightness: None,
141 system_navigation_bar_contrast_enforced: None,
142 status_bar_color: None,
143 status_bar_brightness: None,
144 status_bar_icon_brightness: None,
145 system_status_bar_contrast_enforced: None,
146 }
147 }
148
149 /// Dart `SystemUiOverlayStyle(systemNavigationBarColor:)`.
150 pub fn system_navigation_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
151 self.system_navigation_bar_color = Some(color);
152 self
153 }
154
155 /// Dart `SystemUiOverlayStyle(systemNavigationBarDividerColor:)`.
156 pub fn system_navigation_bar_divider_color(mut self, color: Color) -> SystemUiOverlayStyle {
157 self.system_navigation_bar_divider_color = Some(color);
158 self
159 }
160
161 /// Dart `SystemUiOverlayStyle(systemNavigationBarIconBrightness:)`.
162 pub fn system_navigation_bar_icon_brightness(
163 mut self,
164 brightness: Brightness,
165 ) -> SystemUiOverlayStyle {
166 self.system_navigation_bar_icon_brightness = Some(brightness);
167 self
168 }
169
170 /// Dart `SystemUiOverlayStyle(systemNavigationBarContrastEnforced:)`.
171 pub fn system_navigation_bar_contrast_enforced(
172 mut self,
173 enforced: bool,
174 ) -> SystemUiOverlayStyle {
175 self.system_navigation_bar_contrast_enforced = Some(enforced);
176 self
177 }
178
179 /// Dart `SystemUiOverlayStyle(statusBarColor:)`.
180 pub fn status_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
181 self.status_bar_color = Some(color);
182 self
183 }
184
185 /// Dart `SystemUiOverlayStyle(statusBarBrightness:)`.
186 pub fn status_bar_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
187 self.status_bar_brightness = Some(brightness);
188 self
189 }
190
191 /// Dart `SystemUiOverlayStyle(statusBarIconBrightness:)`.
192 pub fn status_bar_icon_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
193 self.status_bar_icon_brightness = Some(brightness);
194 self
195 }
196
197 /// Dart `SystemUiOverlayStyle(systemStatusBarContrastEnforced:)`.
198 pub fn system_status_bar_contrast_enforced(mut self, enforced: bool) -> SystemUiOverlayStyle {
199 self.system_status_bar_contrast_enforced = Some(enforced);
200 self
201 }
202
203 /// Creates a copy of this theme with the given fields replaced with new values.
204 pub fn copy_with(&self) -> SystemUiOverlayStyle {
205 *self
206 }
207}
208
209/// The kind of haptic feedback to play.
210///
211/// Flutter counterpart: the `HapticFeedbackType.*` argument Dart sends with the
212/// `HapticFeedback.vibrate` message on `SystemChannels.platform`. It lives here
213/// because [`Platform`] names it.
214#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
215pub enum HapticFeedbackType {
216 /// Vibration for a short duration, the argument-less `HapticFeedback.vibrate`.
217 Vibrate,
218 /// A collision impact with a light mass.
219 LightImpact,
220 /// A collision impact with a medium mass.
221 MediumImpact,
222 /// A collision impact with a heavy mass.
223 HeavyImpact,
224 /// A selection changing through discrete values.
225 SelectionClick,
226 /// A task or action completed successfully.
227 SuccessNotification,
228 /// A task or action produced a warning.
229 WarningNotification,
230 /// A task or action failed.
231 ErrorNotification,
232}
233
234/// Describes the contrast of a theme or color palette.
235///
236/// Flutter counterpart: `Brightness` (`dart:ui` `window.dart`).
237#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
238pub enum Brightness {
239 /// The color is dark and will require a light text color to achieve readable
240 /// contrast.
241 ///
242 /// For example, the color might be dark grey, requiring white text.
243 Dark,
244
245 /// The color is light and will require a dark text color to achieve readable
246 /// contrast.
247 ///
248 /// For example, the color might be bright white, requiring black text.
249 Light,
250}
251
252/// States that an application can be in once it is running.
253///
254/// States not supported on a platform are synthesized so the state machine stays the same
255/// everywhere. For example, [`Hidden`](AppLifecycleState::Hidden) is synthesized on mobile
256/// before [`Paused`](AppLifecycleState::Paused) when coming from
257/// [`Inactive`](AppLifecycleState::Inactive), and before [`Inactive`](AppLifecycleState::Inactive)
258/// when coming from [`Paused`](AppLifecycleState::Paused).
259///
260/// The values below are listed in the expected state machine transition order. The initial
261/// state is [`Detached`](AppLifecycleState::Detached).
262///
263/// Flutter counterpart: `AppLifecycleState` (`dart:ui` `platform_dispatcher.dart`).
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
265pub enum AppLifecycleState {
266 /// The application is still hosted by a Flutter engine but is detached from any host
267 /// views.
268 ///
269 /// The application defaults to this state before it initializes, and can be in this
270 /// state (applicable on Android, iOS, and web) after all views have been detached.
271 ///
272 /// When the application is in this state, the engine is running without a view.
273 ///
274 /// This state is only entered on iOS, Android, and web, although on all platforms it is
275 /// the default state before the application begins running.
276 Detached,
277
278 /// On all platforms, this state indicates that the application is in the default
279 /// running mode for a running application that has input focus and is visible.
280 ///
281 /// On Android, this state corresponds to the Flutter host view having focus while in
282 /// Android's "resumed" state. It is possible for the Flutter app to be in the
283 /// [`Inactive`](AppLifecycleState::Inactive) state while still being in Android's
284 /// "onResume" state if the app has lost focus but hasn't had `Activity.onPause` called
285 /// on it.
286 ///
287 /// On iOS and macOS, this corresponds to the app running in the foreground active
288 /// state.
289 Resumed,
290
291 /// At least one view of the application is visible, but none have input focus. The
292 /// application is otherwise running normally.
293 ///
294 /// On non-web desktop platforms, this corresponds to an application that is not in the
295 /// foreground, but still has visible windows.
296 ///
297 /// On the web, this corresponds to an application that is running in a window or tab
298 /// that does not have input focus.
299 ///
300 /// On iOS and macOS, this state corresponds to the Flutter host view running in the
301 /// foreground inactive state. Apps transition to this state when in a phone call, when
302 /// responding to a TouchID request, when entering the app switcher or the control
303 /// center, or when the UIViewController hosting the Flutter app is transitioning.
304 ///
305 /// On Android, this corresponds to the Flutter host view running in Android's paused
306 /// state (i.e. `Activity.onPause` has been called), or in Android's "resumed" state
307 /// (i.e. `Activity.onResume` has been called) but does not have window focus.
308 ///
309 /// On Android and iOS, apps in this state should assume that they may be
310 /// [`Hidden`](AppLifecycleState::Hidden) and [`Paused`](AppLifecycleState::Paused) at any
311 /// time.
312 Inactive,
313
314 /// All views of an application are hidden, either because the application is about to
315 /// be paused (on iOS and Android), or because it has been minimized or placed on a
316 /// desktop that is no longer visible (on non-web desktop), or is running in a window or
317 /// tab that is no longer visible (on the web).
318 ///
319 /// On iOS and Android, in order to keep the state machine the same on all platforms, a
320 /// transition to this state is synthesized before the [`Paused`](AppLifecycleState::Paused)
321 /// state is entered when coming from [`Inactive`](AppLifecycleState::Inactive), and
322 /// before the [`Inactive`](AppLifecycleState::Inactive) state is entered when coming
323 /// from [`Paused`](AppLifecycleState::Paused). This allows cross-platform implementations
324 /// that want to know when an app is conceptually "hidden" to only write one handler.
325 Hidden,
326
327 /// The application is not currently visible to the user, and not responding to user
328 /// input.
329 ///
330 /// When the application is in this state, the engine will not call
331 /// `PlatformDispatcher.onBeginFrame` and `PlatformDispatcher.onDrawFrame`.
332 ///
333 /// This state is only entered on iOS and Android.
334 Paused,
335}
336
337/// The possible responses to a request to exit the application.
338///
339/// The request is typically responded to by creating an `AppLifecycleListener` and supplying
340/// an `on_exit_requested` callback, or by overriding
341/// `WidgetsBindingObserver::did_request_app_exit`.
342///
343/// Flutter counterpart: `AppExitResponse` (`dart:ui` `platform_dispatcher.dart`).
344#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
345pub enum AppExitResponse {
346 /// Exiting the application can proceed.
347 Exit,
348
349 /// Cancel the exit: do not exit the application.
350 Cancel,
351}
352
353/// The answer to one request to exit, which the host made through
354/// [`EmbedderClient::request_app_exit`](crate::EmbedderClient::request_app_exit): Flutter's reply
355/// to `System.requestAppExit`. It is answered once; dropped unanswered, it answers
356/// [`AppExitResponse::Exit`], since an application that lets go of the question has nothing to
357/// keep.
358pub struct AppExitReply(Option<Box<dyn FnOnce(AppExitResponse)>>);
359
360impl AppExitReply {
361 /// A reply that hands its answer to `send`. The answer is given inside a call into the
362 /// client, so `send` may only post it to the host's own loop, never call the client.
363 pub fn new(send: impl FnOnce(AppExitResponse) + 'static) -> AppExitReply {
364 AppExitReply(Some(Box::new(send)))
365 }
366
367 pub fn answer(mut self, response: AppExitResponse) {
368 self.send(response);
369 }
370
371 fn send(&mut self, response: AppExitResponse) {
372 if let Some(send) = self.0.take() {
373 send(response);
374 }
375 }
376}
377
378impl Drop for AppExitReply {
379 fn drop(&mut self) {
380 self.send(AppExitResponse::Exit);
381 }
382}
383
384impl std::fmt::Debug for AppExitReply {
385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386 f.debug_struct("AppExitReply")
387 .field("answered", &self.0.is_none())
388 .finish()
389 }
390}
391
392/// The type of application exit to perform when calling `ServicesBinding::exit_application`.
393///
394/// Flutter counterpart: `AppExitType` (`dart:ui` `platform_dispatcher.dart`).
395#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
396pub enum AppExitType {
397 /// Requests that the application start an orderly exit, sending a request back to the
398 /// framework through the `WidgetsBinding`. If that responds with [`AppExitResponse::Exit`],
399 /// then proceed with the same steps as a [`Required`](AppExitType::Required) exit. If that
400 /// responds with [`AppExitResponse::Cancel`], then the exit request is canceled and the
401 /// application continues executing normally.
402 Cancelable,
403
404 /// A non-cancelable orderly exit request. The engine will shut down the engine and call
405 /// the native UI toolkit's exit API.
406 ///
407 /// If you need an even faster and more dangerous exit, then call
408 /// [`std::process::exit`] directly, and even the native toolkit's exit API won't be
409 /// called. This is quite dangerous, though, since it's possible that the engine will
410 /// crash because it hasn't been properly shut down, causing the app to crash on exit.
411 Required,
412}
413
414/// The platform that user interaction should adapt to target.
415///
416/// Flutter counterpart: `TargetPlatform` (`foundation/platform.dart`). Flutter
417/// reads the current one from a library global that consults `dart:io`. Here it
418/// is a plain value type, and the App-owned [`Platform`] reports which one it
419/// is, so two Apps in one process can differ.
420#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
421pub enum TargetPlatform {
422 /// Android: <https://www.android.com/>
423 Android,
424 /// Fuchsia: <https://fuchsia.dev/fuchsia-src/concepts>
425 Fuchsia,
426 /// iOS: <https://www.apple.com/ios/>
427 IOS,
428 /// Linux: <https://www.linux.org>
429 Linux,
430 /// macOS: <https://www.apple.com/macos>
431 MacOS,
432 /// Windows: <https://www.windows.com>
433 Windows,
434}
435
436/// The OS this build targets, as Flutter names it; `None` for one Flutter does not name, such as
437/// a web page's or a WebAssembly component's.
438const THIS_OS: Option<TargetPlatform> = if cfg!(target_os = "android") {
439 Some(TargetPlatform::Android)
440} else if cfg!(target_os = "fuchsia") {
441 Some(TargetPlatform::Fuchsia)
442} else if cfg!(target_os = "ios") {
443 Some(TargetPlatform::IOS)
444} else if cfg!(target_os = "linux") {
445 Some(TargetPlatform::Linux)
446} else if cfg!(target_os = "macos") {
447 Some(TargetPlatform::MacOS)
448} else if cfg!(target_os = "windows") {
449 Some(TargetPlatform::Windows)
450} else {
451 None
452};
453
454/// The host's threads as any thread reaches them: the two things the framework asks of them.
455/// gpui's `PlatformDispatcher`. Flutter's embedder API has the first as `post_task_callback`,
456/// with its target time, and keeps the second — the engine's own worker threads — to itself.
457pub trait Dispatcher: Send + Sync {
458 /// Sets when the host should next wake the application for a timer.
459 ///
460 /// The host keeps one such time. When it arrives, the host calls
461 /// [`EmbedderClient::wake`](crate::EmbedderClient::wake) on the main thread.
462 ///
463 /// `None` means no timer is waiting, so the host should not wake for one.
464 ///
465 /// Each call replaces the previous one. The new time can be later than the old one, not
466 /// only earlier. Call this only from the thread the application runs on.
467 fn wake_at(&self, timer_wakeup: Option<Instant>);
468
469 /// Asks the host to wake the application as soon as it can, because a task became ready
470 /// to run.
471 ///
472 /// The next wake clears this request. It does not change the timer wakeup set by
473 /// [`wake_at`](Self::wake_at), which is still waiting for its own time. Call this from
474 /// any thread.
475 fn wake_now(&self);
476
477 /// Runs `work` off the main thread, on whatever the host has for that, such as a system
478 /// queue or a worker thread. The framework's `run_in_background` sends its work here.
479 fn dispatch(&self, work: Box<dyn FnOnce() + Send>);
480}
481
482/// A dispatcher for tests, which pump the application by hand: it never wakes the application,
483/// and work runs at once on the calling thread, so its result is there at the next checkpoint.
484pub struct InertDispatcher;
485
486impl Dispatcher for InertDispatcher {
487 fn wake_at(&self, _timer_wakeup: Option<Instant>) {}
488
489 fn wake_now(&self) {}
490
491 fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
492 work();
493 }
494}
495
496/// The long-lived host object held by the application.
497///
498/// Implementations must queue requests and return. They must not synchronously
499/// re-enter the [`EmbedderClient`](crate::EmbedderClient) while `App` is active.
500///
501/// A host may offer more than this interface — a picture from a buffer only its system
502/// has, a handle to its renderer. An app that knows its host reaches that through
503/// [`downcast_ref`](dyn Platform::downcast_ref); the interface itself names nothing of any
504/// one system.
505pub trait Platform: Any {
506 /// Which host this is, for behaviour that follows platform convention
507 /// (Flutter `defaultTargetPlatform`).
508 ///
509 /// Required rather than defaulted: an embedder must say what it is, and a
510 /// default would let one silently claim the wrong conventions.
511 fn target_platform(&self) -> TargetPlatform;
512
513 /// Asks for a frame of `view`: Flutter's `PlatformDispatcher.scheduleFrame`, for one view.
514 ///
515 /// `view` is in the next [`EmbedderClient::frame`](crate::EmbedderClient::frame) of the
516 /// screen it is on once its window is in sight; the host holds the request while the window
517 /// is minimized or occluded. Asked again before then, it is in that refresh once.
518 fn request_frame(&self, view: ViewId);
519
520 /// The host clock used for frame and timer timestamps.
521 fn now(&self) -> Instant;
522
523 /// The host's threads, for waking the application or running work off the main thread: see
524 /// [`Dispatcher`].
525 fn dispatcher(&self) -> Arc<dyn Dispatcher>;
526
527 /// Every view the app has now: those the platform gave it, such as a phone's screen or an
528 /// embedder's views, and those it asked for. An app may run with none, as one on a desktop
529 /// does until it asks.
530 fn views(&self) -> Vec<ViewRef>;
531
532 /// Looks up one of the app's views.
533 fn view(&self, id: ViewId) -> Option<ViewRef>;
534
535 /// Asks for a view in a window configured as `config`, carrying `initial_data`
536 /// ([`View::initial_data`](crate::View::initial_data)), such as which screen to show. A
537 /// desktop makes the window; a platform that gives the app its views and makes none, a phone
538 /// or an embedder, answers [`ViewError::Unsupported`](crate::ViewError::Unsupported). The view
539 /// arrives later, through `EmbedderClient::view_added`, carrying the id answered here; a
540 /// request that fails on the way is reported through `EmbedderClient::view_request_failed`.
541 fn request_view(
542 &self,
543 config: crate::WindowConfig,
544 initial_data: Option<String>,
545 ) -> Result<crate::RequestId, crate::ViewError> {
546 let _ = (config, initial_data);
547 Err(crate::ViewError::Unsupported)
548 }
549
550 /// The full system-reported supported locales of the device (Flutter
551 /// `PlatformDispatcher.locales`), in order of preference; empty until the host
552 /// reports them.
553 fn locales(&self) -> Vec<crate::Locale> {
554 Vec::new()
555 }
556
557 /// Tells the host which locale the application resolved to (Flutter
558 /// `PlatformDispatcher.setApplicationLocale`); the default drops it.
559 fn set_application_locale(&self, locale: &crate::Locale) {
560 let _ = locale;
561 }
562
563 /// The platform's light/dark preference (Flutter
564 /// `PlatformDispatcher.platformBrightness`).
565 ///
566 /// Defaults to [`Brightness::Light`], matching Flutter's view configuration
567 /// default. A live host that can see the OS theme overrides this.
568 fn platform_brightness(&self) -> Brightness {
569 Brightness::Light
570 }
571
572 /// The route the application was started with (Flutter
573 /// `PlatformDispatcher.defaultRouteName`).
574 ///
575 /// Defaults to `"/"`, Flutter's value for a host that was not asked to open a
576 /// particular route; a host that receives a deep link answers it here.
577 fn default_route_name(&self) -> String {
578 String::from("/")
579 }
580
581 /// The lifecycle state immediately after the application started (Flutter
582 /// `PlatformDispatcher.initialLifecycleState`).
583 ///
584 /// This property will not be updated as the lifecycle changes: a host reports each change
585 /// through `EmbedderClient::app_lifecycle_state_changed`.
586 ///
587 /// It is used to initialize `SchedulerBinding::lifecycle_state` at startup. Defaults to
588 /// `None`, Flutter's empty string: a host that has no state to report yet.
589 fn initial_lifecycle_state(&self) -> Option<AppLifecycleState> {
590 None
591 }
592
593 /// The platform's own font lookup: faces by family name and by codepoint, the way
594 /// Flutter's engine asks the OS. `None` for a host without one; text then shapes only
595 /// against fonts the application registers.
596 fn font_source(&self) -> Option<Box<dyn FontSource>> {
597 None
598 }
599
600 /// Flutter's `txt::GetDefaultFontFamilies()`: the families text uses when its style names
601 /// none, first match first.
602 ///
603 /// Defaults to the engine's answer on the OS this build targets
604 /// ([`default_font_families`](crate::default_font_families)). A host that learns its OS at
605 /// run time answers for that OS, and one whose fonts are not the OS's, as a web page's are
606 /// not, answers its own.
607 fn default_font_families(&self) -> Vec<String> {
608 match THIS_OS {
609 Some(os) => crate::default_font_families(os),
610 // `txt/platform.cc`: the engine's answer on an OS it has no file of its own for.
611 None => vec![String::from("Arial")],
612 }
613 }
614
615 /// Open an encoded image, answering with a codec for its frames.
616 ///
617 /// Flutter dart:ui `instantiateImageCodec`. The host decodes with whatever its platform
618 /// provides and uploads each frame, so the framework never sees pixels and the set of
619 /// readable formats is the platform's rather than the framework's.
620 ///
621 /// The default answers a failure at once, so a host with no decoder leaves nobody waiting.
622 fn open_image_codec(&self, bytes: std::sync::Arc<[u8]>) -> crate::ImageCodecFuture {
623 let _ = bytes;
624 Box::pin(std::future::ready(Err(crate::ImageDecodeError::NoDecoder)))
625 }
626
627 /// An image from pixels the app already holds, with no codec in between: Flutter's
628 /// `decodeImageFromPixels`. `None` when the host has no renderer to put it on yet.
629 fn import_pixels(&self, pixels: valo::PixelBuffer) -> Option<crate::Image> {
630 let _ = pixels;
631 None
632 }
633
634 /// The host's compositor, where it has one: composition layers shown beside the
635 /// framework's picture. `None` on the web, on X11, on the inert host, and on an
636 /// embedder that chose not to have one.
637 fn compositor(&self) -> Option<Arc<dyn crate::Compositor>> {
638 None
639 }
640
641 /// Flutter's `VsyncWaiter`, for any screen: the refreshes the host draws the framework's
642 /// frames at, for a producer that paces by them, as a composition layer's does. `None` for a
643 /// host without screen ticks.
644 fn vsync_waiter(&self) -> Option<Arc<dyn crate::VsyncWaiter>> {
645 None
646 }
647
648 /// The screens the app's views are on, and where the pointer is on them (Flutter
649 /// `PlatformDispatcher.displays`), or `None` where the host has none to report. Every host
650 /// with screens answers, a phone with its one included; a view's metrics name the screen it
651 /// is on, and a host that hears the screens change says so through
652 /// `EmbedderClient::screens_changed`.
653 fn screens(&self) -> Option<&dyn Screens> {
654 None
655 }
656
657 /// The system clipboard, or `None` where the host has none.
658 fn clipboard(&self) -> Option<&dyn Clipboard> {
659 None
660 }
661
662 /// The system UI drawn around the app, or `None` where the host has none.
663 fn system_chrome(&self) -> Option<&dyn SystemChrome> {
664 None
665 }
666
667 /// Haptic feedback on the device, or `None` where the host has none.
668 fn haptics(&self) -> Option<&dyn Haptics> {
669 None
670 }
671
672 /// The system's own menu over a text selection, or `None` where the host has none.
673 fn system_context_menu(&self) -> Option<&dyn SystemContextMenu> {
674 None
675 }
676
677 /// The app's menu bar, or `None` where the system has none and the app draws its own, as on
678 /// Windows, Linux and phones (`menus`).
679 fn menu_bar(&self) -> Option<&dyn MenuBar> {
680 None
681 }
682
683 /// Menus the system opens at a point in a view, or `None` where the system has none and the
684 /// framework draws them.
685 fn popup_menus(&self) -> Option<&dyn PopupMenus> {
686 None
687 }
688
689 /// The app's icons in the system's tray, or `None` where the system has no tray.
690 fn tray_icons(&self) -> Option<&dyn TrayIcons> {
691 None
692 }
693
694 /// The system's file dialogs, or `None` where the system has none.
695 fn file_dialogs(&self) -> Option<&dyn FileDialogs> {
696 None
697 }
698
699 /// The system's media players, or `None` where the host has none.
700 fn players(&self) -> Option<&dyn crate::Players> {
701 None
702 }
703
704 /// The system's cameras, or `None` where the host has none. A host with cameras answers
705 /// one with none attached as an empty list.
706 fn cameras(&self) -> Option<&dyn crate::Cameras> {
707 None
708 }
709
710 /// The system's frame readers, which a player or a camera hands its frames to, or `None`
711 /// where the system hands out no frames.
712 fn video_frame_readers(&self) -> Option<&dyn crate::VideoFrameReaders> {
713 None
714 }
715
716 /// The user's caption settings, or `None` where the system keeps none, as Linux and the web
717 /// keep none.
718 fn caption_settings(&self) -> Option<&dyn crate::CaptionSettings> {
719 None
720 }
721
722 /// What the app embeds views of its own with, on layers it makes and places, or `None` where
723 /// the host cannot draw a view into such a layer: everywhere but macOS today.
724 fn view_embedder(&self) -> Option<&dyn crate::ViewEmbedder> {
725 None
726 }
727
728 /// The menu of the app's Dock icon, or `None` where the system has no Dock.
729 fn dock_menu(&self) -> Option<&dyn DockMenu> {
730 None
731 }
732
733 /// The system services a selection toolbar offers, or `None` where the host has none.
734 fn text_services(&self) -> Option<&dyn TextServices> {
735 None
736 }
737
738 /// Application state kept across relaunches, or `None` where the host has none.
739 fn restoration(&self) -> Option<&dyn Restoration> {
740 None
741 }
742
743 /// Quitting at the application's own request, or `None` where the host cannot quit
744 /// itself: a phone, a browser tab.
745 fn app_exit(&self) -> Option<&dyn AppExit> {
746 None
747 }
748
749 /// The system's way to open links and files in other apps, or `None` where it has none.
750 fn opener(&self) -> Option<&dyn Opener> {
751 None
752 }
753
754 /// The system's notifications, or `None` where it has none for the app.
755 fn notifications(&self) -> Option<&dyn Notifications> {
756 None
757 }
758}
759
760impl dyn Platform {
761 /// The host as its own type, for what it offers beyond the interface.
762 pub fn downcast_ref<T: Platform>(&self) -> Option<&T> {
763 (self as &dyn Any).downcast_ref::<T>()
764 }
765}
766
767/// Flutter's `SystemChrome`: the system UI around the app.
768pub trait SystemChrome {
769 /// Styles the system overlays the host draws over the app — the status bar, and on
770 /// Android the system navigation bar (Flutter `SystemChrome.setSystemUIOverlayStyle`).
771 fn set_overlay_style(&self, style: &SystemUiOverlayStyle);
772
773 /// Describes the app in the host's application switcher (Flutter
774 /// `SystemChrome.setApplicationSwitcherDescription`).
775 fn set_application_switcher_description(&self, description: &ApplicationSwitcherDescription);
776}
777
778/// Flutter's `HapticFeedback`.
779pub trait Haptics {
780 /// Plays haptic feedback on the device (Flutter's `HapticFeedback.vibrate`
781 /// message on `SystemChannels.platform`).
782 fn feedback(&self, kind: HapticFeedbackType);
783}
784
785/// Flutter's `SystemContextMenu`: the system's own menu over a text selection.
786pub trait SystemContextMenu {
787 /// Shows the system context menu (Flutter's `ContextMenu.showSystemContextMenu`).
788 ///
789 /// `items` is `None` for the deprecated call that lets the platform pick default
790 /// buttons. Built-in items are performed by the host; custom items are reported
791 /// through [`crate::EmbedderClient::custom_context_menu_action`].
792 fn show(&self, target_rect: Rect, items: Option<&[SystemContextMenuItem]>);
793
794 /// Hides the system context menu (Flutter's `ContextMenu.hideSystemContextMenu`).
795 fn hide(&self);
796}
797
798/// The screens the app's views are on: what drawing on each needs, as Flutter's
799/// `PlatformDispatcher.displays` lists them, and where each is, for an app that places a window
800/// against something on them, such as an edge or its own icon, which Flutter's `Display` has no
801/// place for. Positions are in the coordinates of [`HostWindow::frame`](crate::HostWindow::frame):
802/// logical points from the top-left corner of the main screen, y growing down.
803pub trait Screens {
804 /// The screens, the main one first: the one positions start from.
805 fn screens(&self) -> Vec<Screen>;
806
807 /// Where the pointer is now, over a window of the app or not. `None` when the host cannot
808 /// say.
809 fn pointer_location(&self) -> Option<Offset>;
810}
811
812/// Names a screen: Flutter's `Display.id`, which the embedder API calls a
813/// `FlutterEngineDisplayId`. Each host numbers its screens its own way; a screen keeps its
814/// number while it stays connected.
815#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
816#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
817pub struct ScreenId(pub u64);
818
819/// A monitor: where it is (the frames, for placing windows) and what drawing on it needs
820/// (Flutter's `Display`: `id`, `devicePixelRatio`, `refreshRate`; `Display.size` is the frame's
821/// size at `scale`).
822#[derive(Clone, Debug, PartialEq)]
823pub struct Screen {
824 /// Flutter `Display.id`; stable while the screen stays connected.
825 pub id: ScreenId,
826 /// What the system calls it: "Built-in Retina Display". Empty where the host has no name
827 /// for it.
828 pub name: String,
829 /// All of it, in points, placed on the desktop.
830 pub frame: Rect,
831 /// What the system's own bars leave free: the menu bar and the Dock, or the taskbar. The
832 /// whole frame where the host cannot say.
833 pub visible_frame: Rect,
834 /// Flutter `Display.devicePixelRatio`: physical pixels per point, as on every view on the
835 /// screen.
836 pub scale: f64,
837 /// Flutter `Display.refreshRate`, in hertz; none where the host cannot say.
838 pub refresh_rate: Option<f64>,
839}
840
841/// The text services a selection toolbar offers: Flutter's LookUp, SearchWeb and Share
842/// platform-channel calls.
843pub trait TextServices {
844 /// Looks up the given text (Flutter's `LookUp.invoke`).
845 fn look_up(&self, text: &str);
846
847 /// Searches the web for the given text (Flutter's `SearchWeb.invoke`).
848 fn search_web(&self, text: &str);
849
850 /// Shares the given text (Flutter's `Share.invoke`).
851 fn share(&self, text: &str);
852}
853
854/// Flutter's restoration channel: state kept across relaunches.
855pub trait Restoration {
856 /// The restoration data the host kept for this application (the `get` message of
857 /// Flutter's `SystemChannels.restoration`).
858 fn get(&self) -> Option<RestorationUpdate>;
859
860 /// Hands the host the current restoration data (the `put` message of Flutter's
861 /// `SystemChannels.restoration`), which keeps it until the operating system asks for it.
862 fn put(&self, data: RestorationMap);
863}
864
865/// The application quitting itself: the `System.exitApplication` message of Flutter's
866/// `SystemChannels.platform`, for a required exit. A cancelable exit never reaches the host: the
867/// framework asks its own listeners first (`ServicesBinding::exit_application`), and quits here
868/// once they agree.
869pub trait AppExit {
870 /// Quits the application the way the system quits one, as Flutter's embedders do, on a
871 /// later turn of the host's loop, never inside this call.
872 ///
873 /// `exit_code` is the process's exit status where the system takes one; Flutter's macOS
874 /// and Linux embedders ignore it, and a host may too.
875 fn quit(&self, exit_code: i32);
876}
877
878/// Platform for hand-pumped tests.
879pub struct InertPlatform;
880
881impl Platform for InertPlatform {
882 /// Android, which is what Flutter's own test binding defaults
883 /// `debugDefaultTargetPlatform` to: a bare App has no host to speak of,
884 /// and a deterministic answer beats the machine's.
885 fn target_platform(&self) -> TargetPlatform {
886 TargetPlatform::Android
887 }
888
889 fn request_frame(&self, _view: ViewId) {}
890
891 fn now(&self) -> Instant {
892 Instant::now()
893 }
894
895 fn dispatcher(&self) -> Arc<dyn Dispatcher> {
896 Arc::new(InertDispatcher)
897 }
898
899 fn views(&self) -> Vec<ViewRef> {
900 Vec::new()
901 }
902
903 fn view(&self, _id: ViewId) -> Option<ViewRef> {
904 None
905 }
906}
907
908/// A refresh of a screen, as the host delivers it.
909#[derive(Clone, Copy, Debug, PartialEq, Eq)]
910pub struct Frame {
911 pub elapsed: Duration,
912}
913
914#[cfg(test)]
915mod tests {
916 use super::{Brightness, InertPlatform, Platform, TargetPlatform};
917
918 #[test]
919 fn inert_platform_is_android() {
920 assert_eq!(InertPlatform.target_platform(), TargetPlatform::Android);
921 }
922
923 #[test]
924 fn inert_platform_brightness_is_light() {
925 assert_eq!(InertPlatform.platform_brightness(), Brightness::Light);
926 }
927
928 #[test]
929 fn a_platform_downcasts_to_its_own_type_and_no_other() {
930 struct Other;
931 impl Platform for Other {
932 fn target_platform(&self) -> TargetPlatform {
933 TargetPlatform::Linux
934 }
935 fn request_frame(&self, _view: super::ViewId) {}
936 fn now(&self) -> super::Instant {
937 super::Instant::now()
938 }
939 fn dispatcher(&self) -> std::sync::Arc<dyn super::Dispatcher> {
940 std::sync::Arc::new(super::InertDispatcher)
941 }
942 fn views(&self) -> Vec<super::ViewRef> {
943 Vec::new()
944 }
945 fn view(&self, _id: super::ViewId) -> Option<super::ViewRef> {
946 None
947 }
948 }
949 let platform: super::PlatformRef = std::rc::Rc::new(InertPlatform);
950 assert!(platform.downcast_ref::<InertPlatform>().is_some());
951 assert!(platform.downcast_ref::<Other>().is_none());
952 }
953}