Skip to main content

inset_embedder/
views.rs

1//! Flutter counterpart: dart:ui `FlutterView`. The host owns the native
2//! window; this stable handle exposes its identity and current configuration.
3
4use crate::{
5    Matrix4, Rect, ScreenId, Size, SystemMouseCursorKind, TextEditingValue, TextInputConfiguration,
6};
7
8/// Opaque id for one [`View`] (`FlutterView.viewId`).
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct ViewId(pub u64);
12
13/// An event for the engine to communicate view focus changes to the app.
14/// Flutter counterpart: `ViewFocusEvent` in `dart:ui/platform_dispatcher.dart`.
15#[derive(Clone, Copy, Debug)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct ViewFocusEvent {
18    /// The ID of the view that experienced a focus change.
19    pub view_id: ViewId,
20    /// The state focus changed to.
21    pub state: ViewFocusState,
22    /// The direction focus changed to.
23    pub direction: ViewFocusDirection,
24}
25
26/// Represents the focus state of a given view.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum ViewFocusState {
30    /// The view does not have platform focus.
31    Unfocused,
32    /// The view has platform focus.
33    Focused,
34}
35
36/// Represents the direction in which focus transitioned across views.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub enum ViewFocusDirection {
40    /// The transition did not have a direction, such as a programmatic request or focus loss.
41    Undefined,
42    /// The transition was forward, typically from pressing Tab.
43    Forward,
44    /// The transition was backward, typically from pressing Shift+Tab.
45    Backward,
46}
47
48/// Physical padding on each side of a view (`dart:ui` `ViewPadding`).
49#[derive(Clone, Copy, Debug, PartialEq)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub struct ViewPadding {
52    pub left: f64,
53    pub top: f64,
54    pub right: f64,
55    pub bottom: f64,
56}
57
58impl ViewPadding {
59    /// A view padding that has zeros for each edge.
60    pub const ZERO: ViewPadding = ViewPadding {
61        left: 0.0,
62        top: 0.0,
63        right: 0.0,
64        bottom: 0.0,
65    };
66}
67
68/// Immutable layout constraints for a view (`dart:ui` `ViewConstraints`).
69#[derive(Clone, Copy, Debug, PartialEq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub struct ViewConstraints {
72    pub min_width: f64,
73    pub max_width: f64,
74    pub min_height: f64,
75    pub max_height: f64,
76}
77
78impl ViewConstraints {
79    pub const fn new(
80        min_width: f64,
81        max_width: f64,
82        min_height: f64,
83        max_height: f64,
84    ) -> ViewConstraints {
85        ViewConstraints {
86            min_width,
87            max_width,
88            min_height,
89            max_height,
90        }
91    }
92
93    /// Constraints respected only by the given size.
94    pub const fn tight(width: f64, height: f64) -> ViewConstraints {
95        ViewConstraints {
96            min_width: width,
97            max_width: width,
98            min_height: height,
99            max_height: height,
100        }
101    }
102}
103
104impl Default for ViewConstraints {
105    fn default() -> ViewConstraints {
106        ViewConstraints {
107            min_width: 0.0,
108            max_width: f64::INFINITY,
109            min_height: 0.0,
110            max_height: f64::INFINITY,
111        }
112    }
113}
114
115/// Per-view geometry the host reports (`_ViewConfiguration` fields we use).
116#[derive(Clone, Copy, Debug, PartialEq)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118pub struct ViewMetrics {
119    pub physical_size: [f64; 2],
120    pub physical_constraints: ViewConstraints,
121    pub device_pixel_ratio: f64,
122    pub padding: ViewPadding,
123    pub view_padding: ViewPadding,
124    pub view_insets: ViewPadding,
125    /// The screen the view is on, one of `Platform::screens`: `_ViewConfiguration.displayId`,
126    /// which `FlutterView.display` looks up. `None` while it is on none the host can name, as a
127    /// view without a window is.
128    pub screen: Option<ScreenId>,
129}
130
131impl Default for ViewMetrics {
132    fn default() -> ViewMetrics {
133        ViewMetrics {
134            physical_size: [0.0, 0.0],
135            physical_constraints: ViewConstraints::tight(0.0, 0.0),
136            device_pixel_ratio: 1.0,
137            padding: ViewPadding::ZERO,
138            view_padding: ViewPadding::ZERO,
139            view_insets: ViewPadding::ZERO,
140            screen: None,
141        }
142    }
143}
144
145/// A `null` field indicates that the platform or view does not have a preference
146/// and the fallback constants should be used instead.
147///
148/// Flutter counterpart: `GestureSettings` (`dart:ui` `window.dart`).
149#[derive(Clone, Copy, Debug, Default, PartialEq)]
150pub struct GestureSettings {
151    /// The number of physical pixels a pointer is allowed to drift before it is
152    /// considered an intentional movement.
153    ///
154    /// If `None`, the framework's default touch slop configuration should be used
155    /// instead.
156    pub physical_touch_slop: Option<f64>,
157
158    /// The number of physical pixels that the first and second tap of a double tap
159    /// can drift apart to still be recognized as a double tap.
160    ///
161    /// If `None`, the framework's default double tap slop configuration should be used
162    /// instead.
163    pub physical_double_tap_slop: Option<f64>,
164}
165
166impl GestureSettings {
167    /// Create a new [`GestureSettings`] object from an existing value, overwriting
168    /// all of the provided fields.
169    pub fn copy_with(
170        self,
171        physical_touch_slop: Option<f64>,
172        physical_double_tap_slop: Option<f64>,
173    ) -> GestureSettings {
174        GestureSettings {
175            physical_touch_slop: physical_touch_slop.or(self.physical_touch_slop),
176            physical_double_tap_slop: physical_double_tap_slop.or(self.physical_double_tap_slop),
177        }
178    }
179}
180
181/// A stable handle to one host-provided view.
182///
183/// [`present`](View::present) is Flutter `FlutterView.render`. The host owns
184/// the surface; the framework builds a [`crate::Scene`] and hands it over.
185pub trait View: 'static {
186    fn id(&self) -> ViewId;
187    fn metrics(&self) -> ViewMetrics;
188
189    /// Flutter `FlutterView.gestureSettings`.
190    fn gesture_settings(&self) -> GestureSettings {
191        GestureSettings::default()
192    }
193
194    /// Renders and presents one scene, the frame's layer tree, shared so the host may keep it: to
195    /// present it again when the window is seen again, and to tell a scene equal to the last from
196    /// one worth drawing. Physical pixels; the presenter applies no extra scaling.
197    /// [`slices`](crate::slices) makes of it the picture a host draws and the composition layers a
198    /// host with a compositor places in the same native transaction; a host without one draws the
199    /// picture alone.
200    fn present(&self, scene: std::sync::Arc<crate::Scene>);
201
202    /// What the view asks of the system: its input methods, its cursor and its focus.
203    fn host(&self) -> &dyn ViewHost;
204
205    /// What whoever gave or asked for this view attached to it, such as which screen to show:
206    /// what the app asked for it with ([`crate::Platform::request_view`]), or what an embedder
207    /// said, as Flutter web's `initialData` does. `None` for a view with none.
208    fn initial_data(&self) -> Option<String> {
209        None
210    }
211
212    /// The request this view arrived for ([`crate::Platform::request_view`],
213    /// [`crate::Platform::add_view`]); `None` for a view the platform gave the app unasked.
214    fn request_id(&self) -> Option<crate::RequestId> {
215        None
216    }
217
218    /// The window this view is in, where the platform lets the app manage it, as `NSView.window`
219    /// answers: every window on a desktop, whether the app asked for it or the platform gave it,
220    /// such as WaOS's window around a program's view. `None` for a view in nothing the app may
221    /// manage: a phone's screen, a web page's element, a host app's view. Closing a view is its
222    /// window's: a view with none is not the app's to close, and its way out is to quit.
223    fn window(&self) -> Option<crate::WindowRef> {
224        None
225    }
226}
227
228/// What one view asks of the system, provided by whoever hosts the view: a window's view gets its
229/// window's. Chromium's `RenderWidgetHostView` and Android's `SurfaceControlViewHost` are this
230/// piece; where Flutter's embedders take the same requests on the platform as a whole, through
231/// `SystemChannels.textInput`, `SystemChannels.mouseCursor` and
232/// `PlatformDispatcher.requestViewFocusChange`, here each view's own host takes them.
233pub trait ViewHost {
234    /// The input methods that edit text in this view; none where it takes no text.
235    fn text_input(&self) -> Option<&dyn TextInputHost>;
236
237    /// Shows a system cursor for a pointing device over this view: Flutter's
238    /// `activateSystemCursor` message on `SystemChannels.mouseCursor`. A host with one cursor for
239    /// every view shows it wherever the pointer is.
240    fn activate_system_cursor(&self, device: i64, kind: SystemMouseCursorKind);
241
242    /// Flutter's `PlatformDispatcher.requestViewFocusChange` for this view: asks the system to move
243    /// the focus to it, or away from it. A host whose system has no focus to move ignores it.
244    fn request_focus_change(&self, state: ViewFocusState, direction: ViewFocusDirection);
245}
246
247/// The host side of Flutter's text input connection for one view.
248pub trait TextInputHost {
249    /// Flutter `TextInput.attach` / `TextInputConnection.show`: start an IME
250    /// session for this view.
251    fn start(&self, configuration: &TextInputConfiguration);
252
253    /// Flutter `TextInputConnection.close`: end the IME session.
254    fn stop(&self);
255
256    /// Flutter `TextInputConnection.setEditingState`.
257    fn set_editing_state(&self, value: &TextEditingValue);
258
259    /// Flutter `TextInput.setComposingRect`.
260    fn set_composing_rect(&self, rect: Rect);
261
262    /// Flutter `TextInput.setCaretRect`.
263    fn set_caret_rect(&self, rect: Rect);
264
265    /// Flutter `TextInput.setEditableSizeAndTransform`.
266    fn set_client_geometry(&self, size: Size, transform: &Matrix4);
267
268    /// Whether this text input turns editing keys into edits before the framework sees
269    /// them: backspace and delete, caret movement, the line and document ends.
270    ///
271    /// Flutter has no such question because each of its embedders is written for one host:
272    /// its macOS and iOS embedders do interpret those keys, and its text field bindings for
273    /// those platforms step aside so a key is not acted on twice.
274    ///
275    /// Defaults to false, which is what a host built on a windowing library that reports
276    /// plain key presses should answer, whichever platform it runs on. A host that hands the
277    /// framework the operating system's own editing commands answers true, and the field
278    /// then leaves those keys to it.
279    fn handles_editing_keys(&self) -> bool {
280        false
281    }
282
283    /// Whether the host sends the view Edit's commands as events through a command of its own,
284    /// which the user's ⌘C, ⌘X and ⌘V reach once the framework leaves them unhandled: macOS's Edit
285    /// menu, or the host's of a program that runs inside another app. A text field then leaves
286    /// those keys unhandled and acts on the `clipboard_event` the command sends, as Flutter's text
287    /// fields on the web leave them to the browser.
288    ///
289    /// Defaults to false: a host whose system has no such command, or whose app may have set a
290    /// menu bar without Edit's commands, keeps them in the field.
291    fn provides_edit_commands(&self) -> bool {
292        false
293    }
294}
295
296/// What the app uses to embed views of its own, as Flutter's embedder does: adding one on a layer
297/// it makes and places, giving its metrics and its being in sight, handing it input, and removing
298/// it ([`Platform::view_embedder`](crate::Platform::view_embedder)).
299pub trait ViewEmbedder {
300    /// Flutter's `AddView`, for the app: asks for a view that draws into `layer`, which the app
301    /// made with the platform's [`compositor`](crate::Platform::compositor), opaque or not, and
302    /// places itself, at `metrics`, asking what it asks of the system of `host`. The view arrives
303    /// as a window's does, through `EmbedderClient::view_added`, carrying the id answered here
304    /// ([`View::request_id`]); it is in sight until the app says otherwise
305    /// ([`set_view_in_sight`](Self::set_view_in_sight)), and its frames follow the screen its
306    /// metrics name. Refused at once, with
307    /// [`ViewError::Unsupported`](crate::ViewError::Unsupported), where the host cannot draw a
308    /// view into `layer`.
309    fn add_view(
310        &self,
311        layer: &std::sync::Arc<dyn crate::CompositionLayer>,
312        metrics: ViewMetrics,
313        host: std::rc::Rc<dyn ViewHost>,
314    ) -> Result<crate::RequestId, crate::ViewError>;
315
316    /// Flutter's `SendWindowMetricsEvent`, for a view the app added with
317    /// [`add_view`](Self::add_view), which the framework hears of through
318    /// `EmbedderClient::view_metrics_changed`; refused for any other view, whose holder gives it
319    /// its metrics.
320    fn set_view_metrics(&self, view: ViewId, metrics: ViewMetrics);
321
322    /// Whether a view the app added is in sight, as only the app knows where it placed the view's
323    /// layer: its frames are held while it is not, as a window's are while the window is
324    /// minimized or occluded. Refused for any other view, as its metrics are.
325    fn set_view_in_sight(&self, view: ViewId, in_sight: bool);
326
327    /// Flutter's `RemoveView`, for a view the app added, refused for any other as its metrics
328    /// are: the view lets go of its layer, and the framework hears of it through
329    /// `EmbedderClient::view_removed`.
330    fn remove_view(&self, view: ViewId);
331
332    /// Flutter's `SendPointerEvent`, for the app: what pointing devices did over any view, a
333    /// window's included, which the framework hears as it hears the host's own reports
334    /// (`EmbedderClient::pointer_reports`), through the one converter that numbers every pointer.
335    /// Each device of a forwarded stream (its mouse, trackpad and pen, and each touch from landing
336    /// to lifting) takes a device number no other stream uses, a random 64-bit one for now, since
337    /// the converter keeps one state per device number, a forwarding app's own host numbers its
338    /// devices as this one does, 0, 1, 2 upward, and two streams with no order between them would
339    /// mix their devices' states in one (native-surfaces decision 36).
340    fn send_pointer_reports(&self, reports: Vec<crate::PointerReport>);
341
342    /// Flutter's `SendViewFocusEvent`, for the app: a view's focus change, which the framework
343    /// hears as it hears the host's own (`EmbedderClient::view_focus_changed`), so that the keys
344    /// the host reports go where the framework's focus is.
345    fn send_view_focus_event(&self, event: ViewFocusEvent);
346}