Unverified Commit 5fa1c60b authored by Tong Mu's avatar Tong Mu Committed by GitHub

MouseCursor uses a special class instead of null to defer (#57094)

* Uses a special cursor value MouseCursor.defer to mark deferring. MouseTrackerAnnotation.cursor is now non-null. The mouseCursor argument of widgets or render objects can be null, which indicates using the default value.
* Moves SystemMouseCursors.uncontrolled to MouseCursor.uncontrolled.
* Changes how MouseCursor.debugDescription is defined. Previously MouseCursor.toString returns $runtimeType($debugDescription), while now it returns $debugDescription. Implementations of classes are updated, except for the ones of MouseCursor.defer and MouseCursor.uncontrolled are simply "defer" and "uncontrolled".
parent c81449e5
...@@ -105,7 +105,7 @@ class RawMaterialButton extends StatefulWidget { ...@@ -105,7 +105,7 @@ class RawMaterialButton extends StatefulWidget {
/// {@macro flutter.material.inkwell.mousecursor} /// {@macro flutter.material.inkwell.mousecursor}
/// ///
/// Defaults to null. /// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor; final MouseCursor mouseCursor;
/// Defines the default text style, with [Material.textStyle], for the /// Defines the default text style, with [Material.textStyle], for the
......
...@@ -380,7 +380,7 @@ class FloatingActionButton extends StatelessWidget { ...@@ -380,7 +380,7 @@ class FloatingActionButton extends StatelessWidget {
/// {@macro flutter.material.inkwell.mousecursor} /// {@macro flutter.material.inkwell.mousecursor}
/// ///
/// Defaults to null. /// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor; final MouseCursor mouseCursor;
/// {@macro flutter.widgets.Clip} /// {@macro flutter.widgets.Clip}
......
...@@ -365,12 +365,9 @@ class InkResponse extends StatelessWidget { ...@@ -365,12 +365,9 @@ class InkResponse extends StatelessWidget {
/// {@template flutter.material.inkwell.mousecursor} /// {@template flutter.material.inkwell.mousecursor}
/// The cursor for a mouse pointer when it enters or is hovering over the /// The cursor for a mouse pointer when it enters or is hovering over the
/// region. /// region.
///
/// If the [mouseCursor] is null, then the hovering pointer's cursor will be
/// decided by the widget behind it on the screen in hit-test order.
/// {@endtemplate} /// {@endtemplate}
/// ///
/// Defaults to null. /// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor; final MouseCursor mouseCursor;
/// Whether this ink response should be clipped its bounds. /// Whether this ink response should be clipped its bounds.
...@@ -555,7 +552,7 @@ class InkResponse extends StatelessWidget { ...@@ -555,7 +552,7 @@ class InkResponse extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
onHover: onHover, onHover: onHover,
mouseCursor: mouseCursor, mouseCursor: mouseCursor ?? SystemMouseCursors.click,
containedInkWell: containedInkWell, containedInkWell: containedInkWell,
highlightShape: highlightShape, highlightShape: highlightShape,
radius: radius, radius: radius,
......
...@@ -44,18 +44,16 @@ mixin MouseTrackerCursorMixin on BaseMouseTracker { ...@@ -44,18 +44,16 @@ mixin MouseTrackerCursorMixin on BaseMouseTracker {
final Map<int, MouseCursorSession> _lastSession = <int, MouseCursorSession>{}; final Map<int, MouseCursorSession> _lastSession = <int, MouseCursorSession>{};
// Find the mouse cursor, which fallbacks to SystemMouseCursors.basic. // Find the first non-deferred mouse cursor, which fallbacks to
// [SystemMouseCursors.basic].
// //
// The `annotations` is the current annotations that the device is hovering in // The `annotations` is the current annotations that the device is hovering in
// visual order from front the back. // visual order from front the back.
// The return value is never null. // The return value is never null.
MouseCursor _findFirstCursor(LinkedHashSet<MouseTrackerAnnotation> annotations) { MouseCursor _findFirstCursor(LinkedHashSet<MouseTrackerAnnotation> annotations) {
for (final MouseTrackerAnnotation annotation in annotations) { return _DeferringMouseCursor.firstNonDeferred(
if (annotation.cursor != null) { annotations.map((MouseTrackerAnnotation annotation) => annotation.cursor),
return annotation.cursor; ) ?? SystemMouseCursors.basic;
}
}
return SystemMouseCursors.basic;
} }
// Handles device update and changes mouse cursors. // Handles device update and changes mouse cursors.
...@@ -219,21 +217,68 @@ abstract class MouseCursor with Diagnosticable { ...@@ -219,21 +217,68 @@ abstract class MouseCursor with Diagnosticable {
/// A very short description of the mouse cursor. /// A very short description of the mouse cursor.
/// ///
/// The [debugDescription] shoule be a few words that can differentiate /// The [debugDescription] shoule be a few words that can describe this cursor
/// instances of a class to make debug information more readable. For example, /// to make debug information more readable. It is returned as the [toString]
/// a [SystemMouseCursor] class with description "drag" will be printed as /// when the diagnostic level is at or above [DiagnosticLevel.info].
/// "SystemMouseCursor(drag)".
/// ///
/// The [debugDescription] must not be null, but can be an empty string. /// The [debugDescription] must not be null or empty string.
String get debugDescription; String get debugDescription;
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
final String debugDescription = this.debugDescription; final String debugDescription = this.debugDescription;
if (minLevel.index >= DiagnosticLevel.info.index && debugDescription != null) if (minLevel.index >= DiagnosticLevel.info.index && debugDescription != null)
return '$runtimeType($debugDescription)'; return debugDescription;
return super.toString(minLevel: minLevel); return super.toString(minLevel: minLevel);
} }
/// A special class that indicates that the region with this cursor defers the
/// choice of cursor to the next region behind it.
///
/// When an event occurs, [MouseTracker] will update each pointer's cursor by
/// finding the list of regions that contain the pointer's location, from front
/// to back in hit-test order. The pointer's cursor will be the first cursor in
/// the list that is not a [MouseCursor.defer].
static const MouseCursor defer = _DeferringMouseCursor._();
/// A special value that doesn't change cursor by itself, but make a region
/// that blocks other regions behind it from changing the cursor.
///
/// When a pointer enters a region with a cursor of [uncontrolled], the pointer
/// retains its previous cursor and keeps so until it moves out of the region.
/// Technically, this region absorb the mouse cursor hit test without changing
/// the pointer's cursor.
///
/// This is useful in a region that displays a platform view, which let the
/// operating system handle pointer events and change cursors accordingly. To
/// achieve this, the region's cursor must not be any Flutter cursor, since
/// that might overwrite the system request upon pointer entering; the cursor
/// must not be null either, since that allows the widgets behind the region to
/// change cursors.
static const MouseCursor uncontrolled = _NoopMouseCursor._();
}
class _DeferringMouseCursor extends MouseCursor {
const _DeferringMouseCursor._();
@override
MouseCursorSession createSession(int device) {
assert(false, '_DeferringMouseCursor can not create a session');
throw UnimplementedError();
}
@override
String get debugDescription => 'defer';
/// Returns the first cursor that is not a [MouseCursor.defer].
static MouseCursor firstNonDeferred(Iterable<MouseCursor> cursors) {
for (final MouseCursor cursor in cursors) {
assert(cursor != null);
if (cursor != MouseCursor.defer)
return cursor;
}
return null;
}
} }
class _NoopMouseCursorSession extends MouseCursorSession { class _NoopMouseCursorSession extends MouseCursorSession {
...@@ -252,9 +297,9 @@ class _NoopMouseCursorSession extends MouseCursorSession { ...@@ -252,9 +297,9 @@ class _NoopMouseCursorSession extends MouseCursorSession {
/// Although setting a region's cursor to [NoopMouseCursor] doesn't change the /// Although setting a region's cursor to [NoopMouseCursor] doesn't change the
/// cursor, it blocks regions behind it from changing the cursor, in contrast to /// cursor, it blocks regions behind it from changing the cursor, in contrast to
/// setting the cursor to null. More information about the usage of this class /// setting the cursor to null. More information about the usage of this class
/// can be found at [SystemMouseCursors.uncontrolled]. /// can be found at [MouseCursors.uncontrolled].
/// ///
/// To use this class, use [SystemMouseCursors.uncontrolled]. Directly /// To use this class, use [MouseCursors.uncontrolled]. Directly
/// instantiating this class is not allowed. /// instantiating this class is not allowed.
class _NoopMouseCursor extends MouseCursor { class _NoopMouseCursor extends MouseCursor {
// Application code shouldn't directly instantiate this class, since its only // Application code shouldn't directly instantiate this class, since its only
...@@ -265,9 +310,8 @@ class _NoopMouseCursor extends MouseCursor { ...@@ -265,9 +310,8 @@ class _NoopMouseCursor extends MouseCursor {
@protected @protected
_NoopMouseCursorSession createSession(int device) => _NoopMouseCursorSession(this, device); _NoopMouseCursorSession createSession(int device) => _NoopMouseCursorSession(this, device);
// The [debugDescription] is '' so that its toString() returns 'NoopMouseCursor()'.
@override @override
String get debugDescription => ''; String get debugDescription => 'uncontrolled';
} }
class _SystemMouseCursorSession extends MouseCursorSession { class _SystemMouseCursorSession extends MouseCursorSession {
...@@ -322,7 +366,7 @@ class SystemMouseCursor extends MouseCursor { ...@@ -322,7 +366,7 @@ class SystemMouseCursor extends MouseCursor {
final String kind; final String kind;
@override @override
String get debugDescription => kind; String get debugDescription => '$runtimeType($kind)';
@override @override
@protected @protected
...@@ -364,22 +408,6 @@ class SystemMouseCursors { ...@@ -364,22 +408,6 @@ class SystemMouseCursors {
// extended. // extended.
factory SystemMouseCursors._() => null; factory SystemMouseCursors._() => null;
/// A special value that doesn't change cursor by itself, but make a region
/// that blocks other regions behind it from changing the cursor.
///
/// When a pointer enters a region with a cursor of [uncontrolled], the pointer
/// retains its previous cursor and keeps so until it moves out of the region.
/// Technically, this region absorb the mouse cursor hit test without changing
/// the pointer's cursor.
///
/// This is useful in a region that displays a platform view, which let the
/// operating system handle pointer events and change cursors accordingly. To
/// achieve this, the region's cursor must not be any Flutter cursor, since
/// that might overwrite the system request upon pointer entering; the cursor
/// must not be null either, since that allows the widgets behind the region to
/// change cursors.
static const MouseCursor uncontrolled = _NoopMouseCursor._();
/// Hide the cursor. /// Hide the cursor.
/// ///
/// Any cursor other than [none] or [uncontrolled] unhides the cursor. /// Any cursor other than [none] or [uncontrolled] unhides the cursor.
......
...@@ -48,12 +48,14 @@ typedef PointerHoverEventListener = void Function(PointerHoverEvent event); ...@@ -48,12 +48,14 @@ typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
/// * [BaseMouseTracker], which uses [MouseTrackerAnnotation]. /// * [BaseMouseTracker], which uses [MouseTrackerAnnotation].
class MouseTrackerAnnotation with Diagnosticable { class MouseTrackerAnnotation with Diagnosticable {
/// Creates an immutable [MouseTrackerAnnotation]. /// Creates an immutable [MouseTrackerAnnotation].
///
/// All arguments are optional. The [cursor] must not be null.
const MouseTrackerAnnotation({ const MouseTrackerAnnotation({
this.onEnter, this.onEnter,
this.onHover, this.onHover,
this.onExit, this.onExit,
this.cursor, this.cursor = MouseCursor.defer,
}); }) : assert(cursor != null);
/// Triggered when a mouse pointer, with or without buttons pressed, has /// Triggered when a mouse pointer, with or without buttons pressed, has
/// entered the region. /// entered the region.
...@@ -94,15 +96,14 @@ class MouseTrackerAnnotation with Diagnosticable { ...@@ -94,15 +96,14 @@ class MouseTrackerAnnotation with Diagnosticable {
/// certain cases and does not always match its earier [MouseRegion.onEnter]. /// certain cases and does not always match its earier [MouseRegion.onEnter].
final PointerExitEventListener onExit; final PointerExitEventListener onExit;
/// The mouse cursor for mouse pointers that are hovering over the annotated /// The mouse cursor for mouse pointers that are hovering over the region.
/// region. ///
/// When a mouse enters the region, its cursor will be changed to the [cursor].
/// When the mouse leaves the region, the cursor will be set by the region
/// found at the new location.
/// ///
/// When a mouse enters the annotated region, its cursor will be changed to the /// Defaults to [MouseCursor.defer], deferring the choice of cursor to the next
/// [cursor]. If the [cursor] is null, then the annotated region does not /// region behing it in hit-test order.
/// control cursors, but defers the choice to the next annotation behind this
/// one on the screen in hit-test order, or [SystemMouseCursors.basic] if no
/// others can be found. When the mouse leaves the region, the cursor will be
/// set by the region found at the new location.
/// ///
/// See also: /// See also:
/// ///
...@@ -121,7 +122,7 @@ class MouseTrackerAnnotation with Diagnosticable { ...@@ -121,7 +122,7 @@ class MouseTrackerAnnotation with Diagnosticable {
}, },
ifEmpty: '<none>', ifEmpty: '<none>',
)); ));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: null)); properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
} }
} }
......
...@@ -795,7 +795,7 @@ mixin _PlatformViewGestureMixin on RenderBox { ...@@ -795,7 +795,7 @@ mixin _PlatformViewGestureMixin on RenderBox {
if (_handlePointerEvent != null) if (_handlePointerEvent != null)
_handlePointerEvent(event); _handlePointerEvent(event);
}, },
cursor: SystemMouseCursors.uncontrolled, cursor: MouseCursor.uncontrolled,
); );
} }
MouseTrackerAnnotation _cachedHoverAnnotation; MouseTrackerAnnotation _cachedHoverAnnotation;
......
...@@ -2682,15 +2682,17 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2682,15 +2682,17 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
/// Creates a render object that forwards pointer events to callbacks. /// Creates a render object that forwards pointer events to callbacks.
/// ///
/// All parameters are optional. By default this method creates an opaque /// All parameters are optional. By default this method creates an opaque
/// mouse region with no callbacks. /// mouse region with no callbacks and cursor being [MouseCursor.defer]. The
/// [cursor] must not be null.
RenderMouseRegion({ RenderMouseRegion({
PointerEnterEventListener onEnter, PointerEnterEventListener onEnter,
PointerHoverEventListener onHover, PointerHoverEventListener onHover,
PointerExitEventListener onExit, PointerExitEventListener onExit,
MouseCursor cursor, MouseCursor cursor = MouseCursor.defer,
bool opaque = true, bool opaque = true,
RenderBox child, RenderBox child,
}) : assert(opaque != null), }) : assert(opaque != null),
assert(cursor != null),
_onEnter = onEnter, _onEnter = onEnter,
_onHover = onHover, _onHover = onHover,
_onExit = onExit, _onExit = onExit,
...@@ -2781,7 +2783,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2781,7 +2783,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
_onEnter != null || _onEnter != null ||
_onHover != null || _onHover != null ||
_onExit != null || _onExit != null ||
_cursor != null || _cursor != MouseCursor.defer ||
opaque opaque
) && RendererBinding.instance.mouseTracker.mouseIsConnected; ) && RendererBinding.instance.mouseTracker.mouseIsConnected;
_setAnnotationIsActive(newAnnotationIsActive); _setAnnotationIsActive(newAnnotationIsActive);
...@@ -2853,7 +2855,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2853,7 +2855,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
}, },
ifEmpty: '<none>', ifEmpty: '<none>',
)); ));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: null)); properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true)); properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
} }
} }
......
...@@ -5880,17 +5880,18 @@ class _PointerListener extends SingleChildRenderObjectWidget { ...@@ -5880,17 +5880,18 @@ class _PointerListener extends SingleChildRenderObjectWidget {
class MouseRegion extends StatefulWidget { class MouseRegion extends StatefulWidget {
/// Creates a widget that forwards mouse events to callbacks. /// Creates a widget that forwards mouse events to callbacks.
/// ///
/// By default, all callbacks are empty, `cursor` is unset, and `opaque` is /// By default, all callbacks are empty, [cursor] is [MouseCursor.defer], and
/// `true`. /// [opaque] is true. The [cursor] must not be null.
const MouseRegion({ const MouseRegion({
Key key, Key key,
this.onEnter, this.onEnter,
this.onExit, this.onExit,
this.onHover, this.onHover,
this.cursor, this.cursor = MouseCursor.defer,
this.opaque = true, this.opaque = true,
this.child, this.child,
}) : assert(opaque != null), }) : assert(cursor != null),
assert(opaque != null),
super(key: key); super(key: key);
/// Triggered when a mouse pointer has entered this widget. /// Triggered when a mouse pointer has entered this widget.
...@@ -6099,15 +6100,14 @@ class MouseRegion extends StatefulWidget { ...@@ -6099,15 +6100,14 @@ class MouseRegion extends StatefulWidget {
/// this callback is internally implemented, but without the restriction. /// this callback is internally implemented, but without the restriction.
final PointerExitEventListener onExit; final PointerExitEventListener onExit;
/// The mouse cursor for mouse pointers that are hovering over the annotated /// The mouse cursor for mouse pointers that are hovering over the region.
/// region.
/// ///
/// When a mouse enters the region, its cursor will be changed to the [cursor]. /// When a mouse enters the region, its cursor will be changed to the [cursor].
/// The [cursor] defaults to null, meaning the region does not control cursors,
/// but defers the choice to the next region behind this one on the screen in
/// hit-test order, or [SystemMouseCursors.basic] if no others can be found.
/// When the mouse leaves the region, the cursor will be decided by the region /// When the mouse leaves the region, the cursor will be decided by the region
/// found at the new location. /// found at the new location.
///
/// The [cursor] defaults to [MouseCursor.defer], deferring the choice of
/// cursor to the next region behing it in hit-test order.
final MouseCursor cursor; final MouseCursor cursor;
/// Whether this widget should prevent other [MouseRegion]s visually behind it /// Whether this widget should prevent other [MouseRegion]s visually behind it
......
...@@ -240,7 +240,7 @@ void main() { ...@@ -240,7 +240,7 @@ void main() {
]); ]);
}); });
test('The first annotation with non-null cursor is used', () { test('The first annotation with non-deferring cursor is used', () {
final List<_CursorUpdateDetails> logCursors = <_CursorUpdateDetails>[]; final List<_CursorUpdateDetails> logCursors = <_CursorUpdateDetails>[];
List<MouseTrackerAnnotation> annotations; List<MouseTrackerAnnotation> annotations;
_setUpMouseTracker( _setUpMouseTracker(
...@@ -249,7 +249,7 @@ void main() { ...@@ -249,7 +249,7 @@ void main() {
); );
annotations = <MouseTrackerAnnotation>[ annotations = <MouseTrackerAnnotation>[
const MouseTrackerAnnotation(cursor: null), const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.click), const MouseTrackerAnnotation(cursor: SystemMouseCursors.click),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing), const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing),
]; ];
...@@ -268,7 +268,7 @@ void main() { ...@@ -268,7 +268,7 @@ void main() {
])); ]));
}); });
test('Annotations with null cursors are ignored', () { test('Annotations with deferring cursors are ignored', () {
final List<_CursorUpdateDetails> logCursors = <_CursorUpdateDetails>[]; final List<_CursorUpdateDetails> logCursors = <_CursorUpdateDetails>[];
List<MouseTrackerAnnotation> annotations; List<MouseTrackerAnnotation> annotations;
_setUpMouseTracker( _setUpMouseTracker(
...@@ -277,8 +277,8 @@ void main() { ...@@ -277,8 +277,8 @@ void main() {
); );
annotations = <MouseTrackerAnnotation>[ annotations = <MouseTrackerAnnotation>[
const MouseTrackerAnnotation(cursor: null), const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: null), const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing), const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing),
]; ];
ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[ ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
......
...@@ -1533,7 +1533,7 @@ void main() { ...@@ -1533,7 +1533,7 @@ void main() {
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.forbidden, cursor: SystemMouseCursors.forbidden,
child: MouseRegion( child: MouseRegion(
cursor: null, cursor: MouseCursor.defer,
onEnter: (_) { logEnters.add('enter'); }, onEnter: (_) { logEnters.add('enter'); },
child: CustomPaint(painter: _DelegatedPainter(onPaint: onPaintChild)), child: CustomPaint(painter: _DelegatedPainter(onPaint: onPaintChild)),
), ),
...@@ -1673,6 +1673,7 @@ void main() { ...@@ -1673,6 +1673,7 @@ void main() {
onEnter: (PointerEnterEvent event) {}, onEnter: (PointerEnterEvent event) {},
onExit: (PointerExitEvent event) {}, onExit: (PointerExitEvent event) {},
onHover: (PointerHoverEvent event) {}, onHover: (PointerHoverEvent event) {},
cursor: SystemMouseCursors.click,
child: RenderErrorBox(), child: RenderErrorBox(),
).debugFillProperties(builder); ).debugFillProperties(builder);
...@@ -1683,6 +1684,7 @@ void main() { ...@@ -1683,6 +1684,7 @@ void main() {
'constraints: MISSING', 'constraints: MISSING',
'size: MISSING', 'size: MISSING',
'listeners: enter, hover, exit', 'listeners: enter, hover, exit',
'cursor: SystemMouseCursor(click)',
]); ]);
}); });
......
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