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    /// Open an encoded image, answering with a codec for its frames.
432    ///
433    /// Flutter dart:ui `instantiateImageCodec`. The host decodes with whatever its platform
434    /// provides and uploads each frame, so the framework never sees pixels and the set of
435    /// readable formats is the platform's rather than the framework's.
436    ///
437    /// The default answers a failure at once, so a host with no decoder leaves nobody waiting.
438    fn open_image_codec(&self, bytes: std::sync::Arc<[u8]>) -> crate::ImageCodecFuture {
439        let _ = bytes;
440        Box::pin(std::future::ready(Err(crate::ImageDecodeError::NoDecoder)))
441    }
442
443    /// An image from pixels the app already holds, with no codec in between: Flutter's
444    /// `decodeImageFromPixels`. `None` when the host has no renderer to put it on yet.
445    fn import_pixels(&self, pixels: valo::PixelBuffer) -> Option<crate::Image> {
446        let _ = pixels;
447        None
448    }
449
450    /// Whether the host's own text input turns editing keys into edits before the framework
451    /// sees them — backspace and delete, caret movement, the line and document ends.
452    ///
453    /// Flutter has no such question because each of its embedders is written for one host:
454    /// its macOS and iOS embedders do interpret those keys, and its text field bindings for
455    /// those platforms step aside so a key is not acted on twice.
456    ///
457    /// Defaults to false, which is what a host built on a windowing library that reports
458    /// plain key presses should answer, whichever platform it runs on. A host that hands the
459    /// framework the operating system's own editing commands answers true, and the field
460    /// then leaves those keys to it.
461    fn handles_text_editing_keys(&self) -> bool {
462        false
463    }
464
465    /// The full system-reported supported locales of the device (Flutter
466    /// `PlatformDispatcher.locales`), in order of preference; empty until the host
467    /// reports them.
468    fn locales(&self) -> Vec<crate::Locale> {
469        Vec::new()
470    }
471
472    /// Tells the host which locale the application resolved to (Flutter
473    /// `PlatformDispatcher.setApplicationLocale`); the default drops it.
474    fn set_application_locale(&self, locale: &crate::Locale) {
475        let _ = locale;
476    }
477
478    /// The route the application was started with (Flutter
479    /// `PlatformDispatcher.defaultRouteName`).
480    ///
481    /// Defaults to `"/"`, Flutter's value for a host that was not asked to open a
482    /// particular route; a host that receives a deep link answers it here.
483    fn default_route_name(&self) -> String {
484        String::from("/")
485    }
486
487    /// Describes the app in the host's application switcher (Flutter
488    /// `SystemChrome.setApplicationSwitcherDescription`); the default drops it.
489    fn set_application_switcher_description(&self, description: &ApplicationSwitcherDescription) {
490        let _ = description;
491    }
492
493    /// Styles the system overlays the host draws over the app — the status bar, and on
494    /// Android the system navigation bar (Flutter
495    /// `SystemChrome.setSystemUIOverlayStyle`); the default drops it.
496    fn set_system_ui_overlay_style(&self, style: &SystemUiOverlayStyle) {
497        let _ = style;
498    }
499
500    /// Plays haptic feedback on the device (Flutter's `HapticFeedback.vibrate`
501    /// message on `SystemChannels.platform`); the default drops it.
502    fn haptic_feedback(&self, kind: HapticFeedbackType) {
503        let _ = kind;
504    }
505
506    /// Stores plain text on the system clipboard (Flutter's `Clipboard.setData`
507    /// message on `SystemChannels.platform`); the default drops it.
508    fn clipboard_set_data(&self, text: &str) {
509        let _ = text;
510    }
511
512    /// Retrieves plain text from the system clipboard (Flutter's `Clipboard.getData`);
513    /// the default answers [`None`].
514    fn clipboard_get_data(&self) -> Option<String> {
515        None
516    }
517
518    /// Whether the clipboard contains string data (Flutter's `Clipboard.hasStrings`);
519    /// the default answers `false`.
520    fn clipboard_has_strings(&self) -> bool {
521        false
522    }
523
524    /// Whether this host can show a system-rendered context menu (Flutter
525    /// `PlatformDispatcher.supportsShowingSystemContextMenu`).
526    ///
527    /// Defaults to `false`. A host that can show one (iOS 16+ `UIEditMenuInteraction`)
528    /// answers `true`.
529    fn supports_showing_system_context_menu(&self) -> bool {
530        false
531    }
532
533    /// Shows the system context menu (Flutter's `ContextMenu.showSystemContextMenu`).
534    ///
535    /// `items` is `None` for the deprecated call that lets the platform pick default
536    /// buttons. Defaults to dropping it. Built-in items are performed by the host;
537    /// custom items are reported through [`crate::EmbedderClient::custom_context_menu_action`].
538    fn show_system_context_menu(&self, target_rect: Rect, items: Option<&[SystemContextMenuItem]>) {
539        let _ = (target_rect, items);
540    }
541
542    /// Hides the system context menu (Flutter's `ContextMenu.hideSystemContextMenu`).
543    ///
544    /// Defaults to dropping it.
545    fn hide_system_context_menu(&self) {}
546
547    /// Shows `entries` as the platform's own popup menu at the pointer and waits for it to
548    /// close; answers the index of the entry chosen. Not a Flutter call: Flutter's context
549    /// menus draw inside the view, and a desktop panel narrower than its menu needs the
550    /// system's, which is its own window.
551    ///
552    /// The host runs the menu's event loop inside this call, so nothing else of the app runs
553    /// meanwhile and a native callback that arrives then must post, not borrow. Defaults to
554    /// `None`, for a host with no menus.
555    fn show_popup_menu(&self, entries: &[PopupMenuEntry]) -> Option<usize> {
556        let _ = entries;
557        None
558    }
559
560    /// Looks up the given text (Flutter's `LookUp.invoke`).
561    ///
562    /// Defaults to dropping it.
563    fn look_up(&self, text: &str) {
564        let _ = text;
565    }
566
567    /// Searches the web for the given text (Flutter's `SearchWeb.invoke`).
568    ///
569    /// Defaults to dropping it.
570    fn search_web(&self, text: &str) {
571        let _ = text;
572    }
573
574    /// Shares the given text (Flutter's `Share.invoke`).
575    ///
576    /// Defaults to dropping it.
577    fn share(&self, text: &str) {
578        let _ = text;
579    }
580
581    /// The platform's light/dark preference (Flutter
582    /// `PlatformDispatcher.platformBrightness`).
583    ///
584    /// Defaults to [`Brightness::Light`], matching Flutter's view configuration
585    /// default. A live host that can see the OS theme overrides this.
586    fn platform_brightness(&self) -> Brightness {
587        Brightness::Light
588    }
589
590    /// Requests one isolate frame at the host's next appropriate opportunity.
591    fn request_frame(&self);
592
593    /// The host clock used for frame and timer timestamps.
594    fn now(&self) -> Instant;
595
596    /// Asks the host to wake the application at `deadline`.
597    fn wake_at(&self, deadline: Instant);
598
599    /// Current host-provided views.
600    fn views(&self) -> Vec<ViewRef>;
601
602    /// Looks up a current host-provided view.
603    fn view(&self, id: ViewId) -> Option<ViewRef>;
604
605    /// Flutter `PlatformDispatcher.requestViewFocusChange`: asks the host to move view focus.
606    /// A host without native focus support ignores the request.
607    fn request_view_focus_change(
608        &self,
609        view_id: ViewId,
610        state: ViewFocusState,
611        direction: ViewFocusDirection,
612    ) {
613        let _ = (view_id, state, direction);
614    }
615
616    /// The stable implicit view, when this embedding provides one.
617    fn implicit_view(&self) -> Option<ViewRef>;
618
619    /// The host's window maker, for an app that opens windows of its own. `None`, the
620    /// default, where the implicit view is the only one there can be: mobile and the web.
621    fn windowing_owner(&self) -> Option<Rc<dyn crate::WindowingOwner>> {
622        None
623    }
624
625    /// The platform's own font lookup: faces by family name and by codepoint, the way
626    /// Flutter's engine asks the OS. `None` for a host without one; text then shapes only
627    /// against fonts the application registers.
628    fn font_source(&self) -> Option<Box<dyn FontSource>> {
629        None
630    }
631
632    /// Shows a system cursor for a pointing device (Flutter's `activateSystemCursor`
633    /// message on `SystemChannels.mouseCursor`).
634    ///
635    /// Defaults to nothing: a host without a system cursor ignores the request.
636    fn activate_system_cursor(&self, device: i64, kind: SystemMouseCursorKind) {
637        let _ = (device, kind);
638    }
639
640    /// The restoration data the host kept for this application (the `get` message of
641    /// Flutter's `SystemChannels.restoration`).
642    ///
643    /// Defaults to `None`, Dart's null reply: a host that cannot store restoration data
644    /// leaves state restoration turned off.
645    fn restoration_get(&self) -> Option<RestorationUpdate> {
646        None
647    }
648
649    /// Hands the host the current restoration data (the `put` message of Flutter's
650    /// `SystemChannels.restoration`), which keeps it until the operating system asks for
651    /// it.
652    ///
653    /// Defaults to dropping it: a host without state restoration has nowhere to put it.
654    fn restoration_put(&self, data: RestorationMap) {
655        let _ = data;
656    }
657}
658
659impl dyn Platform {
660    /// The host as its own type, for what it offers beyond the interface.
661    pub fn downcast_ref<T: Platform>(&self) -> Option<&T> {
662        (self as &dyn Any).downcast_ref::<T>()
663    }
664}
665
666/// Platform for hand-pumped tests.
667pub struct InertPlatform;
668
669impl Platform for InertPlatform {
670    /// Android, which is what Flutter's own test binding defaults
671    /// `debugDefaultTargetPlatform` to: a bare App has no host to speak of,
672    /// and a deterministic answer beats the machine's.
673    fn target_platform(&self) -> TargetPlatform {
674        TargetPlatform::Android
675    }
676
677    fn request_frame(&self) {}
678
679    fn now(&self) -> Instant {
680        Instant::now()
681    }
682
683    fn wake_at(&self, _deadline: Instant) {}
684
685    fn views(&self) -> Vec<ViewRef> {
686        Vec::new()
687    }
688
689    fn view(&self, _id: ViewId) -> Option<ViewRef> {
690        None
691    }
692
693    fn implicit_view(&self) -> Option<ViewRef> {
694        None
695    }
696}
697
698/// One complete engine frame delivered to the client.
699#[derive(Clone, Copy, Debug, PartialEq, Eq)]
700pub struct Frame {
701    pub elapsed: Duration,
702}
703
704#[cfg(test)]
705mod tests {
706    use super::{Brightness, InertPlatform, Platform, TargetPlatform};
707
708    #[test]
709    fn inert_platform_is_android() {
710        assert_eq!(InertPlatform.target_platform(), TargetPlatform::Android);
711    }
712
713    #[test]
714    fn inert_platform_brightness_is_light() {
715        assert_eq!(InertPlatform.platform_brightness(), Brightness::Light);
716    }
717
718    #[test]
719    fn a_platform_downcasts_to_its_own_type_and_no_other() {
720        struct Other;
721        impl Platform for Other {
722            fn target_platform(&self) -> TargetPlatform {
723                TargetPlatform::Linux
724            }
725            fn request_frame(&self) {}
726            fn now(&self) -> super::Instant {
727                super::Instant::now()
728            }
729            fn wake_at(&self, _deadline: super::Instant) {}
730            fn views(&self) -> Vec<super::ViewRef> {
731                Vec::new()
732            }
733            fn view(&self, _id: super::ViewId) -> Option<super::ViewRef> {
734                None
735            }
736            fn implicit_view(&self) -> Option<super::ViewRef> {
737                None
738            }
739        }
740        let platform: super::PlatformRef = std::rc::Rc::new(InertPlatform);
741        assert!(platform.downcast_ref::<InertPlatform>().is_some());
742        assert!(platform.downcast_ref::<Other>().is_none());
743    }
744}