Skip to main content

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}