Unverified Commit 1621baaf authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Introduce separate HitTestResults for Box and Sliver (#31894)

parent ff1dbcde
...@@ -6,6 +6,7 @@ import 'dart:math' as math; ...@@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';
const double kTwoPi = 2 * math.pi; const double kTwoPi = 2 * math.pi;
...@@ -76,6 +77,18 @@ class SectorParentData extends ParentData { ...@@ -76,6 +77,18 @@ class SectorParentData extends ParentData {
double theta = 0.0; double theta = 0.0;
} }
/// Base class for [RenderObject]s that live in a polar coordinate space.
///
/// In a polar coordinate system each point on a plane is determined by a
/// distance from a reference point ("radius") and an angle from a reference
/// direction ("theta").
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/Polar_coordinate_system>, which defines
/// the polar coordinate space.
/// * [RenderBox], which is the base class for [RenderObject]s that live in a
/// cartesian coordinate space.
abstract class RenderSector extends RenderObject { abstract class RenderSector extends RenderObject {
@override @override
...@@ -130,15 +143,15 @@ abstract class RenderSector extends RenderObject { ...@@ -130,15 +143,15 @@ abstract class RenderSector extends RenderObject {
@override @override
Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius); Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius);
bool hitTest(HitTestResult result, { double radius, double theta }) { bool hitTest(SectorHitTestResult result, { double radius, double theta }) {
if (radius < parentData.radius || radius >= parentData.radius + deltaRadius || if (radius < parentData.radius || radius >= parentData.radius + deltaRadius ||
theta < parentData.theta || theta >= parentData.theta + deltaTheta) theta < parentData.theta || theta >= parentData.theta + deltaTheta)
return false; return false;
hitTestChildren(result, radius: radius, theta: theta); hitTestChildren(result, radius: radius, theta: theta);
result.add(HitTestEntry(this)); result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
return true; return true;
} }
void hitTestChildren(HitTestResult result, { double radius, double theta }) { } void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) { }
double deltaRadius; double deltaRadius;
double deltaTheta; double deltaTheta;
...@@ -190,7 +203,7 @@ class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRende ...@@ -190,7 +203,7 @@ class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRende
RenderSectorWithChildren(BoxDecoration decoration) : super(decoration); RenderSectorWithChildren(BoxDecoration decoration) : super(decoration);
@override @override
void hitTestChildren(HitTestResult result, { double radius, double theta }) { void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) {
RenderSector child = lastChild; RenderSector child = lastChild;
while (child != null) { while (child != null) {
if (child.hitTest(result, radius: radius, theta: theta)) if (child.hitTest(result, radius: radius, theta: theta))
...@@ -530,7 +543,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil ...@@ -530,7 +543,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (child == null) if (child == null)
return false; return false;
double x = position.dx; double x = position.dx;
...@@ -547,7 +560,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil ...@@ -547,7 +560,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil
return false; return false;
if (theta > child.deltaTheta) if (theta > child.deltaTheta)
return false; return false;
child.hitTest(result, radius: radius, theta: theta); child.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
result.add(BoxHitTestEntry(this, position)); result.add(BoxHitTestEntry(this, position));
return true; return true;
} }
...@@ -585,3 +598,52 @@ class RenderSolidColor extends RenderDecoratedSector { ...@@ -585,3 +598,52 @@ class RenderSolidColor extends RenderDecoratedSector {
} }
} }
} }
/// The result of performing a hit test on [RenderSector]s.
class SectorHitTestResult extends HitTestResult {
/// Creates an empty hit test result for hit testing on [RenderSector].
SectorHitTestResult() : super();
/// Wraps `result` to create a [HitTestResult] that implements the
/// [SectorHitTestResult] protocol for hit testing on [RenderSector]s.
///
/// This method is used by [RenderObject]s that adapt between the
/// [RenderSector]-world and the non-[RenderSector]-world to convert a (subtype of)
/// [HitTestResult] to a [SectorHitTestResult] for hit testing on [RenderSector]s.
///
/// The [HitTestEntry]s added to the returned [SectorHitTestResult] are also
/// added to the wrapped `result` (both share the same underlying data
/// structure to store [HitTestEntry]s).
///
/// See also:
///
/// * [HitTestResult.wrap], which turns a [SectorHitTestResult] back into a
/// generic [HitTestResult].
SectorHitTestResult.wrap(HitTestResult result) : super.wrap(result);
// TODO(goderbauer): Add convenience methods to transform hit test positions
// once we have RenderSector implementations that move the origin of their
// children (e.g. RenderSectorTransform analogues to RenderTransform).
}
/// A hit test entry used by [RenderSector].
class SectorHitTestEntry extends HitTestEntry {
/// Creates a box hit test entry.
///
/// The [radius] and [theta] argument must not be null.
SectorHitTestEntry(RenderSector target, { @required this.radius, @required this.theta })
: assert(radius != null),
assert(theta != null),
super(target);
@override
RenderSector get target => super.target;
/// The radius component of the hit test position in the local coordinates of
/// [target].
final double radius;
/// The theta component of the hit test position in the local coordinates of
/// [target].
final double theta;
}
// Copyright 2019 The Chromium 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:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/src/sector_layout.dart';
void main() {
group('hit testing', () {
test('SectorHitTestResult wrapping HitTestResult', () {
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
final HitTestResult wrapped = HitTestResult();
wrapped.add(entry1);
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
final SectorHitTestResult wrapping = SectorHitTestResult.wrap(wrapped);
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
expect(wrapping.path, same(wrapped.path));
wrapping.add(entry2);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
wrapped.add(entry3);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
});
});
}
class _DummyHitTestTarget implements HitTestTarget {
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
// Nothing to do.
}
}
...@@ -713,17 +713,25 @@ class _RenderCupertinoAlert extends RenderBox { ...@@ -713,17 +713,25 @@ class _RenderCupertinoAlert extends RenderBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
bool isHit = false;
final MultiChildLayoutParentData contentSectionParentData = contentSection.parentData; final MultiChildLayoutParentData contentSectionParentData = contentSection.parentData;
final MultiChildLayoutParentData actionsSectionParentData = actionsSection.parentData; final MultiChildLayoutParentData actionsSectionParentData = actionsSection.parentData;
if (contentSection.hitTest(result, position: position - contentSectionParentData.offset)) { return result.addWithPaintOffset(
isHit = true; offset: contentSectionParentData.offset,
} else if (actionsSection.hitTest(result, position: position,
position: position - actionsSectionParentData.offset)) { hitTest: (BoxHitTestResult result, Offset transformed) {
isHit = true; assert(transformed == position - contentSectionParentData.offset);
} return contentSection.hitTest(result, position: transformed);
return isHit; },
)
|| result.addWithPaintOffset(
offset: actionsSectionParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - actionsSectionParentData.offset);
return actionsSection.hitTest(result, position: transformed);
},
);
} }
} }
...@@ -1261,7 +1269,7 @@ class _RenderCupertinoAlertActions extends RenderBox ...@@ -1261,7 +1269,7 @@ class _RenderCupertinoAlertActions extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
} }
...@@ -775,16 +775,25 @@ class _RenderCupertinoDialog extends RenderBox { ...@@ -775,16 +775,25 @@ class _RenderCupertinoDialog extends RenderBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
bool isHit = false;
final BoxParentData contentSectionParentData = contentSection.parentData; final BoxParentData contentSectionParentData = contentSection.parentData;
final BoxParentData actionsSectionParentData = actionsSection.parentData; final BoxParentData actionsSectionParentData = actionsSection.parentData;
if (contentSection.hitTest(result, position: position - contentSectionParentData.offset)) { return result.addWithPaintOffset(
isHit = true; offset: contentSectionParentData.offset,
} else if (actionsSection.hitTest(result, position: position - actionsSectionParentData.offset)) { position: position,
isHit = true; hitTest: (BoxHitTestResult result, Offset transformed) {
} assert(transformed == position - contentSectionParentData.offset);
return isHit; return contentSection.hitTest(result, position: transformed);
},
)
|| result.addWithPaintOffset(
offset: actionsSectionParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - actionsSectionParentData.offset);
return actionsSection.hitTest(result, position: transformed);
},
);
} }
} }
...@@ -1693,7 +1702,7 @@ class _RenderCupertinoDialogActions extends RenderBox ...@@ -1693,7 +1702,7 @@ class _RenderCupertinoDialogActions extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
} }
...@@ -703,13 +703,21 @@ class _RenderSegmentedControl<T> extends RenderBox ...@@ -703,13 +703,21 @@ class _RenderSegmentedControl<T> extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null); assert(position != null);
RenderBox child = lastChild; RenderBox child = lastChild;
while (child != null) { while (child != null) {
final _SegmentedControlContainerBoxParentData childParentData = child.parentData; final _SegmentedControlContainerBoxParentData childParentData = child.parentData;
if (childParentData.surroundingRect.contains(position)) { if (childParentData.surroundingRect.contains(position)) {
return child.hitTest(result, position: (Offset.zero & child.size).center); final Offset center = (Offset.zero & child.size).center;
return result.addWithRawTransform(
transform: MatrixUtils.forceToPoint(center),
position: center,
hitTest: (BoxHitTestResult result, Offset position) {
assert(position == center);
return child.hitTest(result, position: center);
},
);
} }
child = childParentData.previousSibling; child = childParentData.previousSibling;
} }
......
...@@ -54,12 +54,16 @@ class HitTestEntry { ...@@ -54,12 +54,16 @@ class HitTestEntry {
/// The result of performing a hit test. /// The result of performing a hit test.
class HitTestResult { class HitTestResult {
/// Creates a hit test result. /// Creates an empty hit test result.
HitTestResult() : _path = <HitTestEntry>[];
/// Wraps `result` (usually a subtype of [HitTestResult]) to create a
/// generic [HitTestResult].
/// ///
/// If the [path] argument is null, the [path] field will be initialized with /// The [HitTestEntry]s added to the returned [HitTestResult] are also
/// and empty list. /// added to the wrapped `result` (both share the same underlying data
HitTestResult({ List<HitTestEntry> path }) /// structure to store [HitTestEntry]s).
: _path = path ?? <HitTestEntry>[]; HitTestResult.wrap(HitTestResult result) : _path = result._path;
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test. /// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
/// ///
......
...@@ -324,8 +324,18 @@ class _RenderInputPadding extends RenderShiftedBox { ...@@ -324,8 +324,18 @@ class _RenderInputPadding extends RenderShiftedBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
return super.hitTest(result, position: position) || if (super.hitTest(result, position: position)) {
child.hitTest(result, position: child.size.center(Offset.zero)); return true;
}
final Offset center = child.size.center(Offset.zero);
return result.addWithRawTransform(
transform: MatrixUtils.forceToPoint(center),
position: center,
hitTest: (BoxHitTestResult result, Offset position) {
assert(position == center);
return child.hitTest(result, position: center);
},
);
} }
} }
...@@ -1740,13 +1740,21 @@ class _RenderChipRedirectingHitDetection extends RenderConstrainedBox { ...@@ -1740,13 +1740,21 @@ class _RenderChipRedirectingHitDetection extends RenderConstrainedBox {
_RenderChipRedirectingHitDetection(BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints); _RenderChipRedirectingHitDetection(BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints);
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (!size.contains(position)) if (!size.contains(position))
return false; return false;
// Only redirects hit detection which occurs above and below the render object. // Only redirects hit detection which occurs above and below the render object.
// In order to make this assumption true, I have removed the minimum width // In order to make this assumption true, I have removed the minimum width
// constraints, since any reasonable chip would be at least that wide. // constraints, since any reasonable chip would be at least that wide.
return child.hitTest(result, position: Offset(position.dx, size.height / 2)); final Offset offset = Offset(position.dx, size.height / 2);
return result.addWithRawTransform(
transform: MatrixUtils.forceToPoint(offset),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
assert(position == offset);
return child.hitTest(result, position: offset);
},
);
} }
} }
...@@ -2269,7 +2277,7 @@ class _RenderChip extends RenderBox { ...@@ -2269,7 +2277,7 @@ class _RenderChip extends RenderBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (!size.contains(position)) if (!size.contains(position))
return false; return false;
RenderBox hitTestChild; RenderBox hitTestChild;
...@@ -2287,7 +2295,18 @@ class _RenderChip extends RenderBox { ...@@ -2287,7 +2295,18 @@ class _RenderChip extends RenderBox {
hitTestChild = label ?? avatar; hitTestChild = label ?? avatar;
break; break;
} }
return hitTestChild?.hitTest(result, position: hitTestChild.size.center(Offset.zero)) ?? false; if (hitTestChild != null) {
final Offset center = hitTestChild.size.center(Offset.zero);
return result.addWithRawTransform(
transform: MatrixUtils.forceToPoint(center),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
assert(position == center);
return hitTestChild.hitTest(result, position: center);
},
);
}
return false;
} }
@override @override
......
...@@ -1287,11 +1287,20 @@ class _RenderDecoration extends RenderBox { ...@@ -1287,11 +1287,20 @@ class _RenderDecoration extends RenderBox {
bool hitTestSelf(Offset position) => true; bool hitTestSelf(Offset position) => true;
@override @override
bool hitTestChildren(HitTestResult result, { @required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null); assert(position != null);
for (RenderBox child in _children) { for (RenderBox child in _children) {
// TODO(hansmuller): label must be handled specially since we've transformed it // TODO(hansmuller): label must be handled specially since we've transformed it
if (child.hitTest(result, position: position - _boxParentData(child).offset)) final Offset offset = _boxParentData(child).offset;
final bool isHit = result.addWithPaintOffset(
offset: offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit)
return true; return true;
} }
return false; return false;
......
...@@ -1460,11 +1460,19 @@ class _RenderListTile extends RenderBox { ...@@ -1460,11 +1460,19 @@ class _RenderListTile extends RenderBox {
bool hitTestSelf(Offset position) => true; bool hitTestSelf(Offset position) => true;
@override @override
bool hitTestChildren(HitTestResult result, { @required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null); assert(position != null);
for (RenderBox child in _children) { for (RenderBox child in _children) {
final BoxParentData parentData = child.parentData; final BoxParentData parentData = child.parentData;
if (child.hitTest(result, position: position - parentData.offset)) final bool isHit = result.addWithPaintOffset(
offset: parentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - parentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit)
return true; return true;
} }
return false; return false;
......
...@@ -249,6 +249,13 @@ class MatrixUtils { ...@@ -249,6 +249,13 @@ class MatrixUtils {
// Essentially perspective * view * model. // Essentially perspective * view * model.
return result; return result;
} }
/// Returns a matrix that transforms every point to [offset].
static Matrix4 forceToPoint(Offset offset) {
return Matrix4.identity()
..setRow(0, Vector4(0, 0, 0, offset.dx))
..setRow(1, Vector4(0, 0, 0, offset.dy));
}
} }
/// Returns a list of strings representing the given transform in a format /// Returns a list of strings representing the given transform in a format
......
This diff is collapsed.
...@@ -362,7 +362,7 @@ class RenderCustomMultiChildLayoutBox extends RenderBox ...@@ -362,7 +362,7 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
} }
...@@ -502,7 +502,7 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -502,7 +502,7 @@ class RenderCustomPaint extends RenderProxyBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (_foregroundPainter != null && (_foregroundPainter.hitTest(position) ?? false)) if (_foregroundPainter != null && (_foregroundPainter.hitTest(position) ?? false))
return true; return true;
return super.hitTestChildren(result, position: position); return super.hitTestChildren(result, position: position);
......
...@@ -937,7 +937,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -937,7 +937,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
......
...@@ -369,7 +369,7 @@ class RenderFlow extends RenderBox ...@@ -369,7 +369,7 @@ class RenderFlow extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
final List<RenderBox> children = getChildrenAsList(); final List<RenderBox> children = getChildrenAsList();
for (int i = _lastPaintOrder.length - 1; i >= 0; --i) { for (int i = _lastPaintOrder.length - 1; i >= 0; --i) {
final int childIndex = _lastPaintOrder[i]; final int childIndex = _lastPaintOrder[i];
...@@ -380,15 +380,14 @@ class RenderFlow extends RenderBox ...@@ -380,15 +380,14 @@ class RenderFlow extends RenderBox
final Matrix4 transform = childParentData._transform; final Matrix4 transform = childParentData._transform;
if (transform == null) if (transform == null)
continue; continue;
final Matrix4 inverse = Matrix4.zero(); final bool absorbed = result.addWithPaintTransform(
final double determinate = inverse.copyInverse(transform); transform: transform,
if (determinate == 0.0) { position: position,
// We cannot invert the transform. That means the child doesn't appear hitTest: (BoxHitTestResult result, Offset position) {
// on screen and cannot be hit. return child.hitTest(result, position: position);
continue; },
} );
final Offset childPosition = MatrixUtils.transformPoint(inverse, position); if (absorbed)
if (child.hitTest(result, position: childPosition))
return true; return true;
} }
return false; return false;
......
...@@ -265,7 +265,7 @@ class RenderListBody extends RenderBox ...@@ -265,7 +265,7 @@ class RenderListBody extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
......
...@@ -984,7 +984,7 @@ class RenderListWheelViewport ...@@ -984,7 +984,7 @@ class RenderListWheelViewport
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return false; return false;
} }
......
...@@ -230,7 +230,7 @@ class RenderAndroidView extends RenderBox { ...@@ -230,7 +230,7 @@ class RenderAndroidView extends RenderBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position))
return false; return false;
result.add(BoxHitTestEntry(this, position)); result.add(BoxHitTestEntry(this, position));
...@@ -365,7 +365,7 @@ class RenderUiKitView extends RenderBox { ...@@ -365,7 +365,7 @@ class RenderUiKitView extends RenderBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position))
return false; return false;
result.add(BoxHitTestEntry(this, position)); result.add(BoxHitTestEntry(this, position));
......
...@@ -110,7 +110,7 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi ...@@ -110,7 +110,7 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false; return child?.hitTest(result, position: position) ?? false;
} }
...@@ -155,7 +155,7 @@ abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox { ...@@ -155,7 +155,7 @@ abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox {
HitTestBehavior behavior; HitTestBehavior behavior;
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
bool hitTarget = false; bool hitTarget = false;
if (size.contains(position)) { if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position); hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
...@@ -1278,7 +1278,7 @@ class RenderClipRect extends _RenderCustomClip<Rect> { ...@@ -1278,7 +1278,7 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
Rect get _defaultClip => Offset.zero & size; Rect get _defaultClip => Offset.zero & size;
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) { if (_clipper != null) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
...@@ -1354,7 +1354,7 @@ class RenderClipRRect extends _RenderCustomClip<RRect> { ...@@ -1354,7 +1354,7 @@ class RenderClipRRect extends _RenderCustomClip<RRect> {
RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size); RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size);
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) { if (_clipper != null) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
...@@ -1419,7 +1419,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> { ...@@ -1419,7 +1419,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
Rect get _defaultClip => Offset.zero & size; Rect get _defaultClip => Offset.zero & size;
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
final Offset center = _clip.center; final Offset center = _clip.center;
...@@ -1484,7 +1484,7 @@ class RenderClipPath extends _RenderCustomClip<Path> { ...@@ -1484,7 +1484,7 @@ class RenderClipPath extends _RenderCustomClip<Path> {
Path get _defaultClip => Path()..addRect(Offset.zero & size); Path get _defaultClip => Path()..addRect(Offset.zero & size);
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) { if (_clipper != null) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
...@@ -1677,7 +1677,7 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> { ...@@ -1677,7 +1677,7 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) { if (_clipper != null) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
...@@ -1772,7 +1772,7 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> { ...@@ -1772,7 +1772,7 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
Path get _defaultClip => Path()..addRect(Offset.zero & size); Path get _defaultClip => Path()..addRect(Offset.zero & size);
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) { if (_clipper != null) {
_updateClip(); _updateClip();
assert(_clip != null); assert(_clip != null);
...@@ -2120,7 +2120,7 @@ class RenderTransform extends RenderProxyBox { ...@@ -2120,7 +2120,7 @@ class RenderTransform extends RenderProxyBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderTransform objects don't check if they are // RenderTransform objects don't check if they are
// themselves hit, because it's confusing to think about // themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed // how the untransformed size and the child's transformed
...@@ -2129,17 +2129,15 @@ class RenderTransform extends RenderProxyBox { ...@@ -2129,17 +2129,15 @@ class RenderTransform extends RenderProxyBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (transformHitTests) { assert(!transformHitTests || _effectiveTransform != null);
final Matrix4 inverse = Matrix4.tryInvert(_effectiveTransform); return result.addWithPaintTransform(
if (inverse == null) { transform: transformHitTests ? _effectiveTransform : null,
// We cannot invert the effective transform. That means the child position: position,
// doesn't appear on screen and cannot be hit. hitTest: (BoxHitTestResult result, Offset position) {
return false; return super.hitTestChildren(result, position: position);
} },
position = MatrixUtils.transformPoint(inverse, position); );
}
return super.hitTestChildren(result, position: position);
} }
@override @override
...@@ -2310,18 +2308,17 @@ class RenderFittedBox extends RenderProxyBox { ...@@ -2310,18 +2308,17 @@ class RenderFittedBox extends RenderProxyBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (size.isEmpty) if (size.isEmpty)
return false; return false;
_updatePaintData(); _updatePaintData();
final Matrix4 inverse = Matrix4.tryInvert(_transform); return result.addWithPaintTransform(
if (inverse == null) { transform: _transform,
// We cannot invert the effective transform. That means the child position: position,
// doesn't appear on screen and cannot be hit. hitTest: (BoxHitTestResult result, Offset position) {
return false; return super.hitTestChildren(result, position: position);
} },
position = MatrixUtils.transformPoint(inverse, position); );
return super.hitTestChildren(result, position: position);
} }
@override @override
...@@ -2379,7 +2376,7 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -2379,7 +2376,7 @@ class RenderFractionalTranslation extends RenderProxyBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderFractionalTranslation objects don't check if they are // RenderFractionalTranslation objects don't check if they are
// themselves hit, because it's confusing to think about // themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed // how the untransformed size and the child's transformed
...@@ -2396,15 +2393,17 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -2396,15 +2393,17 @@ class RenderFractionalTranslation extends RenderProxyBox {
bool transformHitTests; bool transformHitTests;
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(!debugNeedsLayout); assert(!debugNeedsLayout);
if (transformHitTests) { return result.addWithPaintOffset(
position = Offset( offset: transformHitTests
position.dx - translation.dx * size.width, ? Offset(translation.dx * size.width, translation.dy * size.height)
position.dy - translation.dy * size.height, : null,
); position: position,
} hitTest: (BoxHitTestResult result, Offset position) {
return super.hitTestChildren(result, position: position); return super.hitTestChildren(result, position: position);
},
);
} }
@override @override
...@@ -2960,7 +2959,7 @@ class RenderIgnorePointer extends RenderProxyBox { ...@@ -2960,7 +2959,7 @@ class RenderIgnorePointer extends RenderProxyBox {
bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? ignoring : ignoringSemantics; bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? ignoring : ignoringSemantics;
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
return ignoring ? false : super.hitTest(result, position: position); return ignoring ? false : super.hitTest(result, position: position);
} }
...@@ -3070,7 +3069,7 @@ class RenderOffstage extends RenderProxyBox { ...@@ -3070,7 +3069,7 @@ class RenderOffstage extends RenderProxyBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
return !offstage && super.hitTest(result, position: position); return !offstage && super.hitTest(result, position: position);
} }
...@@ -3166,7 +3165,7 @@ class RenderAbsorbPointer extends RenderProxyBox { ...@@ -3166,7 +3165,7 @@ class RenderAbsorbPointer extends RenderProxyBox {
bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? absorbing : ignoringSemantics; bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? absorbing : ignoringSemantics;
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
return absorbing return absorbing
? size.contains(position) ? size.contains(position)
: super.hitTest(result, position: position); : super.hitTest(result, position: position);
...@@ -4706,7 +4705,7 @@ class RenderFollowerLayer extends RenderProxyBox { ...@@ -4706,7 +4705,7 @@ class RenderFollowerLayer extends RenderProxyBox {
} }
@override @override
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderFollowerLayer objects don't check if they are // RenderFollowerLayer objects don't check if they are
// themselves hit, because it's confusing to think about // themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed // how the untransformed size and the child's transformed
...@@ -4715,15 +4714,14 @@ class RenderFollowerLayer extends RenderProxyBox { ...@@ -4715,15 +4714,14 @@ class RenderFollowerLayer extends RenderProxyBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
final Matrix4 inverse = Matrix4.tryInvert(getCurrentTransform()); return result.addWithPaintTransform(
if (inverse == null) { transform: getCurrentTransform(),
// We cannot invert the effective transform. That means the child position: position,
// doesn't appear on screen and cannot be hit. hitTest: (BoxHitTestResult result, Offset position) {
return false; return super.hitTestChildren(result, position: position);
} },
position = MatrixUtils.transformPoint(inverse, position); );
return super.hitTestChildren(result, position: position);
} }
@override @override
......
...@@ -90,12 +90,17 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB ...@@ -90,12 +90,17 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(_paintTransform != null || debugNeedsLayout || child == null); assert(_paintTransform != null || debugNeedsLayout || child == null);
if (child == null || _paintTransform == null) if (child == null || _paintTransform == null)
return false; return false;
final Matrix4 inverse = Matrix4.inverted(_paintTransform); return result.addWithPaintTransform(
return child.hitTest(result, position: MatrixUtils.transformPoint(inverse, position)); transform: _paintTransform,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return child.hitTest(result, position: position);
},
);
} }
void _paintChild(PaintingContext context, Offset offset) { void _paintChild(PaintingContext context, Offset offset) {
......
...@@ -72,10 +72,17 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi ...@@ -72,10 +72,17 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (child != null) { if (child != null) {
final BoxParentData childParentData = child.parentData; final BoxParentData childParentData = child.parentData;
return child.hitTest(result, position: position - childParentData.offset); return result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed);
},
);
} }
return false; return false;
} }
......
...@@ -774,6 +774,87 @@ class SliverGeometry extends Diagnosticable { ...@@ -774,6 +774,87 @@ class SliverGeometry extends Diagnosticable {
} }
} }
/// Method signature for hit testing a [RenderSLiver].
///
/// Used by [SliverHitTestResult.addWithAxisOffset] to hit test [RenderSliver]
/// children.
///
/// See also:
///
/// * [RenderSliver.hitTest], which documents more details around hit testing
/// [RenderSliver]s.
typedef SliverHitTest = bool Function(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition });
/// The result of performing a hit test on [RenderSliver]s.
///
/// An instance of this class is provided to [RenderSliver.hitTest] to record
/// the result of the hit test.
class SliverHitTestResult extends HitTestResult {
/// Creates an empty hit test result for hit testing on [RenderSliver].
SliverHitTestResult() : super();
/// Wraps `result` to create a [HitTestResult] that implements the
/// [SliverHitTestResult] protocol for hit testing on [RenderSliver]s.
///
/// This method is used by [RenderObject]s that adapt between the
/// [RenderSliver]-world and the non-[RenderSliver]-world to convert a
/// (subtype of) [HitTestResult] to a [SliverHitTestResult] for hit testing on
/// [RenderSliver]s.
///
/// The [HitTestEntry] instances added to the returned [SliverHitTestResult]
/// are also added to the wrapped `result` (both share the same underlying
/// data structure to store [HitTestEntry] instances).
///
/// See also:
///
/// * [HitTestResult.wrap], which turns a [SliverHitTestResult] back into a
/// generic [HitTestResult].
/// * [BoxHitTestResult.wrap], which turns a [SliverHitTestResult] into a
/// [BoxHitTestResult] for hit testing on [RenderBox] children.
SliverHitTestResult.wrap(HitTestResult result) : super.wrap(result);
/// Transforms `mainAxisPosition` and `crossAxisPosition` to the local
/// coordinate system of a child for hit-testing the child.
///
/// The actual hit testing of the child needs to be implemented in the
/// provided `hitTest` callback, which is invoked with the transformed
/// `position` as argument.
///
/// For the transform `mainAxisOffset` is subtracted from `mainAxisPosition`
/// and `crossAxisOffset` is subtracted from `crossAxisPosition`.
///
/// The `paintOffset` describes how the paint position of a point painted at
/// the provided `mainAxisPosition` and `crossAxisPosition` would change after
/// `mainAxisOffset` and `crossAxisOffset` have been applied. This
/// `paintOffset` is used to properly convert [PointerEvent]s to the local
/// coordinate system of the event receiver.
///
/// The `paintOffset` may be null if `mainAxisOffset` and `crossAxisOffset` are
/// both zero.
///
/// The function returns the return value of `hitTest`.
bool addWithAxisOffset({
@required Offset paintOffset,
@required double mainAxisOffset,
@required double crossAxisOffset,
@required double mainAxisPosition,
@required double crossAxisPosition,
@required SliverHitTest hitTest,
}) {
assert(mainAxisOffset != null);
assert(crossAxisOffset != null);
assert(mainAxisPosition != null);
assert(crossAxisPosition != null);
assert(hitTest != null);
// TODO(goderbauer): use paintOffset when transforming pointer events is implemented.
return hitTest(
this,
mainAxisPosition: mainAxisPosition - mainAxisOffset,
crossAxisPosition: crossAxisPosition - crossAxisOffset,
);
}
}
/// A hit test entry used by [RenderSliver]. /// A hit test entry used by [RenderSliver].
/// ///
/// The coordinate system used by this hit test entry is relative to the /// The coordinate system used by this hit test entry is relative to the
...@@ -1186,7 +1267,7 @@ abstract class RenderSliver extends RenderObject { ...@@ -1186,7 +1267,7 @@ abstract class RenderSliver extends RenderObject {
/// The most straight-forward way to implement hit testing for a new sliver /// The most straight-forward way to implement hit testing for a new sliver
/// render object is to override its [hitTestSelf] and [hitTestChildren] /// render object is to override its [hitTestSelf] and [hitTestChildren]
/// methods. /// methods.
bool hitTest(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTest(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (mainAxisPosition >= 0.0 && mainAxisPosition < geometry.hitTestExtent && if (mainAxisPosition >= 0.0 && mainAxisPosition < geometry.hitTestExtent &&
crossAxisPosition >= 0.0 && crossAxisPosition < constraints.crossAxisExtent) { crossAxisPosition >= 0.0 && crossAxisPosition < constraints.crossAxisExtent) {
if (hitTestChildren(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition) || if (hitTestChildren(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition) ||
...@@ -1224,7 +1305,7 @@ abstract class RenderSliver extends RenderObject { ...@@ -1224,7 +1305,7 @@ abstract class RenderSliver extends RenderObject {
/// ///
/// For a discussion of the semantics of the arguments, see [hitTest]. /// For a discussion of the semantics of the arguments, see [hitTest].
@protected @protected
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) => false; bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) => false;
/// Computes the portion of the region from `from` to `to` that is visible, /// Computes the portion of the region from `from` to `to` that is visible,
/// assuming that only the region from the [SliverConstraints.scrollOffset] /// assuming that only the region from the [SliverConstraints.scrollOffset]
...@@ -1514,22 +1595,41 @@ abstract class RenderSliverHelpers implements RenderSliver { ...@@ -1514,22 +1595,41 @@ abstract class RenderSliverHelpers implements RenderSliver {
/// ///
/// Calling this for a child that is not visible is not valid. /// Calling this for a child that is not visible is not valid.
@protected @protected
bool hitTestBoxChild(HitTestResult result, RenderBox child, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestBoxChild(BoxHitTestResult result, RenderBox child, { @required double mainAxisPosition, @required double crossAxisPosition }) {
final bool rightWayUp = _getRightWayUp(constraints); final bool rightWayUp = _getRightWayUp(constraints);
double absolutePosition = mainAxisPosition - childMainAxisPosition(child); double delta = childMainAxisPosition(child);
final double absoluteCrossAxisPosition = crossAxisPosition - childCrossAxisPosition(child); final double crossAxisDelta = childCrossAxisPosition(child);
double absolutePosition = mainAxisPosition - delta;
final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta;
Offset paintOffset, transformedPosition;
assert(constraints.axis != null); assert(constraints.axis != null);
switch (constraints.axis) { switch (constraints.axis) {
case Axis.horizontal: case Axis.horizontal:
if (!rightWayUp) if (!rightWayUp) {
absolutePosition = child.size.width - absolutePosition; absolutePosition = child.size.width - absolutePosition;
return child.hitTest(result, position: Offset(absolutePosition, absoluteCrossAxisPosition)); delta = geometry.paintExtent - child.size.width - delta;
}
paintOffset = Offset(delta, crossAxisDelta);
transformedPosition = Offset(absolutePosition, absoluteCrossAxisPosition);
break;
case Axis.vertical: case Axis.vertical:
if (!rightWayUp) if (!rightWayUp) {
absolutePosition = child.size.height - absolutePosition; absolutePosition = child.size.height - absolutePosition;
return child.hitTest(result, position: Offset(absoluteCrossAxisPosition, absolutePosition)); delta = geometry.paintExtent - child.size.height - delta;
}
paintOffset = Offset(crossAxisDelta, delta);
transformedPosition = Offset(absoluteCrossAxisPosition, absolutePosition);
break;
} }
return false; assert(paintOffset != null);
assert(transformedPosition != null);
return result.addWithPaintOffset(
offset: paintOffset,
position: null, // Manually adapting from sliver to box position above.
hitTest: (BoxHitTestResult result, Offset _) {
return child.hitTest(result, position: transformedPosition);
},
);
} }
/// Utility function for [applyPaintTransform] for use when the children are /// Utility function for [applyPaintTransform] for use when the children are
...@@ -1614,10 +1714,10 @@ abstract class RenderSliverSingleBoxAdapter extends RenderSliver with RenderObje ...@@ -1614,10 +1714,10 @@ abstract class RenderSliverSingleBoxAdapter extends RenderSliver with RenderObje
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
assert(geometry.hitTestExtent > 0.0); assert(geometry.hitTestExtent > 0.0);
if (child != null) if (child != null)
return hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition); return hitTestBoxChild(BoxHitTestResult.wrap(result), child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false; return false;
} }
......
...@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; ...@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'sliver.dart'; import 'sliver.dart';
...@@ -558,10 +557,11 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...@@ -558,10 +557,11 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
RenderBox child = lastChild; RenderBox child = lastChild;
final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
while (child != null) { while (child != null) {
if (hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition)) if (hitTestBoxChild(boxResult, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
return true; return true;
child = childBefore(child); child = childBefore(child);
} }
......
...@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart'; ...@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'debug.dart'; import 'debug.dart';
import 'object.dart'; import 'object.dart';
import 'sliver.dart'; import 'sliver.dart';
...@@ -261,9 +260,18 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -261,9 +260,18 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child != null && child.geometry.hitTestExtent > 0.0) if (child != null && child.geometry.hitTestExtent > 0.0) {
return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child)); final SliverPhysicalParentData childParentData = child.parentData;
result.addWithAxisOffset(
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
mainAxisOffset: childMainAxisPosition(child),
crossAxisOffset: childCrossAxisPosition(child),
paintOffset: childParentData.paintOffset,
hitTest: child.hitTest,
);
}
return false; return false;
} }
......
...@@ -11,7 +11,6 @@ import 'package:flutter/scheduler.dart'; ...@@ -11,7 +11,6 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart'; import 'package:flutter/semantics.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'sliver.dart'; import 'sliver.dart';
...@@ -169,10 +168,10 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje ...@@ -169,10 +168,10 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
double childMainAxisPosition(covariant RenderObject child) => super.childMainAxisPosition(child); double childMainAxisPosition(covariant RenderObject child) => super.childMainAxisPosition(child);
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
assert(geometry.hitTestExtent > 0.0); assert(geometry.hitTestExtent > 0.0);
if (child != null) if (child != null)
return hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition); return hitTestBoxChild(BoxHitTestResult.wrap(result), child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false; return false;
} }
......
...@@ -581,7 +581,7 @@ class RenderStack extends RenderBox ...@@ -581,7 +581,7 @@ class RenderStack extends RenderBox
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
...@@ -668,13 +668,20 @@ class RenderIndexedStack extends RenderStack { ...@@ -668,13 +668,20 @@ class RenderIndexedStack extends RenderStack {
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
if (firstChild == null || index == null) if (firstChild == null || index == null)
return false; return false;
assert(position != null); assert(position != null);
final RenderBox child = _childAtIndex(); final RenderBox child = _childAtIndex();
final StackParentData childParentData = child.parentData; final StackParentData childParentData = child.parentData;
return child.hitTest(result, position: position - childParentData.offset); return result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed);
},
);
} }
@override @override
......
...@@ -1104,13 +1104,21 @@ class RenderTable extends RenderBox { ...@@ -1104,13 +1104,21 @@ class RenderTable extends RenderBox {
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(_children.length == rows * columns); assert(_children.length == rows * columns);
for (int index = _children.length - 1; index >= 0; index -= 1) { for (int index = _children.length - 1; index >= 0; index -= 1) {
final RenderBox child = _children[index]; final RenderBox child = _children[index];
if (child != null) { if (child != null) {
final BoxParentData childParentData = child.parentData; final BoxParentData childParentData = child.parentData;
if (child.hitTest(result, position: position - childParentData.offset)) final bool isHit = result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit)
return true; return true;
} }
} }
......
...@@ -168,7 +168,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -168,7 +168,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
/// normally be in physical (device) pixels. /// normally be in physical (device) pixels.
bool hitTest(HitTestResult result, { Offset position }) { bool hitTest(HitTestResult result, { Offset position }) {
if (child != null) if (child != null)
child.hitTest(result, position: position); child.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this)); result.add(HitTestEntry(this));
return true; return true;
} }
......
...@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart'; ...@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart'; import 'package:flutter/semantics.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'sliver.dart'; import 'sliver.dart';
...@@ -558,7 +557,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -558,7 +557,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
double mainAxisPosition, crossAxisPosition; double mainAxisPosition, crossAxisPosition;
switch (axis) { switch (axis) {
case Axis.vertical: case Axis.vertical:
...@@ -572,12 +571,25 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -572,12 +571,25 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
} }
assert(mainAxisPosition != null); assert(mainAxisPosition != null);
assert(crossAxisPosition != null); assert(crossAxisPosition != null);
final SliverHitTestResult sliverResult = SliverHitTestResult.wrap(result);
for (RenderSliver child in childrenInHitTestOrder) { for (RenderSliver child in childrenInHitTestOrder) {
if (child.geometry.visible && child.hitTest( if (!child.geometry.visible) {
result, continue;
mainAxisPosition: computeChildMainAxisPosition(child, mainAxisPosition), }
crossAxisPosition: crossAxisPosition, final Matrix4 transform = Matrix4.identity();
)) { applyPaintTransform(child, transform);
final bool isHit = result.addWithPaintTransform(
transform: transform,
position: null, // Manually adapting from box to sliver position below.
hitTest: (BoxHitTestResult result, Offset _) {
return child.hitTest(
sliverResult,
mainAxisPosition: computeChildMainAxisPosition(child, mainAxisPosition),
crossAxisPosition: crossAxisPosition,
);
},
);
if (isHit) {
return true; return true;
} }
} }
......
...@@ -748,7 +748,7 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin<RenderBox, Wr ...@@ -748,7 +748,7 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin<RenderBox, Wr
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position); return defaultHitTestChildren(result, position: position);
} }
......
...@@ -214,7 +214,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren ...@@ -214,7 +214,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false; return child?.hitTest(result, position: position) ?? false;
} }
......
...@@ -1446,7 +1446,7 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil ...@@ -1446,7 +1446,7 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil
} }
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child != null) if (child != null)
return child.hitTest(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition); return child.hitTest(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false; return false;
......
...@@ -555,10 +555,16 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -555,10 +555,16 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
} }
@override @override
bool hitTestChildren(HitTestResult result, { Offset position }) { bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (child != null) { if (child != null) {
final Offset transformed = position + -_paintOffset; return result.addWithPaintOffset(
return child.hitTest(result, position: transformed); offset: _paintOffset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position + -_paintOffset);
return child.hitTest(result, position: transformed);
},
);
} }
return false; return false;
} }
......
// Copyright 2019 The Chromium 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:flutter/gestures.dart';
import '../flutter_test_alternative.dart';
void main() {
test('wrpped HitTestResult gets HitTestEntry added to wrapping HitTestResult', () async {
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
final HitTestResult wrapped = HitTestResult();
wrapped.add(entry1);
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
final HitTestResult wrapping = HitTestResult.wrap(wrapped);
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
expect(wrapping.path, same(wrapped.path));
wrapping.add(entry2);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
wrapped.add(entry3);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
});
}
class _DummyHitTestTarget implements HitTestTarget {
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
// Nothing to do.
}
}
...@@ -1819,8 +1819,8 @@ void main() { ...@@ -1819,8 +1819,8 @@ void main() {
expect(fourthPos.dx, 0); expect(fourthPos.dx, 0);
expect(firstPos.dx, fourthPos.dx); expect(firstPos.dx, fourthPos.dx);
expect(firstPos.dy, lessThan(fourthPos.dy)); expect(firstPos.dy, lessThan(fourthPos.dy));
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse);
TestGesture gesture = await tester.startGesture(firstPos, pointer: 7); TestGesture gesture = await tester.startGesture(firstPos, pointer: 7);
await tester.pump(); await tester.pump();
...@@ -1839,8 +1839,8 @@ void main() { ...@@ -1839,8 +1839,8 @@ void main() {
Offset newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth')); Offset newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
expect(newFirstPos.dy, lessThan(firstPos.dy)); expect(newFirstPos.dy, lessThan(firstPos.dy));
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isTrue); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isTrue);
// Now try scrolling by dragging the selection handle. // Now try scrolling by dragging the selection handle.
// Long press the middle of the word "won't" in the fourth line. // Long press the middle of the word "won't" in the fourth line.
...@@ -1885,8 +1885,8 @@ void main() { ...@@ -1885,8 +1885,8 @@ void main() {
newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First')); newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth')); newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
expect(newFirstPos.dy, firstPos.dy); expect(newFirstPos.dy, firstPos.dy);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
}); });
testWidgets('TextField smoke test', (WidgetTester tester) async { testWidgets('TextField smoke test', (WidgetTester tester) async {
......
...@@ -86,4 +86,39 @@ void main() { ...@@ -86,4 +86,39 @@ void main() {
0.0, moreOrLessEquals(-86.60254037844386), moreOrLessEquals(-50.0), 1.05, 0.0, moreOrLessEquals(-86.60254037844386), moreOrLessEquals(-50.0), 1.05,
]); ]);
}); });
test('forceToPoint', () {
const Offset forcedOffset = Offset(20, -30);
final Matrix4 forcedTransform = MatrixUtils.forceToPoint(forcedOffset);
expect(
MatrixUtils.transformPoint(forcedTransform, forcedOffset),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, Offset.zero),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(1, 1)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-1, -1)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-20, 30)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-1.2344, 1422434.23)),
forcedOffset,
);
});
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -258,4 +259,261 @@ void main() { ...@@ -258,4 +259,261 @@ void main() {
expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width'); expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width');
expect(unconstrained.size.height, equals(100.0), reason: 'constrained height'); expect(unconstrained.size.height, equals(100.0), reason: 'constrained height');
}); });
group('hit testing', () {
test('BoxHitTestResult wrapping HitTestResult', () {
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
final HitTestResult wrapped = HitTestResult();
wrapped.add(entry1);
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
final BoxHitTestResult wrapping = BoxHitTestResult.wrap(wrapped);
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
expect(wrapping.path, same(wrapped.path));
wrapping.add(entry2);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
wrapped.add(entry3);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
});
test('addWithPaintTransform', () {
final BoxHitTestResult result = BoxHitTestResult();
final List<Offset> positions = <Offset>[];
bool isHit = result.addWithPaintTransform(
transform: null,
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
isHit = result.addWithPaintTransform(
transform: Matrix4.translationValues(20, 30, 0),
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
const Offset position = Offset(3, 4);
isHit = result.addWithPaintTransform(
transform: null,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return false;
},
);
expect(isHit, isFalse);
expect(positions.single, position);
positions.clear();
isHit = result.addWithPaintTransform(
transform: Matrix4.identity(),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position);
positions.clear();
isHit = result.addWithPaintTransform(
transform: Matrix4.translationValues(20, 30, 0),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position - const Offset(20, 30));
positions.clear();
isHit = result.addWithPaintTransform(
transform: MatrixUtils.forceToPoint(position), // cannot be inverted
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isFalse);
expect(positions, isEmpty);
positions.clear();
});
test('addWithPaintOffset', () {
final BoxHitTestResult result = BoxHitTestResult();
final List<Offset> positions = <Offset>[];
bool isHit = result.addWithPaintOffset(
offset: null,
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
isHit = result.addWithPaintOffset(
offset: const Offset(55, 32),
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
const Offset position = Offset(3, 4);
isHit = result.addWithPaintOffset(
offset: null,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return false;
},
);
expect(isHit, isFalse);
expect(positions.single, position);
positions.clear();
isHit = result.addWithPaintOffset(
offset: Offset.zero,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position);
positions.clear();
isHit = result.addWithPaintOffset(
offset: const Offset(20, 30),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position - const Offset(20, 30));
positions.clear();
});
test('addWithRawTransform', () {
final BoxHitTestResult result = BoxHitTestResult();
final List<Offset> positions = <Offset>[];
bool isHit = result.addWithRawTransform(
transform: null,
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
isHit = result.addWithRawTransform(
transform: Matrix4.translationValues(20, 30, 0),
position: null,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, isNull);
positions.clear();
const Offset position = Offset(3, 4);
isHit = result.addWithRawTransform(
transform: null,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return false;
},
);
expect(isHit, isFalse);
expect(positions.single, position);
positions.clear();
isHit = result.addWithRawTransform(
transform: Matrix4.identity(),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position);
positions.clear();
isHit = result.addWithRawTransform(
transform: Matrix4.translationValues(20, 30, 0),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
expect(result, isNotNull);
positions.add(position);
return true;
},
);
expect(isHit, isTrue);
expect(positions.single, position + const Offset(20, 30));
positions.clear();
});
});
}
class _DummyHitTestTarget implements HitTestTarget {
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
// Nothing to do.
}
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -200,7 +201,7 @@ void main() { ...@@ -200,7 +201,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0)); root.hitTest(result, position: const Offset(130.0, 150.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -254,7 +255,7 @@ void main() { ...@@ -254,7 +255,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0)); root.hitTest(result, position: const Offset(150.0, 350.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -343,7 +344,7 @@ void main() { ...@@ -343,7 +344,7 @@ void main() {
expect(_getPaintOrigin(sliverD), const Offset(300.0, 0.0)); expect(_getPaintOrigin(sliverD), const Offset(300.0, 0.0));
expect(_getPaintOrigin(sliverE), const Offset(700.0, 0.0)); expect(_getPaintOrigin(sliverE), const Offset(700.0, 0.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 450.0)); root.hitTest(result, position: const Offset(150.0, 450.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -397,7 +398,7 @@ void main() { ...@@ -397,7 +398,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-300.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-300.0, 0.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(550.0, 150.0)); root.hitTest(result, position: const Offset(550.0, 150.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -471,7 +472,7 @@ void main() { ...@@ -471,7 +472,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0)); root.hitTest(result, position: const Offset(130.0, 150.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -525,7 +526,7 @@ void main() { ...@@ -525,7 +526,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0)); root.hitTest(result, position: const Offset(150.0, 350.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -579,7 +580,7 @@ void main() { ...@@ -579,7 +580,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(300.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(300.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 0.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 450.0)); root.hitTest(result, position: const Offset(150.0, 450.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -633,7 +634,7 @@ void main() { ...@@ -633,7 +634,7 @@ void main() {
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-300.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-300.0, 0.0));
final HitTestResult result = HitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(550.0, 150.0)); root.hitTest(result, position: const Offset(550.0, 150.0));
expect(result.path.first.target, equals(c)); expect(result.path.first.target, equals(c));
}); });
...@@ -824,4 +825,99 @@ void main() { ...@@ -824,4 +825,99 @@ void main() {
// offset is not expected to impact the precedingScrollExtent. // offset is not expected to impact the precedingScrollExtent.
expect(sliver3.constraints.precedingScrollExtent, 300.0); expect(sliver3.constraints.precedingScrollExtent, 300.0);
}); });
group('hit testing', () {
test('SliverHitTestResult wrapping HitTestResult', () {
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry2 = HitTestEntry(_DummyHitTestTarget());
final HitTestEntry entry3 = HitTestEntry(_DummyHitTestTarget());
final HitTestResult wrapped = HitTestResult();
wrapped.add(entry1);
expect(wrapped.path, equals(<HitTestEntry>[entry1]));
final SliverHitTestResult wrapping = SliverHitTestResult.wrap(wrapped);
expect(wrapping.path, equals(<HitTestEntry>[entry1]));
expect(wrapping.path, same(wrapped.path));
wrapping.add(entry2);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2]));
wrapped.add(entry3);
expect(wrapping.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
});
test('addWithAxisOffset', () {
final SliverHitTestResult result = SliverHitTestResult();
final List<double> mainAxisPositions = <double>[];
final List<double> crossAxisPositions = <double>[];
const Offset paintOffsetDummy = Offset.zero;
bool isHit = result.addWithAxisOffset(
paintOffset: paintOffsetDummy,
mainAxisOffset: 0.0,
crossAxisOffset: 0.0,
mainAxisPosition: 0.0,
crossAxisPosition: 0.0,
hitTest: (SliverHitTestResult result, { double mainAxisPosition, double crossAxisPosition }) {
expect(result, isNotNull);
mainAxisPositions.add(mainAxisPosition);
crossAxisPositions.add(crossAxisPosition);
return true;
},
);
expect(isHit, isTrue);
expect(mainAxisPositions.single, 0.0);
expect(crossAxisPositions.single, 0.0);
mainAxisPositions.clear();
crossAxisPositions.clear();
isHit = result.addWithAxisOffset(
paintOffset: paintOffsetDummy,
mainAxisOffset: 5.0,
crossAxisOffset: 6.0,
mainAxisPosition: 10.0,
crossAxisPosition: 20.0,
hitTest: (SliverHitTestResult result, { double mainAxisPosition, double crossAxisPosition }) {
expect(result, isNotNull);
mainAxisPositions.add(mainAxisPosition);
crossAxisPositions.add(crossAxisPosition);
return false;
},
);
expect(isHit, isFalse);
expect(mainAxisPositions.single, 10.0 - 5.0);
expect(crossAxisPositions.single, 20.0 - 6.0);
mainAxisPositions.clear();
crossAxisPositions.clear();
isHit = result.addWithAxisOffset(
paintOffset: paintOffsetDummy,
mainAxisOffset: -5.0,
crossAxisOffset: -6.0,
mainAxisPosition: 10.0,
crossAxisPosition: 20.0,
hitTest: (SliverHitTestResult result, { double mainAxisPosition, double crossAxisPosition }) {
expect(result, isNotNull);
mainAxisPositions.add(mainAxisPosition);
crossAxisPositions.add(crossAxisPosition);
return false;
},
);
expect(isHit, isFalse);
expect(mainAxisPositions.single, 10.0 + 5.0);
expect(crossAxisPositions.single, 20.0 + 6.0);
mainAxisPositions.clear();
crossAxisPositions.clear();
});
});
}
class _DummyHitTestTarget implements HitTestTarget {
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
// Nothing to do.
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment