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}