inset-embedder 0.19.0

dart:ui host traits and value types for Inset
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
//! Host capabilities the framework calls at runtime.
//!
//! Unlike Dart's isolate-global `PlatformDispatcher`, this object is supplied
//! by the embedder and held by `App`. Incoming host events travel through
//! `EmbedderClient`, so this interface contains requests and state only.

use std::any::Any;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;

use web_time::Instant;

use crate::clipboard::Clipboard;
use crate::file_dialogs::FileDialogs;
use crate::fonts::FontSource;
use crate::geometry::{Color, Offset, Rect};
use crate::menus::{DockMenu, MenuBar, PopupMenus, TrayIcons};
use crate::notifications::Notifications;
use crate::opener::Opener;
use crate::restoration::{RestorationMap, RestorationUpdate};
use crate::system_context_menu::SystemContextMenuItem;
use crate::{View, ViewId};

pub type PlatformRef = Rc<dyn Platform>;

pub type ViewRef = Rc<dyn View>;

/// Specifies a description of the application that is pertinent to the
/// embedder's application switcher (also known as "recent tasks") user
/// interface.
///
/// Flutter counterpart: `ApplicationSwitcherDescription` (`services/system_chrome.dart`).
/// It lives here because [`Platform`] names it.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ApplicationSwitcherDescription {
    /// A label and description of the current state of the application.
    pub label: Option<String>,
    /// The application's primary color, as a 32-bit ARGB value.
    ///
    /// This may influence the color that the operating system uses to represent
    /// the application.
    pub primary_color: Option<u32>,
}

/// Specifies a system overlay style for a portion of the app.
///
/// Flutter counterpart: `SystemUiOverlayStyle` (`services/system_chrome.dart`).
/// It lives here because [`Platform`] names it.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SystemUiOverlayStyle {
    /// The color of the system bottom navigation bar.
    ///
    /// Only honored in Android versions O and greater.
    pub system_navigation_bar_color: Option<Color>,

    /// The color of the divider between the system's bottom navigation bar and the app's
    /// content.
    ///
    /// Only honored in Android versions P and greater.
    pub system_navigation_bar_divider_color: Option<Color>,

    /// The brightness of the system navigation bar icons.
    ///
    /// Only honored in Android versions O and greater.
    /// When set to [`Brightness::Light`], the system navigation bar icons are light.
    /// When set to [`Brightness::Dark`], the system navigation bar icons are dark.
    pub system_navigation_bar_icon_brightness: Option<Brightness>,

    /// Overrides the contrast enforcement when setting a transparent navigation bar.
    ///
    /// When setting a transparent navigation bar in SDK 29+, or Android 10 and up, a
    /// translucent body scrim may be applied behind the button navigation bar to ensure
    /// contrast with buttons and the background of the application.
    ///
    /// SDK 28-, or Android P and lower, will not apply this body scrim.
    ///
    /// Setting this to false overrides the default body scrim.
    pub system_navigation_bar_contrast_enforced: Option<bool>,

    /// The color of top status bar.
    ///
    /// Only honored in Android version M and greater.
    pub status_bar_color: Option<Color>,

    /// The brightness of top status bar.
    ///
    /// Only honored in iOS.
    pub status_bar_brightness: Option<Brightness>,

    /// The brightness of the top status bar icons.
    ///
    /// Only honored in Android version M and greater.
    pub status_bar_icon_brightness: Option<Brightness>,

    /// Overrides the contrast enforcement when setting a transparent status bar.
    ///
    /// When setting a transparent status bar in SDK 29+, or Android 10 and up, a translucent
    /// body scrim may be applied to ensure contrast with icons and the background of the
    /// application.
    ///
    /// SDK 28-, or Android P and lower, will not apply this body scrim.
    ///
    /// Setting this to false overrides the default body scrim.
    pub system_status_bar_contrast_enforced: Option<bool>,
}

impl SystemUiOverlayStyle {
    /// System overlays should be drawn with a light color. Intended for applications with a
    /// dark background.
    pub const LIGHT: SystemUiOverlayStyle = SystemUiOverlayStyle {
        system_navigation_bar_color: Some(Color::new(0xFF000000)),
        system_navigation_bar_icon_brightness: Some(Brightness::Light),
        status_bar_icon_brightness: Some(Brightness::Light),
        status_bar_brightness: Some(Brightness::Dark),
        system_navigation_bar_divider_color: None,
        system_navigation_bar_contrast_enforced: None,
        status_bar_color: None,
        system_status_bar_contrast_enforced: None,
    };

    /// System overlays should be drawn with a dark color. Intended for applications with a
    /// light background.
    pub const DARK: SystemUiOverlayStyle = SystemUiOverlayStyle {
        system_navigation_bar_color: Some(Color::new(0xFF000000)),
        system_navigation_bar_icon_brightness: Some(Brightness::Light),
        status_bar_icon_brightness: Some(Brightness::Dark),
        status_bar_brightness: Some(Brightness::Light),
        system_navigation_bar_divider_color: None,
        system_navigation_bar_contrast_enforced: None,
        status_bar_color: None,
        system_status_bar_contrast_enforced: None,
    };

    /// Creates a new [`SystemUiOverlayStyle`]; Dart's named arguments are the setters.
    pub const fn new() -> SystemUiOverlayStyle {
        SystemUiOverlayStyle {
            system_navigation_bar_color: None,
            system_navigation_bar_divider_color: None,
            system_navigation_bar_icon_brightness: None,
            system_navigation_bar_contrast_enforced: None,
            status_bar_color: None,
            status_bar_brightness: None,
            status_bar_icon_brightness: None,
            system_status_bar_contrast_enforced: None,
        }
    }

    /// Dart `SystemUiOverlayStyle(systemNavigationBarColor:)`.
    pub fn system_navigation_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
        self.system_navigation_bar_color = Some(color);
        self
    }

    /// Dart `SystemUiOverlayStyle(systemNavigationBarDividerColor:)`.
    pub fn system_navigation_bar_divider_color(mut self, color: Color) -> SystemUiOverlayStyle {
        self.system_navigation_bar_divider_color = Some(color);
        self
    }

    /// Dart `SystemUiOverlayStyle(systemNavigationBarIconBrightness:)`.
    pub fn system_navigation_bar_icon_brightness(
        mut self,
        brightness: Brightness,
    ) -> SystemUiOverlayStyle {
        self.system_navigation_bar_icon_brightness = Some(brightness);
        self
    }

    /// Dart `SystemUiOverlayStyle(systemNavigationBarContrastEnforced:)`.
    pub fn system_navigation_bar_contrast_enforced(
        mut self,
        enforced: bool,
    ) -> SystemUiOverlayStyle {
        self.system_navigation_bar_contrast_enforced = Some(enforced);
        self
    }

    /// Dart `SystemUiOverlayStyle(statusBarColor:)`.
    pub fn status_bar_color(mut self, color: Color) -> SystemUiOverlayStyle {
        self.status_bar_color = Some(color);
        self
    }

    /// Dart `SystemUiOverlayStyle(statusBarBrightness:)`.
    pub fn status_bar_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
        self.status_bar_brightness = Some(brightness);
        self
    }

    /// Dart `SystemUiOverlayStyle(statusBarIconBrightness:)`.
    pub fn status_bar_icon_brightness(mut self, brightness: Brightness) -> SystemUiOverlayStyle {
        self.status_bar_icon_brightness = Some(brightness);
        self
    }

    /// Dart `SystemUiOverlayStyle(systemStatusBarContrastEnforced:)`.
    pub fn system_status_bar_contrast_enforced(mut self, enforced: bool) -> SystemUiOverlayStyle {
        self.system_status_bar_contrast_enforced = Some(enforced);
        self
    }

    /// Creates a copy of this theme with the given fields replaced with new values.
    pub fn copy_with(&self) -> SystemUiOverlayStyle {
        *self
    }
}

/// The kind of haptic feedback to play.
///
/// Flutter counterpart: the `HapticFeedbackType.*` argument Dart sends with the
/// `HapticFeedback.vibrate` message on `SystemChannels.platform`. It lives here
/// because [`Platform`] names it.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HapticFeedbackType {
    /// Vibration for a short duration, the argument-less `HapticFeedback.vibrate`.
    Vibrate,
    /// A collision impact with a light mass.
    LightImpact,
    /// A collision impact with a medium mass.
    MediumImpact,
    /// A collision impact with a heavy mass.
    HeavyImpact,
    /// A selection changing through discrete values.
    SelectionClick,
    /// A task or action completed successfully.
    SuccessNotification,
    /// A task or action produced a warning.
    WarningNotification,
    /// A task or action failed.
    ErrorNotification,
}

/// Describes the contrast of a theme or color palette.
///
/// Flutter counterpart: `Brightness` (`dart:ui` `window.dart`).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Brightness {
    /// The color is dark and will require a light text color to achieve readable
    /// contrast.
    ///
    /// For example, the color might be dark grey, requiring white text.
    Dark,

    /// The color is light and will require a dark text color to achieve readable
    /// contrast.
    ///
    /// For example, the color might be bright white, requiring black text.
    Light,
}

/// States that an application can be in once it is running.
///
/// States not supported on a platform are synthesized so the state machine stays the same
/// everywhere. For example, [`Hidden`](AppLifecycleState::Hidden) is synthesized on mobile
/// before [`Paused`](AppLifecycleState::Paused) when coming from
/// [`Inactive`](AppLifecycleState::Inactive), and before [`Inactive`](AppLifecycleState::Inactive)
/// when coming from [`Paused`](AppLifecycleState::Paused).
///
/// The values below are listed in the expected state machine transition order. The initial
/// state is [`Detached`](AppLifecycleState::Detached).
///
/// Flutter counterpart: `AppLifecycleState` (`dart:ui` `platform_dispatcher.dart`).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AppLifecycleState {
    /// The application is still hosted by a Flutter engine but is detached from any host
    /// views.
    ///
    /// The application defaults to this state before it initializes, and can be in this
    /// state (applicable on Android, iOS, and web) after all views have been detached.
    ///
    /// When the application is in this state, the engine is running without a view.
    ///
    /// This state is only entered on iOS, Android, and web, although on all platforms it is
    /// the default state before the application begins running.
    Detached,

    /// On all platforms, this state indicates that the application is in the default
    /// running mode for a running application that has input focus and is visible.
    ///
    /// On Android, this state corresponds to the Flutter host view having focus while in
    /// Android's "resumed" state. It is possible for the Flutter app to be in the
    /// [`Inactive`](AppLifecycleState::Inactive) state while still being in Android's
    /// "onResume" state if the app has lost focus but hasn't had `Activity.onPause` called
    /// on it.
    ///
    /// On iOS and macOS, this corresponds to the app running in the foreground active
    /// state.
    Resumed,

    /// At least one view of the application is visible, but none have input focus. The
    /// application is otherwise running normally.
    ///
    /// On non-web desktop platforms, this corresponds to an application that is not in the
    /// foreground, but still has visible windows.
    ///
    /// On the web, this corresponds to an application that is running in a window or tab
    /// that does not have input focus.
    ///
    /// On iOS and macOS, this state corresponds to the Flutter host view running in the
    /// foreground inactive state. Apps transition to this state when in a phone call, when
    /// responding to a TouchID request, when entering the app switcher or the control
    /// center, or when the UIViewController hosting the Flutter app is transitioning.
    ///
    /// On Android, this corresponds to the Flutter host view running in Android's paused
    /// state (i.e. `Activity.onPause` has been called), or in Android's "resumed" state
    /// (i.e. `Activity.onResume` has been called) but does not have window focus.
    ///
    /// On Android and iOS, apps in this state should assume that they may be
    /// [`Hidden`](AppLifecycleState::Hidden) and [`Paused`](AppLifecycleState::Paused) at any
    /// time.
    Inactive,

    /// All views of an application are hidden, either because the application is about to
    /// be paused (on iOS and Android), or because it has been minimized or placed on a
    /// desktop that is no longer visible (on non-web desktop), or is running in a window or
    /// tab that is no longer visible (on the web).
    ///
    /// On iOS and Android, in order to keep the state machine the same on all platforms, a
    /// transition to this state is synthesized before the [`Paused`](AppLifecycleState::Paused)
    /// state is entered when coming from [`Inactive`](AppLifecycleState::Inactive), and
    /// before the [`Inactive`](AppLifecycleState::Inactive) state is entered when coming
    /// from [`Paused`](AppLifecycleState::Paused). This allows cross-platform implementations
    /// that want to know when an app is conceptually "hidden" to only write one handler.
    Hidden,

    /// The application is not currently visible to the user, and not responding to user
    /// input.
    ///
    /// When the application is in this state, the engine will not call
    /// `PlatformDispatcher.onBeginFrame` and `PlatformDispatcher.onDrawFrame`.
    ///
    /// This state is only entered on iOS and Android.
    Paused,
}

/// The possible responses to a request to exit the application.
///
/// The request is typically responded to by creating an `AppLifecycleListener` and supplying
/// an `on_exit_requested` callback, or by overriding
/// `WidgetsBindingObserver::did_request_app_exit`.
///
/// Flutter counterpart: `AppExitResponse` (`dart:ui` `platform_dispatcher.dart`).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AppExitResponse {
    /// Exiting the application can proceed.
    Exit,

    /// Cancel the exit: do not exit the application.
    Cancel,
}

/// The answer to one request to exit, which the host made through
/// [`EmbedderClient::request_app_exit`](crate::EmbedderClient::request_app_exit): Flutter's reply
/// to `System.requestAppExit`. It is answered once; dropped unanswered, it answers
/// [`AppExitResponse::Exit`], since an application that lets go of the question has nothing to
/// keep.
pub struct AppExitReply(Option<Box<dyn FnOnce(AppExitResponse)>>);

impl AppExitReply {
    /// A reply that hands its answer to `send`. The answer is given inside a call into the
    /// client, so `send` may only post it to the host's own loop, never call the client.
    pub fn new(send: impl FnOnce(AppExitResponse) + 'static) -> AppExitReply {
        AppExitReply(Some(Box::new(send)))
    }

    pub fn answer(mut self, response: AppExitResponse) {
        self.send(response);
    }

    fn send(&mut self, response: AppExitResponse) {
        if let Some(send) = self.0.take() {
            send(response);
        }
    }
}

impl Drop for AppExitReply {
    fn drop(&mut self) {
        self.send(AppExitResponse::Exit);
    }
}

impl std::fmt::Debug for AppExitReply {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AppExitReply")
            .field("answered", &self.0.is_none())
            .finish()
    }
}

/// The type of application exit to perform when calling `ServicesBinding::exit_application`.
///
/// Flutter counterpart: `AppExitType` (`dart:ui` `platform_dispatcher.dart`).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AppExitType {
    /// Requests that the application start an orderly exit, sending a request back to the
    /// framework through the `WidgetsBinding`. If that responds with [`AppExitResponse::Exit`],
    /// then proceed with the same steps as a [`Required`](AppExitType::Required) exit. If that
    /// responds with [`AppExitResponse::Cancel`], then the exit request is canceled and the
    /// application continues executing normally.
    Cancelable,

    /// A non-cancelable orderly exit request. The engine will shut down the engine and call
    /// the native UI toolkit's exit API.
    ///
    /// If you need an even faster and more dangerous exit, then call
    /// [`std::process::exit`] directly, and even the native toolkit's exit API won't be
    /// called. This is quite dangerous, though, since it's possible that the engine will
    /// crash because it hasn't been properly shut down, causing the app to crash on exit.
    Required,
}

/// The platform that user interaction should adapt to target.
///
/// Flutter counterpart: `TargetPlatform` (`foundation/platform.dart`). Flutter
/// reads the current one from a library global that consults `dart:io`. Here it
/// is a plain value type, and the App-owned [`Platform`] reports which one it
/// is, so two Apps in one process can differ.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TargetPlatform {
    /// Android: <https://www.android.com/>
    Android,
    /// Fuchsia: <https://fuchsia.dev/fuchsia-src/concepts>
    Fuchsia,
    /// iOS: <https://www.apple.com/ios/>
    IOS,
    /// Linux: <https://www.linux.org>
    Linux,
    /// macOS: <https://www.apple.com/macos>
    MacOS,
    /// Windows: <https://www.windows.com>
    Windows,
}

/// The OS this build targets, as Flutter names it; `None` for one Flutter does not name, such as
/// a web page's or a WebAssembly component's.
const THIS_OS: Option<TargetPlatform> = if cfg!(target_os = "android") {
    Some(TargetPlatform::Android)
} else if cfg!(target_os = "fuchsia") {
    Some(TargetPlatform::Fuchsia)
} else if cfg!(target_os = "ios") {
    Some(TargetPlatform::IOS)
} else if cfg!(target_os = "linux") {
    Some(TargetPlatform::Linux)
} else if cfg!(target_os = "macos") {
    Some(TargetPlatform::MacOS)
} else if cfg!(target_os = "windows") {
    Some(TargetPlatform::Windows)
} else {
    None
};

/// The host's threads as any thread reaches them: the two things the framework asks of them.
/// gpui's `PlatformDispatcher`. Flutter's embedder API has the first as `post_task_callback`,
/// with its target time, and keeps the second — the engine's own worker threads — to itself.
pub trait Dispatcher: Send + Sync {
    /// Sets when the host should next wake the application for a timer.
    ///
    /// The host keeps one such time. When it arrives, the host calls
    /// [`EmbedderClient::wake`](crate::EmbedderClient::wake) on the main thread.
    ///
    /// `None` means no timer is waiting, so the host should not wake for one.
    ///
    /// Each call replaces the previous one. The new time can be later than the old one, not
    /// only earlier. Call this only from the thread the application runs on.
    fn wake_at(&self, timer_wakeup: Option<Instant>);

    /// Asks the host to wake the application as soon as it can, because a task became ready
    /// to run.
    ///
    /// The next wake clears this request. It does not change the timer wakeup set by
    /// [`wake_at`](Self::wake_at), which is still waiting for its own time. Call this from
    /// any thread.
    fn wake_now(&self);

    /// Runs `work` off the main thread, on whatever the host has for that, such as a system
    /// queue or a worker thread. The framework's `run_in_background` sends its work here.
    fn dispatch(&self, work: Box<dyn FnOnce() + Send>);
}

/// A dispatcher for tests, which pump the application by hand: it never wakes the application,
/// and work runs at once on the calling thread, so its result is there at the next checkpoint.
pub struct InertDispatcher;

impl Dispatcher for InertDispatcher {
    fn wake_at(&self, _timer_wakeup: Option<Instant>) {}

    fn wake_now(&self) {}

    fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
        work();
    }
}

/// The long-lived host object held by the application.
///
/// Implementations must queue requests and return. They must not synchronously
/// re-enter the [`EmbedderClient`](crate::EmbedderClient) while `App` is active.
///
/// A host may offer more than this interface — a picture from a buffer only its system
/// has, a handle to its renderer. An app that knows its host reaches that through
/// [`downcast_ref`](dyn Platform::downcast_ref); the interface itself names nothing of any
/// one system.
pub trait Platform: Any {
    /// Which host this is, for behaviour that follows platform convention
    /// (Flutter `defaultTargetPlatform`).
    ///
    /// Required rather than defaulted: an embedder must say what it is, and a
    /// default would let one silently claim the wrong conventions.
    fn target_platform(&self) -> TargetPlatform;

    /// Asks for a frame of `view`: Flutter's `PlatformDispatcher.scheduleFrame`, for one view.
    ///
    /// `view` is in the next [`EmbedderClient::frame`](crate::EmbedderClient::frame) of the
    /// screen it is on once its window is in sight; the host holds the request while the window
    /// is minimized or occluded. Asked again before then, it is in that refresh once.
    fn request_frame(&self, view: ViewId);

    /// The host clock used for frame and timer timestamps.
    fn now(&self) -> Instant;

    /// The host's threads, for waking the application or running work off the main thread: see
    /// [`Dispatcher`].
    fn dispatcher(&self) -> Arc<dyn Dispatcher>;

    /// Every view the app has now: those the platform gave it, such as a phone's screen or an
    /// embedder's views, and those it asked for. An app may run with none, as one on a desktop
    /// does until it asks.
    fn views(&self) -> Vec<ViewRef>;

    /// Looks up one of the app's views.
    fn view(&self, id: ViewId) -> Option<ViewRef>;

    /// Asks for a view in a window configured as `config`, carrying `initial_data`
    /// ([`View::initial_data`](crate::View::initial_data)), such as which screen to show. A
    /// desktop makes the window; a platform that gives the app its views and makes none, a phone
    /// or an embedder, answers [`ViewError::Unsupported`](crate::ViewError::Unsupported). The view
    /// arrives later, through `EmbedderClient::view_added`, carrying the id answered here; a
    /// request that fails on the way is reported through `EmbedderClient::view_request_failed`.
    fn request_view(
        &self,
        config: crate::WindowConfig,
        initial_data: Option<String>,
    ) -> Result<crate::RequestId, crate::ViewError> {
        let _ = (config, initial_data);
        Err(crate::ViewError::Unsupported)
    }

    /// The full system-reported supported locales of the device (Flutter
    /// `PlatformDispatcher.locales`), in order of preference; empty until the host
    /// reports them.
    fn locales(&self) -> Vec<crate::Locale> {
        Vec::new()
    }

    /// Tells the host which locale the application resolved to (Flutter
    /// `PlatformDispatcher.setApplicationLocale`); the default drops it.
    fn set_application_locale(&self, locale: &crate::Locale) {
        let _ = locale;
    }

    /// The platform's light/dark preference (Flutter
    /// `PlatformDispatcher.platformBrightness`).
    ///
    /// Defaults to [`Brightness::Light`], matching Flutter's view configuration
    /// default. A live host that can see the OS theme overrides this.
    fn platform_brightness(&self) -> Brightness {
        Brightness::Light
    }

    /// The route the application was started with (Flutter
    /// `PlatformDispatcher.defaultRouteName`).
    ///
    /// Defaults to `"/"`, Flutter's value for a host that was not asked to open a
    /// particular route; a host that receives a deep link answers it here.
    fn default_route_name(&self) -> String {
        String::from("/")
    }

    /// The lifecycle state immediately after the application started (Flutter
    /// `PlatformDispatcher.initialLifecycleState`).
    ///
    /// This property will not be updated as the lifecycle changes: a host reports each change
    /// through `EmbedderClient::app_lifecycle_state_changed`.
    ///
    /// It is used to initialize `SchedulerBinding::lifecycle_state` at startup. Defaults to
    /// `None`, Flutter's empty string: a host that has no state to report yet.
    fn initial_lifecycle_state(&self) -> Option<AppLifecycleState> {
        None
    }

    /// The platform's own font lookup: faces by family name and by codepoint, the way
    /// Flutter's engine asks the OS. `None` for a host without one; text then shapes only
    /// against fonts the application registers.
    fn font_source(&self) -> Option<Box<dyn FontSource>> {
        None
    }

    /// Flutter's `txt::GetDefaultFontFamilies()`: the families text uses when its style names
    /// none, first match first.
    ///
    /// Defaults to the engine's answer on the OS this build targets
    /// ([`default_font_families`](crate::default_font_families)). A host that learns its OS at
    /// run time answers for that OS, and one whose fonts are not the OS's, as a web page's are
    /// not, answers its own.
    fn default_font_families(&self) -> Vec<String> {
        match THIS_OS {
            Some(os) => crate::default_font_families(os),
            // `txt/platform.cc`: the engine's answer on an OS it has no file of its own for.
            None => vec![String::from("Arial")],
        }
    }

    /// Open an encoded image, answering with a codec for its frames.
    ///
    /// Flutter dart:ui `instantiateImageCodec`. The host decodes with whatever its platform
    /// provides and uploads each frame, so the framework never sees pixels and the set of
    /// readable formats is the platform's rather than the framework's.
    ///
    /// The default answers a failure at once, so a host with no decoder leaves nobody waiting.
    fn open_image_codec(&self, bytes: std::sync::Arc<[u8]>) -> crate::ImageCodecFuture {
        let _ = bytes;
        Box::pin(std::future::ready(Err(crate::ImageDecodeError::NoDecoder)))
    }

    /// An image from pixels the app already holds, with no codec in between: Flutter's
    /// `decodeImageFromPixels`. `None` when the host has no renderer to put it on yet.
    fn import_pixels(&self, pixels: valo::PixelBuffer) -> Option<crate::Image> {
        let _ = pixels;
        None
    }

    /// The host's compositor, where it has one: composition layers shown beside the
    /// framework's picture. `None` on the web, on X11, on the inert host, and on an
    /// embedder that chose not to have one.
    fn compositor(&self) -> Option<Arc<dyn crate::Compositor>> {
        None
    }

    /// Flutter's `VsyncWaiter`, for any screen: the refreshes the host draws the framework's
    /// frames at, for a producer that paces by them, as a composition layer's does. `None` for a
    /// host without screen ticks.
    fn vsync_waiter(&self) -> Option<Arc<dyn crate::VsyncWaiter>> {
        None
    }

    /// The screens the app's views are on, and where the pointer is on them (Flutter
    /// `PlatformDispatcher.displays`), or `None` where the host has none to report. Every host
    /// with screens answers, a phone with its one included; a view's metrics name the screen it
    /// is on, and a host that hears the screens change says so through
    /// `EmbedderClient::screens_changed`.
    fn screens(&self) -> Option<&dyn Screens> {
        None
    }

    /// The system clipboard, or `None` where the host has none.
    fn clipboard(&self) -> Option<&dyn Clipboard> {
        None
    }

    /// The system UI drawn around the app, or `None` where the host has none.
    fn system_chrome(&self) -> Option<&dyn SystemChrome> {
        None
    }

    /// Haptic feedback on the device, or `None` where the host has none.
    fn haptics(&self) -> Option<&dyn Haptics> {
        None
    }

    /// The system's own menu over a text selection, or `None` where the host has none.
    fn system_context_menu(&self) -> Option<&dyn SystemContextMenu> {
        None
    }

    /// The app's menu bar, or `None` where the system has none and the app draws its own, as on
    /// Windows, Linux and phones (`menus`).
    fn menu_bar(&self) -> Option<&dyn MenuBar> {
        None
    }

    /// Menus the system opens at a point in a view, or `None` where the system has none and the
    /// framework draws them.
    fn popup_menus(&self) -> Option<&dyn PopupMenus> {
        None
    }

    /// The app's icons in the system's tray, or `None` where the system has no tray.
    fn tray_icons(&self) -> Option<&dyn TrayIcons> {
        None
    }

    /// The system's file dialogs, or `None` where the system has none.
    fn file_dialogs(&self) -> Option<&dyn FileDialogs> {
        None
    }

    /// The system's media players, or `None` where the host has none.
    fn players(&self) -> Option<&dyn crate::Players> {
        None
    }

    /// The system's cameras, or `None` where the host has none. A host with cameras answers
    /// one with none attached as an empty list.
    fn cameras(&self) -> Option<&dyn crate::Cameras> {
        None
    }

    /// The system's frame readers, which a player or a camera hands its frames to, or `None`
    /// where the system hands out no frames.
    fn video_frame_readers(&self) -> Option<&dyn crate::VideoFrameReaders> {
        None
    }

    /// The user's caption settings, or `None` where the system keeps none, as Linux and the web
    /// keep none.
    fn caption_settings(&self) -> Option<&dyn crate::CaptionSettings> {
        None
    }

    /// What the app embeds views of its own with, on layers it makes and places, or `None` where
    /// the host cannot draw a view into such a layer: everywhere but macOS today.
    fn view_embedder(&self) -> Option<&dyn crate::ViewEmbedder> {
        None
    }

    /// The menu of the app's Dock icon, or `None` where the system has no Dock.
    fn dock_menu(&self) -> Option<&dyn DockMenu> {
        None
    }

    /// The system services a selection toolbar offers, or `None` where the host has none.
    fn text_services(&self) -> Option<&dyn TextServices> {
        None
    }

    /// Application state kept across relaunches, or `None` where the host has none.
    fn restoration(&self) -> Option<&dyn Restoration> {
        None
    }

    /// Quitting at the application's own request, or `None` where the host cannot quit
    /// itself: a phone, a browser tab.
    fn app_exit(&self) -> Option<&dyn AppExit> {
        None
    }

    /// The system's way to open links and files in other apps, or `None` where it has none.
    fn opener(&self) -> Option<&dyn Opener> {
        None
    }

    /// The system's notifications, or `None` where it has none for the app.
    fn notifications(&self) -> Option<&dyn Notifications> {
        None
    }
}

impl dyn Platform {
    /// The host as its own type, for what it offers beyond the interface.
    pub fn downcast_ref<T: Platform>(&self) -> Option<&T> {
        (self as &dyn Any).downcast_ref::<T>()
    }
}

/// Flutter's `SystemChrome`: the system UI around the app.
pub trait SystemChrome {
    /// Styles the system overlays the host draws over the app — the status bar, and on
    /// Android the system navigation bar (Flutter `SystemChrome.setSystemUIOverlayStyle`).
    fn set_overlay_style(&self, style: &SystemUiOverlayStyle);

    /// Describes the app in the host's application switcher (Flutter
    /// `SystemChrome.setApplicationSwitcherDescription`).
    fn set_application_switcher_description(&self, description: &ApplicationSwitcherDescription);
}

/// Flutter's `HapticFeedback`.
pub trait Haptics {
    /// Plays haptic feedback on the device (Flutter's `HapticFeedback.vibrate`
    /// message on `SystemChannels.platform`).
    fn feedback(&self, kind: HapticFeedbackType);
}

/// Flutter's `SystemContextMenu`: the system's own menu over a text selection.
pub trait SystemContextMenu {
    /// Shows the system context menu (Flutter's `ContextMenu.showSystemContextMenu`).
    ///
    /// `items` is `None` for the deprecated call that lets the platform pick default
    /// buttons. Built-in items are performed by the host; custom items are reported
    /// through [`crate::EmbedderClient::custom_context_menu_action`].
    fn show(&self, target_rect: Rect, items: Option<&[SystemContextMenuItem]>);

    /// Hides the system context menu (Flutter's `ContextMenu.hideSystemContextMenu`).
    fn hide(&self);
}

/// The screens the app's views are on: what drawing on each needs, as Flutter's
/// `PlatformDispatcher.displays` lists them, and where each is, for an app that places a window
/// against something on them, such as an edge or its own icon, which Flutter's `Display` has no
/// place for. Positions are in the coordinates of [`HostWindow::frame`](crate::HostWindow::frame):
/// logical points from the top-left corner of the main screen, y growing down.
pub trait Screens {
    /// The screens, the main one first: the one positions start from.
    fn screens(&self) -> Vec<Screen>;

    /// Where the pointer is now, over a window of the app or not. `None` when the host cannot
    /// say.
    fn pointer_location(&self) -> Option<Offset>;
}

/// Names a screen: Flutter's `Display.id`, which the embedder API calls a
/// `FlutterEngineDisplayId`. Each host numbers its screens its own way; a screen keeps its
/// number while it stays connected.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScreenId(pub u64);

/// A monitor: where it is (the frames, for placing windows) and what drawing on it needs
/// (Flutter's `Display`: `id`, `devicePixelRatio`, `refreshRate`; `Display.size` is the frame's
/// size at `scale`).
#[derive(Clone, Debug, PartialEq)]
pub struct Screen {
    /// Flutter `Display.id`; stable while the screen stays connected.
    pub id: ScreenId,
    /// What the system calls it: "Built-in Retina Display". Empty where the host has no name
    /// for it.
    pub name: String,
    /// All of it, in points, placed on the desktop.
    pub frame: Rect,
    /// What the system's own bars leave free: the menu bar and the Dock, or the taskbar. The
    /// whole frame where the host cannot say.
    pub visible_frame: Rect,
    /// Flutter `Display.devicePixelRatio`: physical pixels per point, as on every view on the
    /// screen.
    pub scale: f64,
    /// Flutter `Display.refreshRate`, in hertz; none where the host cannot say.
    pub refresh_rate: Option<f64>,
}

/// The text services a selection toolbar offers: Flutter's LookUp, SearchWeb and Share
/// platform-channel calls.
pub trait TextServices {
    /// Looks up the given text (Flutter's `LookUp.invoke`).
    fn look_up(&self, text: &str);

    /// Searches the web for the given text (Flutter's `SearchWeb.invoke`).
    fn search_web(&self, text: &str);

    /// Shares the given text (Flutter's `Share.invoke`).
    fn share(&self, text: &str);
}

/// Flutter's restoration channel: state kept across relaunches.
pub trait Restoration {
    /// The restoration data the host kept for this application (the `get` message of
    /// Flutter's `SystemChannels.restoration`).
    fn get(&self) -> Option<RestorationUpdate>;

    /// Hands the host the current restoration data (the `put` message of Flutter's
    /// `SystemChannels.restoration`), which keeps it until the operating system asks for it.
    fn put(&self, data: RestorationMap);
}

/// The application quitting itself: the `System.exitApplication` message of Flutter's
/// `SystemChannels.platform`, for a required exit. A cancelable exit never reaches the host: the
/// framework asks its own listeners first (`ServicesBinding::exit_application`), and quits here
/// once they agree.
pub trait AppExit {
    /// Quits the application the way the system quits one, as Flutter's embedders do, on a
    /// later turn of the host's loop, never inside this call.
    ///
    /// `exit_code` is the process's exit status where the system takes one; Flutter's macOS
    /// and Linux embedders ignore it, and a host may too.
    fn quit(&self, exit_code: i32);
}

/// Platform for hand-pumped tests.
pub struct InertPlatform;

impl Platform for InertPlatform {
    /// Android, which is what Flutter's own test binding defaults
    /// `debugDefaultTargetPlatform` to: a bare App has no host to speak of,
    /// and a deterministic answer beats the machine's.
    fn target_platform(&self) -> TargetPlatform {
        TargetPlatform::Android
    }

    fn request_frame(&self, _view: ViewId) {}

    fn now(&self) -> Instant {
        Instant::now()
    }

    fn dispatcher(&self) -> Arc<dyn Dispatcher> {
        Arc::new(InertDispatcher)
    }

    fn views(&self) -> Vec<ViewRef> {
        Vec::new()
    }

    fn view(&self, _id: ViewId) -> Option<ViewRef> {
        None
    }
}

/// A refresh of a screen, as the host delivers it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame {
    pub elapsed: Duration,
}

#[cfg(test)]
mod tests {
    use super::{Brightness, InertPlatform, Platform, TargetPlatform};

    #[test]
    fn inert_platform_is_android() {
        assert_eq!(InertPlatform.target_platform(), TargetPlatform::Android);
    }

    #[test]
    fn inert_platform_brightness_is_light() {
        assert_eq!(InertPlatform.platform_brightness(), Brightness::Light);
    }

    #[test]
    fn a_platform_downcasts_to_its_own_type_and_no_other() {
        struct Other;
        impl Platform for Other {
            fn target_platform(&self) -> TargetPlatform {
                TargetPlatform::Linux
            }
            fn request_frame(&self, _view: super::ViewId) {}
            fn now(&self) -> super::Instant {
                super::Instant::now()
            }
            fn dispatcher(&self) -> std::sync::Arc<dyn super::Dispatcher> {
                std::sync::Arc::new(super::InertDispatcher)
            }
            fn views(&self) -> Vec<super::ViewRef> {
                Vec::new()
            }
            fn view(&self, _id: super::ViewId) -> Option<super::ViewRef> {
                None
            }
        }
        let platform: super::PlatformRef = std::rc::Rc::new(InertPlatform);
        assert!(platform.downcast_ref::<InertPlatform>().is_some());
        assert!(platform.downcast_ref::<Other>().is_none());
    }
}