Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
pointer_delegate.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "pointer_delegate.h"
6
7#include <lib/trace/event.h>
8#include <zircon/status.h>
9#include <zircon/types.h>
10
11#include <limits>
12
13#include "flutter/fml/logging.h"
14#include "flutter/fml/trace_event.h"
15
16// TODO(fxbug.dev/87076): Add MouseSource tests.
17
19// For using TouchInteractionId as a map key.
20bool operator==(const fuchsia::ui::pointer::TouchInteractionId& a,
21 const fuchsia::ui::pointer::TouchInteractionId& b) {
22 return a.device_id == b.device_id && a.pointer_id == b.pointer_id &&
23 a.interaction_id == b.interaction_id;
24}
25} // namespace fuchsia::ui::pointer
26
27namespace flutter_runner {
28
29using fup_EventPhase = fuchsia::ui::pointer::EventPhase;
30using fup_MouseDeviceInfo = fuchsia::ui::pointer::MouseDeviceInfo;
31using fup_MouseEvent = fuchsia::ui::pointer::MouseEvent;
32using fup_TouchEvent = fuchsia::ui::pointer::TouchEvent;
33using fup_TouchIxnStatus = fuchsia::ui::pointer::TouchInteractionStatus;
34using fup_TouchResponse = fuchsia::ui::pointer::TouchResponse;
35using fup_TouchResponseType = fuchsia::ui::pointer::TouchResponseType;
36using fup_ViewParameters = fuchsia::ui::pointer::ViewParameters;
37
38namespace {
39void IssueTouchTraceEvent(const fup_TouchEvent& event) {
40 FML_DCHECK(event.has_trace_flow_id()) << "API guarantee";
41 TRACE_FLOW_END("input", "dispatch_event_to_client", event.trace_flow_id());
42}
43
44void IssueMouseTraceEvent(const fup_MouseEvent& event) {
45 FML_DCHECK(event.has_trace_flow_id()) << "API guarantee";
46 TRACE_FLOW_END("input", "dispatch_event_to_client", event.trace_flow_id());
47}
48
49bool HasValidatedTouchSample(const fup_TouchEvent& event) {
50 if (!event.has_pointer_sample()) {
51 return false;
52 }
53 FML_DCHECK(event.pointer_sample().has_interaction()) << "API guarantee";
54 FML_DCHECK(event.pointer_sample().has_phase()) << "API guarantee";
55 FML_DCHECK(event.pointer_sample().has_position_in_viewport())
56 << "API guarantee";
57 return true;
58}
59
60bool HasValidatedMouseSample(const fup_MouseEvent& event) {
61 if (!event.has_pointer_sample()) {
62 return false;
63 }
64 const auto& sample = event.pointer_sample();
65 FML_DCHECK(sample.has_device_id()) << "API guarantee";
66 FML_DCHECK(sample.has_position_in_viewport()) << "API guarantee";
67 FML_DCHECK(!sample.has_pressed_buttons() || !sample.pressed_buttons().empty())
68 << "API guarantee";
69
70 return true;
71}
72
73std::array<float, 2> ViewportToViewCoordinates(
74 std::array<float, 2> viewport_coordinates,
75 const std::array<float, 9>& viewport_to_view_transform) {
76 // The transform matrix is a FIDL array with matrix data in column-major
77 // order. For a matrix with data [a b c d e f g h i], and with the viewport
78 // coordinates expressed as homogeneous coordinates, the logical view
79 // coordinates are obtained with the following formula:
80 // |a d g| |x| |x'|
81 // |b e h| * |y| = |y'|
82 // |c f i| |1| |w'|
83 // which we then normalize based on the w component:
84 // if z' not zero: (x'/w', y'/w')
85 // else (x', y')
86 const auto& M = viewport_to_view_transform;
87 const float x = viewport_coordinates[0];
88 const float y = viewport_coordinates[1];
89 const float xp = M[0] * x + M[3] * y + M[6];
90 const float yp = M[1] * x + M[4] * y + M[7];
91 const float wp = M[2] * x + M[5] * y + M[8];
92 if (wp != 0) {
93 return {xp / wp, yp / wp};
94 } else {
95 return {xp, yp};
96 }
97}
98
99flutter::PointerData::Change GetChangeFromTouchEventPhase(
100 fup_EventPhase phase) {
101 switch (phase) {
102 case fup_EventPhase::ADD:
104 case fup_EventPhase::CHANGE:
106 case fup_EventPhase::REMOVE:
108 case fup_EventPhase::CANCEL:
110 default:
112 }
113}
114
115std::array<float, 2> ClampToViewSpace(const float x,
116 const float y,
117 const fup_ViewParameters& p) {
118 const float min_x = p.view.min[0];
119 const float min_y = p.view.min[1];
120 const float max_x = p.view.max[0];
121 const float max_y = p.view.max[1];
122 if (min_x <= x && x < max_x && min_y <= y && y < max_y) {
123 return {x, y}; // No clamping to perform.
124 }
125
126 // View boundary is [min_x, max_x) x [min_y, max_y). Note that min is
127 // inclusive, but max is exclusive - so we subtract epsilon.
128 const float max_x_inclusive = max_x - std::numeric_limits<float>::epsilon();
129 const float max_y_inclusive = max_y - std::numeric_limits<float>::epsilon();
130 const float& clamped_x = std::clamp(x, min_x, max_x_inclusive);
131 const float& clamped_y = std::clamp(y, min_y, max_y_inclusive);
132 FML_LOG(INFO) << "Clamped (" << x << ", " << y << ") to (" << clamped_x
133 << ", " << clamped_y << ").";
134 return {clamped_x, clamped_y};
135}
136
138 bool any_button_down,
139 std::unordered_set<uint32_t>& mouse_down,
140 uint32_t id) {
141 if (!mouse_down.count(id) && !any_button_down) {
143 } else if (!mouse_down.count(id) && any_button_down) {
144 mouse_down.insert(id);
146 } else if (mouse_down.count(id) && any_button_down) {
148 } else if (mouse_down.count(id) && !any_button_down) {
149 mouse_down.erase(id);
151 }
152
155}
156
157// Flutter's PointerData.device field is 64 bits and is expected to be unique
158// for each pointer. We pack Fuchsia's device ID (hi) and pointer ID (lo) into
159// 64 bits to retain uniqueness across multiple touch devices.
160uint64_t PackFuchsiaDeviceIdAndPointerId(uint32_t fuchsia_device_id,
161 uint32_t fuchsia_pointer_id) {
162 return (((uint64_t)fuchsia_device_id) << 32) | fuchsia_pointer_id;
163}
164
165// It returns a "draft" because the coordinates are logical. Later, view pixel
166// ratio is applied to obtain physical coordinates.
167//
168// The flutter pointerdata state machine has extra phases, which this function
169// synthesizes on the fly. Hence the return data is a flutter pointerdata, and
170// optionally a second one.
171// For example: <ADD, DOWN>, <MOVE, nullopt>, <UP, REMOVE>.
172// TODO(fxbug.dev/87074): Let PointerDataPacketConverter synthesize events.
173//
174// Flutter gestures expect a gesture to start within the logical view space, and
175// is not tolerant of floating point drift. This function coerces just the DOWN
176// event's coordinate to start within the logical view.
177std::pair<flutter::PointerData, std::optional<flutter::PointerData>>
178CreateTouchDraft(const fup_TouchEvent& event,
179 const fup_ViewParameters& view_parameters) {
180 FML_DCHECK(HasValidatedTouchSample(event)) << "precondition";
181 const auto& sample = event.pointer_sample();
182 const auto& ixn = sample.interaction();
183
185 ptr.Clear();
186 ptr.time_stamp = event.timestamp() / 1000; // in microseconds
187 ptr.change = GetChangeFromTouchEventPhase(sample.phase());
189 // Load Fuchsia's pointer ID onto Flutter's |device| field, and not the
190 // |pointer_identifier| field. The latter is written by
191 // PointerDataPacketConverter, to track individual gesture interactions.
192 ptr.device = PackFuchsiaDeviceIdAndPointerId(ixn.device_id, ixn.pointer_id);
193 // View parameters can change mid-interaction; apply transform on the fly.
194 auto logical =
195 ViewportToViewCoordinates(sample.position_in_viewport(),
196 view_parameters.viewport_to_view_transform);
197 ptr.physical_x = logical[0]; // Not yet physical; adjusted in PlatformView.
198 ptr.physical_y = logical[1]; // Not yet physical; adjusted in PlatformView.
199
200 // Match Flutter pointer's state machine with synthesized events.
203 memcpy(&down, &ptr, sizeof(flutter::PointerData));
205 { // Ensure gesture recognition: DOWN starts in the logical view space.
206 auto [x, y] =
207 ClampToViewSpace(down.physical_x, down.physical_y, view_parameters);
208 down.physical_x = x;
209 down.physical_y = y;
210 }
211 return {std::move(ptr), std::move(down)};
214 memcpy(&up, &ptr, sizeof(flutter::PointerData));
216 return {std::move(up), std::move(ptr)};
217 } else {
218 return {std::move(ptr), std::nullopt};
219 }
220}
221
222// It returns a "draft" because the coordinates are logical. Later, view pixel
223// ratio is applied to obtain physical coordinates.
224//
225// Phase data is computed before this call; it involves state tracking based on
226// button-down state.
227//
228// Button data, if available, gets packed into the |buttons| field, in flutter
229// button order (kMousePrimaryButton, etc). The device-assigned button IDs are
230// provided in priority order in MouseEvent.device_info (at the start of channel
231// connection), and maps from device button ID (given in fup_MouseEvent) to
232// flutter button ID (flutter::PointerData).
233//
234// Scroll data, if available, gets packed into the |scroll_delta_x| or
235// |scroll_delta_y| fields, and the |signal_kind| field is set to kScroll.
236// The PointerDataPacketConverter reads this field to synthesize events to match
237// Flutter's expected pointer stream.
238// TODO(fxbug.dev/87073): PointerDataPacketConverter should synthesize a
239// discrete scroll event on kDown or kUp, to match engine expectations.
240//
241// Flutter gestures expect a gesture to start within the logical view space, and
242// is not tolerant of floating point drift. This function coerces just the DOWN
243// event's coordinate to start within the logical view.
244flutter::PointerData CreateMouseDraft(const fup_MouseEvent& event,
246 const fup_ViewParameters& view_parameters,
247 const fup_MouseDeviceInfo& device_info) {
248 FML_DCHECK(HasValidatedMouseSample(event)) << "precondition";
249 const auto& sample = event.pointer_sample();
250
252 ptr.Clear();
253 ptr.time_stamp = event.timestamp() / 1000; // in microseconds
254 ptr.change = phase;
256 ptr.device = sample.device_id();
257
258 // View parameters can change mid-interaction; apply transform on the fly.
259 auto logical =
260 ViewportToViewCoordinates(sample.position_in_viewport(),
261 view_parameters.viewport_to_view_transform);
262 ptr.physical_x = logical[0]; // Not yet physical; adjusted in PlatformView.
263 ptr.physical_y = logical[1]; // Not yet physical; adjusted in PlatformView.
264
265 // Ensure gesture recognition: DOWN starts in the logical view space.
267 auto [x, y] =
268 ClampToViewSpace(ptr.physical_x, ptr.physical_y, view_parameters);
269 ptr.physical_x = x;
270 ptr.physical_y = y;
271 }
272
273 if (sample.has_pressed_buttons()) {
274 int64_t flutter_buttons = 0;
275 const auto& pressed = sample.pressed_buttons();
276 for (size_t idx = 0; idx < pressed.size(); ++idx) {
277 const uint8_t button_id = pressed[idx];
278 FML_DCHECK(device_info.has_buttons()) << "API guarantee";
279 // Priority 0 maps to kPrimaryButton, and so on.
280 for (uint8_t prio = 0; prio < device_info.buttons().size(); ++prio) {
281 if (button_id == device_info.buttons()[prio]) {
282 flutter_buttons |= (1 << prio);
283 }
284 }
285 }
286 FML_DCHECK(flutter_buttons != 0);
287 ptr.buttons = flutter_buttons;
288 }
289
290 // Fuchsia previously only provided scroll data in "ticks", not physical
291 // pixels. On legacy platforms, since Flutter expects scroll data in physical
292 // pixels, to compensate for lack of guidance, we make up a "reasonable
293 // amount".
294 // TODO(fxbug.dev/103443): Remove the tick based scrolling after the
295 // transition.
296 const int kScrollOffsetMultiplier = 20;
297
298 double dy = 0;
299 double dx = 0;
300 bool is_scroll = false;
301
302 if (sample.has_scroll_v_physical_pixel()) {
303 dy = -sample.scroll_v_physical_pixel();
304 is_scroll = true;
305 } else if (sample.has_scroll_v()) {
306 dy = -sample.scroll_v() *
307 kScrollOffsetMultiplier; // logical amount, not yet physical; adjusted
308 // in Platform View.
309 is_scroll = true;
310 }
311
312 if (sample.has_scroll_h_physical_pixel()) {
313 dx = sample.scroll_h_physical_pixel();
314 is_scroll = true;
315 } else if (sample.has_scroll_h()) {
316 dx = sample.scroll_h() * kScrollOffsetMultiplier; // logical amount
317 is_scroll = true;
318 }
319
320 if (is_scroll) {
322 ptr.scroll_delta_y = dy;
323 ptr.scroll_delta_x = dx;
324 }
325
326 return ptr;
327}
328
329// Helper to insert one or two events into a vector buffer.
330void InsertIntoBuffer(
331 std::pair<flutter::PointerData, std::optional<flutter::PointerData>> events,
332 std::vector<flutter::PointerData>* buffer) {
334 buffer->emplace_back(std::move(events.first));
335 if (events.second.has_value()) {
336 buffer->emplace_back(std::move(events.second.value()));
337 }
338}
339} // namespace
340
342 fuchsia::ui::pointer::TouchSourceHandle touch_source,
343 fuchsia::ui::pointer::MouseSourceHandle mouse_source)
344 : touch_source_(touch_source.Bind()), mouse_source_(mouse_source.Bind()) {
345 if (touch_source_) {
346 touch_source_.set_error_handler([](zx_status_t status) {
347 FML_LOG(ERROR) << "TouchSource channel error: << "
348 << zx_status_get_string(status);
349 });
350 }
351 if (mouse_source_) {
352 mouse_source_.set_error_handler([](zx_status_t status) {
353 FML_LOG(ERROR) << "MouseSource channel error: << "
354 << zx_status_get_string(status);
355 });
356 }
357}
358// Core logic of this class.
359// Aim to keep state management in this function.
361 std::function<void(std::vector<flutter::PointerData>)> callback) {
362 FML_LOG(INFO) << "Flutter - PointerDelegate started.";
363 if (touch_responder_) {
364 FML_LOG(ERROR) << "PointerDelegate::WatchLoop() must be called once.";
365 return;
366 }
367
368 touch_responder_ = [this, callback](std::vector<fup_TouchEvent> events) {
369 TRACE_EVENT0("flutter", "PointerDelegate::TouchHandler");
370 FML_DCHECK(touch_responses_.empty()) << "precondition";
371 std::vector<flutter::PointerData> to_client;
372 for (const fup_TouchEvent& event : events) {
373 IssueTouchTraceEvent(event);
375 response; // Response per event, matched on event's index.
376 if (event.has_view_parameters()) {
377 touch_view_parameters_ = std::move(event.view_parameters());
378 }
379 if (HasValidatedTouchSample(event)) {
380 const auto& sample = event.pointer_sample();
381 const auto& ixn = sample.interaction();
382 if (sample.phase() == fup_EventPhase::ADD &&
383 !event.has_interaction_result()) {
384 touch_buffer_.emplace(ixn, std::vector<flutter::PointerData>());
385 }
386
387 FML_DCHECK(touch_view_parameters_.has_value()) << "API guarantee";
388 auto events = CreateTouchDraft(event, touch_view_parameters_.value());
389 if (touch_buffer_.count(ixn) > 0) {
390 InsertIntoBuffer(std::move(events), &touch_buffer_[ixn]);
391 } else {
392 InsertIntoBuffer(std::move(events), &to_client);
393 }
394 // For this simple client, always claim we want the gesture.
395 response.set_response_type(fup_TouchResponseType::YES);
396 }
397 if (event.has_interaction_result()) {
398 const auto& result = event.interaction_result();
399 const auto& ixn = result.interaction;
400 if (result.status == fup_TouchIxnStatus::GRANTED &&
401 touch_buffer_.count(ixn) > 0) {
402 FML_DCHECK(to_client.empty()) << "invariant";
403 to_client.insert(to_client.end(), touch_buffer_[ixn].begin(),
404 touch_buffer_[ixn].end());
405 }
406 touch_buffer_.erase(ixn); // Result seen, delete the buffer.
407 }
408 touch_responses_.push_back(std::move(response));
409 }
410 callback(std::move(to_client)); // Notify client of touch events, if any.
411
412 touch_source_->Watch(std::move(touch_responses_),
413 /*copy*/ touch_responder_);
414 touch_responses_.clear();
415 };
416
417 mouse_responder_ = [this, callback](std::vector<fup_MouseEvent> events) {
418 TRACE_EVENT0("flutter", "PointerDelegate::MouseHandler");
419 std::vector<flutter::PointerData> to_client;
420 for (fup_MouseEvent& event : events) {
421 IssueMouseTraceEvent(event);
422 if (event.has_device_info()) {
423 const auto& id = event.device_info().id();
424 mouse_device_info_[id] = std::move(*event.mutable_device_info());
425 }
426 if (event.has_view_parameters()) {
427 mouse_view_parameters_ = std::move(event.view_parameters());
428 }
429 if (HasValidatedMouseSample(event)) {
430 const auto& sample = event.pointer_sample();
431 const auto& id = sample.device_id();
432 const bool any_button_down = sample.has_pressed_buttons();
433 FML_DCHECK(mouse_view_parameters_.has_value()) << "API guarantee";
434 FML_DCHECK(mouse_device_info_.count(id) > 0) << "API guarantee";
435
436 const auto phase = ComputePhase(any_button_down, mouse_down_, id);
438 CreateMouseDraft(event, phase, mouse_view_parameters_.value(),
439 mouse_device_info_[id]);
440 to_client.emplace_back(std::move(data));
441 }
442 }
443 callback(std::move(to_client));
444 mouse_source_->Watch(/*copy*/ mouse_responder_);
445 };
446
447 // Start watching both channels.
448 touch_source_->Watch(std::move(touch_responses_), /*copy*/ touch_responder_);
449 touch_responses_.clear();
450 if (mouse_source_) {
451 mouse_source_->Watch(/*copy*/ mouse_responder_);
452 }
453}
454
455} // namespace flutter_runner
void WatchLoop(std::function< void(std::vector< flutter::PointerData >)> callback)
PointerDelegate(fuchsia::ui::pointer::TouchSourceHandle touch_source, fuchsia::ui::pointer::MouseSourceHandle mouse_source)
static bool b
struct MyStruct a[10]
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
static const uint8_t buffer[]
GAsyncResult * result
#define FML_LOG(severity)
Definition logging.h:82
#define FML_UNREACHABLE()
Definition logging.h:109
#define FML_DCHECK(condition)
Definition logging.h:103
bool pressed
double y
double x
fuchsia::ui::pointer::MouseEvent fup_MouseEvent
fuchsia::ui::pointer::TouchInteractionStatus fup_TouchIxnStatus
fuchsia::ui::pointer::MouseDeviceInfo fup_MouseDeviceInfo
fuchsia::ui::pointer::TouchResponse fup_TouchResponse
fuchsia::ui::pointer::ViewParameters fup_ViewParameters
fuchsia::ui::pointer::TouchEvent fup_TouchEvent
fuchsia::ui::pointer::TouchResponseType fup_TouchResponseType
fuchsia::ui::pointer::EventPhase fup_EventPhase
bool operator==(const fuchsia::ui::pointer::TouchInteractionId &a, const fuchsia::ui::pointer::TouchInteractionId &b)
#define M(PROC, DITHER)
const uintptr_t id
#define ERROR(message)
#define TRACE_EVENT0(category_group, name)
#define TRACE_FLOW_END(category, name, id)