mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Refactor mouse hit testing system: Direct mouse hit test (#59883)
This commit is contained in:
parent
4b12050112
commit
6f4c4b3cf8
|
@ -908,7 +908,7 @@ class PointerEnterEvent extends PointerEvent {
|
||||||
down: event?.down,
|
down: event?.down,
|
||||||
synthesized: event?.synthesized,
|
synthesized: event?.synthesized,
|
||||||
transform: event?.transform,
|
transform: event?.transform,
|
||||||
original: event?.original as PointerEnterEvent,
|
original: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1054,7 +1054,7 @@ class PointerExitEvent extends PointerEvent {
|
||||||
down: event?.down,
|
down: event?.down,
|
||||||
synthesized: event?.synthesized,
|
synthesized: event?.synthesized,
|
||||||
transform: event?.transform,
|
transform: event?.transform,
|
||||||
original: event?.original as PointerExitEvent,
|
original: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
@ -73,12 +71,51 @@ class HitTestEntry {
|
||||||
Matrix4 _transform;
|
Matrix4 _transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A type of data that can be applied to a matrix by left-multiplication.
|
||||||
|
@immutable
|
||||||
|
abstract class _TransformPart {
|
||||||
|
const _TransformPart();
|
||||||
|
|
||||||
|
// Apply this transform part to `rhs` from the left.
|
||||||
|
//
|
||||||
|
// This should work as if this transform part is first converted to a matrix
|
||||||
|
// and then left-multiplied to `rhs`.
|
||||||
|
//
|
||||||
|
// For example, if this transform part is a vector `v1`, whose corresponding
|
||||||
|
// matrix is `m1 = Matrix4.translation(v1)`, then the result of
|
||||||
|
// `_VectorTransformPart(v1).multiply(rhs)` should equal to `m1 * rhs`.
|
||||||
|
Matrix4 multiply(Matrix4 rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MatrixTransformPart extends _TransformPart {
|
||||||
|
const _MatrixTransformPart(this.matrix);
|
||||||
|
|
||||||
|
final Matrix4 matrix;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Matrix4 multiply(Matrix4 rhs) {
|
||||||
|
return matrix * rhs as Matrix4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OffsetTransformPart extends _TransformPart {
|
||||||
|
const _OffsetTransformPart(this.offset);
|
||||||
|
|
||||||
|
final Offset offset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Matrix4 multiply(Matrix4 rhs) {
|
||||||
|
return rhs.clone()..leftTranslate(offset.dx, offset.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The result of performing a hit test.
|
/// The result of performing a hit test.
|
||||||
class HitTestResult {
|
class HitTestResult {
|
||||||
/// Creates an empty hit test result.
|
/// Creates an empty hit test result.
|
||||||
HitTestResult()
|
HitTestResult()
|
||||||
: _path = <HitTestEntry>[],
|
: _path = <HitTestEntry>[],
|
||||||
_transforms = Queue<Matrix4>();
|
_transforms = <Matrix4>[Matrix4.identity()],
|
||||||
|
_localTransforms = <_TransformPart>[];
|
||||||
|
|
||||||
/// Wraps `result` (usually a subtype of [HitTestResult]) to create a
|
/// Wraps `result` (usually a subtype of [HitTestResult]) to create a
|
||||||
/// generic [HitTestResult].
|
/// generic [HitTestResult].
|
||||||
|
@ -88,7 +125,8 @@ class HitTestResult {
|
||||||
/// structure to store [HitTestEntry]s).
|
/// structure to store [HitTestEntry]s).
|
||||||
HitTestResult.wrap(HitTestResult result)
|
HitTestResult.wrap(HitTestResult result)
|
||||||
: _path = result._path,
|
: _path = result._path,
|
||||||
_transforms = result._transforms;
|
_transforms = result._transforms,
|
||||||
|
_localTransforms = result._localTransforms;
|
||||||
|
|
||||||
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
|
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
|
||||||
///
|
///
|
||||||
|
@ -98,7 +136,40 @@ class HitTestResult {
|
||||||
Iterable<HitTestEntry> get path => _path;
|
Iterable<HitTestEntry> get path => _path;
|
||||||
final List<HitTestEntry> _path;
|
final List<HitTestEntry> _path;
|
||||||
|
|
||||||
final Queue<Matrix4> _transforms;
|
// A stack of transform parts.
|
||||||
|
//
|
||||||
|
// The transform part stack leading from global to the current object is stored
|
||||||
|
// in 2 parts:
|
||||||
|
//
|
||||||
|
// * `_transforms` are globalized matrices, meaning they have been multiplied
|
||||||
|
// by the ancestors and are thus relative to the global coordinate space.
|
||||||
|
// * `_localTransforms` are local transform parts, which are relative to the
|
||||||
|
// parent's coordinate space.
|
||||||
|
//
|
||||||
|
// When new transform parts are added they're appended to `_localTransforms`,
|
||||||
|
// and are converted to global ones and moved to `_transforms` only when used.
|
||||||
|
final List<Matrix4> _transforms;
|
||||||
|
final List<_TransformPart> _localTransforms;
|
||||||
|
|
||||||
|
// Globalize all transform parts in `_localTransforms` and move them to
|
||||||
|
// _transforms.
|
||||||
|
void _globalizeTransforms() {
|
||||||
|
if (_localTransforms.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Matrix4 last = _transforms.last;
|
||||||
|
for (final _TransformPart part in _localTransforms) {
|
||||||
|
last = part.multiply(last);
|
||||||
|
_transforms.add(last);
|
||||||
|
}
|
||||||
|
_localTransforms.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4 get _lastTransform {
|
||||||
|
_globalizeTransforms();
|
||||||
|
assert(_localTransforms.isEmpty);
|
||||||
|
return _transforms.last;
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a [HitTestEntry] to the path.
|
/// Add a [HitTestEntry] to the path.
|
||||||
///
|
///
|
||||||
|
@ -107,7 +178,7 @@ class HitTestResult {
|
||||||
/// upward walk of the tree being hit tested.
|
/// upward walk of the tree being hit tested.
|
||||||
void add(HitTestEntry entry) {
|
void add(HitTestEntry entry) {
|
||||||
assert(entry._transform == null);
|
assert(entry._transform == null);
|
||||||
entry._transform = _transforms.isEmpty ? null : _transforms.last;
|
entry._transform = _lastTransform;
|
||||||
_path.add(entry);
|
_path.add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +196,9 @@ class HitTestResult {
|
||||||
/// through [PointerEvent.removePerspectiveTransform] to remove
|
/// through [PointerEvent.removePerspectiveTransform] to remove
|
||||||
/// the perspective component.
|
/// the perspective component.
|
||||||
///
|
///
|
||||||
|
/// If the provided `transform` is a translation matrix, it is much faster
|
||||||
|
/// to use [pushOffset] with the translation offset instead.
|
||||||
|
///
|
||||||
/// [HitTestable]s need to call this method indirectly through a convenience
|
/// [HitTestable]s need to call this method indirectly through a convenience
|
||||||
/// method defined on a subclass before hit testing a child that does not
|
/// method defined on a subclass before hit testing a child that does not
|
||||||
/// have the same origin as the parent. After hit testing the child,
|
/// have the same origin as the parent. After hit testing the child,
|
||||||
|
@ -132,10 +206,10 @@ class HitTestResult {
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
/// * [pushOffset], which is similar to [pushTransform] but is limited to
|
||||||
|
/// translations, and is faster in such cases.
|
||||||
/// * [BoxHitTestResult.addWithPaintTransform], which is a public wrapper
|
/// * [BoxHitTestResult.addWithPaintTransform], which is a public wrapper
|
||||||
/// around this function for hit testing on [RenderBox]s.
|
/// around this function for hit testing on [RenderBox]s.
|
||||||
/// * [SliverHitTestResult.addWithAxisOffset], which is a public wrapper
|
|
||||||
/// around this function for hit testing on [RenderSliver]s.
|
|
||||||
@protected
|
@protected
|
||||||
void pushTransform(Matrix4 transform) {
|
void pushTransform(Matrix4 transform) {
|
||||||
assert(transform != null);
|
assert(transform != null);
|
||||||
|
@ -148,26 +222,60 @@ class HitTestResult {
|
||||||
'matrix through PointerEvent.removePerspectiveTransform? '
|
'matrix through PointerEvent.removePerspectiveTransform? '
|
||||||
'The provided matrix is:\n$transform'
|
'The provided matrix is:\n$transform'
|
||||||
);
|
);
|
||||||
_transforms.add(_transforms.isEmpty ? transform : (transform * _transforms.last as Matrix4));
|
_localTransforms.add(_MatrixTransformPart(transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the last transform added via [pushTransform].
|
/// Pushes a new translation offset that is to be applied to all future
|
||||||
|
/// [HitTestEntry]s added via [add] until it is removed via [popTransform].
|
||||||
|
///
|
||||||
|
/// This method is only to be used by subclasses, which must provide
|
||||||
|
/// coordinate space specific public wrappers around this function for their
|
||||||
|
/// users (see [BoxHitTestResult.addWithPaintOffset] for such an example).
|
||||||
|
///
|
||||||
|
/// The provided `offset` should describe how to transform [PointerEvent]s from
|
||||||
|
/// the coordinate space of the method caller to the coordinate space of its
|
||||||
|
/// children. Usually `offset` is the inverse of the offset of the child
|
||||||
|
/// relative to the parent.
|
||||||
|
///
|
||||||
|
/// [HitTestable]s need to call this method indirectly through a convenience
|
||||||
|
/// method defined on a subclass before hit testing a child that does not
|
||||||
|
/// have the same origin as the parent. After hit testing the child,
|
||||||
|
/// [popTransform] has to be called to remove the child-specific `transform`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [pushTransform], which is similar to [pushOffset] but allows general
|
||||||
|
/// transform besides translation.
|
||||||
|
/// * [BoxHitTestResult.addWithPaintOffset], which is a public wrapper
|
||||||
|
/// around this function for hit testing on [RenderBox]s.
|
||||||
|
/// * [SliverHitTestResult.addWithAxisOffset], which is a public wrapper
|
||||||
|
/// around this function for hit testing on [RenderSliver]s.
|
||||||
|
@protected
|
||||||
|
void pushOffset(Offset offset) {
|
||||||
|
assert(offset != null);
|
||||||
|
_localTransforms.add(_OffsetTransformPart(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the last transform added via [pushTransform] or [pushOffset].
|
||||||
///
|
///
|
||||||
/// This method is only to be used by subclasses, which must provide
|
/// This method is only to be used by subclasses, which must provide
|
||||||
/// coordinate space specific public wrappers around this function for their
|
/// coordinate space specific public wrappers around this function for their
|
||||||
/// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
|
/// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
|
||||||
///
|
///
|
||||||
/// This method must be called after hit testing is done on a child that
|
/// This method must be called after hit testing is done on a child that
|
||||||
/// required a call to [pushTransform].
|
/// required a call to [pushTransform] or [pushOffset].
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [pushTransform], which describes the use case of this function pair in
|
/// * [pushTransform] and [pushOffset], which describes the use case of this
|
||||||
/// more details.
|
/// function pair in more details.
|
||||||
@protected
|
@protected
|
||||||
void popTransform() {
|
void popTransform() {
|
||||||
|
if (_localTransforms.isNotEmpty)
|
||||||
|
_localTransforms.removeLast();
|
||||||
|
else
|
||||||
|
_transforms.removeLast();
|
||||||
assert(_transforms.isNotEmpty);
|
assert(_transforms.isNotEmpty);
|
||||||
_transforms.removeLast();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _debugVectorMoreOrLessEquals(Vector4 a, Vector4 b, { double epsilon = precisionErrorTolerance }) {
|
bool _debugVectorMoreOrLessEquals(Vector4 a, Vector4 b, { double epsilon = precisionErrorTolerance }) {
|
||||||
|
|
|
@ -1148,12 +1148,12 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return IgnorePointer(
|
return MouseRegion(
|
||||||
ignoring: !_isEnabled,
|
cursor: effectiveMouseCursor,
|
||||||
child: MouseRegion(
|
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
||||||
cursor: effectiveMouseCursor,
|
onExit: (PointerExitEvent event) => _handleHover(false),
|
||||||
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
child: IgnorePointer(
|
||||||
onExit: (PointerExitEvent event) => _handleHover(false),
|
ignoring: !_isEnabled,
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation: controller, // changes the _currentLength
|
animation: controller, // changes the _currentLength
|
||||||
builder: (BuildContext context, Widget child) {
|
builder: (BuildContext context, Widget child) {
|
||||||
|
|
|
@ -764,11 +764,17 @@ class BoxHitTestResult extends HitTestResult {
|
||||||
@required BoxHitTest hitTest,
|
@required BoxHitTest hitTest,
|
||||||
}) {
|
}) {
|
||||||
assert(hitTest != null);
|
assert(hitTest != null);
|
||||||
return addWithRawTransform(
|
final Offset transformedPosition = position == null || offset == null
|
||||||
transform: offset != null ? Matrix4.translationValues(-offset.dx, -offset.dy, 0.0) : null,
|
? position
|
||||||
position: position,
|
: position - offset;
|
||||||
hitTest: hitTest,
|
if (offset != null) {
|
||||||
);
|
pushOffset(-offset);
|
||||||
|
}
|
||||||
|
final bool isHit = hitTest(this, transformedPosition);
|
||||||
|
if (offset != null) {
|
||||||
|
popTransform();
|
||||||
|
}
|
||||||
|
return isHit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms `position` to the local coordinate system of a child for
|
/// Transforms `position` to the local coordinate system of a child for
|
||||||
|
|
|
@ -14,7 +14,6 @@ import 'package:flutter/painting.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'mouse_tracking.dart';
|
|
||||||
|
|
||||||
/// Information collected for an annotation that is found in the layer tree.
|
/// Information collected for an annotation that is found in the layer tree.
|
||||||
///
|
///
|
||||||
|
@ -633,7 +632,6 @@ class PlatformViewLayer extends Layer {
|
||||||
PlatformViewLayer({
|
PlatformViewLayer({
|
||||||
@required this.rect,
|
@required this.rect,
|
||||||
@required this.viewId,
|
@required this.viewId,
|
||||||
this.hoverAnnotation,
|
|
||||||
}) : assert(rect != null),
|
}) : assert(rect != null),
|
||||||
assert(viewId != null);
|
assert(viewId != null);
|
||||||
|
|
||||||
|
@ -645,25 +643,6 @@ class PlatformViewLayer extends Layer {
|
||||||
/// A UIView with this identifier must have been created by [PlatformViewsServices.initUiKitView].
|
/// A UIView with this identifier must have been created by [PlatformViewsServices.initUiKitView].
|
||||||
final int viewId;
|
final int viewId;
|
||||||
|
|
||||||
/// [MouseTrackerAnnotation] that handles mouse events for this layer.
|
|
||||||
///
|
|
||||||
/// If [hoverAnnotation] is non-null, [PlatformViewLayer] will annotate the
|
|
||||||
/// region of this platform view such that annotation callbacks will receive
|
|
||||||
/// mouse events, including mouse enter, exit, and hover, but not including
|
|
||||||
/// mouse down, move, and up. The layer will be treated as opaque during an
|
|
||||||
/// annotation search, which will prevent layers behind it from receiving
|
|
||||||
/// these events.
|
|
||||||
///
|
|
||||||
/// By default, [hoverAnnotation] is null, and [PlatformViewLayer] will not
|
|
||||||
/// receive mouse events, and will therefore appear translucent during the
|
|
||||||
/// annotation search.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [MouseRegion], which explains more about the mouse events and opacity
|
|
||||||
/// during annotation search.
|
|
||||||
final MouseTrackerAnnotation hoverAnnotation;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
|
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
|
||||||
final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
|
final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
|
||||||
|
@ -674,24 +653,6 @@ class PlatformViewLayer extends Layer {
|
||||||
height: shiftedRect.height,
|
height: shiftedRect.height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
@protected
|
|
||||||
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
|
|
||||||
if (hoverAnnotation == null || !rect.contains(localPosition)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (S == MouseTrackerAnnotation) {
|
|
||||||
final Object untypedValue = hoverAnnotation;
|
|
||||||
final S typedValue = untypedValue as S;
|
|
||||||
result.add(AnnotationEntry<S>(
|
|
||||||
annotation: typedValue,
|
|
||||||
localPosition: localPosition,
|
|
||||||
));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layer that indicates to the compositor that it should display
|
/// A layer that indicates to the compositor that it should display
|
||||||
|
|
|
@ -622,7 +622,6 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
|
||||||
context.addLayer(PlatformViewLayer(
|
context.addLayer(PlatformViewLayer(
|
||||||
rect: offset & size,
|
rect: offset & size,
|
||||||
viewId: _controller.viewId,
|
viewId: _controller.viewId,
|
||||||
hoverAnnotation: _hoverAnnotation,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,32 +635,13 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Mixin handling the pointer events and gestures of a platform view render box.
|
/// The Mixin handling the pointer events and gestures of a platform view render box.
|
||||||
mixin _PlatformViewGestureMixin on RenderBox {
|
mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
|
||||||
|
|
||||||
/// How to behave during hit testing.
|
/// How to behave during hit testing.
|
||||||
// The implicit setter is enough here as changing this value will just affect
|
// The implicit setter is enough here as changing this value will just affect
|
||||||
// any newly arriving events there's nothing we need to invalidate.
|
// any newly arriving events there's nothing we need to invalidate.
|
||||||
PlatformViewHitTestBehavior hitTestBehavior;
|
PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
/// [MouseTrackerAnnotation] associated with the platform view layer.
|
|
||||||
///
|
|
||||||
/// Gesture recognizers don't receive hover events due to the performance
|
|
||||||
/// cost associated with hit testing a sequence of potentially thousands of
|
|
||||||
/// events -- move events only hit-test the down event, then cache the result
|
|
||||||
/// and apply it to all subsequent move events, but there is no down event
|
|
||||||
/// for a hover. To support native hover gesture handling by platform views,
|
|
||||||
/// we attach/detach this layer annotation as necessary.
|
|
||||||
MouseTrackerAnnotation get _hoverAnnotation {
|
|
||||||
return _cachedHoverAnnotation ??= MouseTrackerAnnotation(
|
|
||||||
onHover: (PointerHoverEvent event) {
|
|
||||||
if (_handlePointerEvent != null)
|
|
||||||
_handlePointerEvent(event);
|
|
||||||
},
|
|
||||||
cursor: MouseCursor.uncontrolled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
MouseTrackerAnnotation _cachedHoverAnnotation;
|
|
||||||
|
|
||||||
_HandlePointerEvent _handlePointerEvent;
|
_HandlePointerEvent _handlePointerEvent;
|
||||||
|
|
||||||
/// {@macro flutter.rendering.platformView.updateGestureRecognizers}
|
/// {@macro flutter.rendering.platformView.updateGestureRecognizers}
|
||||||
|
@ -696,6 +676,22 @@ mixin _PlatformViewGestureMixin on RenderBox {
|
||||||
@override
|
@override
|
||||||
bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent;
|
bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerEnterEventListener get onEnter => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerHoverEventListener get onHover => _handleHover;
|
||||||
|
void _handleHover(PointerHoverEvent event) {
|
||||||
|
if (_handlePointerEvent != null)
|
||||||
|
_handlePointerEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointerExitEventListener get onExit => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MouseCursor get cursor => MouseCursor.uncontrolled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
||||||
if (event is PointerDownEvent) {
|
if (event is PointerDownEvent) {
|
||||||
|
|
|
@ -2720,6 +2720,15 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
|
||||||
_annotationIsActive = false,
|
_annotationIsActive = false,
|
||||||
super(child);
|
super(child);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
@override
|
||||||
|
bool hitTestSelf(Offset position) => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hitTest(BoxHitTestResult result, { @required Offset position }) {
|
||||||
|
return super.hitTest(result, position: position) && _opaque;
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this object should prevent [RenderMouseRegion]s visually behind it
|
/// Whether this object should prevent [RenderMouseRegion]s visually behind it
|
||||||
/// from detecting the pointer, thus affecting how their [onHover], [onEnter],
|
/// from detecting the pointer, thus affecting how their [onHover], [onEnter],
|
||||||
/// and [onExit] behave.
|
/// and [onExit] behave.
|
||||||
|
@ -2838,25 +2847,6 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
|
||||||
super.detach();
|
super.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool get needsCompositing => super.needsCompositing || _annotationIsActive;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
if (_annotationIsActive) {
|
|
||||||
// Annotated region layers are not retained because they do not create engine layers.
|
|
||||||
final AnnotatedRegionLayer<MouseTrackerAnnotation> layer = AnnotatedRegionLayer<MouseTrackerAnnotation>(
|
|
||||||
this,
|
|
||||||
size: size,
|
|
||||||
offset: offset,
|
|
||||||
opaque: opaque,
|
|
||||||
);
|
|
||||||
context.pushLayer(layer, super.paint, offset);
|
|
||||||
} else {
|
|
||||||
super.paint(context, offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performResize() {
|
void performResize() {
|
||||||
size = constraints.biggest;
|
size = constraints.biggest;
|
||||||
|
|
|
@ -861,7 +861,7 @@ class SliverHitTestResult extends HitTestResult {
|
||||||
assert(crossAxisPosition != null);
|
assert(crossAxisPosition != null);
|
||||||
assert(hitTest != null);
|
assert(hitTest != null);
|
||||||
if (paintOffset != null) {
|
if (paintOffset != null) {
|
||||||
pushTransform(Matrix4.translationValues(-paintOffset.dx, -paintOffset.dy, 0));
|
pushOffset(-paintOffset);
|
||||||
}
|
}
|
||||||
final bool isHit = hitTest(
|
final bool isHit = hitTest(
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'dart:io' show Platform;
|
||||||
import 'dart:ui' as ui show Scene, SceneBuilder, Window;
|
import 'dart:ui' as ui show Scene, SceneBuilder, Window;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
@ -201,9 +202,17 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
|
||||||
// Layer hit testing is done using device pixels, so we have to convert
|
// Layer hit testing is done using device pixels, so we have to convert
|
||||||
// the logical coordinates of the event location back to device pixels
|
// the logical coordinates of the event location back to device pixels
|
||||||
// here.
|
// here.
|
||||||
return layer.findAllAnnotations<MouseTrackerAnnotation>(
|
final BoxHitTestResult result = BoxHitTestResult();
|
||||||
position * configuration.devicePixelRatio
|
if (child != null)
|
||||||
).annotations;
|
child.hitTest(result, position: position);
|
||||||
|
result.add(HitTestEntry(this));
|
||||||
|
final List<MouseTrackerAnnotation> annotations = <MouseTrackerAnnotation>[];
|
||||||
|
for (final HitTestEntry entry in result.path) {
|
||||||
|
if (entry.target is MouseTrackerAnnotation) {
|
||||||
|
annotations.add(entry.target as MouseTrackerAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -36,6 +36,94 @@ void main() {
|
||||||
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
expect(wrapped.path, equals(<HitTestEntry>[entry1, entry2, entry3]));
|
||||||
expect(entry3.transform, transform);
|
expect(entry3.transform, transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('HitTestResult should correctly push and pop transforms', () {
|
||||||
|
Matrix4 currentTransform(HitTestResult targetResult) {
|
||||||
|
final HitTestEntry entry = HitTestEntry(_DummyHitTestTarget());
|
||||||
|
targetResult.add(entry);
|
||||||
|
return entry.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MyHitTestResult result = MyHitTestResult();
|
||||||
|
|
||||||
|
final Matrix4 m1 = Matrix4.translationValues(10, 20, 0);
|
||||||
|
final Matrix4 m2 = Matrix4.rotationZ(1);
|
||||||
|
final Matrix4 m3 = Matrix4.diagonal3Values(1.1, 1.2, 1.0);
|
||||||
|
|
||||||
|
result.publicPushTransform(m1);
|
||||||
|
expect(currentTransform(result), equals(m1));
|
||||||
|
|
||||||
|
result.publicPushTransform(m2);
|
||||||
|
expect(currentTransform(result), equals(m2 * m1));
|
||||||
|
expect(currentTransform(result), equals(m2 * m1)); // Test repeated add
|
||||||
|
|
||||||
|
// The `wrapped` is wrapped at [m1, m2]
|
||||||
|
final MyHitTestResult wrapped = MyHitTestResult.wrap(result);
|
||||||
|
expect(currentTransform(wrapped), equals(m2 * m1));
|
||||||
|
|
||||||
|
result.publicPushTransform(m3);
|
||||||
|
expect(currentTransform(result), equals(m3 * m2 * m1));
|
||||||
|
expect(currentTransform(wrapped), equals(m3 * m2 * m1));
|
||||||
|
|
||||||
|
result.publicPopTransform();
|
||||||
|
result.publicPopTransform();
|
||||||
|
expect(currentTransform(result), equals(m1));
|
||||||
|
|
||||||
|
result.publicPopTransform();
|
||||||
|
result.publicPushTransform(m3);
|
||||||
|
expect(currentTransform(result), equals(m3));
|
||||||
|
|
||||||
|
result.publicPushTransform(m2);
|
||||||
|
expect(currentTransform(result), equals(m2 * m3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('HitTestResult should correctly push and pop offsets', () {
|
||||||
|
Matrix4 currentTransform(HitTestResult targetResult) {
|
||||||
|
final HitTestEntry entry = HitTestEntry(_DummyHitTestTarget());
|
||||||
|
targetResult.add(entry);
|
||||||
|
return entry.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MyHitTestResult result = MyHitTestResult();
|
||||||
|
|
||||||
|
final Matrix4 m1 = Matrix4.rotationZ(1);
|
||||||
|
final Matrix4 m2 = Matrix4.diagonal3Values(1.1, 1.2, 1.0);
|
||||||
|
const Offset o3 = Offset(10, 20);
|
||||||
|
final Matrix4 m3 = Matrix4.translationValues(o3.dx, o3.dy, 0.0);
|
||||||
|
|
||||||
|
// Test pushing offset as the first element
|
||||||
|
result.publicPushOffset(o3);
|
||||||
|
expect(currentTransform(result), equals(m3));
|
||||||
|
result.publicPopTransform();
|
||||||
|
|
||||||
|
result.publicPushOffset(o3);
|
||||||
|
result.publicPushTransform(m1);
|
||||||
|
expect(currentTransform(result), equals(m1 * m3));
|
||||||
|
expect(currentTransform(result), equals(m1 * m3)); // Test repeated add
|
||||||
|
|
||||||
|
// The `wrapped` is wrapped at [m1, m2]
|
||||||
|
final MyHitTestResult wrapped = MyHitTestResult.wrap(result);
|
||||||
|
expect(currentTransform(wrapped), equals(m1 * m3));
|
||||||
|
|
||||||
|
result.publicPushTransform(m2);
|
||||||
|
expect(currentTransform(result), equals(m2 * m1 * m3));
|
||||||
|
expect(currentTransform(wrapped), equals(m2 * m1 * m3));
|
||||||
|
|
||||||
|
result.publicPopTransform();
|
||||||
|
result.publicPopTransform();
|
||||||
|
result.publicPopTransform();
|
||||||
|
expect(currentTransform(result), equals(Matrix4.identity()));
|
||||||
|
|
||||||
|
result.publicPushTransform(m2);
|
||||||
|
result.publicPushOffset(o3);
|
||||||
|
result.publicPushTransform(m1);
|
||||||
|
|
||||||
|
expect(currentTransform(result), equals(m1 * m3 * m2));
|
||||||
|
|
||||||
|
result.publicPopTransform();
|
||||||
|
|
||||||
|
expect(currentTransform(result), equals(m3 * m2));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DummyHitTestTarget implements HitTestTarget {
|
class _DummyHitTestTarget implements HitTestTarget {
|
||||||
|
@ -46,5 +134,10 @@ class _DummyHitTestTarget implements HitTestTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHitTestResult extends HitTestResult {
|
class MyHitTestResult extends HitTestResult {
|
||||||
|
MyHitTestResult();
|
||||||
|
MyHitTestResult.wrap(HitTestResult result) : super.wrap(result);
|
||||||
|
|
||||||
void publicPushTransform(Matrix4 transform) => pushTransform(transform);
|
void publicPushTransform(Matrix4 transform) => pushTransform(transform);
|
||||||
|
void publicPushOffset(Offset offset) => pushOffset(offset);
|
||||||
|
void publicPopTransform() => popTransform();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -398,6 +399,162 @@ void main() {
|
||||||
);
|
);
|
||||||
// TODO(Piinks): Remove skip once web goldens are supported, https://github.com/flutter/flutter/issues/40297
|
// TODO(Piinks): Remove skip once web goldens are supported, https://github.com/flutter/flutter/issues/40297
|
||||||
}, skip: isBrowser);
|
}, skip: isBrowser);
|
||||||
|
|
||||||
|
testWidgets('IgnorePointer ignores pointers', (WidgetTester tester) async {
|
||||||
|
final List<String> logs = <String>[];
|
||||||
|
Widget target({bool ignoring}) => Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down1'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
onEnter: (_) { logs.add('enter1'); },
|
||||||
|
onExit: (_) { logs.add('exit1'); },
|
||||||
|
cursor: SystemMouseCursors.forbidden,
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down2'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) { logs.add('enter2'); },
|
||||||
|
onExit: (_) { logs.add('exit2'); },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IgnorePointer(
|
||||||
|
ignoring: ignoring,
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down3'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.text,
|
||||||
|
onEnter: (_) { logs.add('enter3'); },
|
||||||
|
onExit: (_) { logs.add('exit3'); },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer(location: const Offset(200, 200));
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(ignoring: true));
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await gesture.moveTo(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['enter1', 'enter2']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.down(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['down2', 'down1']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.up();
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(ignoring: false));
|
||||||
|
expect(logs, <String>['exit2', 'enter3']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.down(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['down3', 'down1']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.up();
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(ignoring: true));
|
||||||
|
expect(logs, <String>['exit3', 'enter2']);
|
||||||
|
logs.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('AbsorbPointer absorbs pointers', (WidgetTester tester) async {
|
||||||
|
final List<String> logs = <String>[];
|
||||||
|
Widget target({bool absorbing}) => Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down1'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
onEnter: (_) { logs.add('enter1'); },
|
||||||
|
onExit: (_) { logs.add('exit1'); },
|
||||||
|
cursor: SystemMouseCursors.forbidden,
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down2'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) { logs.add('enter2'); },
|
||||||
|
onExit: (_) { logs.add('exit2'); },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AbsorbPointer(
|
||||||
|
absorbing: absorbing,
|
||||||
|
child: Listener(
|
||||||
|
onPointerDown: (_) { logs.add('down3'); },
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.text,
|
||||||
|
onEnter: (_) { logs.add('enter3'); },
|
||||||
|
onExit: (_) { logs.add('exit3'); },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer(location: const Offset(200, 200));
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(absorbing: true));
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await gesture.moveTo(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['enter1']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.down(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['down1']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.up();
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(absorbing: false));
|
||||||
|
expect(logs, <String>['enter3']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.down(const Offset(50, 50));
|
||||||
|
expect(logs, <String>['down3', 'down1']);
|
||||||
|
logs.clear();
|
||||||
|
|
||||||
|
await gesture.up();
|
||||||
|
expect(logs, isEmpty);
|
||||||
|
|
||||||
|
await tester.pumpWidget(target(absorbing: true));
|
||||||
|
expect(logs, <String>['exit3']);
|
||||||
|
logs.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
|
HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
|
||||||
|
|
|
@ -436,7 +436,7 @@ void main() {
|
||||||
events.clear();
|
events.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('needsCompositing updates correctly and is respected', (WidgetTester tester) async {
|
testWidgets('needsCompositing is always false', (WidgetTester tester) async {
|
||||||
// Pretend that we have a mouse connected.
|
// Pretend that we have a mouse connected.
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
await gesture.addPointer(location: Offset.zero);
|
await gesture.addPointer(location: Offset.zero);
|
||||||
|
@ -467,22 +467,9 @@ void main() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(listener.needsCompositing, isTrue);
|
|
||||||
// Compositing is required, therefore a dedicated TransformLayer for
|
|
||||||
// `Transform.scale` is added.
|
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(2));
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Transform.scale(
|
|
||||||
scale: 2.0,
|
|
||||||
child: Listener(
|
|
||||||
onPointerDown: (PointerDownEvent _) { },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(listener.needsCompositing, isFalse);
|
expect(listener.needsCompositing, isFalse);
|
||||||
// TransformLayer for `Transform.scale` is removed again as transform is
|
// If compositing was required, a dedicated TransformLayer for
|
||||||
// executed directly on the canvas.
|
// `Transform.scale` would be added.
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -168,10 +168,6 @@ void main() {
|
||||||
await gesture.addPointer(location: const Offset(400, 300));
|
await gesture.addPointer(location: const Offset(400, 300));
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
expect(move, isNull);
|
expect(move, isNull);
|
||||||
expect(enter, isNull);
|
|
||||||
expect(exit, isNull);
|
|
||||||
await tester.pump();
|
|
||||||
expect(move, isNull);
|
|
||||||
expect(enter, isNotNull);
|
expect(enter, isNotNull);
|
||||||
expect(enter.position, equals(const Offset(400.0, 300.0)));
|
expect(enter.position, equals(const Offset(400.0, 300.0)));
|
||||||
expect(exit, isNull);
|
expect(exit, isNull);
|
||||||
|
@ -585,13 +581,13 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await tester.pumpWidget(hoverableContainer(
|
await tester.pumpWidget(hoverableContainer(
|
||||||
onEnter: (PointerEnterEvent details) => logs.add('enter1'),
|
onEnter: (PointerEnterEvent details) { logs.add('enter1'); },
|
||||||
onHover: (PointerHoverEvent details) => logs.add('hover1'),
|
onHover: (PointerHoverEvent details) { logs.add('hover1'); },
|
||||||
onExit: (PointerExitEvent details) => logs.add('exit1'),
|
onExit: (PointerExitEvent details) { logs.add('exit1'); },
|
||||||
));
|
));
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
await gesture.addPointer();
|
await gesture.addPointer(location: const Offset(150.0, 150.0));
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
|
|
||||||
// Start outside, move inside, then move outside
|
// Start outside, move inside, then move outside
|
||||||
|
@ -709,7 +705,7 @@ void main() {
|
||||||
events.clear();
|
events.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('needsCompositing updates correctly and is respected', (WidgetTester tester) async {
|
testWidgets('needsCompositing is always false', (WidgetTester tester) async {
|
||||||
// Pretend that we have a mouse connected.
|
// Pretend that we have a mouse connected.
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
await gesture.addPointer();
|
await gesture.addPointer();
|
||||||
|
@ -729,7 +725,7 @@ void main() {
|
||||||
// transform.)
|
// transform.)
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
||||||
|
|
||||||
// Test that needsCompositing updates correctly with callback change
|
// Test that needsCompositing stays false with callback change
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Transform.scale(
|
Transform.scale(
|
||||||
scale: 2.0,
|
scale: 2.0,
|
||||||
|
@ -739,35 +735,10 @@ void main() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(mouseRegion.needsCompositing, isTrue);
|
|
||||||
// Compositing is required, therefore a dedicated TransformLayer for
|
|
||||||
// `Transform.scale` is added.
|
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(2));
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Transform.scale(
|
|
||||||
scale: 2.0,
|
|
||||||
child: const MouseRegion(opaque: false),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(mouseRegion.needsCompositing, isFalse);
|
expect(mouseRegion.needsCompositing, isFalse);
|
||||||
// TransformLayer for `Transform.scale` is removed again as transform is
|
// If compositing was required, a dedicated TransformLayer for
|
||||||
// executed directly on the canvas.
|
// `Transform.scale` would be added.
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
||||||
|
|
||||||
// Test that needsCompositing updates correctly with `opaque` change
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Transform.scale(
|
|
||||||
scale: 2.0,
|
|
||||||
child: const MouseRegion(
|
|
||||||
opaque: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(mouseRegion.needsCompositing, isTrue);
|
|
||||||
// Compositing is required, therefore a dedicated TransformLayer for
|
|
||||||
// `Transform.scale` is added.
|
|
||||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(2));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("Callbacks aren't called during build", (WidgetTester tester) async {
|
testWidgets("Callbacks aren't called during build", (WidgetTester tester) async {
|
||||||
|
|
Loading…
Reference in a new issue