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 {
/// {@macro flutter.material.inkwell.mousecursor}
///
/// Defaults to null.
/// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor;
/// Defines the default text style, with [Material.textStyle], for the
......
......@@ -380,7 +380,7 @@ class FloatingActionButton extends StatelessWidget {
/// {@macro flutter.material.inkwell.mousecursor}
///
/// Defaults to null.
/// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor;
/// {@macro flutter.widgets.Clip}
......
......@@ -365,12 +365,9 @@ class InkResponse extends StatelessWidget {
/// {@template flutter.material.inkwell.mousecursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// 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}
///
/// Defaults to null.
/// If the property is null, [SystemMouseCursor.click] is used.
final MouseCursor mouseCursor;
/// Whether this ink response should be clipped its bounds.
......@@ -555,7 +552,7 @@ class InkResponse extends StatelessWidget {
onLongPress: onLongPress,
onHighlightChanged: onHighlightChanged,
onHover: onHover,
mouseCursor: mouseCursor,
mouseCursor: mouseCursor ?? SystemMouseCursors.click,
containedInkWell: containedInkWell,
highlightShape: highlightShape,
radius: radius,
......
......@@ -44,18 +44,16 @@ mixin MouseTrackerCursorMixin on BaseMouseTracker {
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
// visual order from front the back.
// The return value is never null.
MouseCursor _findFirstCursor(LinkedHashSet<MouseTrackerAnnotation> annotations) {
for (final MouseTrackerAnnotation annotation in annotations) {
if (annotation.cursor != null) {
return annotation.cursor;
}
}
return SystemMouseCursors.basic;
return _DeferringMouseCursor.firstNonDeferred(
annotations.map((MouseTrackerAnnotation annotation) => annotation.cursor),
) ?? SystemMouseCursors.basic;
}
// Handles device update and changes mouse cursors.
......@@ -219,21 +217,68 @@ abstract class MouseCursor with Diagnosticable {
/// A very short description of the mouse cursor.
///
/// The [debugDescription] shoule be a few words that can differentiate
/// instances of a class to make debug information more readable. For example,
/// a [SystemMouseCursor] class with description "drag" will be printed as
/// "SystemMouseCursor(drag)".
/// The [debugDescription] shoule be a few words that can describe this cursor
/// to make debug information more readable. It is returned as the [toString]
/// when the diagnostic level is at or above [DiagnosticLevel.info].
///
/// The [debugDescription] must not be null, but can be an empty string.
/// The [debugDescription] must not be null or empty string.
String get debugDescription;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
final String debugDescription = this.debugDescription;
if (minLevel.index >= DiagnosticLevel.info.index && debugDescription != null)
return '$runtimeType($debugDescription)';
return debugDescription;
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 {
......@@ -252,9 +297,9 @@ class _NoopMouseCursorSession extends MouseCursorSession {
/// 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
/// 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.
class _NoopMouseCursor extends MouseCursor {
// Application code shouldn't directly instantiate this class, since its only
......@@ -265,9 +310,8 @@ class _NoopMouseCursor extends MouseCursor {
@protected
_NoopMouseCursorSession createSession(int device) => _NoopMouseCursorSession(this, device);
// The [debugDescription] is '' so that its toString() returns 'NoopMouseCursor()'.
@override
String get debugDescription => '';
String get debugDescription => 'uncontrolled';
}
class _SystemMouseCursorSession extends MouseCursorSession {
......@@ -322,7 +366,7 @@ class SystemMouseCursor extends MouseCursor {
final String kind;
@override
String get debugDescription => kind;
String get debugDescription => '$runtimeType($kind)';
@override
@protected
......@@ -364,22 +408,6 @@ class SystemMouseCursors {
// extended.
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.
///
/// Any cursor other than [none] or [uncontrolled] unhides the cursor.
......
......@@ -48,12 +48,14 @@ typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
/// * [BaseMouseTracker], which uses [MouseTrackerAnnotation].
class MouseTrackerAnnotation with Diagnosticable {
/// Creates an immutable [MouseTrackerAnnotation].
///
/// All arguments are optional. The [cursor] must not be null.
const MouseTrackerAnnotation({
this.onEnter,
this.onHover,
this.onExit,
this.cursor,
});
this.cursor = MouseCursor.defer,
}) : assert(cursor != null);
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// entered the region.
......@@ -94,15 +96,14 @@ class MouseTrackerAnnotation with Diagnosticable {
/// certain cases and does not always match its earier [MouseRegion.onEnter].
final PointerExitEventListener onExit;
/// The mouse cursor for mouse pointers that are hovering over the annotated
/// region.
/// The mouse cursor for mouse pointers that are hovering over the 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
/// [cursor]. If the [cursor] is null, then the annotated region does not
/// 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.
/// Defaults to [MouseCursor.defer], deferring the choice of cursor to the next
/// region behing it in hit-test order.
///
/// See also:
///
......@@ -121,7 +122,7 @@ class MouseTrackerAnnotation with Diagnosticable {
},
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 {
if (_handlePointerEvent != null)
_handlePointerEvent(event);
},
cursor: SystemMouseCursors.uncontrolled,
cursor: MouseCursor.uncontrolled,
);
}
MouseTrackerAnnotation _cachedHoverAnnotation;
......
......@@ -2682,15 +2682,17 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
/// 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.
/// mouse region with no callbacks and cursor being [MouseCursor.defer]. The
/// [cursor] must not be null.
RenderMouseRegion({
PointerEnterEventListener onEnter,
PointerHoverEventListener onHover,
PointerExitEventListener onExit,
MouseCursor cursor,
MouseCursor cursor = MouseCursor.defer,
bool opaque = true,
RenderBox child,
}) : assert(opaque != null),
assert(cursor != null),
_onEnter = onEnter,
_onHover = onHover,
_onExit = onExit,
......@@ -2781,7 +2783,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
_onEnter != null ||
_onHover != null ||
_onExit != null ||
_cursor != null ||
_cursor != MouseCursor.defer ||
opaque
) && RendererBinding.instance.mouseTracker.mouseIsConnected;
_setAnnotationIsActive(newAnnotationIsActive);
......@@ -2853,7 +2855,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
},
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));
}
}
......
......@@ -5880,17 +5880,18 @@ class _PointerListener extends SingleChildRenderObjectWidget {
class MouseRegion extends StatefulWidget {
/// Creates a widget that forwards mouse events to callbacks.
///
/// By default, all callbacks are empty, `cursor` is unset, and `opaque` is
/// `true`.
/// By default, all callbacks are empty, [cursor] is [MouseCursor.defer], and
/// [opaque] is true. The [cursor] must not be null.
const MouseRegion({
Key key,
this.onEnter,
this.onExit,
this.onHover,
this.cursor,
this.cursor = MouseCursor.defer,
this.opaque = true,
this.child,
}) : assert(opaque != null),
}) : assert(cursor != null),
assert(opaque != null),
super(key: key);
/// Triggered when a mouse pointer has entered this widget.
......@@ -6099,15 +6100,14 @@ class MouseRegion extends StatefulWidget {
/// this callback is internally implemented, but without the restriction.
final PointerExitEventListener onExit;
/// The mouse cursor for mouse pointers that are hovering over the annotated
/// region.
/// The mouse cursor for mouse pointers that are hovering over the region.
///
/// 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
/// 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;
/// Whether this widget should prevent other [MouseRegion]s visually behind it
......
......@@ -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>[];
List<MouseTrackerAnnotation> annotations;
_setUpMouseTracker(
......@@ -249,7 +249,7 @@ void main() {
);
annotations = <MouseTrackerAnnotation>[
const MouseTrackerAnnotation(cursor: null),
const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.click),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing),
];
......@@ -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>[];
List<MouseTrackerAnnotation> annotations;
_setUpMouseTracker(
......@@ -277,8 +277,8 @@ void main() {
);
annotations = <MouseTrackerAnnotation>[
const MouseTrackerAnnotation(cursor: null),
const MouseTrackerAnnotation(cursor: null),
const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: MouseCursor.defer),
const MouseTrackerAnnotation(cursor: SystemMouseCursors.grabbing),
];
ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
......
......@@ -1533,7 +1533,7 @@ void main() {
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: MouseRegion(
cursor: null,
cursor: MouseCursor.defer,
onEnter: (_) { logEnters.add('enter'); },
child: CustomPaint(painter: _DelegatedPainter(onPaint: onPaintChild)),
),
......@@ -1673,6 +1673,7 @@ void main() {
onEnter: (PointerEnterEvent event) {},
onExit: (PointerExitEvent event) {},
onHover: (PointerHoverEvent event) {},
cursor: SystemMouseCursors.click,
child: RenderErrorBox(),
).debugFillProperties(builder);
......@@ -1683,6 +1684,7 @@ void main() {
'constraints: MISSING',
'size: MISSING',
'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