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