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;
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';
const double kTwoPi = 2 * math.pi;
......@@ -76,6 +77,18 @@ class SectorParentData extends ParentData {
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 {
@override
......@@ -130,15 +143,15 @@ abstract class RenderSector extends RenderObject {
@override
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 ||
theta < parentData.theta || theta >= parentData.theta + deltaTheta)
return false;
hitTestChildren(result, radius: radius, theta: theta);
result.add(HitTestEntry(this));
result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
return true;
}
void hitTestChildren(HitTestResult result, { double radius, double theta }) { }
void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) { }
double deltaRadius;
double deltaTheta;
......@@ -190,7 +203,7 @@ class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRende
RenderSectorWithChildren(BoxDecoration decoration) : super(decoration);
@override
void hitTestChildren(HitTestResult result, { double radius, double theta }) {
void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) {
RenderSector child = lastChild;
while (child != null) {
if (child.hitTest(result, radius: radius, theta: theta))
......@@ -530,7 +543,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (child == null)
return false;
double x = position.dx;
......@@ -547,7 +560,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil
return false;
if (theta > child.deltaTheta)
return false;
child.hitTest(result, radius: radius, theta: theta);
child.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
result.add(BoxHitTestEntry(this, position));
return true;
}
......@@ -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 {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool isHit = false;
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
final MultiChildLayoutParentData contentSectionParentData = contentSection.parentData;
final MultiChildLayoutParentData actionsSectionParentData = actionsSection.parentData;
if (contentSection.hitTest(result, position: position - contentSectionParentData.offset)) {
isHit = true;
} else if (actionsSection.hitTest(result,
position: position - actionsSectionParentData.offset)) {
isHit = true;
}
return isHit;
return result.addWithPaintOffset(
offset: contentSectionParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - contentSectionParentData.offset);
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);
},
);
}
}
......@@ -1261,7 +1269,7 @@ class _RenderCupertinoAlertActions extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
}
......@@ -775,16 +775,25 @@ class _RenderCupertinoDialog extends RenderBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool isHit = false;
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
final BoxParentData contentSectionParentData = contentSection.parentData;
final BoxParentData actionsSectionParentData = actionsSection.parentData;
if (contentSection.hitTest(result, position: position - contentSectionParentData.offset)) {
isHit = true;
} else if (actionsSection.hitTest(result, position: position - actionsSectionParentData.offset)) {
isHit = true;
}
return isHit;
return result.addWithPaintOffset(
offset: contentSectionParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - contentSectionParentData.offset);
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
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
}
......@@ -703,13 +703,21 @@ class _RenderSegmentedControl<T> extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { @required Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null);
RenderBox child = lastChild;
while (child != null) {
final _SegmentedControlContainerBoxParentData childParentData = child.parentData;
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;
}
......
......@@ -54,12 +54,16 @@ class HitTestEntry {
/// The result of performing a hit test.
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
/// and empty list.
HitTestResult({ List<HitTestEntry> path })
: _path = path ?? <HitTestEntry>[];
/// The [HitTestEntry]s added to the returned [HitTestResult] are also
/// added to the wrapped `result` (both share the same underlying data
/// structure to store [HitTestEntry]s).
HitTestResult.wrap(HitTestResult result) : _path = result._path;
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
///
......
......@@ -324,8 +324,18 @@ class _RenderInputPadding extends RenderShiftedBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
return super.hitTest(result, position: position) ||
child.hitTest(result, position: child.size.center(Offset.zero));
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (super.hitTest(result, position: position)) {
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 {
_RenderChipRedirectingHitDetection(BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints);
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (!size.contains(position))
return false;
// 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
// 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 {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (!size.contains(position))
return false;
RenderBox hitTestChild;
......@@ -2287,7 +2295,18 @@ class _RenderChip extends RenderBox {
hitTestChild = label ?? avatar;
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
......
......@@ -1287,11 +1287,20 @@ class _RenderDecoration extends RenderBox {
bool hitTestSelf(Offset position) => true;
@override
bool hitTestChildren(HitTestResult result, { @required Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null);
for (RenderBox child in _children) {
// 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 false;
......
......@@ -1460,11 +1460,19 @@ class _RenderListTile extends RenderBox {
bool hitTestSelf(Offset position) => true;
@override
bool hitTestChildren(HitTestResult result, { @required Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
assert(position != null);
for (RenderBox child in _children) {
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 false;
......
......@@ -249,6 +249,13 @@ class MatrixUtils {
// Essentially perspective * view * model.
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
......
This diff is collapsed.
......@@ -362,7 +362,7 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
}
......@@ -502,7 +502,7 @@ class RenderCustomPaint extends RenderProxyBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (_foregroundPainter != null && (_foregroundPainter.hitTest(position) ?? false))
return true;
return super.hitTestChildren(result, position: position);
......
......@@ -937,7 +937,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
......
......@@ -369,7 +369,7 @@ class RenderFlow extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
final List<RenderBox> children = getChildrenAsList();
for (int i = _lastPaintOrder.length - 1; i >= 0; --i) {
final int childIndex = _lastPaintOrder[i];
......@@ -380,15 +380,14 @@ class RenderFlow extends RenderBox
final Matrix4 transform = childParentData._transform;
if (transform == null)
continue;
final Matrix4 inverse = Matrix4.zero();
final double determinate = inverse.copyInverse(transform);
if (determinate == 0.0) {
// We cannot invert the transform. That means the child doesn't appear
// on screen and cannot be hit.
continue;
}
final Offset childPosition = MatrixUtils.transformPoint(inverse, position);
if (child.hitTest(result, position: childPosition))
final bool absorbed = result.addWithPaintTransform(
transform: transform,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return child.hitTest(result, position: position);
},
);
if (absorbed)
return true;
}
return false;
......
......@@ -265,7 +265,7 @@ class RenderListBody extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
......
......@@ -984,7 +984,7 @@ class RenderListWheelViewport
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return false;
}
......
......@@ -230,7 +230,7 @@ class RenderAndroidView extends RenderBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position))
return false;
result.add(BoxHitTestEntry(this, position));
......@@ -365,7 +365,7 @@ class RenderUiKitView extends RenderBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position))
return false;
result.add(BoxHitTestEntry(this, position));
......
......@@ -110,7 +110,7 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false;
}
......@@ -155,7 +155,7 @@ abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox {
HitTestBehavior behavior;
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
......@@ -1278,7 +1278,7 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
Rect get _defaultClip => Offset.zero & size;
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
......@@ -1354,7 +1354,7 @@ class RenderClipRRect extends _RenderCustomClip<RRect> {
RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size);
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
......@@ -1419,7 +1419,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
Rect get _defaultClip => Offset.zero & size;
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
_updateClip();
assert(_clip != null);
final Offset center = _clip.center;
......@@ -1484,7 +1484,7 @@ class RenderClipPath extends _RenderCustomClip<Path> {
Path get _defaultClip => Path()..addRect(Offset.zero & size);
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
......@@ -1677,7 +1677,7 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
......@@ -1772,7 +1772,7 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
Path get _defaultClip => Path()..addRect(Offset.zero & size);
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
......@@ -2120,7 +2120,7 @@ class RenderTransform extends RenderProxyBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderTransform objects don't check if they are
// themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed
......@@ -2129,17 +2129,15 @@ class RenderTransform extends RenderProxyBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
if (transformHitTests) {
final Matrix4 inverse = Matrix4.tryInvert(_effectiveTransform);
if (inverse == null) {
// We cannot invert the effective transform. That means the child
// doesn't appear on screen and cannot be hit.
return false;
}
position = MatrixUtils.transformPoint(inverse, position);
}
return super.hitTestChildren(result, position: position);
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(!transformHitTests || _effectiveTransform != null);
return result.addWithPaintTransform(
transform: transformHitTests ? _effectiveTransform : null,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return super.hitTestChildren(result, position: position);
},
);
}
@override
......@@ -2310,18 +2308,17 @@ class RenderFittedBox extends RenderProxyBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (size.isEmpty)
return false;
_updatePaintData();
final Matrix4 inverse = Matrix4.tryInvert(_transform);
if (inverse == null) {
// We cannot invert the effective transform. That means the child
// doesn't appear on screen and cannot be hit.
return false;
}
position = MatrixUtils.transformPoint(inverse, position);
return super.hitTestChildren(result, position: position);
return result.addWithPaintTransform(
transform: _transform,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return super.hitTestChildren(result, position: position);
},
);
}
@override
......@@ -2379,7 +2376,7 @@ class RenderFractionalTranslation extends RenderProxyBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderFractionalTranslation objects don't check if they are
// themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed
......@@ -2396,15 +2393,17 @@ class RenderFractionalTranslation extends RenderProxyBox {
bool transformHitTests;
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(!debugNeedsLayout);
if (transformHitTests) {
position = Offset(
position.dx - translation.dx * size.width,
position.dy - translation.dy * size.height,
);
}
return super.hitTestChildren(result, position: position);
return result.addWithPaintOffset(
offset: transformHitTests
? Offset(translation.dx * size.width, translation.dy * size.height)
: null,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return super.hitTestChildren(result, position: position);
},
);
}
@override
......@@ -2960,7 +2959,7 @@ class RenderIgnorePointer extends RenderProxyBox {
bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? ignoring : ignoringSemantics;
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
return ignoring ? false : super.hitTest(result, position: position);
}
......@@ -3070,7 +3069,7 @@ class RenderOffstage extends RenderProxyBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
return !offstage && super.hitTest(result, position: position);
}
......@@ -3166,7 +3165,7 @@ class RenderAbsorbPointer extends RenderProxyBox {
bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? absorbing : ignoringSemantics;
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
return absorbing
? size.contains(position)
: super.hitTest(result, position: position);
......@@ -4706,7 +4705,7 @@ class RenderFollowerLayer extends RenderProxyBox {
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTest(BoxHitTestResult result, { Offset position }) {
// RenderFollowerLayer objects don't check if they are
// themselves hit, because it's confusing to think about
// how the untransformed size and the child's transformed
......@@ -4715,15 +4714,14 @@ class RenderFollowerLayer extends RenderProxyBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
final Matrix4 inverse = Matrix4.tryInvert(getCurrentTransform());
if (inverse == null) {
// We cannot invert the effective transform. That means the child
// doesn't appear on screen and cannot be hit.
return false;
}
position = MatrixUtils.transformPoint(inverse, position);
return super.hitTestChildren(result, position: position);
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return result.addWithPaintTransform(
transform: getCurrentTransform(),
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return super.hitTestChildren(result, position: position);
},
);
}
@override
......
......@@ -90,12 +90,17 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(_paintTransform != null || debugNeedsLayout || child == null);
if (child == null || _paintTransform == null)
return false;
final Matrix4 inverse = Matrix4.inverted(_paintTransform);
return child.hitTest(result, position: MatrixUtils.transformPoint(inverse, position));
return result.addWithPaintTransform(
transform: _paintTransform,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return child.hitTest(result, position: position);
},
);
}
void _paintChild(PaintingContext context, Offset offset) {
......
......@@ -72,10 +72,17 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (child != null) {
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;
}
......
......@@ -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].
///
/// The coordinate system used by this hit test entry is relative to the
......@@ -1186,7 +1267,7 @@ abstract class RenderSliver extends RenderObject {
/// The most straight-forward way to implement hit testing for a new sliver
/// render object is to override its [hitTestSelf] and [hitTestChildren]
/// 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 &&
crossAxisPosition >= 0.0 && crossAxisPosition < constraints.crossAxisExtent) {
if (hitTestChildren(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition) ||
......@@ -1224,7 +1305,7 @@ abstract class RenderSliver extends RenderObject {
///
/// For a discussion of the semantics of the arguments, see [hitTest].
@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,
/// assuming that only the region from the [SliverConstraints.scrollOffset]
......@@ -1514,22 +1595,41 @@ abstract class RenderSliverHelpers implements RenderSliver {
///
/// Calling this for a child that is not visible is not valid.
@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);
double absolutePosition = mainAxisPosition - childMainAxisPosition(child);
final double absoluteCrossAxisPosition = crossAxisPosition - childCrossAxisPosition(child);
double delta = childMainAxisPosition(child);
final double crossAxisDelta = childCrossAxisPosition(child);
double absolutePosition = mainAxisPosition - delta;
final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta;
Offset paintOffset, transformedPosition;
assert(constraints.axis != null);
switch (constraints.axis) {
case Axis.horizontal:
if (!rightWayUp)
if (!rightWayUp) {
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:
if (!rightWayUp)
if (!rightWayUp) {
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
......@@ -1614,10 +1714,10 @@ abstract class RenderSliverSingleBoxAdapter extends RenderSliver with RenderObje
}
@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);
if (child != null)
return hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return hitTestBoxChild(BoxHitTestResult.wrap(result), child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false;
}
......
......@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'object.dart';
import 'sliver.dart';
......@@ -558,10 +557,11 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
}
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
RenderBox child = lastChild;
final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
while (child != null) {
if (hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
if (hitTestBoxChild(boxResult, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
return true;
child = childBefore(child);
}
......
......@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'debug.dart';
import 'object.dart';
import 'sliver.dart';
......@@ -261,9 +260,18 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
}
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child != null && child.geometry.hitTestExtent > 0.0)
return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child));
bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child != null && child.geometry.hitTestExtent > 0.0) {
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;
}
......
......@@ -11,7 +11,6 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'object.dart';
import 'sliver.dart';
......@@ -169,10 +168,10 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
double childMainAxisPosition(covariant RenderObject child) => super.childMainAxisPosition(child);
@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);
if (child != null)
return hitTestBoxChild(result, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return hitTestBoxChild(BoxHitTestResult.wrap(result), child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false;
}
......
......@@ -581,7 +581,7 @@ class RenderStack extends RenderBox
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
......@@ -668,13 +668,20 @@ class RenderIndexedStack extends RenderStack {
}
@override
bool hitTestChildren(HitTestResult result, { @required Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
if (firstChild == null || index == null)
return false;
assert(position != null);
final RenderBox child = _childAtIndex();
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
......
......@@ -1104,13 +1104,21 @@ class RenderTable extends RenderBox {
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
assert(_children.length == rows * columns);
for (int index = _children.length - 1; index >= 0; index -= 1) {
final RenderBox child = _children[index];
if (child != null) {
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;
}
}
......
......@@ -168,7 +168,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
/// normally be in physical (device) pixels.
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
child.hitTest(result, position: position);
child.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
......
......@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart';
import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'object.dart';
import 'sliver.dart';
......@@ -558,7 +557,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
double mainAxisPosition, crossAxisPosition;
switch (axis) {
case Axis.vertical:
......@@ -572,12 +571,25 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
}
assert(mainAxisPosition != null);
assert(crossAxisPosition != null);
final SliverHitTestResult sliverResult = SliverHitTestResult.wrap(result);
for (RenderSliver child in childrenInHitTestOrder) {
if (child.geometry.visible && child.hitTest(
result,
mainAxisPosition: computeChildMainAxisPosition(child, mainAxisPosition),
crossAxisPosition: crossAxisPosition,
)) {
if (!child.geometry.visible) {
continue;
}
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;
}
}
......
......@@ -748,7 +748,7 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin<RenderBox, Wr
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
......
......@@ -214,7 +214,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false;
}
......
......@@ -1446,7 +1446,7 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil
}
@override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child != null)
return child.hitTest(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
return false;
......
......@@ -555,10 +555,16 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
if (child != null) {
final Offset transformed = position + -_paintOffset;
return child.hitTest(result, position: transformed);
return result.addWithPaintOffset(
offset: _paintOffset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position + -_paintOffset);
return child.hitTest(result, position: transformed);
},
);
}
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() {
expect(fourthPos.dx, 0);
expect(firstPos.dx, fourthPos.dx);
expect(firstPos.dy, lessThan(fourthPos.dy));
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse);
TestGesture gesture = await tester.startGesture(firstPos, pointer: 7);
await tester.pump();
......@@ -1839,8 +1839,8 @@ void main() {
Offset newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
expect(newFirstPos.dy, lessThan(firstPos.dy));
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isTrue);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isTrue);
// Now try scrolling by dragging the selection handle.
// Long press the middle of the word "won't" in the fourth line.
......@@ -1885,8 +1885,8 @@ void main() {
newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
expect(newFirstPos.dy, firstPos.dy);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
});
testWidgets('TextField smoke test', (WidgetTester tester) async {
......
......@@ -86,4 +86,39 @@ void main() {
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 @@
// 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -258,4 +259,261 @@ void main() {
expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width');
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 @@
// 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -200,7 +201,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0));
expect(result.path.first.target, equals(c));
});
......@@ -254,7 +255,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0));
expect(result.path.first.target, equals(c));
});
......@@ -343,7 +344,7 @@ void main() {
expect(_getPaintOrigin(sliverD), const Offset(300.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));
expect(result.path.first.target, equals(c));
});
......@@ -397,7 +398,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(550.0, 150.0));
expect(result.path.first.target, equals(c));
});
......@@ -471,7 +472,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0));
expect(result.path.first.target, equals(c));
});
......@@ -525,7 +526,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0));
expect(result.path.first.target, equals(c));
});
......@@ -579,7 +580,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(150.0, 450.0));
expect(result.path.first.target, equals(c));
});
......@@ -633,7 +634,7 @@ void main() {
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));
final HitTestResult result = HitTestResult();
final BoxHitTestResult result = BoxHitTestResult();
root.hitTest(result, position: const Offset(550.0, 150.0));
expect(result.path.first.target, equals(c));
});
......@@ -824,4 +825,99 @@ void main() {
// offset is not expected to impact the precedingScrollExtent.
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