Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
da0a7d8b
Unverified
Commit
da0a7d8b
authored
Jan 28, 2020
by
Tong Mu
Committed by
GitHub
Jan 28, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MouseTracker no longer requires annotations attached (#48453)
parent
dd98046f
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
519 additions
and
599 deletions
+519
-599
mouse_tracking.dart
packages/flutter/lib/src/gestures/mouse_tracking.dart
+45
-157
platform_view.dart
packages/flutter/lib/src/rendering/platform_view.dart
+0
-3
proxy_box.dart
packages/flutter/lib/src/rendering/proxy_box.dart
+53
-49
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+217
-67
mouse_tracking_test.dart
packages/flutter/test/gestures/mouse_tracking_test.dart
+11
-201
proxy_box_test.dart
packages/flutter/test/rendering/proxy_box_test.dart
+14
-0
listener_deprecated_test.dart
packages/flutter/test/widgets/listener_deprecated_test.dart
+0
-20
mouse_region_test.dart
packages/flutter/test/widgets/mouse_region_test.dart
+179
-102
No files found.
packages/flutter/lib/src/gestures/mouse_tracking.dart
View file @
da0a7d8b
...
...
@@ -30,7 +30,7 @@ typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
/// movements.
///
/// This is added to a layer and managed by the [MouseRegion] widget.
class
MouseTrackerAnnotation
{
class
MouseTrackerAnnotation
extends
Diagnosticable
{
/// Creates an annotation that can be used to find layers interested in mouse
/// movements.
const
MouseTrackerAnnotation
({
this
.
onEnter
,
this
.
onHover
,
this
.
onExit
});
...
...
@@ -39,24 +39,13 @@ class MouseTrackerAnnotation {
/// entered the annotated region.
///
/// This callback is triggered when the pointer has started to be contained
/// by the annotationed region for any reason.
///
/// More specifically, the callback is triggered by the following cases:
///
/// * A new annotated region has appeared under a pointer.
/// * An existing annotated region has moved to under a pointer.
/// * A new pointer has been added to somewhere within an annotated region.
/// * An existing pointer has moved into an annotated region.
///
/// This callback is not always matched by an [onExit]. If the render object
/// that owns the annotation is disposed while being hovered by a pointer,
/// the [onExit] callback of that annotation will never called, despite
/// the earlier call of [onEnter]. For more details, see [onExit].
/// by the annotationed region for any reason, which means it always matches a
/// later [onExit].
///
/// See also:
///
/// * [MouseRegion.onEnter], which uses this callback.
/// * [onExit], which is triggered when a mouse pointer exits the region.
/// * [MouseRegion.onEnter], which uses this callback.
final
PointerEnterEventListener
onEnter
;
/// Triggered when a pointer has moved within the annotated region without
...
...
@@ -69,7 +58,7 @@ class MouseTrackerAnnotation {
/// * A pointer has moved onto, or moved within an annotation without buttons
/// pressed.
///
/// This callback is not triggered when
/// This callback is not triggered when
:
///
/// * An annotation that is containing the pointer has moved, and still
/// contains the pointer.
...
...
@@ -78,59 +67,30 @@ class MouseTrackerAnnotation {
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// exited the annotated region when the annotated region still exists.
///
/// This callback is triggered when the pointer has stopped to be contained
/// by the region, except when it's caused by the removal of the render object
/// that owns the annotation. More specifically, the callback is triggered by
/// the following cases:
///
/// * An annotated region that used to contain a pointer has moved away.
/// * A pointer that used to be within an annotated region has been removed.
/// * A pointer that used to be within an annotated region has moved away.
///
/// And is __not__ triggered by the following case,
///
/// * An annotated region that used to contain a pointer has disappeared.
///
/// The last case is the only case when [onExit] does not match an earlier
/// This callback is triggered when the pointer has stopped being contained
/// by the region for any reason, which means it always matches an earlier
/// [onEnter].
/// {@template flutter.mouseTracker.onExit}
/// This design is because the last case is very likely to be
/// handled improperly and cause exceptions (such as calling `setState` of the
/// disposed widget). There are a few ways to mitigate this limit:
///
/// * If the state of hovering is contained within a widget that
/// unconditionally attaches the annotation (as long as a mouse is
/// connected), then this will not be a concern, since when the annotation
/// is disposed the state is no longer used.
/// * If you're accessible to the condition that controls whether the
/// annotation is attached, then you can call the callback when that
/// condition goes from true to false.
/// * In the cases where the solutions above won't work, you can always
/// override [State.dispose] or [RenderObject.detach].
/// {@endtemplate}
///
/// Technically, whether [onExit] will be called is controlled by
/// [MouseTracker.attachAnnotation] and [MouseTracker.detachAnnotation].
///
/// See also:
///
/// * [MouseRegion.onExit], which uses this callback.
/// * [onEnter], which is triggered when a mouse pointer enters the region.
/// * [RenderMouseRegion.onExit], which uses this callback.
/// * [MouseRegion.onExit], which uses this callback, but is not triggered in
/// certain cases and does not always match its earier [MouseRegion.onEnter].
final
PointerExitEventListener
onExit
;
@override
String
toString
()
{
final
List
<
String
>
callbacks
=
<
String
>[];
if
(
onEnter
!=
null
)
callbacks
.
add
(
'enter'
);
if
(
onHover
!=
null
)
callbacks
.
add
(
'hover'
);
if
(
onExit
!=
null
)
callbacks
.
add
(
'exit'
);
final
String
describeCallbacks
=
callbacks
.
isEmpty
?
'<none>'
:
callbacks
.
join
(
' '
);
return
'
${describeIdentity(this)}
(callbacks:
$describeCallbacks
)'
;
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
FlagsSummary
<
Function
>(
'callbacks'
,
<
String
,
Function
>
{
'enter'
:
onEnter
,
'hover'
:
onHover
,
'exit'
:
onExit
,
},
ifEmpty:
'<none>'
,
));
}
}
...
...
@@ -194,11 +154,9 @@ class _MouseState {
///
/// ### Details
///
/// The state of [MouseTracker] consists of
3
parts:
/// The state of [MouseTracker] consists of
two
parts:
///
/// * The mouse devices that are connected.
/// * The annotations that are attached, i.e. whose owner render object is
/// painted on the screen.
/// * In which annotations each device is contained.
///
/// The states remain stable most of the time, and are only changed at the
...
...
@@ -247,10 +205,6 @@ class MouseTracker extends ChangeNotifier {
// mouse events from.
final
PointerRouter
_router
;
// The collection of annotations that are currently being tracked. It is
// operated on by [attachAnnotation] and [detachAnnotation].
final
Set
<
MouseTrackerAnnotation
>
_trackedAnnotations
=
<
MouseTrackerAnnotation
>{};
// Tracks the state of connected mouse devices.
//
// It is the source of truth for the list of connected mouse devices.
...
...
@@ -298,7 +252,6 @@ class MouseTracker extends ChangeNotifier {
nextAnnotations:
mouseState
.
annotations
,
previousEvent:
previousEvent
,
unhandledEvent:
event
,
trackedAnnotations:
_trackedAnnotations
,
);
},
);
...
...
@@ -306,12 +259,12 @@ class MouseTracker extends ChangeNotifier {
// Find the annotations that is hovered by the device of the `state`.
//
// If the device is not connected
or there are no annotations attached, empty
//
is returned without calling
`annotationFinder`.
// If the device is not connected
, an empty set is returned without calling
// `annotationFinder`.
LinkedHashSet
<
MouseTrackerAnnotation
>
_findAnnotations
(
_MouseState
state
)
{
final
Offset
globalPosition
=
state
.
latestEvent
.
position
;
final
int
device
=
state
.
device
;
return
(
_mouseStates
.
containsKey
(
device
)
&&
_trackedAnnotations
.
isNotEmpty
)
return
(
_mouseStates
.
containsKey
(
device
))
?
LinkedHashSet
<
MouseTrackerAnnotation
>.
from
(
annotationFinder
(
globalPosition
))
:
<
MouseTrackerAnnotation
>{}
as
LinkedHashSet
<
MouseTrackerAnnotation
>;
}
...
...
@@ -332,7 +285,6 @@ class MouseTracker extends ChangeNotifier {
nextAnnotations:
mouseState
.
annotations
,
previousEvent:
mouseState
.
latestEvent
,
unhandledEvent:
null
,
trackedAnnotations:
_trackedAnnotations
,
);
}
);
...
...
@@ -428,16 +380,15 @@ class MouseTracker extends ChangeNotifier {
// null, which means the update is triggered by a new event.
// The `unhandledEvent` can be null, which means the update is not triggered
// by an event.
// However, one of `previousEvent` or `unhandledEvent` must not be null.
static
void
_dispatchDeviceCallbacks
({
@required
LinkedHashSet
<
MouseTrackerAnnotation
>
lastAnnotations
,
@required
LinkedHashSet
<
MouseTrackerAnnotation
>
nextAnnotations
,
@required
PointerEvent
previousEvent
,
@required
PointerEvent
unhandledEvent
,
@required
Set
<
MouseTrackerAnnotation
>
trackedAnnotations
,
})
{
assert
(
lastAnnotations
!=
null
);
assert
(
nextAnnotations
!=
null
);
assert
(
trackedAnnotations
!=
null
);
final
PointerEvent
latestEvent
=
unhandledEvent
??
previousEvent
;
assert
(
latestEvent
!=
null
);
// Order is important for mouse event callbacks. The `findAnnotations`
...
...
@@ -446,45 +397,41 @@ class MouseTracker extends ChangeNotifier {
// The algorithm here is explained in
// https://github.com/flutter/flutter/issues/41420
// Send exit events in visual order.
final
Iterable
<
MouseTrackerAnnotation
>
exitingAnnotations
=
lastAnnotations
.
difference
(
nextAnnotations
);
// Send exit events to annotations that are in last but not in next, in
// visual order.
final
Iterable
<
MouseTrackerAnnotation
>
exitingAnnotations
=
lastAnnotations
.
where
(
(
MouseTrackerAnnotation
value
)
=>
!
nextAnnotations
.
contains
(
value
),
);
for
(
final
MouseTrackerAnnotation
annotation
in
exitingAnnotations
)
{
final
bool
attached
=
trackedAnnotations
.
contains
(
annotation
);
// Exit is not sent if annotation is no longer attached, because this
// trigger may cause exceptions and has safer alternatives. See
// [MouseRegion.onExit] for details.
if
(
annotation
.
onExit
!=
null
&&
attached
)
{
if
(
annotation
.
onExit
!=
null
)
{
annotation
.
onExit
(
PointerExitEvent
.
fromMouseEvent
(
latestEvent
));
}
}
// Send enter events in reverse visual order.
// Send enter events to annotations that are not in last but in next, in
// reverse visual order.
final
Iterable
<
MouseTrackerAnnotation
>
enteringAnnotations
=
nextAnnotations
.
difference
(
lastAnnotations
).
toList
().
reversed
;
for
(
final
MouseTrackerAnnotation
annotation
in
enteringAnnotations
)
{
assert
(
trackedAnnotations
.
contains
(
annotation
));
if
(
annotation
.
onEnter
!=
null
)
{
annotation
.
onEnter
(
PointerEnterEvent
.
fromMouseEvent
(
latestEvent
));
}
}
// Send hover events
in reverse visual order.
//
For now the order between the hover events is designed this way for no
//
solid reasons but to keep it aligned with enter events for simplicity
.
// Send hover events
to annotations that are in next, in reverse visual
//
order. The reverse visual order is chosen only because of the simplicity
//
by keeping the hover events aligned with enter events
.
if
(
unhandledEvent
is
PointerHoverEvent
)
{
final
Iterable
<
MouseTrackerAnnotation
>
hoveringAnnotations
=
nextAnnotations
.
toList
().
reversed
;
final
Offset
lastHoverPosition
=
previousEvent
is
PointerHoverEvent
?
previousEvent
.
position
:
null
;
final
bool
pointerHasMoved
=
lastHoverPosition
==
null
||
lastHoverPosition
!=
unhandledEvent
.
position
;
// If the hover event follows a non-hover event, or has moved since the
// last hover, then trigger the hover callback on all annotations.
// Otherwise, trigger the hover callback only on annotations that it
// newly enters.
final
Iterable
<
MouseTrackerAnnotation
>
hoveringAnnotations
=
pointerHasMoved
?
nextAnnotations
.
toList
().
reversed
:
enteringAnnotations
;
for
(
final
MouseTrackerAnnotation
annotation
in
hoveringAnnotations
)
{
// Deduplicate: Trigger hover if it's a newly hovered annotation
// or the position has changed
assert
(
trackedAnnotations
.
contains
(
annotation
));
if
(!
lastAnnotations
.
contains
(
annotation
)
||
lastHoverPosition
!=
unhandledEvent
.
position
)
{
if
(
annotation
.
onHover
!=
null
)
{
annotation
.
onHover
(
unhandledEvent
);
}
if
(
annotation
.
onHover
!=
null
)
{
annotation
.
onHover
(
unhandledEvent
);
}
}
}
...
...
@@ -519,63 +466,4 @@ class MouseTracker extends ChangeNotifier {
/// Whether or not a mouse is connected and has produced events.
bool
get
mouseIsConnected
=>
_mouseStates
.
isNotEmpty
;
/// Checks if the given [MouseTrackerAnnotation] is attached to this
/// [MouseTracker].
///
/// This function is only public to allow for proper testing of the
/// MouseTracker. Do not call in other contexts.
@visibleForTesting
bool
isAnnotationAttached
(
MouseTrackerAnnotation
annotation
)
{
return
_trackedAnnotations
.
contains
(
annotation
);
}
/// Notify [MouseTracker] that a new [MouseTrackerAnnotation] has started to
/// take effect.
///
/// This method is typically called by the [RenderObject] that owns an
/// annotation, as soon as the render object is added to the render tree.
///
/// {@template flutter.mouseTracker.attachAnnotation}
/// Render objects that call this method might want to schedule a frame as
/// well, typically by calling [RenderObject.markNeedsPaint], because this
/// method does not cause any immediate effect, since the state it changes is
/// used during a post-frame callback or when handling certain pointer events.
///
/// ### About annotation attachment
///
/// It is the responsibility of the render object that owns the annotation to
/// maintain the attachment of the annotation. Whether an annotation is
/// attached should be kept in sync with whether its owner object is mounted,
/// which is used in the following ways:
///
/// * If a pointer enters an annotation, it is asserted that the annotation
/// is attached.
/// * If a pointer stops being contained by an annotation,
/// the exit event is triggered only if the annotation is still attached.
/// This is to prevent exceptions caused calling setState of a disposed
/// widget. See [MouseTrackerAnnotation.onExit] for more details.
/// * The [MouseTracker] also uses the attachment to track the number of
/// attached annotations, and will skip mouse position checks if there is no
/// annotations attached.
/// {@endtemplate}
/// * Attaching an annotation that has been attached will assert.
void
attachAnnotation
(
MouseTrackerAnnotation
annotation
)
{
assert
(!
_duringDeviceUpdate
);
assert
(!
_trackedAnnotations
.
contains
(
annotation
));
_trackedAnnotations
.
add
(
annotation
);
}
/// Notify [MouseTracker] that a mouse tracker annotation that was previously
/// attached has stopped taking effect.
///
/// This method is typically called by the [RenderObject] that owns an
/// annotation, as soon as the render object is removed from the render tree.
/// {@macro flutter.mouseTracker.attachAnnotation}
/// * Detaching an annotation that has not been attached will assert.
void
detachAnnotation
(
MouseTrackerAnnotation
annotation
)
{
assert
(!
_duringDeviceUpdate
);
assert
(
_trackedAnnotations
.
contains
(
annotation
));
_trackedAnnotations
.
remove
(
annotation
);
}
}
packages/flutter/lib/src/rendering/platform_view.dart
View file @
da0a7d8b
...
...
@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart';
import
'package:flutter/semantics.dart'
;
import
'package:flutter/services.dart'
;
import
'binding.dart'
;
import
'box.dart'
;
import
'layer.dart'
;
import
'object.dart'
;
...
...
@@ -838,13 +837,11 @@ mixin _PlatformViewGestureMixin on RenderBox {
if
(
_handlePointerEvent
!=
null
)
_handlePointerEvent
(
event
);
});
RendererBinding
.
instance
.
mouseTracker
.
attachAnnotation
(
_hoverAnnotation
);
}
@override
void
detach
()
{
_gestureRecognizer
.
reset
();
RendererBinding
.
instance
.
mouseTracker
.
detachAnnotation
(
_hoverAnnotation
);
_hoverAnnotation
=
null
;
super
.
detach
();
}
...
...
packages/flutter/lib/src/rendering/proxy_box.dart
View file @
da0a7d8b
...
...
@@ -2659,6 +2659,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// [RenderMouseRegion].
class
RenderMouseRegion
extends
RenderProxyBox
{
/// Creates a render object that forwards pointer events to callbacks.
///
/// All parameters are optional. By default this method creates an opaque
/// mouse region with no callbacks.
RenderMouseRegion
({
PointerEnterEventListener
onEnter
,
PointerHoverEventListener
onHover
,
...
...
@@ -2698,17 +2701,23 @@ class RenderMouseRegion extends RenderProxyBox {
set
opaque
(
bool
value
)
{
if
(
_opaque
!=
value
)
{
_opaque
=
value
;
_
updateAnnotations
(
);
_
markPropertyUpdated
(
mustRepaint:
true
);
}
}
/// Called when a mouse pointer enters the region (with or without buttons
/// pressed).
/// Called when a mouse pointer starts being contained by the region (with or
/// without buttons pressed) for any reason.
///
/// This callback is always matched by a later [onExit].
///
/// See also:
///
/// * [MouseRegion.onEnter], which uses this callback.
PointerEnterEventListener
get
onEnter
=>
_onEnter
;
set
onEnter
(
PointerEnterEventListener
value
)
{
if
(
_onEnter
!=
value
)
{
_onEnter
=
value
;
_
updateAnnotations
(
);
_
markPropertyUpdated
(
mustRepaint:
false
);
}
}
PointerEnterEventListener
_onEnter
;
...
...
@@ -2723,7 +2732,7 @@ class RenderMouseRegion extends RenderProxyBox {
set
onHover
(
PointerHoverEventListener
value
)
{
if
(
_onHover
!=
value
)
{
_onHover
=
value
;
_
updateAnnotations
(
);
_
markPropertyUpdated
(
mustRepaint:
false
);
}
}
PointerHoverEventListener
_onHover
;
...
...
@@ -2732,13 +2741,20 @@ class RenderMouseRegion extends RenderProxyBox {
_onHover
(
event
);
}
/// Called when a pointer leaves the region (with or without buttons pressed)
/// and the annotation is still attached.
/// Called when a pointer is no longer contained by the region (with or
/// without buttons pressed) for any reason.
///
/// This callback is always matched by an earlier [onEnter].
///
/// See also:
///
/// * [MouseRegion.onExit], which uses this callback, but is not triggered in
/// certain cases and does not always match its earier [MouseRegion.onEnter].
PointerExitEventListener
get
onExit
=>
_onExit
;
set
onExit
(
PointerExitEventListener
value
)
{
if
(
_onExit
!=
value
)
{
_onExit
=
value
;
_
updateAnnotations
(
);
_
markPropertyUpdated
(
mustRepaint:
false
);
}
}
PointerExitEventListener
_onExit
;
...
...
@@ -2757,64 +2773,52 @@ class RenderMouseRegion extends RenderProxyBox {
@visibleForTesting
MouseTrackerAnnotation
get
hoverAnnotation
=>
_hoverAnnotation
;
void
_updateAnnotations
()
{
final
bool
annotationWasActive
=
_annotationIsActive
;
final
bool
annotationWillBeActive
=
(
// Call this method when a property has changed and might affect the
// `_annotationIsActive` bit.
//
// If `mustRepaint` is false, this method does NOT call `markNeedsPaint`
// unless the `_annotationIsActive` bit is changed. If there is a property
// that needs updating while `_annotationIsActive` stays true, make
// `mustRepaint` true.
//
// This method must not be called during `paint`.
void
_markPropertyUpdated
({
@required
bool
mustRepaint
})
{
assert
(
owner
==
null
||
!
owner
.
debugDoingPaint
);
final
bool
newAnnotationIsActive
=
(
_onEnter
!=
null
||
_onHover
!=
null
||
_onExit
!=
null
||
opaque
)
&&
RendererBinding
.
instance
.
mouseTracker
.
mouseIsConnected
;
if
(
annotationWasActive
!=
annotationWillBeActive
)
{
)
&&
RendererBinding
.
instance
.
mouseTracker
.
mouseIsConnected
;
_setAnnotationIsActive
(
newAnnotationIsActive
);
if
(
mustRepaint
)
markNeedsPaint
();
}
void
_setAnnotationIsActive
(
bool
value
)
{
final
bool
annotationWasActive
=
_annotationIsActive
;
_annotationIsActive
=
value
;
if
(
annotationWasActive
!=
value
)
{
markNeedsPaint
();
markNeedsCompositingBitsUpdate
();
if
(
annotationWillBeActive
)
{
RendererBinding
.
instance
.
mouseTracker
.
attachAnnotation
(
_hoverAnnotation
);
}
else
{
RendererBinding
.
instance
.
mouseTracker
.
detachAnnotation
(
_hoverAnnotation
);
}
_annotationIsActive
=
annotationWillBeActive
;
}
}
void
_handleUpdatedMouseIsConnected
()
{
_markPropertyUpdated
(
mustRepaint:
false
);
}
@override
void
attach
(
PipelineOwner
owner
)
{
super
.
attach
(
owner
);
// Add a listener to listen for changes in mouseIsConnected.
RendererBinding
.
instance
.
mouseTracker
.
addListener
(
_updateAnnotations
);
_updateAnnotations
();
}
/// Attaches the annotation for this render object, if any.
///
/// This is called by the [MouseRegion]'s [Element] to tell this
/// [RenderMouseRegion] that it has transitioned from "inactive"
/// state to "active". We call it here so that
/// [MouseTrackerAnnotation.onEnter] isn't called during the build step for
/// the widget that provided the callback, and [State.setState] can safely be
/// called within that callback.
void
postActivate
()
{
if
(
_annotationIsActive
)
RendererBinding
.
instance
.
mouseTracker
.
attachAnnotation
(
_hoverAnnotation
);
}
/// Detaches the annotation for this render object, if any.
///
/// This is called by the [MouseRegion]'s [Element] to tell this
/// [RenderMouseRegion] that it will shortly be transitioned from "active"
/// state to "inactive". We call it here so that
/// [MouseTrackerAnnotation.onExit] isn't called during the build step for the
/// widget that provided the callback, and [State.setState] can safely be
/// called within that callback.
void
preDeactivate
()
{
if
(
_annotationIsActive
)
RendererBinding
.
instance
.
mouseTracker
.
detachAnnotation
(
_hoverAnnotation
);
RendererBinding
.
instance
.
mouseTracker
.
addListener
(
_handleUpdatedMouseIsConnected
);
_markPropertyUpdated
(
mustRepaint:
false
);
}
@override
void
detach
()
{
RendererBinding
.
instance
.
mouseTracker
.
removeListener
(
_
updateAnnotations
);
RendererBinding
.
instance
.
mouseTracker
.
removeListener
(
_
handleUpdatedMouseIsConnected
);
super
.
detach
();
}
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
da0a7d8b
...
...
@@ -5854,7 +5854,7 @@ class _PointerListener extends SingleChildRenderObjectWidget {
///
/// * [Listener], a similar widget that tracks pointer events when the pointer
/// have buttons pressed.
class
MouseRegion
extends
S
ingleChildRenderObject
Widget
{
class
MouseRegion
extends
S
tateful
Widget
{
/// Creates a widget that forwards mouse events to callbacks.
const
MouseRegion
({
Key
key
,
...
...
@@ -5862,16 +5862,15 @@ class MouseRegion extends SingleChildRenderObjectWidget {
this
.
onExit
,
this
.
onHover
,
this
.
opaque
=
true
,
Widget
child
,
this
.
child
,
})
:
assert
(
opaque
!=
null
),
super
(
key:
key
,
child:
child
);
super
(
key:
key
);
/// Called when a mouse pointer, with or without buttons pressed, has
/// entered this widget.
/// Called when a mouse pointer has entered this widget.
///
/// This callback is triggered when the pointer
has started to be contained
///
by the region of this widget. More specifically, the callback is triggered
/// by the following cases:
/// This callback is triggered when the pointer
, with or without buttons
///
pressed, has started to be contained by the region of this widget. More
///
specifically, the callback is triggered
by the following cases:
///
/// * This widget has appeared under a pointer.
/// * This widget has moved to under a pointer.
...
...
@@ -5880,8 +5879,13 @@ class MouseRegion extends SingleChildRenderObjectWidget {
///
/// This callback is not always matched by an [onExit]. If the [MouseRegion]
/// is unmounted while being hovered by a pointer, the [onExit] of the widget
/// callback will never called, despite the earlier call of [onEnter]. For
/// more details, see [onExit].
/// callback will never called. For more details, see [onExit].
///
/// {@template flutter.mouseRegion.triggerTime}
/// The time that this callback is triggered is always between frames: either
/// during the post-frame callbacks, or during the callback of a pointer
/// event.
/// {@endtemplate}
///
/// See also:
///
...
...
@@ -5890,47 +5894,182 @@ class MouseRegion extends SingleChildRenderObjectWidget {
/// internally implemented.
final
PointerEnterEventListener
onEnter
;
/// Called when a mouse pointer
changes position without buttons pressed, and
///
the new position is within the region defined by this widget
.
/// Called when a mouse pointer
moves within this widget without buttons
///
pressed
.
///
/// This callback is triggered when:
/// This callback is not triggered when the [MouseRegion] has moved
/// while being hovered by the mouse pointer.
///
/// * An annotation that did not contain the pointer has moved to under a
/// pointer that has no buttons pressed.
/// * A pointer has moved onto, or moved within an annotation without buttons
/// pressed.
///
/// This callback is not triggered when
///
/// * An annotation that is containing the pointer has moved, and still
/// contains the pointer.
/// {@macro flutter.mouseRegion.triggerTime}
final
PointerHoverEventListener
onHover
;
/// Called when a mouse pointer, with or without buttons pressed, has exited
/// this widget when the widget is still mounted.
/// Called when a mouse pointer has exited this widget when the widget is
/// still mounted.
///
/// This callback is triggered when the pointer, with or without buttons
/// pressed, has stopped being contained by the region of this widget, except
/// when the exit is caused by the disappearance of this widget. More
/// specifically, this callback is triggered by the following cases:
///
/// * A pointer that is hovering this widget has moved away.
/// * A pointer that is hovering this widget has been removed.
/// * This widget, which is being hovered by a pointer, has moved away.
///
/// And is __not__ triggered by the following case:
///
/// * This widget, which is being hovered by a pointer, has disappeared.
///
/// This means that a [MouseRegion.onExit] might not be matched by a
/// [MouseRegion.onEnter].
///
/// This restriction aims to prevent a common misuse: if [setState] is called
/// during [MouseRegion.onExit] without checking whether the widget is still
/// mounted, an exception will occur. This is because the callback is
/// triggered during the post-frame phase, at which point the widget has been
/// unmounted. Since [setState] is exclusive to widgets, the restriction is
/// specific to [MouseRegion], and does not apply to its lower-level
/// counterparts, [RenderMouseRegion] and [MouseTrackerAnnotation].
///
/// There are a few ways to mitigate this restriction:
///
/// This callback is triggered when the pointer has stopped to be contained
/// by the region of this widget, except when it's caused by the removal of
/// this widget. More specifically, the callback is triggered by
/// the following cases:
/// * If the hover state is completely contained within a widget that
/// unconditionally creates this [MouseRegion], then this will not be a
/// concern, since after the [MouseRegion] is unmounted the state is no
/// longer used.
/// * Otherwise, the outer widget very likely has access to the variable that
/// controls whether this [MouseRegion] is present. If so, call [onExit] at
/// the event that turns the condition from true to false.
/// * In cases where the solutions above won't work, you can always
/// override [State.dispose] and call [onExit], or create your own widget
/// using [RenderMouseRegion].
///
/// * This widget, which used to contain a pointer, has moved away.
/// * A pointer that used to be within this widget has been removed.
/// * A pointer that used to be within this widget has moved away.
/// {@tool sample --template=stateful_widget_scaffold_center}
/// The following example shows a blue rectangular that turns yellow when
/// hovered. Since the hover state is completely contained within a widget
/// that unconditionally creates the `MouseRegion`, you can ignore the
/// aforementioned restriction.
///
/// And is __not__ triggered by the following case,
/// ```dart
/// bool hovered = false;
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// height: 100,
/// width: 100,
/// decoration: BoxDecoration(color: hovered ? Colors.yellow : Colors.blue),
/// child: MouseRegion(
/// onEnter: (_) {
/// setState(() { hovered = true; });
/// },
/// onExit: (_) {
/// setState(() { hovered = false; });
/// },
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// * This widget, which used to contain a pointer, has disappeared.
/// {@tool sample --template=stateful_widget_scaffold_center}
/// The following example shows a widget that hides its content one second
/// after behing hovered, and also exposes the enter and exit callbacks.
/// Because the widget conditionally creates the `MouseRegion`, and leaks the
/// hover state, it needs to take the restriction into consideration. In this
/// case, since it has access to the event that triggers the disappearance of
/// the `MouseRegion`, it simply trigger the exit callback during that event
/// as well.
///
/// ```dart preamble
/// // A region that hides its content one second after being hovered.
/// class MyTimedButton extends StatefulWidget {
/// MyTimedButton({ Key key, this.onEnterButton, this.onExitButton })
/// : super(key: key);
///
/// final VoidCallback onEnterButton;
/// final VoidCallback onExitButton;
///
/// @override
/// _MyTimedButton createState() => _MyTimedButton();
/// }
///
/// class _MyTimedButton extends State<MyTimedButton> {
/// bool regionIsHidden = false;
/// bool hovered = false;
///
/// void startCountdown() async {
/// await Future.delayed(const Duration(seconds: 1));
/// hideButton();
/// }
///
/// void hideButton() {
/// setState(() { regionIsHidden = true; });
/// // This statement is necessary.
/// if (hovered)
/// widget.onExitButton();
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// width: 100,
/// height: 100,
/// child: MouseRegion(
/// child: regionIsHidden ? null : MouseRegion(
/// onEnter: (_) {
/// widget.onEnterButton();
/// setState(() { hovered = true; });
/// startCountdown();
/// },
/// onExit: (_) {
/// setState(() { hovered = false; });
/// widget.onExitButton();
/// },
/// child: Container(color: Colors.red),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// The last case is the only case when [onExit] does not match an earlier
/// [onEnter].
/// {@macro flutter.mouseTracker.onExit}
/// ```dart
/// Key key = UniqueKey();
/// bool hovering = false;
///
/// @override
/// Widget build(BuildContext context) {
/// return Column(
/// children: <Widget>[
/// RaisedButton(
/// onPressed: () {
/// setState(() { key = UniqueKey(); });
/// },
/// child: Text('Refresh'),
/// ),
/// hovering ? Text('Hovering') : Text('Not hovering'),
/// MyTimedButton(
/// key: key,
/// onEnterButton: () {
/// setState(() { hovering = true; });
/// },
/// onExitButton: () {
/// setState(() { hovering = false; });
/// },
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// {@macro flutter.mouseRegion.triggerTime}
///
/// See also:
///
/// * [onEnter], which is triggered when a mouse pointer enters the region.
/// * [
MouseTrackerAnnotation.onExit], which is how this callback is
///
internally implemented
.
/// * [
RenderMouseRegion] and [MouseTrackerAnnotation.onExit], which are how
///
this callback is internally implemented, but without the restriction
.
final
PointerExitEventListener
onExit
;
/// Whether this widget should prevent other [MouseRegion]s visually behind it
...
...
@@ -5949,27 +6088,13 @@ class MouseRegion extends SingleChildRenderObjectWidget {
/// This defaults to true.
final
bool
opaque
;
@override
_MouseRegionElement
createElement
()
=>
_MouseRegionElement
(
this
);
@override
RenderMouseRegion
createRenderObject
(
BuildContext
context
)
{
return
RenderMouseRegion
(
onEnter:
onEnter
,
onHover:
onHover
,
onExit:
onExit
,
opaque:
opaque
,
);
}
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final
Widget
child
;
@override
void
updateRenderObject
(
BuildContext
context
,
RenderMouseRegion
renderObject
)
{
renderObject
..
onEnter
=
onEnter
..
onHover
=
onHover
..
onExit
=
onExit
..
opaque
=
opaque
;
}
_MouseRegionState
createState
()
=>
_MouseRegionState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
...
...
@@ -5986,21 +6111,46 @@ class MouseRegion extends SingleChildRenderObjectWidget {
}
}
class
_MouseRegionElement
extends
SingleChildRenderObjectElement
{
_MouseRegionElement
(
SingleChildRenderObjectWidget
widget
)
:
super
(
widget
);
class
_MouseRegionState
extends
State
<
MouseRegion
>
{
void
handleExit
(
PointerExitEvent
event
)
{
if
(
widget
.
onExit
!=
null
&&
mounted
)
widget
.
onExit
(
event
);
}
PointerExitEventListener
getHandleExit
()
{
return
widget
.
onExit
==
null
?
null
:
handleExit
;
}
@override
void
activate
()
{
super
.
activate
();
final
RenderMouseRegion
renderMouseRegion
=
renderObject
as
RenderMouseRegion
;
renderMouseRegion
.
postActivate
();
Widget
build
(
BuildContext
context
)
{
return
_RawMouseRegion
(
this
);
}
}
class
_RawMouseRegion
extends
SingleChildRenderObjectWidget
{
_RawMouseRegion
(
this
.
owner
)
:
super
(
child:
owner
.
widget
.
child
);
final
_MouseRegionState
owner
;
@override
RenderMouseRegion
createRenderObject
(
BuildContext
context
)
{
final
MouseRegion
widget
=
owner
.
widget
;
return
RenderMouseRegion
(
onEnter:
widget
.
onEnter
,
onHover:
widget
.
onHover
,
onExit:
owner
.
getHandleExit
(),
opaque:
widget
.
opaque
,
);
}
@override
void
deactivate
()
{
final
RenderMouseRegion
renderMouseRegion
=
renderObject
as
RenderMouseRegion
;
renderMouseRegion
.
preDeactivate
();
super
.
deactivate
();
void
updateRenderObject
(
BuildContext
context
,
RenderMouseRegion
renderObject
)
{
final
MouseRegion
widget
=
owner
.
widget
;
renderObject
..
onEnter
=
widget
.
onEnter
..
onHover
=
widget
.
onHover
..
onExit
=
owner
.
getHandleExit
()
..
opaque
=
widget
.
opaque
;
}
}
...
...
packages/flutter/test/gestures/mouse_tracking_test.dart
View file @
da0a7d8b
...
...
@@ -85,7 +85,6 @@ void main() {
yield
annotation
;
},
);
_mouseTracker
.
attachAnnotation
(
annotation
);
return
annotation
;
}
...
...
@@ -102,7 +101,7 @@ void main() {
);
expect
(
annotation1
.
toString
(),
equals
(
'MouseTrackerAnnotation#
${shortHash(annotation1)}
(callbacks:
enter hover exit
)'
),
equals
(
'MouseTrackerAnnotation#
${shortHash(annotation1)}
(callbacks:
[enter, hover, exit]
)'
),
);
const
MouseTrackerAnnotation
annotation2
=
MouseTrackerAnnotation
();
...
...
@@ -249,73 +248,6 @@ void main() {
events
.
clear
();
});
test
(
'should not flip out when attaching and detaching during callbacks'
,
()
{
// It is a common pattern that a callback that listens to the changes of
// [MouseTracker.mouseIsConnected] triggers annotation attaching and
// detaching. This test ensures that no exceptions are thrown for this
// pattern.
bool
isInHitRegion
=
false
;
final
List
<
PointerEvent
>
events
=
<
PointerEvent
>[];
final
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
(
onEnter:
(
PointerEnterEvent
event
)
=>
events
.
add
(
event
),
onHover:
(
PointerHoverEvent
event
)
=>
events
.
add
(
event
),
onExit:
(
PointerExitEvent
event
)
=>
events
.
add
(
event
),
);
_setUpMouseAnnotationFinder
((
Offset
position
)
sync
*
{
if
(
isInHitRegion
)
{
yield
annotation
;
}
});
void
mockMarkNeedsPaint
()
{
_binding
.
scheduleMouseTrackerPostFrameCheck
();
}
final
VoidCallback
firstListener
=
()
{
if
(!
_mouseTracker
.
mouseIsConnected
)
{
_mouseTracker
.
detachAnnotation
(
annotation
);
isInHitRegion
=
false
;
}
else
{
_mouseTracker
.
attachAnnotation
(
annotation
);
isInHitRegion
=
true
;
}
mockMarkNeedsPaint
();
};
_mouseTracker
.
addListener
(
firstListener
);
// The pointer is added onto the annotation, triggering attaching callback.
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
1.0
,
0.0
)),
]));
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_mouseTracker
.
mouseIsConnected
,
isTrue
);
_binding
.
flushPostFrameCallbacks
(
Duration
.
zero
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
const
PointerEnterEvent
(
position:
Offset
(
1.0
,
0.0
)),
]));
expect
(
_mouseTracker
.
mouseIsConnected
,
isTrue
);
events
.
clear
();
// The pointer is removed while on the annotation, triggering dettaching callback.
_mouseTracker
.
removeListener
(
firstListener
);
_mouseTracker
.
addListener
(()
{
if
(!
_mouseTracker
.
mouseIsConnected
)
{
_mouseTracker
.
detachAnnotation
(
annotation
);
isInHitRegion
=
false
;
}
});
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
remove
,
const
Offset
(
1.0
,
0.0
)),
]));
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
const
PointerExitEvent
(
position:
Offset
(
1.0
,
0.0
)),
]));
expect
(
_mouseTracker
.
mouseIsConnected
,
isFalse
);
events
.
clear
();
});
test
(
'should not handle non-hover events'
,
()
{
final
List
<
PointerEvent
>
events
=
<
PointerEvent
>[];
_setUpWithOneAnnotation
(
logEvents:
events
);
...
...
@@ -346,7 +278,7 @@ void main() {
events
.
clear
();
});
test
(
'should correctly handle when the annotation
is attached or detached
on the pointer'
,
()
{
test
(
'should correctly handle when the annotation
appears or disappears
on the pointer'
,
()
{
bool
isInHitRegion
;
final
List
<
Object
>
events
=
<
PointerEvent
>[];
final
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
(
...
...
@@ -371,13 +303,8 @@ void main() {
expect
(
_mouseTracker
.
mouseIsConnected
,
isTrue
);
events
.
clear
();
// A
ttach
ing an annotation should trigger Enter event.
// A
dd
ing an annotation should trigger Enter event.
isInHitRegion
=
true
;
_mouseTracker
.
attachAnnotation
(
annotation
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_binding
.
scheduleMouseTrackerPostFrameCheck
();
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
1
));
...
...
@@ -387,18 +314,14 @@ void main() {
]));
events
.
clear
();
//
Detaching an annotation should not
trigger events.
//
Removing an annotation should
trigger events.
isInHitRegion
=
false
;
_mouseTracker
.
detachAnnotation
(
annotation
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_binding
.
scheduleMouseTrackerPostFrameCheck
();
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
1
));
_binding
.
flushPostFrameCallbacks
(
Duration
.
zero
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
const
PointerExitEvent
(
position:
Offset
(
0.0
,
100.0
)),
]));
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
});
...
...
@@ -417,8 +340,6 @@ void main() {
}
});
// Start with an annotation attached.
_mouseTracker
.
attachAnnotation
(
annotation
);
isInHitRegion
=
false
;
// Connect a mouse.
...
...
@@ -468,8 +389,6 @@ void main() {
}
});
// Start with an annotation attached.
_mouseTracker
.
attachAnnotation
(
annotation
);
isInHitRegion
=
false
;
// Connect a mouse in the region. Should trigger Enter.
...
...
@@ -508,8 +427,6 @@ void main() {
}
});
// Start with annotation and mouse attached.
_mouseTracker
.
attachAnnotation
(
annotation
);
isInHitRegion
=
false
;
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
200.0
,
100.0
)),
...
...
@@ -541,75 +458,17 @@ void main() {
]));
});
test
(
'should correctly handle when annotation is attached or detached while not containing the pointer'
,
()
{
final
List
<
PointerEvent
>
events
=
<
PointerEvent
>[];
final
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
(
onEnter:
(
PointerEnterEvent
event
)
=>
events
.
add
(
event
),
onHover:
(
PointerHoverEvent
event
)
=>
events
.
add
(
event
),
onExit:
(
PointerExitEvent
event
)
=>
events
.
add
(
event
),
);
_setUpMouseAnnotationFinder
((
Offset
position
)
sync
*
{
// This annotation is never in the region.
});
// Connect a mouse when there is no annotation.
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
0.0
,
100.0
)),
]));
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_mouseTracker
.
mouseIsConnected
,
isTrue
);
events
.
clear
();
// Attaching an annotation should not trigger events.
_mouseTracker
.
attachAnnotation
(
annotation
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_binding
.
scheduleMouseTrackerPostFrameCheck
();
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
1
));
_binding
.
flushPostFrameCallbacks
(
Duration
.
zero
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
events
.
clear
();
// Detaching an annotation should not trigger events.
_mouseTracker
.
detachAnnotation
(
annotation
);
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_binding
.
scheduleMouseTrackerPostFrameCheck
();
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
1
));
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
remove
,
const
Offset
(
0.0
,
100.0
)),
]));
expect
(
events
,
_equalToEventsOnCriticalFields
(<
PointerEvent
>[
]));
});
test
(
'should not schedule postframe callbacks when no mouse is connected'
,
()
{
const
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
();
_setUpMouseAnnotationFinder
((
Offset
position
)
sync
*
{
});
//
This device only supports touching
//
Connect a touch device, which should not be recognized by MouseTracker
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
0.0
,
100.0
),
kind:
PointerDeviceKind
.
touch
),
]));
expect
(
_mouseTracker
.
mouseIsConnected
,
isFalse
);
// Attaching an annotation just in case
_mouseTracker
.
attachAnnotation
(
annotation
);
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_binding
.
scheduleMouseTrackerPostFrameCheck
();
expect
(
_binding
.
postFrameCallbacks
,
hasLength
(
0
));
_mouseTracker
.
detachAnnotation
(
annotation
);
});
test
(
'should not flip out if not all mouse events are listened to'
,
()
{
...
...
@@ -628,60 +487,15 @@ void main() {
yield
annotation2
;
});
final
ui
.
PointerDataPacket
packet
=
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
0.0
,
101.0
)),
_pointerData
(
PointerChange
.
hover
,
const
Offset
(
1.0
,
101.0
)),
]);
isInHitRegionOne
=
false
;
isInHitRegionTwo
=
true
;
_mouseTracker
.
attachAnnotation
(
annotation2
);
ui
.
window
.
onPointerDataPacket
(
packet
);
_mouseTracker
.
detachAnnotation
(
annotation2
);
isInHitRegionTwo
=
false
;
// Passes if no errors are thrown.
});
test
(
'should not call annotationFinder when no annotations are attached'
,
()
{
final
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
(
onEnter:
(
PointerEnterEvent
event
)
{},
);
int
finderCalled
=
0
;
_setUpMouseAnnotationFinder
((
Offset
position
)
sync
*
{
finderCalled
++;
// This annotation is never in the region.
});
// When no annotations are attached, hovering should not call finder.
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
add
,
const
Offset
(
0.0
,
101.0
)),
_pointerData
(
PointerChange
.
hover
,
const
Offset
(
1.0
,
101.0
)),
]));
expect
(
finderCalled
,
0
);
// Attaching should not call finder.
_mouseTracker
.
attachAnnotation
(
annotation
);
_binding
.
flushPostFrameCallbacks
(
Duration
.
zero
);
expect
(
finderCalled
,
0
);
// When annotations are attached, hovering should call finder.
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
hover
,
const
Offset
(
0.0
,
201.0
)),
]));
expect
(
finderCalled
,
1
);
finderCalled
=
0
;
// Detaching an annotation should not call finder.
_mouseTracker
.
detachAnnotation
(
annotation
);
_binding
.
flushPostFrameCallbacks
(
Duration
.
zero
);
expect
(
finderCalled
,
0
);
// When all annotations are detached, hovering should not call finder.
ui
.
window
.
onPointerDataPacket
(
ui
.
PointerDataPacket
(
data:
<
ui
.
PointerData
>[
_pointerData
(
PointerChange
.
hover
,
const
Offset
(
0.0
,
201.0
)),
]));
expect
(
finderCalled
,
0
);
// Passes if no errors are thrown.
});
test
(
'should trigger callbacks between parents and children in correct order'
,
()
{
...
...
@@ -713,8 +527,6 @@ void main() {
yield
annotationA
;
}
});
_mouseTracker
.
attachAnnotation
(
annotationA
);
_mouseTracker
.
attachAnnotation
(
annotationB
);
// Starts out of A.
isInB
=
false
;
...
...
@@ -768,8 +580,6 @@ void main() {
yield
annotationB
;
}
});
_mouseTracker
.
attachAnnotation
(
annotationA
);
_mouseTracker
.
attachAnnotation
(
annotationB
);
// Starts within A.
isInA
=
true
;
...
...
@@ -868,13 +678,13 @@ class _EventCriticalFieldsMatcher extends Matcher {
return
mismatchDescription
.
add
(
'is '
)
.
addDescriptionOf
(
item
.
runtimeType
)
.
add
(
' and doesn
\'
t match '
)
.
add
(
" and doesn't match "
)
.
addDescriptionOf
(
_expected
.
runtimeType
);
}
return
mismatchDescription
.
add
(
'has '
)
.
addDescriptionOf
(
matchState
[
'actual'
])
.
add
(
' at field `
${matchState['field']}
`, which doesn
\'
t match the expected '
)
.
add
(
" at field `
${matchState['field']}
`, which doesn't match the expected "
)
.
addDescriptionOf
(
matchState
[
'expected'
]);
}
}
...
...
@@ -940,7 +750,7 @@ class _EventListCriticalFieldsMatcher extends Matcher {
mismatchDescription
.
add
(
'has
\n
'
)
.
addDescriptionOf
(
matchState
[
'actual'
])
.
add
(
'
\n
at index
${matchState['index']}
, which doesn
\'
t match
\n
'
)
.
add
(
"
\n
at index
${matchState['index']}
, which doesn't match
\n
"
)
.
addDescriptionOf
(
matchState
[
'expected'
])
.
add
(
'
\n
since it '
);
final
Description
subDescription
=
StringDescription
();
...
...
packages/flutter/test/rendering/proxy_box_test.dart
View file @
da0a7d8b
...
...
@@ -467,6 +467,20 @@ void main() {
// transform -> clip
_testFittedBoxWithClipRectLayer
();
});
test
(
'RenderMouseRegion can change properties when detached'
,
()
{
renderer
.
initMouseTracker
(
MouseTracker
(
renderer
.
pointerRouter
,
(
_
)
=>
<
MouseTrackerAnnotation
>[],
));
final
RenderMouseRegion
object
=
RenderMouseRegion
();
object
..
opaque
=
false
..
onEnter
=
(
_
)
{}
..
onExit
=
(
_
)
{}
..
onHover
=
(
_
)
{};
// Passes if no error is thrown
});
}
class
_TestRectClipper
extends
CustomClipper
<
Rect
>
{
...
...
packages/flutter/test/widgets/listener_deprecated_test.dart
View file @
da0a7d8b
...
...
@@ -162,7 +162,6 @@ void main() {
),
),
);
final
RenderMouseRegion
renderListener
=
tester
.
renderObject
(
find
.
byType
(
MouseRegion
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
(
location:
const
Offset
(
400.0
,
300.0
));
addTearDown
(
gesture
.
removePointer
);
...
...
@@ -178,7 +177,6 @@ void main() {
),
));
expect
(
exit
,
isNull
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener
.
hoverAnnotation
),
isFalse
);
});
testWidgets
(
'Hover works with nested listeners'
,
(
WidgetTester
tester
)
async
{
final
UniqueKey
key1
=
UniqueKey
();
...
...
@@ -229,9 +227,6 @@ void main() {
],
),
);
final
List
<
RenderObject
>
listeners
=
tester
.
renderObjectList
(
find
.
byType
(
MouseRegion
)).
toList
();
final
RenderMouseRegion
renderListener1
=
listeners
[
0
]
as
RenderMouseRegion
;
final
RenderMouseRegion
renderListener2
=
listeners
[
1
]
as
RenderMouseRegion
;
Offset
center
=
tester
.
getCenter
(
find
.
byKey
(
key2
));
await
gesture
.
moveTo
(
center
);
await
tester
.
pump
();
...
...
@@ -243,8 +238,6 @@ void main() {
expect
(
enter1
,
isNotEmpty
);
expect
(
enter1
.
last
.
position
,
equals
(
center
));
expect
(
exit1
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
// Now make sure that exiting the child only triggers the child exit, not
...
...
@@ -259,8 +252,6 @@ void main() {
expect
(
move1
.
last
.
position
,
equals
(
center
));
expect
(
enter1
,
isEmpty
);
expect
(
exit1
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
});
testWidgets
(
'Hover transfers between two listeners'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -314,9 +305,6 @@ void main() {
],
),
);
final
List
<
RenderObject
>
listeners
=
tester
.
renderObjectList
(
find
.
byType
(
MouseRegion
)).
toList
();
final
RenderMouseRegion
renderListener1
=
listeners
[
0
]
as
RenderMouseRegion
;
final
RenderMouseRegion
renderListener2
=
listeners
[
1
]
as
RenderMouseRegion
;
final
Offset
center1
=
tester
.
getCenter
(
find
.
byKey
(
key1
));
final
Offset
center2
=
tester
.
getCenter
(
find
.
byKey
(
key2
));
await
gesture
.
moveTo
(
center1
);
...
...
@@ -329,8 +317,6 @@ void main() {
expect
(
move2
,
isEmpty
);
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
gesture
.
moveTo
(
center2
);
await
tester
.
pump
();
...
...
@@ -343,8 +329,6 @@ void main() {
expect
(
enter2
,
isNotEmpty
);
expect
(
enter2
.
last
.
position
,
equals
(
center2
));
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
gesture
.
moveTo
(
const
Offset
(
400.0
,
450.0
));
await
tester
.
pump
();
...
...
@@ -355,8 +339,6 @@ void main() {
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isNotEmpty
);
expect
(
exit2
.
last
.
position
,
equals
(
const
Offset
(
400.0
,
450.0
)));
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
tester
.
pumpWidget
(
Container
());
expect
(
move1
,
isEmpty
);
...
...
@@ -365,8 +347,6 @@ void main() {
expect
(
move2
,
isEmpty
);
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isFalse
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isFalse
);
});
testWidgets
(
'needsCompositing set when parent class needsCompositing is set'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/widgets/mouse_region_test.dart
View file @
da0a7d8b
...
...
@@ -261,7 +261,6 @@ void main() {
onExit:
(
PointerExitEvent
details
)
=>
exit
=
details
,
),
));
final
RenderMouseRegion
renderListener
=
tester
.
renderObject
(
find
.
byType
(
MouseRegion
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
(
location:
Offset
.
zero
);
addTearDown
(
gesture
.
removePointer
);
...
...
@@ -279,7 +278,6 @@ void main() {
expect
(
enter
,
isNull
);
expect
(
move
,
isNull
);
expect
(
exit
,
isNull
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener
.
hoverAnnotation
),
isFalse
);
});
testWidgets
(
'triggers pointer enter when widget moves in'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -415,8 +413,6 @@ void main() {
],
),
);
final
RenderMouseRegion
renderListener1
=
tester
.
renderObject
(
find
.
byKey
(
key1
));
final
RenderMouseRegion
renderListener2
=
tester
.
renderObject
(
find
.
byKey
(
key2
));
Offset
center
=
tester
.
getCenter
(
find
.
byKey
(
key2
));
await
gesture
.
moveTo
(
center
);
await
tester
.
pump
();
...
...
@@ -428,8 +424,6 @@ void main() {
expect
(
enter1
,
isNotEmpty
);
expect
(
enter1
.
last
.
position
,
equals
(
center
));
expect
(
exit1
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
// Now make sure that exiting the child only triggers the child exit, not
...
...
@@ -444,8 +438,6 @@ void main() {
expect
(
move1
.
last
.
position
,
equals
(
center
));
expect
(
enter1
,
isEmpty
);
expect
(
exit1
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
});
...
...
@@ -500,8 +492,6 @@ void main() {
],
),
);
final
RenderMouseRegion
renderListener1
=
tester
.
renderObject
(
find
.
byKey
(
key1
));
final
RenderMouseRegion
renderListener2
=
tester
.
renderObject
(
find
.
byKey
(
key2
));
final
Offset
center1
=
tester
.
getCenter
(
find
.
byKey
(
key1
));
final
Offset
center2
=
tester
.
getCenter
(
find
.
byKey
(
key2
));
await
gesture
.
moveTo
(
center1
);
...
...
@@ -514,8 +504,6 @@ void main() {
expect
(
move2
,
isEmpty
);
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
gesture
.
moveTo
(
center2
);
await
tester
.
pump
();
...
...
@@ -528,8 +516,6 @@ void main() {
expect
(
enter2
,
isNotEmpty
);
expect
(
enter2
.
last
.
position
,
equals
(
center2
));
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
gesture
.
moveTo
(
const
Offset
(
400.0
,
450.0
));
await
tester
.
pump
();
...
...
@@ -540,8 +526,6 @@ void main() {
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isNotEmpty
);
expect
(
exit2
.
last
.
position
,
equals
(
const
Offset
(
400.0
,
450.0
)));
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isTrue
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isTrue
);
clearLists
();
await
tester
.
pumpWidget
(
Container
());
expect
(
move1
,
isEmpty
);
...
...
@@ -550,8 +534,6 @@ void main() {
expect
(
move2
,
isEmpty
);
expect
(
enter2
,
isEmpty
);
expect
(
exit2
,
isEmpty
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener1
.
hoverAnnotation
),
isFalse
);
expect
(
tester
.
binding
.
mouseTracker
.
isAnnotationAttached
(
renderListener2
.
hoverAnnotation
),
isFalse
);
});
testWidgets
(
'MouseRegion uses updated callbacks'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -713,8 +695,8 @@ void main() {
child:
const
MouseRegion
(
opaque:
false
),
),
);
final
RenderMouseRegion
listener
=
tester
.
renderObject
(
find
.
byType
(
MouseRegion
));
expect
(
listener
.
needsCompositing
,
isFalse
);
final
RenderMouseRegion
mouseRegion
=
tester
.
renderObject
(
find
.
byType
(
MouseRegion
));
expect
(
mouseRegion
.
needsCompositing
,
isFalse
);
// No TransformLayer for `Transform.scale` is added because composting is
// not required and therefore the transform is executed on the canvas
// directly. (One TransformLayer is always present for the root
...
...
@@ -731,7 +713,7 @@ void main() {
),
),
);
expect
(
listener
.
needsCompositing
,
isTrue
);
expect
(
mouseRegion
.
needsCompositing
,
isTrue
);
// Compositing is required, therefore a dedicated TransformLayer for
// `Transform.scale` is added.
expect
(
tester
.
layers
.
whereType
<
TransformLayer
>(),
hasLength
(
2
));
...
...
@@ -742,7 +724,7 @@ void main() {
child:
const
MouseRegion
(
opaque:
false
),
),
);
expect
(
listener
.
needsCompositing
,
isFalse
);
expect
(
mouseRegion
.
needsCompositing
,
isFalse
);
// TransformLayer for `Transform.scale` is removed again as transform is
// executed directly on the canvas.
expect
(
tester
.
layers
.
whereType
<
TransformLayer
>(),
hasLength
(
1
));
...
...
@@ -756,7 +738,7 @@ void main() {
),
),
);
expect
(
listener
.
needsCompositing
,
isTrue
);
expect
(
mouseRegion
.
needsCompositing
,
isTrue
);
// Compositing is required, therefore a dedicated TransformLayer for
// `Transform.scale` is added.
expect
(
tester
.
layers
.
whereType
<
TransformLayer
>(),
hasLength
(
2
));
...
...
@@ -767,20 +749,20 @@ void main() {
addTearDown
(
gesture
.
removePointer
);
await
gesture
.
addPointer
(
location:
Offset
.
zero
);
int
numEntr
i
es
=
0
;
int
numEntr
anc
es
=
0
;
int
numExits
=
0
;
await
tester
.
pumpWidget
(
Center
(
child:
HoverFeedback
(
onEnter:
()
=>
numEntries
++
,
onExit:
()
=>
numExits
++
,
onEnter:
()
{
numEntrances
+=
1
;
}
,
onExit:
()
{
numExits
+=
1
;
}
,
)),
);
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
Text
)));
await
tester
.
pumpAndSettle
();
expect
(
numEntr
i
es
,
equals
(
1
));
expect
(
numEntr
anc
es
,
equals
(
1
));
expect
(
numExits
,
equals
(
0
));
expect
(
find
.
text
(
'HOVERING'
),
findsOneWidget
);
...
...
@@ -788,18 +770,18 @@ void main() {
Container
(),
);
await
tester
.
pump
();
expect
(
numEntr
i
es
,
equals
(
1
));
expect
(
numEntr
anc
es
,
equals
(
1
));
expect
(
numExits
,
equals
(
0
));
await
tester
.
pumpWidget
(
Center
(
child:
HoverFeedback
(
onEnter:
()
=>
numEntries
++
,
onExit:
()
=>
numExits
++
,
onEnter:
()
{
numEntrances
+=
1
;
}
,
onExit:
()
{
numExits
+=
1
;
}
,
)),
);
await
tester
.
pump
();
expect
(
numEntr
i
es
,
equals
(
2
));
expect
(
numEntr
anc
es
,
equals
(
2
));
expect
(
numExits
,
equals
(
0
));
});
...
...
@@ -809,41 +791,43 @@ void main() {
await
gesture
.
addPointer
();
addTearDown
(
gesture
.
removePointer
);
int
numEntr
i
es
=
0
;
int
numEntr
anc
es
=
0
;
int
numExits
=
0
;
await
tester
.
pumpWidget
(
Center
(
child:
HoverFeedback
(
key:
feedbackKey
,
onEnter:
()
=>
numEntries
++
,
onExit:
()
=>
numExits
++
,
onEnter:
()
{
numEntrances
+=
1
;
}
,
onExit:
()
{
numExits
+=
1
;
}
,
)),
);
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
Text
)));
await
tester
.
pumpAndSettle
();
expect
(
numEntr
i
es
,
equals
(
1
));
expect
(
numEntr
anc
es
,
equals
(
1
));
expect
(
numExits
,
equals
(
0
));
expect
(
find
.
text
(
'HOVERING'
),
findsOneWidget
);
await
tester
.
pumpWidget
(
Center
(
child:
Container
(
child:
HoverFeedback
(
key:
feedbackKey
,
onEnter:
()
=>
numEntries
++,
onExit:
()
=>
numExits
++,
))),
child:
Container
(
child:
HoverFeedback
(
key:
feedbackKey
,
onEnter:
()
{
numEntrances
+=
1
;
},
onExit:
()
{
numExits
+=
1
;
},
),
),
),
);
await
tester
.
pump
();
expect
(
numEntr
i
es
,
equals
(
1
));
expect
(
numEntr
anc
es
,
equals
(
1
));
expect
(
numExits
,
equals
(
0
));
await
tester
.
pumpWidget
(
Container
(),
);
await
tester
.
pump
();
expect
(
numEntr
i
es
,
equals
(
1
));
expect
(
numEntr
anc
es
,
equals
(
1
));
expect
(
numExits
,
equals
(
0
));
});
...
...
@@ -920,8 +904,8 @@ void main() {
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
onEnter:
(
PointerEnterEvent
e
)
{},
child:
_PaintDelegateWidge
t
(
onPaint:
_VoidDelegate
(()
=>
paintCount
++
),
child:
CustomPain
t
(
painter:
_DelegatedPainter
(
onPaint:
()
{
paintCount
+=
1
;
}
),
child:
const
Text
(
'123'
),
),
),
...
...
@@ -943,8 +927,8 @@ void main() {
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
onEnter:
(
PointerEnterEvent
e
)
{},
child:
_PaintDelegateWidge
t
(
onPaint:
_VoidDelegate
(()
=>
paintCount
++
),
child:
CustomPain
t
(
painter:
_DelegatedPainter
(
onPaint:
()
{
paintCount
+=
1
;
}
),
child:
const
Text
(
'123'
),
),
),
...
...
@@ -1299,12 +1283,12 @@ void main() {
Align
(
alignment:
Alignment
.
topLeft
,
child:
MouseRegion
(
onEnter:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
onHover:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
onExit:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
child:
Container
(
width:
10
,
height:
10
,
onEnter:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
onHover:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
onExit:
(
_
)
{
bottomRegionIsHovered
=
true
;
},
child:
Container
(
width:
10
,
height:
10
,
),
),
),
...
...
@@ -1325,7 +1309,118 @@ void main() {
expect
(
bottomRegionIsHovered
,
isFalse
);
});
testWidgets
(
'RenderMouseRegion
\'
s debugFillProperties when default'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
"Changing MouseRegion's callbacks is effective and doesn't repaint"
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
logs
=
<
String
>[];
const
Key
key
=
ValueKey
<
int
>(
1
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
(
location:
const
Offset
(
20
,
20
));
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pumpWidget
(
_Scaffold
(
topLeft:
Container
(
height:
10
,
width:
10
,
child:
MouseRegion
(
onEnter:
(
_
)
{
logs
.
add
(
'enter1'
);
},
onHover:
(
_
)
{
logs
.
add
(
'hover1'
);
},
onExit:
(
_
)
{
logs
.
add
(
'exit1'
);
},
child:
CustomPaint
(
painter:
_DelegatedPainter
(
onPaint:
()
{
logs
.
add
(
'paint'
);
},
key:
key
),
),
),
),
));
expect
(
logs
,
<
String
>[
'paint'
]);
logs
.
clear
();
await
gesture
.
moveTo
(
const
Offset
(
5
,
5
));
expect
(
logs
,
<
String
>[
'enter1'
,
'hover1'
]);
logs
.
clear
();
await
tester
.
pumpWidget
(
_Scaffold
(
topLeft:
Container
(
height:
10
,
width:
10
,
child:
MouseRegion
(
onEnter:
(
_
)
{
logs
.
add
(
'enter2'
);
},
onHover:
(
_
)
{
logs
.
add
(
'hover2'
);
},
onExit:
(
_
)
{
logs
.
add
(
'exit2'
);
},
child:
CustomPaint
(
painter:
_DelegatedPainter
(
onPaint:
()
{
logs
.
add
(
'paint'
);
},
key:
key
),
),
),
),
));
expect
(
logs
,
isEmpty
);
await
gesture
.
moveTo
(
const
Offset
(
6
,
6
));
expect
(
logs
,
<
String
>[
'hover2'
]);
logs
.
clear
();
// Compare: It repaints if the MouseRegion is unactivated.
await
tester
.
pumpWidget
(
_Scaffold
(
topLeft:
Container
(
height:
10
,
width:
10
,
child:
MouseRegion
(
opaque:
false
,
child:
CustomPaint
(
painter:
_DelegatedPainter
(
onPaint:
()
{
logs
.
add
(
'paint'
);
},
key:
key
),
),
),
),
));
expect
(
logs
,
<
String
>[
'paint'
]);
});
testWidgets
(
'Changing MouseRegion.opaque is effective and repaints'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
logs
=
<
String
>[];
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
(
location:
const
Offset
(
5
,
5
));
addTearDown
(
gesture
.
removePointer
);
final
PointerHoverEventListener
onHover
=
(
_
)
{};
final
VoidCallback
onPaintChild
=
()
{
logs
.
add
(
'paint'
);
};
await
tester
.
pumpWidget
(
_Scaffold
(
topLeft:
Container
(
height:
10
,
width:
10
,
child:
MouseRegion
(
opaque:
true
,
// Dummy callback so that MouseRegion stays affective after opaque
// turns false.
onHover:
onHover
,
child:
CustomPaint
(
painter:
_DelegatedPainter
(
onPaint:
onPaintChild
)),
),
),
background:
MouseRegion
(
onEnter:
(
_
)
{
logs
.
add
(
'hover-enter'
);
})
));
expect
(
logs
,
<
String
>[
'paint'
]);
logs
.
clear
();
expect
(
logs
,
isEmpty
);
logs
.
clear
();
await
tester
.
pumpWidget
(
_Scaffold
(
topLeft:
Container
(
height:
10
,
width:
10
,
child:
MouseRegion
(
opaque:
false
,
onHover:
onHover
,
child:
CustomPaint
(
painter:
_DelegatedPainter
(
onPaint:
onPaintChild
)),
),
),
background:
MouseRegion
(
onEnter:
(
_
)
{
logs
.
add
(
'hover-enter'
);
})
));
expect
(
logs
,
<
String
>[
'paint'
,
'hover-enter'
]);
});
testWidgets
(
"RenderMouseRegion's debugFillProperties when default"
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
RenderMouseRegion
().
debugFillProperties
(
builder
);
...
...
@@ -1339,7 +1434,7 @@ void main() {
]);
});
testWidgets
(
'RenderMouseRegion
\'
s debugFillProperties when full'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
"RenderMouseRegion's debugFillProperties when full"
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
RenderMouseRegion
(
onEnter:
(
PointerEnterEvent
event
)
{},
...
...
@@ -1377,64 +1472,46 @@ void main() {
await
gesture
.
moveBy
(
const
Offset
(
10.0
,
10.0
));
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
});
testWidgets
(
"MouseTracker's attachAnnotation doesn't schedule any frames"
,
(
WidgetTester
tester
)
async
{
// This test is here because MouseTracker can't use testWidgets.
final
MouseTrackerAnnotation
annotation
=
MouseTrackerAnnotation
(
onEnter:
(
PointerEnterEvent
event
)
{},
onHover:
(
PointerHoverEvent
event
)
{},
onExit:
(
PointerExitEvent
event
)
{},
);
RendererBinding
.
instance
.
mouseTracker
.
attachAnnotation
(
annotation
);
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
expect
(
RendererBinding
.
instance
.
mouseTracker
.
isAnnotationAttached
(
annotation
),
isTrue
);
RendererBinding
.
instance
.
mouseTracker
.
detachAnnotation
(
annotation
);
});
}
// This widget allows you to send a callback that is called during `onPaint`.
@immutable
class
_PaintDelegateWidget
extends
SingleChildRenderObjectWidget
{
const
_PaintDelegateWidget
({
Key
key
,
Widget
child
,
this
.
onPaint
,
})
:
super
(
key:
key
,
child:
child
);
final
_VoidDelegate
onPaint
;
// Render widget `topLeft` at the top-left corner, stacking on top of the widget
// `background`.
class
_Scaffold
extends
StatelessWidget
{
const
_Scaffold
({
this
.
topLeft
,
this
.
background
});
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_PaintCallbackObject
(
onPaint:
onPaint
?.
callback
);
}
final
Widget
topLeft
;
final
Widget
background
;
@override
void
updateRenderObject
(
BuildContext
context
,
_PaintCallbackObject
renderObject
)
{
renderObject
..
onPaint
=
onPaint
?.
callback
;
Widget
build
(
BuildContext
context
)
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Stack
(
children:
<
Widget
>[
if
(
background
!=
null
)
background
,
Align
(
alignment:
Alignment
.
topLeft
,
child:
topLeft
,
),
],
),
);
}
}
class
_VoidDelegate
{
_VoidDelegate
(
this
.
callback
);
void
Function
()
callback
;
}
class
_PaintCallbackObject
extends
RenderProxyBox
{
_PaintCallbackObject
({
RenderBox
child
,
this
.
onPaint
,
})
:
super
(
child
);
void
Function
()
onPaint
;
class
_DelegatedPainter
extends
CustomPainter
{
_DelegatedPainter
({
this
.
key
,
this
.
onPaint
});
final
Key
key
;
final
VoidCallback
onPaint
;
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
onPaint
!=
null
)
{
onPaint
();
}
super
.
paint
(
context
,
offset
);
void
paint
(
Canvas
canvas
,
Size
size
)
{
onPaint
();
}
@override
bool
shouldRepaint
(
CustomPainter
oldDelegate
)
=>
!(
oldDelegate
is
_DelegatedPainter
&&
key
==
oldDelegate
.
key
);
}
class
_HoverClientWithClosures
extends
StatefulWidget
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment