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}