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::time::Duration;
10
11use web_time::Instant;
12
13use crate::fonts::FontSource;
14use crate::geometry::{Color, Rect};
15use crate::mouse_cursor::SystemMouseCursorKind;
16use crate::restoration::{RestorationMap, RestorationUpdate};
17use crate::system_context_menu::SystemContextMenuItem;
18use crate::{View, ViewFocusDirection, ViewFocusState, ViewId};
19
20pub type PlatformRef = Rc<dyn Platform>;
21
22pub type ViewRef = Rc<dyn View>;
23
24/// Specifies a description of the application that is pertinent to the
25/// embedder's application switcher (also known as "recent tasks") user
26/// interface.
27///
28/// Flutter counterpart: `ApplicationSwitcherDescription` (`services/system_chrome.dart`).
29/// It lives here because [`Platform`] names it.
30#[derive(Clone, Debug, Default, PartialEq, Eq)]
31pub struct ApplicationSwitcherDescription {
32    /// A label and description of the current state of the application.
33    pub label: Option<String>,
34    /// The application's primary color, as a 32-bit ARGB value.
35    ///
36    /// This may influence the color that the operating system uses to represent
37    /// the application.
38    pub primary_color: Option<u32>,
39}
40
41/// Specifies a system overlay style for a portion of the app.
42///
43/// Flutter counterpart: `SystemUiOverlayStyle` (`services/system_chrome.dart`).
44/// It lives here because [`Platform`] names it.
45#[derive(Clone, Copy, Debug, Default, PartialEq)]
46pub struct SystemUiOverlayStyle {
47    /// The color of the system bottom navigation bar.
48    ///
49    /// Only honored in Android versions O and greater.
50    pub system_navigation_bar_color: Option<Color>,
51
52    /// The color of the divider between the system's bottom navigation bar and the app's
53    /// content.
54    ///
55    /// Only honored in Android versions P and greater.
56    pub system_navigation_bar_divider_color: Option<Color>,
57
58    /// The brightness of the system navigation bar icons.
59    ///
60    /// Only honored in Android versions O and greater.
61    /// When set to [`Brightness::Light`], the system navigation bar icons are light.
62    /// When set to [`Brightness::Dark`], the system navigation bar icons are dark.
63    pub system_navigation_bar_icon_brightness: Option<Brightness>,
64
65    /// Overrides the contrast enforcement when setting a transparent navigation bar.
66    ///
67    /// When setting a transparent navigation bar in SDK 29+, or Android 10 and up, a
68    /// translucent body scrim may be applied behind the button navigation bar to ensure
69    /// contrast with buttons and the background of the application.
70    ///
71    /// SDK 28-, or Android P and lower, will not apply this body scrim.
72    ///
73    /// Setting this to false overrides the default body scrim.
74    pub system_navigation_bar_contrast_enforced: Option<bool>,
75
76    /// The color of top status bar.
77    ///
78    /// Only honored in Android version M and greater.
79    pub status_bar_color: Option<Color>,
80
81    /// The brightness of top status bar.
82    ///
83    /// Only honored in iOS.
84    pub status_bar_brightness: Option<Brightness>,
85
86    /// The brightness of the top status bar icons.
87    ///
88    /// Only honored in Android version M and greater.
89    pub status_bar_icon_brightness: Option<Brightness>,
90
91    /// Overrides the contrast enforcement when setting a transparent status bar.
92    ///
93    /// When setting a transparent status bar in SDK 29+, or Android 10 and up, a translucent
94    /// body scrim may be applied to ensure contrast with icons and the background of the
95    /// application.
96    ///
97    /// SDK 28-, or Android P and lower, will not apply this body scrim.
98    ///
99    /// Setting this to false overrides the default body scrim.
100    pub system_status_bar_contrast_enforced: Option<bool>,
101}
102
103impl SystemUiOverlayStyle {
104    /// System overlays should be drawn with a light color. Intended for applications with a
105    /// dark background.
106    pub const LIGHT: SystemUiOverlayStyle = SystemUiOverlayStyle {
107        system_navigation_bar_color: Some(Color::new(0xFF000000)),
108        system_navigation_bar_icon_brightness: Some(Brightness::Light),
109        status_bar_icon_brightness: Some(Brightness::Light),
110        status_bar_brightness: Some(Brightness::Dark),
111        system_navigation_bar_divider_color: None,
112        system_navigation_bar_contrast_enforced: None,
113        status_bar_color: None,
114        system_status_bar_contrast_enforced: None,
115    };
116
117    /// System overlays should be drawn with a dark color. Intended for applications with a
118    /// light background.
119    pub const DARK: SystemUiOverlayStyle = SystemUiOverlayStyle {
120        system_navigation_bar_color: Some(Color::new(0xFF000000)),
121        system_navigation_bar_icon_brightness: Some(Brightness::Light),
122        status_bar_icon_brightness: Some(Brightness::Dark),
123        status_bar_brightness: Some(Brightness::Light),
124        system_navigation_bar_divider_color: None,
125        system_navigation_bar_contrast_enforced: None,
126        status_bar_color: None,
127        system_status_bar_contrast_enforced: None,
128    };
129
130    /// Creates a new [`SystemUiOverlayStyle`]; Dart's named arguments are the setters.
131    pub const fn new() -> SystemUiOverlayStyle {
132        SystemUiOverlayStyle {
133            system_navigation_bar_color: None,
134            system_navigation_bar_divider_color: None,
135            system_navigation_bar_icon_brightness: None,
136            system_navigation_bar_contrast_enforced: None,
137            status_bar_color: None,
138            status_bar_brightness: None,
139            status_bar_icon_brightness: None,
140            system_status_bar_contrast_enforced: None,
141        }
142    }
143
144    /// Dart `SystemUiOverlayStyle(systemNavigationBarColor:)`.
145    pub fn system_navigation_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
146        self.system_navigation_bar_color = Some(color);
147        self
148    }
149
150    /// Dart `SystemUiOverlayStyle(systemNavigationBarDividerColor:)`.
151    pub fn system_navigation_bar_divider_color(mut self, color: Color) -> SystemUiOverlayStyle {
152        self.system_navigation_bar_divider_color = Some(color);
153        self
154    }
155
156    /// Dart `SystemUiOverlayStyle(systemNavigationBarIconBrightness:)`.
157    pub fn system_navigation_bar_icon_brightness(
158        mut self,
159        brightness: Brightness,
160    ) -> SystemUiOverlayStyle {
161        self.system_navigation_bar_icon_brightness = Some(brightness);
162        self
163    }
164
165    /// Dart `SystemUiOverlayStyle(systemNavigationBarContrastEnforced:)`.
166    pub fn system_navigation_bar_contrast_enforced(
167        mut self,
168        enforced: bool,
169    ) -> SystemUiOverlayStyle {
170        self.system_navigation_bar_contrast_enforced = Some(enforced);
171        self
172    }
173
174    /// Dart `SystemUiOverlayStyle(statusBarColor:)`.
175    pub fn status_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
176        self.status_bar_color = Some(color);
177        self
178    }
179
180    /// Dart `SystemUiOverlayStyle(statusBarBrightness:)`.
181    pub fn status_bar_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
182        self.status_bar_brightness = Some(brightness);
183        self
184    }
185
186    /// Dart `SystemUiOverlayStyle(statusBarIconBrightness:)`.
187    pub fn status_bar_icon_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
188        self.status_bar_icon_brightness = Some(brightness);
189        self
190    }
191
192    /// Dart `SystemUiOverlayStyle(systemStatusBarContrastEnforced:)`.
193    pub fn system_status_bar_contrast_enforced(mut self, enforced: bool) -> SystemUiOverlayStyle {
194        self.system_status_bar_contrast_enforced = Some(enforced);
195        self
196    }
197
198    /// Creates a copy of this theme with the given fields replaced with new values.
199    pub fn copy_with(&self) -> SystemUiOverlayStyle {
200        *self
201    }
202}
203
204/// The kind of haptic feedback to play.
205///
206/// Flutter counterpart: the `HapticFeedbackType.*` argument Dart sends with the
207/// `HapticFeedback.vibrate` message on `SystemChannels.platform`. It lives here
208/// because [`Platform`] names it.
209#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
210pub enum HapticFeedbackType {
211    /// Vibration for a short duration, the argument-less `HapticFeedback.vibrate`.
212    Vibrate,
213    /// A collision impact with a light mass.
214    LightImpact,
215    /// A collision impact with a medium mass.
216    MediumImpact,
217    /// A collision impact with a heavy mass.
218    HeavyImpact,
219    /// A selection changing through discrete values.
220    SelectionClick,
221    /// A task or action completed successfully.
222    SuccessNotification,
223    /// A task or action produced a warning.
224    WarningNotification,
225    /// A task or action failed.
226    ErrorNotification,
227}
228
229/// Describes the contrast of a theme or color palette.
230///
231/// Flutter counterpart: `Brightness` (`dart:ui` `window.dart`).
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
233pub enum Brightness {
234    /// The color is dark and will require a light text color to achieve readable
235    /// contrast.
236    ///
237    /// For example, the color might be dark grey, requiring white text.
238    Dark,
239
240    /// The color is light and will require a dark text color to achieve readable
241    /// contrast.
242    ///
243    /// For example, the color might be bright white, requiring black text.
244    Light,
245}
246
247/// States that an application can be in once it is running.
248///
249/// States not supported on a platform are synthesized so the state machine stays the same
250/// everywhere. For example, [`Hidden`](AppLifecycleState::Hidden) is synthesized on mobile
251/// before [`Paused`](AppLifecycleState::Paused) when coming from
252/// [`Inactive`](AppLifecycleState::Inactive), and before [`Inactive`](AppLifecycleState::Inactive)
253/// when coming from [`Paused`](AppLifecycleState::Paused).
254///
255/// The values below are listed in the expected state machine transition order. The initial
256/// state is [`Detached`](AppLifecycleState::Detached).
257///
258/// Flutter counterpart: `AppLifecycleState` (`dart:ui` `platform_dispatcher.dart`).
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
260pub enum AppLifecycleState {
261    /// The application is still hosted by a Flutter engine but is detached from any host
262    /// views.
263    ///
264    /// The application defaults to this state before it initializes, and can be in this
265    /// state (applicable on Android, iOS, and web) after all views have been detached.
266    ///
267    /// When the application is in this state, the engine is running without a view.
268    ///
269    /// This state is only entered on iOS, Android, and web, although on all platforms it is
270    /// the default state before the application begins running.
271    Detached,
272
273    /// On all platforms, this state indicates that the application is in the default
274    /// running mode for a running application that has input focus and is visible.
275    ///
276    /// On Android, this state corresponds to the Flutter host view having focus while in
277    /// Android's "resumed" state. It is possible for the Flutter app to be in the
278    /// [`Inactive`](AppLifecycleState::Inactive) state while still being in Android's
279    /// "onResume" state if the app has lost focus but hasn't had `Activity.onPause` called
280    /// on it.
281    ///
282    /// On iOS and macOS, this corresponds to the app running in the foreground active
283    /// state.
284    Resumed,
285
286    /// At least one view of the application is visible, but none have input focus. The
287    /// application is otherwise running normally.
288    ///
289    /// On non-web desktop platforms, this corresponds to an application that is not in the
290    /// foreground, but still has visible windows.
291    ///
292    /// On the web, this corresponds to an application that is running in a window or tab
293    /// that does not have input focus.
294    ///
295    /// On iOS and macOS, this state corresponds to the Flutter host view running in the
296    /// foreground inactive state. Apps transition to this state when in a phone call, when
297    /// responding to a TouchID request, when entering the app switcher or the control
298    /// center, or when the UIViewController hosting the Flutter app is transitioning.
299    ///
300    /// On Android, this corresponds to the Flutter host view running in Android's paused
301    /// state (i.e. `Activity.onPause` has been called), or in Android's "resumed" state
302    /// (i.e. `Activity.onResume` has been called) but does not have window focus.
303    ///
304    /// On Android and iOS, apps in this state should assume that they may be
305    /// [`Hidden`](AppLifecycleState::Hidden) and [`Paused`](AppLifecycleState::Paused) at any
306    /// time.
307    Inactive,
308
309    /// All views of an application are hidden, either because the application is about to
310    /// be paused (on iOS and Android), or because it has been minimized or placed on a
311    /// desktop that is no longer visible (on non-web desktop), or is running in a window or
312    /// tab that is no longer visible (on the web).
313    ///
314    /// On iOS and Android, in order to keep the state machine the same on all platforms, a
315    /// transition to this state is synthesized before the [`Paused`](AppLifecycleState::Paused)
316    /// state is entered when coming from [`Inactive`](AppLifecycleState::Inactive), and
317    /// before the [`Inactive`](AppLifecycleState::Inactive) state is entered when coming
318    /// from [`Paused`](AppLifecycleState::Paused). This allows cross-platform implementations
319    /// that want to know when an app is conceptually "hidden" to only write one handler.
320    Hidden,
321
322    /// The application is not currently visible to the user, and not responding to user
323    /// input.
324    ///
325    /// When the application is in this state, the engine will not call
326    /// `PlatformDispatcher.onBeginFrame` and `PlatformDispatcher.onDrawFrame`.
327    ///
328    /// This state is only entered on iOS and Android.
329    Paused,
330}
331
332/// The possible responses to a request to exit the application.
333///
334/// The request is typically responded to by creating an `AppLifecycleListener` and supplying
335/// an `on_exit_requested` callback, or by overriding
336/// `WidgetsBindingObserver::did_request_app_exit`.
337///
338/// Flutter counterpart: `AppExitResponse` (`dart:ui` `platform_dispatcher.dart`).
339#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
340pub enum AppExitResponse {
341    /// Exiting the application can proceed.
342    Exit,
343
344    /// Cancel the exit: do not exit the application.
345    Cancel,
346}
347
348/// The platform that user interaction should adapt to target.
349///
350/// Flutter counterpart: `TargetPlatform` (`foundation/platform.dart`). Flutter
351/// reads the current one from a library global that consults `dart:io`. Here it
352/// is a plain value type, and the App-owned [`Platform`] reports which one it
353/// is, so two Apps in one process can differ.
354#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
355pub enum TargetPlatform {
356    /// Android: <https://www.android.com/>
357    Android,
358    /// Fuchsia: <https://fuchsia.dev/fuchsia-src/concepts>
359    Fuchsia,
360    /// iOS: <https://www.apple.com/ios/>
361    IOS,
362    /// Linux: <https://www.linux.org>
363    Linux,
364    /// macOS: <https://www.apple.com/macos>
365    MacOS,
366    /// Windows: <https://www.windows.com>
367    Windows,
368}
369
370/// One line of a popup menu the host shows with its own menu system.
371#[derive(Clone, Debug, PartialEq, Eq)]
372pub enum PopupMenuEntry {
373    Item {
374        label: String,
375        enabled: bool,
376        /// Drawn with a check mark, for a setting the menu toggles.
377        checked: bool,
378    },
379    Separator,
380}
381
382impl PopupMenuEntry {
383    pub fn item(label: impl Into<String>) -> PopupMenuEntry {
384        PopupMenuEntry::Item {
385            label: label.into(),
386            enabled: true,
387            checked: false,
388        }
389    }
390
391    pub fn checked(self, checked: bool) -> PopupMenuEntry {
392        match self {
393            PopupMenuEntry::Item { label, enabled, .. } => PopupMenuEntry::Item {
394                label,
395                enabled,
396                checked,
397            },
398            separator => separator,
399        }
400    }
401
402    pub fn enabled(self, enabled: bool) -> PopupMenuEntry {
403        match self {
404            PopupMenuEntry::Item { label, checked, .. } => PopupMenuEntry::Item {
405                label,
406                enabled,
407                checked,
408            },
409            separator => separator,
410        }
411    }
412}
413
414/// The long-lived host object held by the application.
415///
416/// Implementations must queue requests and return. They must not synchronously
417/// re-enter the [`EmbedderClient`](crate::EmbedderClient) while `App` is active.
418///
419/// A host may offer more than this interface — a picture from a buffer only its system
420/// has, a handle to its renderer. An app that knows its host reaches that through
421/// [`downcast_ref`](dyn Platform::downcast_ref); the interface itself names nothing of any
422/// one system.
423pub trait Platform: Any {
424    /// Which host this is, for behaviour that follows platform convention
425    /// (Flutter `defaultTargetPlatform`).
426    ///
427    /// Required rather than defaulted: an embedder must say what it is, and a
428    /// default would let one silently claim the wrong conventions.
429    fn target_platform(&self) -> TargetPlatform;
430
431    /// Requests one isolate frame at the host's next appropriate opportunity.
432    fn request_frame(&self);
433
434    /// The host clock used for frame and timer timestamps.
435    fn now(&self) -> Instant;
436
437    /// Asks the host to wake the application at `deadline`.
438    fn wake_at(&self, deadline: Instant);
439
440    /// Current host-provided views.
441    fn views(&self) -> Vec<ViewRef>;
442
443    /// Looks up a current host-provided view.
444    fn view(&self, id: ViewId) -> Option<ViewRef>;
445
446    /// The stable implicit view, when this embedding provides one.
447    fn implicit_view(&self) -> Option<ViewRef>;
448
449    /// Flutter `PlatformDispatcher.requestViewFocusChange`: asks the host to move view focus.
450    /// A host without native focus support ignores the request.
451    fn request_view_focus_change(
452        &self,
453        view_id: ViewId,
454        state: ViewFocusState,
455        direction: ViewFocusDirection,
456    ) {
457        let _ = (view_id, state, direction);
458    }
459
460    /// The full system-reported supported locales of the device (Flutter
461    /// `PlatformDispatcher.locales`), in order of preference; empty until the host
462    /// reports them.
463    fn locales(&self) -> Vec<crate::Locale> {
464        Vec::new()
465    }
466
467    /// Tells the host which locale the application resolved to (Flutter
468    /// `PlatformDispatcher.setApplicationLocale`); the default drops it.
469    fn set_application_locale(&self, locale: &crate::Locale) {
470        let _ = locale;
471    }
472
473    /// The platform's light/dark preference (Flutter
474    /// `PlatformDispatcher.platformBrightness`).
475    ///
476    /// Defaults to [`Brightness::Light`], matching Flutter's view configuration
477    /// default. A live host that can see the OS theme overrides this.
478    fn platform_brightness(&self) -> Brightness {
479        Brightness::Light
480    }
481
482    /// The route the application was started with (Flutter
483    /// `PlatformDispatcher.defaultRouteName`).
484    ///
485    /// Defaults to `"/"`, Flutter's value for a host that was not asked to open a
486    /// particular route; a host that receives a deep link answers it here.
487    fn default_route_name(&self) -> String {
488        String::from("/")
489    }
490
491    /// The platform's own font lookup: faces by family name and by codepoint, the way
492    /// Flutter's engine asks the OS. `None` for a host without one; text then shapes only
493    /// against fonts the application registers.
494    fn font_source(&self) -> Option<Box<dyn FontSource>> {
495        None
496    }
497
498    /// Open an encoded image, answering with a codec for its frames.
499    ///
500    /// Flutter dart:ui `instantiateImageCodec`. The host decodes with whatever its platform
501    /// provides and uploads each frame, so the framework never sees pixels and the set of
502    /// readable formats is the platform's rather than the framework's.
503    ///
504    /// The default answers a failure at once, so a host with no decoder leaves nobody waiting.
505    fn open_image_codec(&self, bytes: std::sync::Arc<[u8]>) -> crate::ImageCodecFuture {
506        let _ = bytes;
507        Box::pin(std::future::ready(Err(crate::ImageDecodeError::NoDecoder)))
508    }
509
510    /// An image from pixels the app already holds, with no codec in between: Flutter's
511    /// `decodeImageFromPixels`. `None` when the host has no renderer to put it on yet.
512    fn import_pixels(&self, pixels: valo::PixelBuffer) -> Option<crate::Image> {
513        let _ = pixels;
514        None
515    }
516
517    /// The host's window maker, for an app that opens windows of its own. `None`, the
518    /// default, where the implicit view is the only one there can be: mobile and the web.
519    fn windowing_owner(&self) -> Option<Rc<dyn crate::WindowingOwner>> {
520        None
521    }
522
523    /// The system clipboard, or `None` where the host has none.
524    fn clipboard(&self) -> Option<&dyn Clipboard> {
525        None
526    }
527
528    /// The system UI drawn around the app, or `None` where the host has none.
529    fn system_chrome(&self) -> Option<&dyn SystemChrome> {
530        None
531    }
532
533    /// Haptic feedback on the device, or `None` where the host has none.
534    fn haptics(&self) -> Option<&dyn Haptics> {
535        None
536    }
537
538    /// The system's own menu over a text selection, or `None` where the host has none.
539    fn system_context_menu(&self) -> Option<&dyn SystemContextMenu> {
540        None
541    }
542
543    /// Native pop-up menus shown at the pointer, or `None` where the host has none.
544    fn popup_menus(&self) -> Option<&dyn PopupMenus> {
545        None
546    }
547
548    /// The system services a selection toolbar offers, or `None` where the host has none.
549    fn text_services(&self) -> Option<&dyn TextServices> {
550        None
551    }
552
553    /// The system cursor shown for a pointing device, or `None` where the host has none.
554    fn mouse_cursor(&self) -> Option<&dyn MouseCursor> {
555        None
556    }
557
558    /// Application state kept across relaunches, or `None` where the host has none.
559    fn restoration(&self) -> Option<&dyn Restoration> {
560        None
561    }
562}
563
564impl dyn Platform {
565    /// The host as its own type, for what it offers beyond the interface.
566    pub fn downcast_ref<T: Platform>(&self) -> Option<&T> {
567        (self as &dyn Any).downcast_ref::<T>()
568    }
569}
570
571/// The system clipboard: Flutter's `Clipboard` over the platform channel.
572pub trait Clipboard {
573    /// Stores plain text on the system clipboard (Flutter's `Clipboard.setData`).
574    fn set_text(&self, text: &str);
575
576    /// Retrieves plain text from the system clipboard (Flutter's `Clipboard.getData`).
577    fn text(&self) -> Option<String>;
578
579    /// Whether the clipboard contains string data (Flutter's `Clipboard.hasStrings`).
580    fn has_strings(&self) -> bool;
581}
582
583/// Flutter's `SystemChrome`: the system UI around the app.
584pub trait SystemChrome {
585    /// Styles the system overlays the host draws over the app — the status bar, and on
586    /// Android the system navigation bar (Flutter `SystemChrome.setSystemUIOverlayStyle`).
587    fn set_overlay_style(&self, style: &SystemUiOverlayStyle);
588
589    /// Describes the app in the host's application switcher (Flutter
590    /// `SystemChrome.setApplicationSwitcherDescription`).
591    fn set_application_switcher_description(&self, description: &ApplicationSwitcherDescription);
592}
593
594/// Flutter's `HapticFeedback`.
595pub trait Haptics {
596    /// Plays haptic feedback on the device (Flutter's `HapticFeedback.vibrate`
597    /// message on `SystemChannels.platform`).
598    fn feedback(&self, kind: HapticFeedbackType);
599}
600
601/// Flutter's `SystemContextMenu`: the system's own menu over a text selection.
602pub trait SystemContextMenu {
603    /// Shows the system context menu (Flutter's `ContextMenu.showSystemContextMenu`).
604    ///
605    /// `items` is `None` for the deprecated call that lets the platform pick default
606    /// buttons. Built-in items are performed by the host; custom items are reported
607    /// through [`crate::EmbedderClient::custom_context_menu_action`].
608    fn show(&self, target_rect: Rect, items: Option<&[SystemContextMenuItem]>);
609
610    /// Hides the system context menu (Flutter's `ContextMenu.hideSystemContextMenu`).
611    fn hide(&self);
612}
613
614/// A native pop-up menu shown at the pointer, answered with the index chosen.
615pub trait PopupMenus {
616    /// Shows `entries` as the platform's own popup menu at the pointer and waits for it to
617    /// close; answers the index of the entry chosen. Not a Flutter call: Flutter's context
618    /// menus draw inside the view, and a desktop panel narrower than its menu needs the
619    /// system's, which is its own window.
620    ///
621    /// The host runs the menu's event loop inside this call, so nothing else of the app runs
622    /// meanwhile and a native callback that arrives then must post, not borrow.
623    fn show(&self, entries: &[PopupMenuEntry]) -> Option<usize>;
624}
625
626/// The text services a selection toolbar offers: Flutter's LookUp, SearchWeb and Share
627/// platform-channel calls.
628pub trait TextServices {
629    /// Looks up the given text (Flutter's `LookUp.invoke`).
630    fn look_up(&self, text: &str);
631
632    /// Searches the web for the given text (Flutter's `SearchWeb.invoke`).
633    fn search_web(&self, text: &str);
634
635    /// Shares the given text (Flutter's `Share.invoke`).
636    fn share(&self, text: &str);
637}
638
639/// Flutter's mouse cursor channel.
640pub trait MouseCursor {
641    /// Shows a system cursor for a pointing device (Flutter's `activateSystemCursor`
642    /// message on `SystemChannels.mouseCursor`).
643    fn activate_system_cursor(&self, device: i64, kind: SystemMouseCursorKind);
644}
645
646/// Flutter's restoration channel: state kept across relaunches.
647pub trait Restoration {
648    /// The restoration data the host kept for this application (the `get` message of
649    /// Flutter's `SystemChannels.restoration`).
650    fn get(&self) -> Option<RestorationUpdate>;
651
652    /// Hands the host the current restoration data (the `put` message of Flutter's
653    /// `SystemChannels.restoration`), which keeps it until the operating system asks for it.
654    fn put(&self, data: RestorationMap);
655}
656
657/// Platform for hand-pumped tests.
658pub struct InertPlatform;
659
660impl Platform for InertPlatform {
661    /// Android, which is what Flutter's own test binding defaults
662    /// `debugDefaultTargetPlatform` to: a bare App has no host to speak of,
663    /// and a deterministic answer beats the machine's.
664    fn target_platform(&self) -> TargetPlatform {
665        TargetPlatform::Android
666    }
667
668    fn request_frame(&self) {}
669
670    fn now(&self) -> Instant {
671        Instant::now()
672    }
673
674    fn wake_at(&self, _deadline: Instant) {}
675
676    fn views(&self) -> Vec<ViewRef> {
677        Vec::new()
678    }
679
680    fn view(&self, _id: ViewId) -> Option<ViewRef> {
681        None
682    }
683
684    fn implicit_view(&self) -> Option<ViewRef> {
685        None
686    }
687}
688
689/// One complete engine frame delivered to the client.
690#[derive(Clone, Copy, Debug, PartialEq, Eq)]
691pub struct Frame {
692    pub elapsed: Duration,
693}
694
695#[cfg(test)]
696mod tests {
697    use super::{Brightness, InertPlatform, Platform, TargetPlatform};
698
699    #[test]
700    fn inert_platform_is_android() {
701        assert_eq!(InertPlatform.target_platform(), TargetPlatform::Android);
702    }
703
704    #[test]
705    fn inert_platform_brightness_is_light() {
706        assert_eq!(InertPlatform.platform_brightness(), Brightness::Light);
707    }
708
709    #[test]
710    fn a_platform_downcasts_to_its_own_type_and_no_other() {
711        struct Other;
712        impl Platform for Other {
713            fn target_platform(&self) -> TargetPlatform {
714                TargetPlatform::Linux
715            }
716            fn request_frame(&self) {}
717            fn now(&self) -> super::Instant {
718                super::Instant::now()
719            }
720            fn wake_at(&self, _deadline: super::Instant) {}
721            fn views(&self) -> Vec<super::ViewRef> {
722                Vec::new()
723            }
724            fn view(&self, _id: super::ViewId) -> Option<super::ViewRef> {
725                None
726            }
727            fn implicit_view(&self) -> Option<super::ViewRef> {
728                None
729            }
730        }
731        let platform: super::PlatformRef = std::rc::Rc::new(InertPlatform);
732        assert!(platform.downcast_ref::<InertPlatform>().is_some());
733        assert!(platform.downcast_ref::<Other>().is_none());
734    }
735}