Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
Fix compute physicalX/Y for TalkBack events.
Browse files Browse the repository at this point in the history
Extracted compute function to a helper file.
  • Loading branch information
ditman committed Dec 21, 2022
1 parent b293d36 commit 76cec66
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 24 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -4413,6 +4413,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/slots.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/profiler.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/raw_keyboard.dart
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export 'engine/platform_views/message_handler.dart';
export 'engine/platform_views/slots.dart';
export 'engine/plugins.dart';
export 'engine/pointer_binding.dart';
export 'engine/pointer_binding/event_position_helper.dart';
export 'engine/pointer_converter.dart';
export 'engine/profiler.dart';
export 'engine/raw_keyboard.dart';
Expand Down
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ class DomHTMLElement extends DomElement {}

extension DomHTMLElementExtension on DomHTMLElement {
external double get offsetWidth;
external double get offsetLeft;
external double get offsetTop;
external DomHTMLElement? get offsetParent;
}

@JS()
Expand Down Expand Up @@ -1090,6 +1093,8 @@ extension DomMouseEventExtension on DomMouseEvent {
external double get clientY;
external double get offsetX;
external double get offsetY;
external double get pageX;
external double get pageY;
DomPoint get client => DomPoint(clientX, clientY);
DomPoint get offset => DomPoint(offsetX, offsetY);
external double get button;
Expand Down
28 changes: 4 additions & 24 deletions lib/web_ui/lib/src/engine/pointer_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../engine.dart' show registerHotRestartListener;
import 'browser_detection.dart';
import 'dom.dart';
import 'platform_dispatcher.dart';
import 'pointer_binding/event_position_helper.dart';
import 'pointer_converter.dart';
import 'safe_browser_api.dart';
import 'semantics.dart';
Expand Down Expand Up @@ -342,27 +343,6 @@ abstract class _BaseAdapter {
((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
return Duration(milliseconds: ms, microseconds: micro);
}

/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
///
/// The offset is *not* multiplied by DPR or anything else, it's the closest
/// to what the DOM would return if we had currentTarget readily available.
///
// TODO(dit): Make this understand 3D transforms in the platform view case, https://github.com/flutter/flutter/issues/117091
static ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
if (event.target != actualTarget) {
// We're on top of a platform view.
final DomElement target = event.target! as DomElement;
// We can't use currentTarget because it gets lost when the PointerEvents
// are coalesced!
final DomRect targetRect = target.getBoundingClientRect();
final DomRect actualTargetRect = actualTarget.getBoundingClientRect();
final double offsetTop = targetRect.y - actualTargetRect.y;
final double offsetLeft = targetRect.x - actualTargetRect.x;
return ui.Offset(event.offsetX + offsetLeft, event.offsetY + offsetTop);
}
return ui.Offset(event.offsetX, event.offsetY);
}
}

mixin _WheelEventListenerMixin on _BaseAdapter {
Expand Down Expand Up @@ -472,7 +452,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
}

final List<ui.PointerData> data = <ui.PointerData>[];
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
_pointerDataConverter.convert(
data,
change: ui.PointerChange.hover,
Expand Down Expand Up @@ -844,7 +824,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
final double tilt = _computeHighestTilt(event);
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!);
final num? pressure = event.pressure;
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
_pointerDataConverter.convert(
data,
change: details.change,
Expand Down Expand Up @@ -1170,7 +1150,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin {
assert(data != null);
assert(event != null);
assert(details != null);
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
_pointerDataConverter.convert(
data,
change: details.change,
Expand Down
113 changes: 113 additions & 0 deletions lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:ui/ui.dart' as ui show Offset;

import '../dom.dart';
import '../semantics.dart' show EngineSemanticsOwner;

/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
///
/// The offset is *not* multiplied by DPR or anything else, it's the closest
/// to what the DOM would return if we had currentTarget readily available.
///
/// This needs an `actualTarget`, because the `event.currentTarget` (which is what
/// this would really need to use) gets lost when the `event` comes from a "coalesced"
/// event.
///
/// It also takes into account semantics being enabled to fix the case where
/// offsetX, offsetY == 0 (TalkBack events).
//
// TODO(dit): Make this understand 3D transforms in the platform view case, https://github.com/flutter/flutter/issues/117091
ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
// On top of a platform view
if (event.target != actualTarget) {
return _computeOffsetOnPlatformView(event, actualTarget);
}
// On a TalkBack event
if (EngineSemanticsOwner.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) {
return _computeOffsetForTalkbackEvent(event, actualTarget);
}
// Return the offsetX/Y in the normal case.
return ui.Offset(event.offsetX, event.offsetY);
}

/// Computes the event offset when hovering over a platformView.
///
/// This still uses offsetX/Y, but adds the offset from the top/left corner of the
/// platform view to the glass pane (`actualTarget`).
///
/// ×--FlutterView(actualTarget)--------------+
/// |\ |
/// | x1,y1 |
/// | |
/// | |
/// | ×-PlatformView(target)---------+ |
/// | |\ | |
/// | | x2,y2 | |
/// | | | |
/// | | × (event) | |
/// | | \ | |
/// | | offsetX, offsetY | |
/// | | (Relative to PlatformView) | |
/// | +------------------------------+ |
/// +-----------------------------------------+
///
/// Offset between PlatformView and FlutterView (xP, yP) = (x2 - x1, y2 - y1)
///
/// Event offset relative to FlutterView = (offsetX + xP, offsetY + yP)
ui.Offset _computeOffsetOnPlatformView(DomMouseEvent event, DomElement actualTarget) {
final DomElement target = event.target! as DomElement;
final DomRect targetRect = target.getBoundingClientRect();
final DomRect actualTargetRect = actualTarget.getBoundingClientRect();
final double offsetTop = targetRect.y - actualTargetRect.y;
final double offsetLeft = targetRect.x - actualTargetRect.x;
return ui.Offset(event.offsetX + offsetLeft, event.offsetY + offsetTop);
}

/// Computes the event offset when TalkBack is firing the event.
///
/// In this case, we need to use the clientX/Y position of the event (which are
/// relative to the absolute top-left corner of the page, including scroll), then
/// deduct the offsetLeft/Top from every offsetParent of the `actualTarget`.
///
/// ×-Page----║-------------------------------+
/// | ║ |
/// | ×-------║--------offsetParent(s)-----+ |
/// | |\ | |
/// | | offsetLeft, offsetTop | |
/// | | | |
/// | | | |
/// | | ×-----║-------------actualTarget-+ | |
/// | | | | | |
/// ═════ × ─ (scrollLeft, scrollTop)═ ═ ═
/// | | | | | |
/// | | | × | | |
/// | | | \ | | |
/// | | | clientX, clientY | | |
/// | | | (Relative to Page + Scroll) | | |
/// | | +-----║--------------------------+ | |
/// | +-------║----------------------------+ |
/// +---------║-------------------------------+
///
/// Computing the offset of the event relative to the actualTarget requires to
/// compute the clientX, clientY of the actualTarget. To do that, we iterate
/// up the offsetParent elements of actualTarget adding their offset and scroll
/// positions. Finally, we deduct that from clientX, clientY of the event.
ui.Offset _computeOffsetForTalkbackEvent(DomMouseEvent event, DomElement actualTarget) {
assert(EngineSemanticsOwner.instance.semanticsEnabled);
// Use clientX/clientY as the position of the event (this is relative to
// the top left of the page, including scroll)
double offsetX = event.clientX;
double offsetY = event.clientY;
// Compute the scroll offset of actualTarget
DomHTMLElement parent = actualTarget as DomHTMLElement;
while(parent.offsetParent != null){
offsetX -= parent.offsetLeft - parent.scrollLeft;
offsetY -= parent.offsetTop - parent.scrollTop;
parent = parent.offsetParent!;
}
return ui.Offset(offsetX, offsetY);
}

0 comments on commit 76cec66

Please sign in to comment.