Unverified Commit a71d69ba authored by Tong Mu's avatar Tong Mu Committed by GitHub

Add opacity control to MouseRegion. Add findAnnotations to Layer. (#37896)

* Adds a new parameter bool opaque to MouseRegion
* The same to RenderMouseRegion and AnnotatedRegionLayer
* Add findAnnotations to Layer 
parent 6b4a10ae
...@@ -54,7 +54,7 @@ class HitTestEntry { ...@@ -54,7 +54,7 @@ class HitTestEntry {
final HitTestTarget target; final HitTestTarget target;
@override @override
String toString() => '$target'; String toString() => '${describeIdentity(this)}($target)';
/// Returns a matrix describing how [PointerEvent]s delivered to this /// Returns a matrix describing how [PointerEvent]s delivered to this
/// [HitTestEntry] should be transformed from the global coordinate space of /// [HitTestEntry] should be transformed from the global coordinate space of
......
...@@ -878,9 +878,6 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe ...@@ -878,9 +878,6 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
} }
} }
void _handleMouseEnter(PointerEnterEvent event) => _handleHover(true);
void _handleMouseExit(PointerExitEvent event) => _handleHover(false);
void _handleHover(bool hovering) { void _handleHover(bool hovering) {
if (hovering != _isHovering) { if (hovering != _isHovering) {
setState(() { setState(() {
...@@ -1007,8 +1004,8 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe ...@@ -1007,8 +1004,8 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
return IgnorePointer( return IgnorePointer(
ignoring: !_isEnabled, ignoring: !_isEnabled,
child: MouseRegion( child: MouseRegion(
onEnter: _handleMouseEnter, onEnter: (PointerEnterEvent event) => _handleHover(true),
onExit: _handleMouseExit, onExit: (PointerExitEvent event) => _handleHover(false),
child: AnimatedBuilder( child: AnimatedBuilder(
animation: controller, // changes the _currentLength animation: controller, // changes the _currentLength
builder: (BuildContext context, Widget child) { builder: (BuildContext context, Widget child) {
......
...@@ -2591,8 +2591,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2591,8 +2591,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// Calls callbacks in response to pointer events that are exclusive to mice. /// Calls callbacks in response to pointer events that are exclusive to mice.
/// ///
/// Simply put, it responds to events that are related to hovering, /// It responds to events that are related to hovering, i.e. when the mouse
/// i.e. when the mouse enters, exits or hovers a region without pressing. /// enters, exits (with or without pressing buttons), or moves over a region
/// without pressing buttons.
/// ///
/// It does not respond to common events that construct gestures, such as when /// It does not respond to common events that construct gestures, such as when
/// the pointer is pressed, moved, then released or canceled. For these events, /// the pointer is pressed, moved, then released or canceled. For these events,
...@@ -2601,14 +2602,21 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2601,14 +2602,21 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// If it has a child, it defers to the child for sizing behavior. /// If it has a child, it defers to the child for sizing behavior.
/// ///
/// If it does not have a child, it grows to fit the parent-provided constraints. /// If it does not have a child, it grows to fit the parent-provided constraints.
///
/// See also:
///
/// * [MouseRegion], a widget that listens to hover events using
/// [RenderMouseRegion].
class RenderMouseRegion extends RenderProxyBox { class RenderMouseRegion extends RenderProxyBox {
/// Creates a render object that forwards pointer events to callbacks. /// Creates a render object that forwards pointer events to callbacks.
RenderMouseRegion({ RenderMouseRegion({
PointerEnterEventListener onEnter, PointerEnterEventListener onEnter,
PointerHoverEventListener onHover, PointerHoverEventListener onHover,
PointerExitEventListener onExit, PointerExitEventListener onExit,
this.opaque = true,
RenderBox child, RenderBox child,
}) : _onEnter = onEnter, }) : assert(opaque != null),
_onEnter = onEnter,
_onHover = onHover, _onHover = onHover,
_onExit = onExit, _onExit = onExit,
_annotationIsActive = false, _annotationIsActive = false,
...@@ -2620,10 +2628,24 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2620,10 +2628,24 @@ class RenderMouseRegion extends RenderProxyBox {
); );
} }
/// Called when a hovering pointer enters the region for this widget. /// Whether this object should prevent [RenderMouseRegion]s visually behind it
/// from detecting the pointer, thus affecting how their [onHover], [onEnter],
/// and [onExit] behave.
///
/// If [opaque] is true, this object will absorb the mouse pointer and
/// prevent this object's siblings (or any other objects that are not
/// ancestors or descendants of this object) from detecting the mouse
/// pointer even when the pointer is within their areas.
/// ///
/// If this is a mouse pointer, this will fire when the mouse pointer enters /// If [opaque] is false, this object will not affect how [RenderMouseRegion]s
/// the region defined by this widget. /// behind it behave, which will detect the mouse pointer as long as the
/// pointer is within their areas.
///
/// This defaults to true.
bool opaque;
/// Called when a mouse pointer enters the region (with or without buttons
/// pressed).
PointerEnterEventListener get onEnter => _onEnter; PointerEnterEventListener get onEnter => _onEnter;
set onEnter(PointerEnterEventListener value) { set onEnter(PointerEnterEventListener value) {
if (_onEnter != value) { if (_onEnter != value) {
...@@ -2637,10 +2659,8 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2637,10 +2659,8 @@ class RenderMouseRegion extends RenderProxyBox {
_onEnter(event); _onEnter(event);
} }
/// Called when a pointer that has not triggered an [onPointerDown] changes /// Called when a pointer changes position without buttons pressed and the end
/// position. /// position is within the region.
///
/// Typically only triggered for mouse pointers.
PointerHoverEventListener get onHover => _onHover; PointerHoverEventListener get onHover => _onHover;
set onHover(PointerHoverEventListener value) { set onHover(PointerHoverEventListener value) {
if (_onHover != value) { if (_onHover != value) {
...@@ -2654,10 +2674,7 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2654,10 +2674,7 @@ class RenderMouseRegion extends RenderProxyBox {
_onHover(event); _onHover(event);
} }
/// Called when a hovering pointer leaves the region for this widget. /// Called when a pointer leaves the region (with or without buttons pressed).
///
/// If this is a mouse pointer, this will fire when the mouse pointer leaves
/// the region defined by this widget.
PointerExitEventListener get onExit => _onExit; PointerExitEventListener get onExit => _onExit;
set onExit(PointerExitEventListener value) { set onExit(PointerExitEventListener value) {
if (_onExit != value) { if (_onExit != value) {
...@@ -2754,6 +2771,7 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2754,6 +2771,7 @@ class RenderMouseRegion extends RenderProxyBox {
_hoverAnnotation, _hoverAnnotation,
size: size, size: size,
offset: offset, offset: offset,
opaque: opaque,
); );
context.pushLayer(layer, super.paint, offset); context.pushLayer(layer, super.paint, offset);
} else { } else {
...@@ -2778,6 +2796,7 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2778,6 +2796,7 @@ class RenderMouseRegion extends RenderProxyBox {
}, },
ifEmpty: '<none>', ifEmpty: '<none>',
)); ));
properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
} }
} }
......
...@@ -196,7 +196,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -196,7 +196,9 @@ 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.findAll<MouseTrackerAnnotation>(position * configuration.devicePixelRatio); return layer.findAll<MouseTrackerAnnotation>(
position * configuration.devicePixelRatio
).annotations;
} }
@override @override
...@@ -241,12 +243,12 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -241,12 +243,12 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
final Rect bounds = paintBounds; final Rect bounds = paintBounds;
final Offset top = Offset(bounds.center.dx, _window.padding.top / _window.devicePixelRatio); final Offset top = Offset(bounds.center.dx, _window.padding.top / _window.devicePixelRatio);
final Offset bottom = Offset(bounds.center.dx, bounds.center.dy - _window.padding.bottom / _window.devicePixelRatio); final Offset bottom = Offset(bounds.center.dx, bounds.center.dy - _window.padding.bottom / _window.devicePixelRatio);
final SystemUiOverlayStyle upperOverlayStyle = layer.find<SystemUiOverlayStyle>(top); final SystemUiOverlayStyle upperOverlayStyle = layer.find<SystemUiOverlayStyle>(top)?.annotation;
// Only android has a customizable system navigation bar. // Only android has a customizable system navigation bar.
SystemUiOverlayStyle lowerOverlayStyle; SystemUiOverlayStyle lowerOverlayStyle;
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
case TargetPlatform.android: case TargetPlatform.android:
lowerOverlayStyle = layer.find<SystemUiOverlayStyle>(bottom); lowerOverlayStyle = layer.find<SystemUiOverlayStyle>(bottom)?.annotation;
break; break;
case TargetPlatform.iOS: case TargetPlatform.iOS:
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
......
...@@ -5575,11 +5575,11 @@ class Listener extends StatelessWidget { ...@@ -5575,11 +5575,11 @@ class Listener extends StatelessWidget {
// TODO(tongmu): After it goes stable, remove these 3 parameters from Listener // TODO(tongmu): After it goes stable, remove these 3 parameters from Listener
// and Listener should no longer need an intermediate class _PointerListener. // and Listener should no longer need an intermediate class _PointerListener.
// https://github.com/flutter/flutter/issues/36085 // https://github.com/flutter/flutter/issues/36085
@Deprecated('Use MouseRegion.onEnter instead') @Deprecated('Use MouseRegion.onEnter instead. See MouseRegion.opaque for behavioral difference.')
this.onPointerEnter, // ignore: deprecated_member_use_from_same_package this.onPointerEnter, // ignore: deprecated_member_use_from_same_package
@Deprecated('Use MouseRegion.onExit instead') @Deprecated('Use MouseRegion.onExit instead. See MouseRegion.opaque for behavioral difference.')
this.onPointerExit, // ignore: deprecated_member_use_from_same_package this.onPointerExit, // ignore: deprecated_member_use_from_same_package
@Deprecated('Use MouseRegion.onHover instead') @Deprecated('Use MouseRegion.onHover instead. See MouseRegion.opaque for behavioral difference.')
this.onPointerHover, // ignore: deprecated_member_use_from_same_package this.onPointerHover, // ignore: deprecated_member_use_from_same_package
this.onPointerUp, this.onPointerUp,
this.onPointerCancel, this.onPointerCancel,
...@@ -5656,6 +5656,7 @@ class Listener extends StatelessWidget { ...@@ -5656,6 +5656,7 @@ class Listener extends StatelessWidget {
onEnter: onPointerEnter, onEnter: onPointerEnter,
onExit: onPointerExit, onExit: onPointerExit,
onHover: onPointerHover, onHover: onPointerHover,
opaque: false,
child: result, child: result,
); );
} }
...@@ -5815,8 +5816,10 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5815,8 +5816,10 @@ class MouseRegion extends SingleChildRenderObjectWidget {
this.onEnter, this.onEnter,
this.onExit, this.onExit,
this.onHover, this.onHover,
this.opaque = true,
Widget child, Widget child,
}) : super(key: key, child: child); }) : assert(opaque != null),
super(key: key, child: child);
/// Called when a mouse pointer (with or without buttons pressed) enters the /// Called when a mouse pointer (with or without buttons pressed) enters the
/// region defined by this widget, or when the widget appears under the /// region defined by this widget, or when the widget appears under the
...@@ -5832,6 +5835,22 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5832,6 +5835,22 @@ class MouseRegion extends SingleChildRenderObjectWidget {
/// the pointer. /// the pointer.
final PointerExitEventListener onExit; final PointerExitEventListener onExit;
/// Whether this widget should prevent other [MouseRegion]s visually behind it
/// from detecting the pointer, thus affecting how their [onHover], [onEnter],
/// and [onExit] behave.
///
/// If [opaque] is true, this widget will absorb the mouse pointer and
/// prevent this widget's siblings (or any other widgets that are not
/// ancestors or descendants of this widget) from detecting the mouse
/// pointer even when the pointer is within their areas.
///
/// If [opaque] is false, this object will not affect how [MouseRegion]s
/// behind it behave, which will detect the mouse pointer as long as the
/// pointer is within their areas.
///
/// This defaults to true.
final bool opaque;
@override @override
_MouseRegionElement createElement() => _MouseRegionElement(this); _MouseRegionElement createElement() => _MouseRegionElement(this);
...@@ -5841,6 +5860,7 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5841,6 +5860,7 @@ class MouseRegion extends SingleChildRenderObjectWidget {
onEnter: onEnter, onEnter: onEnter,
onHover: onHover, onHover: onHover,
onExit: onExit, onExit: onExit,
opaque: opaque,
); );
} }
...@@ -5849,7 +5869,8 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5849,7 +5869,8 @@ class MouseRegion extends SingleChildRenderObjectWidget {
renderObject renderObject
..onEnter = onEnter ..onEnter = onEnter
..onHover = onHover ..onHover = onHover
..onExit = onExit; ..onExit = onExit
..opaque = opaque;
} }
@override @override
...@@ -5863,6 +5884,7 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5863,6 +5884,7 @@ class MouseRegion extends SingleChildRenderObjectWidget {
if (onHover != null) if (onHover != null)
listeners.add('hover'); listeners.add('hover');
properties.add(IterableProperty<String>('listeners', listeners, ifEmpty: '<none>')); properties.add(IterableProperty<String>('listeners', listeners, ifEmpty: '<none>'));
properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
} }
} }
......
...@@ -57,10 +57,14 @@ class _ProxyLayer extends Layer { ...@@ -57,10 +57,14 @@ class _ProxyLayer extends Layer {
} }
@override @override
S find<S>(Offset regionOffset) => _layer.find(regionOffset); @protected
bool findAnnotations<S>(
@override AnnotationResult<S> result,
Iterable<S> findAll<S>(Offset regionOffset) => <S>[]; Offset localPosition, {
@required bool onlyFirst,
}) {
return _layer.findAnnotations(result, localPosition, onlyFirst: onlyFirst);
}
} }
/// A [Canvas] that multicasts all method calls to a main canvas and a /// A [Canvas] that multicasts all method calls to a main canvas and a
...@@ -2662,10 +2666,14 @@ class _InspectorOverlayLayer extends Layer { ...@@ -2662,10 +2666,14 @@ class _InspectorOverlayLayer extends Layer {
} }
@override @override
S find<S>(Offset regionOffset) => null; @protected
bool findAnnotations<S>(
@override AnnotationResult<S> result,
Iterable<S> findAll<S>(Offset regionOffset) => <S>[]; Offset localPosition, {
bool onlyFirst,
}) {
return false;
}
} }
const double _kScreenEdgeMargin = 10.0; const double _kScreenEdgeMargin = 10.0;
......
...@@ -22,9 +22,9 @@ void main() { ...@@ -22,9 +22,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0); expect(containerLayer.find<int>(const Offset(0.0, 1.0)).annotation, 0);
expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1); expect(containerLayer.find<int>(const Offset(0.0, 101.0)).annotation, 1);
expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2); expect(containerLayer.find<int>(const Offset(0.0, 201.0)).annotation, 2);
}); });
test('finds a value within the clip in a ClipRectLayer', () { test('finds a value within the clip in a ClipRectLayer', () {
...@@ -41,9 +41,9 @@ void main() { ...@@ -41,9 +41,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0); expect(containerLayer.find<int>(const Offset(0.0, 1.0)).annotation, 0);
expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1); expect(containerLayer.find<int>(const Offset(0.0, 101.0)).annotation, 1);
expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2); expect(containerLayer.find<int>(const Offset(0.0, 201.0)).annotation, 2);
}); });
...@@ -61,9 +61,9 @@ void main() { ...@@ -61,9 +61,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.find<int>(const Offset(5.0, 5.0)), 0); expect(containerLayer.find<int>(const Offset(5.0, 5.0)).annotation, 0);
expect(containerLayer.find<int>(const Offset(5.0, 105.0)), 1); expect(containerLayer.find<int>(const Offset(5.0, 105.0)).annotation, 1);
expect(containerLayer.find<int>(const Offset(5.0, 205.0)), 2); expect(containerLayer.find<int>(const Offset(5.0, 205.0)).annotation, 2);
}); });
test('finds a value under a TransformLayer', () { test('finds a value under a TransformLayer', () {
...@@ -87,11 +87,11 @@ void main() { ...@@ -87,11 +87,11 @@ void main() {
i += 1; i += 1;
} }
expect(transformLayer.find<int>(const Offset(0.0, 100.0)), 0); expect(transformLayer.find<int>(const Offset(0.0, 100.0)).annotation, 0);
expect(transformLayer.find<int>(const Offset(0.0, 200.0)), 0); expect(transformLayer.find<int>(const Offset(0.0, 200.0)).annotation, 0);
expect(transformLayer.find<int>(const Offset(0.0, 270.0)), 1); expect(transformLayer.find<int>(const Offset(0.0, 270.0)).annotation, 1);
expect(transformLayer.find<int>(const Offset(0.0, 400.0)), 1); expect(transformLayer.find<int>(const Offset(0.0, 400.0)).annotation, 1);
expect(transformLayer.find<int>(const Offset(0.0, 530.0)), 2); expect(transformLayer.find<int>(const Offset(0.0, 530.0)).annotation, 2);
}); });
test('looks for child AnnotatedRegions before parents', () { test('looks for child AnnotatedRegions before parents', () {
...@@ -101,7 +101,7 @@ void main() { ...@@ -101,7 +101,7 @@ void main() {
parent.append(child); parent.append(child);
layer.append(parent); layer.append(parent);
expect(parent.find<int>(Offset.zero), 2); expect(parent.find<int>(Offset.zero).annotation, 2);
}); });
test('looks for correct type', () { test('looks for correct type', () {
...@@ -111,7 +111,7 @@ void main() { ...@@ -111,7 +111,7 @@ void main() {
layer.append(child2); layer.append(child2);
layer.append(child1); layer.append(child1);
expect(layer.find<String>(Offset.zero), 'hello'); expect(layer.find<String>(Offset.zero).annotation, 'hello');
}); });
test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () { test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () {
...@@ -121,7 +121,7 @@ void main() { ...@@ -121,7 +121,7 @@ void main() {
parent.append(child); parent.append(child);
layer.append(parent); layer.append(parent);
expect(layer.find<int>(const Offset(100.0, 100.0)), 1); expect(layer.find<int>(const Offset(100.0, 100.0)).annotation, 1);
}); });
test('handles non-invertable transforms', () { test('handles non-invertable transforms', () {
...@@ -133,7 +133,7 @@ void main() { ...@@ -133,7 +133,7 @@ void main() {
parent.transform = Matrix4.diagonal3Values(1.0, 1.0, 1.0); parent.transform = Matrix4.diagonal3Values(1.0, 1.0, 1.0);
expect(parent.find<int>(const Offset(0.0, 0.0)), 1); expect(parent.find<int>(const Offset(0.0, 0.0)).annotation, 1);
}); });
}); });
group('$AnnotatedRegion findAll', () { group('$AnnotatedRegion findAll', () {
...@@ -151,9 +151,9 @@ void main() { ...@@ -151,9 +151,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.findAll<int>(const Offset(0.0, 1.0)), equals(<int>[0])); expect(containerLayer.findAll<int>(const Offset(0.0, 1.0)).annotations.toList(), equals(<int>[0]));
expect(containerLayer.findAll<int>(const Offset(0.0, 101.0)),equals(<int>[1])); expect(containerLayer.findAll<int>(const Offset(0.0, 101.0)).annotations.toList(), equals(<int>[1]));
expect(containerLayer.findAll<int>(const Offset(0.0, 201.0)), equals(<int>[2])); expect(containerLayer.findAll<int>(const Offset(0.0, 201.0)).annotations.toList(), equals(<int>[2]));
}); });
test('finds a value within the clip in a ClipRectLayer', () { test('finds a value within the clip in a ClipRectLayer', () {
...@@ -170,9 +170,9 @@ void main() { ...@@ -170,9 +170,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.findAll<int>(const Offset(0.0, 1.0)), equals(<int>[0])); expect(containerLayer.findAll<int>(const Offset(0.0, 1.0)).annotations.toList(), equals(<int>[0]));
expect(containerLayer.findAll<int>(const Offset(0.0, 101.0)), equals(<int>[1])); expect(containerLayer.findAll<int>(const Offset(0.0, 101.0)).annotations.toList(), equals(<int>[1]));
expect(containerLayer.findAll<int>(const Offset(0.0, 201.0)), equals(<int>[2])); expect(containerLayer.findAll<int>(const Offset(0.0, 201.0)).annotations.toList(), equals(<int>[2]));
}); });
...@@ -190,9 +190,9 @@ void main() { ...@@ -190,9 +190,9 @@ void main() {
i += 1; i += 1;
} }
expect(containerLayer.findAll<int>(const Offset(5.0, 5.0)), equals(<int>[0])); expect(containerLayer.findAll<int>(const Offset(5.0, 5.0)).annotations.toList(), equals(<int>[0]));
expect(containerLayer.findAll<int>(const Offset(5.0, 105.0)), equals(<int>[1])); expect(containerLayer.findAll<int>(const Offset(5.0, 105.0)).annotations.toList(), equals(<int>[1]));
expect(containerLayer.findAll<int>(const Offset(5.0, 205.0)), equals(<int>[2])); expect(containerLayer.findAll<int>(const Offset(5.0, 205.0)).annotations.toList(), equals(<int>[2]));
}); });
test('finds a value under a TransformLayer', () { test('finds a value under a TransformLayer', () {
...@@ -216,11 +216,11 @@ void main() { ...@@ -216,11 +216,11 @@ void main() {
i += 1; i += 1;
} }
expect(transformLayer.findAll<int>(const Offset(0.0, 100.0)), equals(<int>[0])); expect(transformLayer.findAll<int>(const Offset(0.0, 100.0)).annotations.toList(), equals(<int>[0]));
expect(transformLayer.findAll<int>(const Offset(0.0, 200.0)), equals(<int>[0])); expect(transformLayer.findAll<int>(const Offset(0.0, 200.0)).annotations.toList(), equals(<int>[0]));
expect(transformLayer.findAll<int>(const Offset(0.0, 270.0)), equals(<int>[1])); expect(transformLayer.findAll<int>(const Offset(0.0, 270.0)).annotations.toList(), equals(<int>[1]));
expect(transformLayer.findAll<int>(const Offset(0.0, 400.0)), equals(<int>[1])); expect(transformLayer.findAll<int>(const Offset(0.0, 400.0)).annotations.toList(), equals(<int>[1]));
expect(transformLayer.findAll<int>(const Offset(0.0, 530.0)), equals(<int>[2])); expect(transformLayer.findAll<int>(const Offset(0.0, 530.0)).annotations.toList(), equals(<int>[2]));
}); });
test('finds multiple nested, overlapping regions', () { test('finds multiple nested, overlapping regions', () {
...@@ -237,7 +237,7 @@ void main() { ...@@ -237,7 +237,7 @@ void main() {
parent.append(layer); parent.append(layer);
} }
expect(parent.findAll<int>(const Offset(0.0, 0.0)), equals(<int>[3, 1, 2, 0,])); expect(parent.findAll<int>(const Offset(0.0, 0.0)).annotations.toList(), equals(<int>[3, 1, 2, 0,]));
}); });
test('looks for child AnnotatedRegions before parents', () { test('looks for child AnnotatedRegions before parents', () {
...@@ -251,7 +251,7 @@ void main() { ...@@ -251,7 +251,7 @@ void main() {
parent.append(child3); parent.append(child3);
layer.append(parent); layer.append(parent);
expect(parent.findAll<int>(Offset.zero), equals(<int>[4, 3, 2, 1])); expect(parent.findAll<int>(Offset.zero).annotations.toList(), equals(<int>[4, 3, 2, 1]));
}); });
test('looks for correct type', () { test('looks for correct type', () {
...@@ -261,7 +261,7 @@ void main() { ...@@ -261,7 +261,7 @@ void main() {
layer.append(child2); layer.append(child2);
layer.append(child1); layer.append(child1);
expect(layer.findAll<String>(Offset.zero), equals(<String>['hello'])); expect(layer.findAll<String>(Offset.zero).annotations.toList(), equals(<String>['hello']));
}); });
test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () { test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () {
...@@ -271,7 +271,7 @@ void main() { ...@@ -271,7 +271,7 @@ void main() {
parent.append(child); parent.append(child);
layer.append(parent); layer.append(parent);
expect(layer.findAll<int>(const Offset(100.0, 100.0)), equals(<int>[1])); expect(layer.findAll<int>(const Offset(100.0, 100.0)).annotations.toList(), equals(<int>[1]));
}); });
test('handles non-invertable transforms', () { test('handles non-invertable transforms', () {
...@@ -279,11 +279,11 @@ void main() { ...@@ -279,11 +279,11 @@ void main() {
final TransformLayer parent = TransformLayer(transform: Matrix4.diagonal3Values(0.0, 1.0, 1.0)); final TransformLayer parent = TransformLayer(transform: Matrix4.diagonal3Values(0.0, 1.0, 1.0));
parent.append(child); parent.append(child);
expect(parent.findAll<int>(const Offset(0.0, 0.0)), equals(<int>[])); expect(parent.findAll<int>(const Offset(0.0, 0.0)).annotations.toList(), equals(<int>[]));
parent.transform = Matrix4.diagonal3Values(1.0, 1.0, 1.0); parent.transform = Matrix4.diagonal3Values(1.0, 1.0, 1.0);
expect(parent.findAll<int>(const Offset(0.0, 0.0)), equals(<int>[1])); expect(parent.findAll<int>(const Offset(0.0, 0.0)).annotations.toList(), equals(<int>[1]));
}); });
}); });
} }
This diff is collapsed.
...@@ -34,12 +34,12 @@ void main() { ...@@ -34,12 +34,12 @@ void main() {
int result = RendererBinding.instance.renderView.debugLayer.find<int>(Offset( int result = RendererBinding.instance.renderView.debugLayer.find<int>(Offset(
10.0 * window.devicePixelRatio, 10.0 * window.devicePixelRatio,
10.0 * window.devicePixelRatio, 10.0 * window.devicePixelRatio,
)); ))?.annotation;
expect(result, null); expect(result, null);
result = RendererBinding.instance.renderView.debugLayer.find<int>(Offset( result = RendererBinding.instance.renderView.debugLayer.find<int>(Offset(
50.0 * window.devicePixelRatio, 50.0 * window.devicePixelRatio,
50.0 * window.devicePixelRatio, 50.0 * window.devicePixelRatio,
)); )).annotation;
expect(result, 1); expect(result, 1);
}); });
} }
...@@ -752,6 +752,196 @@ void main() { ...@@ -752,6 +752,196 @@ void main() {
expect(paintCount, 1); expect(paintCount, 1);
}); });
group('MouseRegion respects opacity:', () {
// A widget that contains 3 MouseRegions:
// y
// —————————————————————— 0
// | ——————————— A | 20
// | | B | |
// | | ——————————— | 50
// | | | C | |
// | ——————| | | 100
// | | | |
// | ——————————— | 130
// —————————————————————— 150
// x 0 20 50 100 130 150
Widget tripleRegions({bool opaqueC, void Function(String) addLog}) {
// Same as MouseRegion, but when opaque is null, use the default value.
Widget mouseRegionWithOptionalOpaque({
void Function(PointerEnterEvent e) onEnter,
void Function(PointerExitEvent e) onExit,
Widget child,
bool opaque,
}) {
if (opaque == null) {
return MouseRegion(onEnter: onEnter, onExit: onExit, child: child);
}
return MouseRegion(onEnter: onEnter, onExit: onExit, child: child, opaque: opaque);
}
return Directionality(
textDirection: TextDirection.ltr,
child: Align(
alignment: Alignment.topLeft,
child: MouseRegion(
onEnter: (PointerEnterEvent e) { addLog('enterA'); },
onExit: (PointerExitEvent e) { addLog('exitA'); },
child: SizedBox(
width: 150,
height: 150,
child: Stack(
children: <Widget>[
Positioned(
left: 20,
top: 20,
width: 80,
height: 80,
child: MouseRegion(
onEnter: (PointerEnterEvent e) { addLog('enterB'); },
onExit: (PointerExitEvent e) { addLog('exitB'); },
),
),
Positioned(
left: 50,
top: 50,
width: 80,
height: 80,
child: mouseRegionWithOptionalOpaque(
opaque: opaqueC,
onEnter: (PointerEnterEvent e) { addLog('enterC'); },
onExit: (PointerExitEvent e) { addLog('exitC'); },
),
),
],
),
),
),
),
);
}
testWidgets('a transparent one should allow MouseRegions behind it to receive pointers', (WidgetTester tester) async {
final List<String> logs = <String>[];
await tester.pumpWidget(tripleRegions(
opaqueC: false,
addLog: (String log) => logs.add(log),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await tester.pumpAndSettle();
// Move to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterC', 'enterB']);
logs.clear();
// Move to the B only area
await gesture.moveTo(const Offset(25, 75));
await tester.pumpAndSettle();
expect(logs, <String>['exitC']);
logs.clear();
// Move back to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterC']);
logs.clear();
// Move to the C only area
await gesture.moveTo(const Offset(125, 75));
await tester.pumpAndSettle();
expect(logs, <String>['exitB']);
logs.clear();
// Move back to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterB']);
logs.clear();
// Move out
await gesture.moveTo(const Offset(160, 160));
await tester.pumpAndSettle();
expect(logs, <String>['exitA', 'exitB', 'exitC']);
});
testWidgets('an opaque one should prevent MouseRegions behind it receiving pointers', (WidgetTester tester) async {
final List<String> logs = <String>[];
await tester.pumpWidget(tripleRegions(
opaqueC: true,
addLog: (String log) => logs.add(log),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await tester.pumpAndSettle();
// Move to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterC']);
logs.clear();
// Move to the B only area
await gesture.moveTo(const Offset(25, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterB', 'exitC']);
logs.clear();
// Move back to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterC', 'exitB']);
logs.clear();
// Move to the C only area
await gesture.moveTo(const Offset(125, 75));
await tester.pumpAndSettle();
expect(logs, <String>[]);
logs.clear();
// Move back to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>[]);
logs.clear();
// Move out
await gesture.moveTo(const Offset(160, 160));
await tester.pumpAndSettle();
expect(logs, <String>['exitA', 'exitC']);
});
testWidgets('opaque should default to true', (WidgetTester tester) async {
final List<String> logs = <String>[];
await tester.pumpWidget(tripleRegions(
opaqueC: null,
addLog: (String log) => logs.add(log),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await tester.pumpAndSettle();
// Move to the overlapping area
await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterC']);
logs.clear();
// Move out
await gesture.moveTo(const Offset(160, 160));
await tester.pumpAndSettle();
expect(logs, <String>['exitA', 'exitC']);
});
});
testWidgets('RenderMouseRegion\'s debugFillProperties when default', (WidgetTester tester) async { testWidgets('RenderMouseRegion\'s debugFillProperties when default', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
RenderMouseRegion().debugFillProperties(builder); RenderMouseRegion().debugFillProperties(builder);
......
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