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

Refactor: Move mouse cursor classes to "services" package (#77751)

parent 95d3ac70
......@@ -46,8 +46,7 @@ export 'src/rendering/layer.dart';
export 'src/rendering/layout_helper.dart';
export 'src/rendering/list_body.dart';
export 'src/rendering/list_wheel_viewport.dart';
export 'src/rendering/mouse_cursor.dart';
export 'src/rendering/mouse_tracking.dart';
export 'src/rendering/mouse_tracker.dart';
export 'src/rendering/object.dart';
export 'src/rendering/paragraph.dart';
export 'src/rendering/performance_overlay.dart';
......
......@@ -22,6 +22,8 @@ export 'src/services/keyboard_key.dart';
export 'src/services/keyboard_maps.dart';
export 'src/services/message_codec.dart';
export 'src/services/message_codecs.dart';
export 'src/services/mouse_cursor.dart';
export 'src/services/mouse_tracking.dart';
export 'src/services/platform_channel.dart';
export 'src/services/platform_views.dart';
export 'src/services/raw_keyboard.dart';
......
......@@ -4,6 +4,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
/// Interactive states that some of the Material widgets can take on when
/// receiving input from the user.
......
......@@ -12,7 +12,7 @@ import 'package:flutter/services.dart';
import 'box.dart';
import 'debug.dart';
import 'mouse_tracking.dart';
import 'mouse_tracker.dart';
import 'object.dart';
import 'view.dart';
......
......@@ -7,131 +7,23 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart' show Matrix4;
import 'mouse_cursor.dart';
import 'object.dart';
/// Signature for listening to [PointerEnterEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerEnterEventListener = void Function(PointerEnterEvent event);
/// Signature for listening to [PointerExitEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerExitEventListener = void Function(PointerExitEvent event);
/// Signature for listening to [PointerHoverEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
/// The annotation object used to annotate regions that are interested in mouse
/// movements.
///
/// To use an annotation, push it with [AnnotatedRegionLayer] during painting.
/// The annotation's callbacks or configurations will be used depending on the
/// relationship between annotations and mouse pointers.
///
/// A [RenderObject] who uses this class must not dispose this class in its
/// `detach`, even if it recreates a new one in `attach`, because the object
/// might be detached and attached during the same frame during a reparent, and
/// replacing the `MouseTrackerAnnotation` will cause an unnecessary `onExit` and
/// `onEnter`.
///
/// This class is also the type parameter of the annotation search started by
/// [BaseMouseTracker].
///
/// See also:
///
/// * [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.onExit,
this.cursor = MouseCursor.defer,
this.validForMouseTracker = true,
}) : assert(cursor != null);
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// entered the region and [validForMouseTracker] is true.
///
/// This callback is triggered when the pointer has started to be contained by
/// the region, either due to a pointer event, or due to the movement or
/// disappearance of the region. This method is always matched by a later
/// [onExit].
///
/// See also:
///
/// * [onExit], which is triggered when a mouse pointer exits the region.
/// * [MouseRegion.onEnter], which uses this callback.
final PointerEnterEventListener? onEnter;
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// exited the region and [validForMouseTracker] is true.
///
/// This callback is triggered when the pointer has stopped being contained
/// by the region, either due to a pointer event, or due to the movement or
/// disappearance of the region. This method always matches an earlier
/// [onEnter].
///
/// See also:
///
/// * [onEnter], which is triggered when a mouse pointer enters the region.
/// * [MouseRegion.onExit], which uses this callback, but is not triggered in
/// certain cases and does not always match its earlier [MouseRegion.onEnter].
final PointerExitEventListener? onExit;
/// 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.
///
/// Defaults to [MouseCursor.defer], deferring the choice of cursor to the next
/// region behind it in hit-test order.
///
/// See also:
///
/// * [MouseRegion.cursor], which provide values to this field.
final MouseCursor cursor;
/// Whether this is included when [MouseTracker] collects the list of annotations.
///
/// If [validForMouseTracker] is false, this object is excluded from the current annotation list
/// even if it's included in the hit test, affecting mouse-related behavior such as enter events,
/// exit events, and mouse cursors. The [validForMouseTracker] does not affect hit testing.
///
/// The [validForMouseTracker] is true for [MouseTrackerAnnotation]s built by the constructor.
final bool validForMouseTracker;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagsSummary<Function?>(
'callbacks',
<String, Function?> {
'enter': onEnter,
'exit': onExit,
},
ifEmpty: '<none>',
));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
}
}
export 'package:flutter/services.dart' show
MouseCursor,
SystemMouseCursors;
/// Signature for searching for [MouseTrackerAnnotation]s at the given offset.
///
/// It is used by the [BaseMouseTracker] to fetch annotations for the mouse
/// It is used by the [MouseTracker] to fetch annotations for the mouse
/// position.
typedef MouseDetectorAnnotationFinder = HitTestResult Function(Offset offset);
// Various states of a connected mouse device used by [BaseMouseTracker].
// Various states of a connected mouse device used by [MouseTracker].
class _MouseState {
_MouseState({
required PointerEvent initialEvent,
......@@ -173,18 +65,18 @@ class _MouseState {
}
}
/// Used by [BaseMouseTracker] to provide the details of an update of a mouse
/// device.
///
/// This class contains the information needed to handle the update that might
/// change the state of a mouse device, or the [MouseTrackerAnnotation]s that
/// the mouse device is hovering.
// The information in `MouseTracker._handleDeviceUpdate` to provide the details
// of an update of a mouse device.
//
// This class contains the information needed to handle the update that might
// change the state of a mouse device, or the [MouseTrackerAnnotation]s that
// the mouse device is hovering.
@immutable
class MouseTrackerUpdateDetails with Diagnosticable {
class _MouseTrackerUpdateDetails with Diagnosticable {
/// When device update is triggered by a new frame.
///
/// All parameters are required.
const MouseTrackerUpdateDetails.byNewFrame({
const _MouseTrackerUpdateDetails.byNewFrame({
required this.lastAnnotations,
required this.nextAnnotations,
required PointerEvent this.previousEvent,
......@@ -197,7 +89,7 @@ class MouseTrackerUpdateDetails with Diagnosticable {
///
/// The [lastAnnotations], [nextAnnotations], and [triggeringEvent] are
/// required.
const MouseTrackerUpdateDetails.byPointerEvent({
const _MouseTrackerUpdateDetails.byPointerEvent({
required this.lastAnnotations,
required this.nextAnnotations,
this.previousEvent,
......@@ -258,33 +150,31 @@ class MouseTrackerUpdateDetails with Diagnosticable {
}
}
/// A base class that tracks the relationship between mouse devices and
/// [MouseTrackerAnnotation]s.
/// Tracks the relationship between mouse devices and annotations, and
/// triggers mouse events and cursor changes accordingly.
///
/// An event (not necessarily a pointer event) that might change the relationship
/// between mouse devices and [MouseTrackerAnnotation]s is called a _device
/// update_.
/// The [MouseTracker] tracks the relationship between mouse devices and
/// [MouseTrackerAnnotation], notified by [updateWithEvent] and
/// [updateAllDevices]. At every update, [MouseTracker] triggers the following
/// changes if applicable:
///
/// [MouseTracker] is notified of device updates by [updateWithEvent] or
/// [updateAllDevices], and processes effects as defined in [handleDeviceUpdate]
/// by subclasses.
/// * Dispatches mouse-related pointer events (pointer enter, hover, and exit).
/// * Changes mouse cursors.
/// * Notifies when [mouseIsConnected] changes.
///
/// This class is a [ChangeNotifier] that notifies its listeners if the value of
/// [mouseIsConnected] changes.
///
/// See also:
///
/// * [MouseTracker], which is a subclass of [BaseMouseTracker] with definition
/// of how to process mouse event callbacks and mouse cursors.
/// * [MouseTrackerCursorMixin], which is a mixin for [BaseMouseTracker] that
/// defines how to process mouse cursors.
abstract class BaseMouseTracker extends ChangeNotifier {
/// Whether or not at least one mouse is connected and has produced events.
bool get mouseIsConnected => _mouseStates.isNotEmpty;
/// An instance of [MouseTracker] is owned by the global singleton
/// [RendererBinding].
class MouseTracker extends ChangeNotifier {
final MouseCursorManager _mouseCursorMixin = MouseCursorManager(
SystemMouseCursors.basic
);
// Tracks the state of connected mouse devices.
//
// It is the source of truth for the list of connected mouse devices, and is
// It is the source of truth for the list of connected mouse devices, and
// consists of two parts:
//
// * The mouse devices that are connected.
......@@ -303,7 +193,7 @@ abstract class BaseMouseTracker extends ChangeNotifier {
}
bool _debugDuringDeviceUpdate = false;
// Used to wrap any procedure that might call [handleDeviceUpdate].
// Used to wrap any procedure that might call `_handleDeviceUpdate`.
//
// In debug mode, this method uses `_debugDuringDeviceUpdate` to prevent
// `_deviceUpdatePhase` being recursively called.
......@@ -367,40 +257,41 @@ abstract class BaseMouseTracker extends ChangeNotifier {
return _hitTestResultToAnnotations(hitTest(globalPosition));
}
/// A callback that is called on the update of a device.
///
/// This method should be called only by [BaseMouseTracker], each time when the
/// relationship between a device and annotations has changed.
///
/// By default the [handleDeviceUpdate] does nothing effective. Subclasses
/// should override this method to first call to their inherited
/// [handleDeviceUpdate] method, and then process the update as desired.
///
/// The update can be caused by two kinds of triggers:
///
/// * Triggered by the addition, movement, or removal of a pointer. Such
/// calls occur during the handler of the event, indicated by
/// `details.triggeringEvent` being non-null.
/// * Triggered by the appearance, movement, or disappearance of an annotation.
/// Such calls occur after each new frame, during the post-frame callbacks,
/// indicated by `details.triggeringEvent` being null.
///
/// Calling of this method must be wrapped in `_deviceUpdatePhase`.
@protected
@mustCallSuper
void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
// A callback that is called on the update of a device.
//
// An event (not necessarily a pointer event) that might change the
// relationship between mouse devices and [MouseTrackerAnnotation]s is called
// a _device update_. This method should be called at each such update.
//
// The update can be caused by two kinds of triggers:
//
// * Triggered by the addition, movement, or removal of a pointer. Such calls
// occur during the handler of the event, indicated by
// `details.triggeringEvent` being non-null.
// * Triggered by the appearance, movement, or disappearance of an annotation.
// Such calls occur after each new frame, during the post-frame callbacks,
// indicated by `details.triggeringEvent` being null.
//
// Calls of this method must be wrapped in `_deviceUpdatePhase`.
void _handleDeviceUpdate(_MouseTrackerUpdateDetails details) {
assert(_debugDuringDeviceUpdate);
_handleDeviceUpdateMouseEvents(details);
_mouseCursorMixin.handleDeviceCursorUpdate(
details.device,
details.triggeringEvent,
details.nextAnnotations.keys.map((MouseTrackerAnnotation annotaion) => annotaion.cursor),
);
}
/// Whether or not at least one mouse is connected and has produced events.
bool get mouseIsConnected => _mouseStates.isNotEmpty;
/// Trigger a device update with a new event and its corresponding hit test
/// result.
///
/// The [updateWithEvent] indicates that an event has been observed, and
/// is called during the handler of the event. The `getResult` should return
/// the hit test result at the position of the event.
///
/// The [updateWithEvent] will generate the new state for the pointer based on
/// given information, and call [handleDeviceUpdate] based on the state changes.
void updateWithEvent(PointerEvent event, ValueGetter<HitTestResult> getResult) {
assert(event != null);
final HitTestResult result = event is PointerRemovedEvent ? HitTestResult() : getResult();
......@@ -435,7 +326,7 @@ abstract class BaseMouseTracker extends ChangeNotifier {
_hitTestResultToAnnotations(result);
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = targetState.replaceAnnotations(nextAnnotations);
handleDeviceUpdate(MouseTrackerUpdateDetails.byPointerEvent(
_handleDeviceUpdate(_MouseTrackerUpdateDetails.byPointerEvent(
lastAnnotations: lastAnnotations,
nextAnnotations: nextAnnotations,
previousEvent: lastEvent,
......@@ -449,13 +340,12 @@ abstract class BaseMouseTracker extends ChangeNotifier {
///
/// The [updateAllDevices] is typically called during the post frame phase,
/// indicating a frame has passed and all objects have potentially moved. The
/// `hitTest` is a function that can acquire the hit test result at a given
/// `hitTest` is a function that acquires the hit test result at a given
/// position, and must not be empty.
///
/// For each connected device, the [updateAllDevices] will make a hit test on
/// the device's last seen position, generate the new state for the pointer
/// based on given information, and call [handleDeviceUpdate] based on the
/// state changes.
/// the device's last seen position, and check if necessary changes need to be
/// made.
void updateAllDevices(MouseDetectorAnnotationFinder hitTest) {
_deviceUpdatePhase(() {
for (final _MouseState dirtyState in _mouseStates.values) {
......@@ -463,7 +353,7 @@ abstract class BaseMouseTracker extends ChangeNotifier {
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = _findAnnotations(dirtyState, hitTest);
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = dirtyState.replaceAnnotations(nextAnnotations);
handleDeviceUpdate(MouseTrackerUpdateDetails.byNewFrame(
_handleDeviceUpdate(_MouseTrackerUpdateDetails.byNewFrame(
lastAnnotations: lastAnnotations,
nextAnnotations: nextAnnotations,
previousEvent: lastEvent,
......@@ -471,29 +361,33 @@ abstract class BaseMouseTracker extends ChangeNotifier {
}
});
}
}
// A mixin for [BaseMouseTracker] that dispatches mouse events on device update.
//
// See also:
//
// * [MouseTracker], which uses this mixin.
mixin _MouseTrackerEventMixin on BaseMouseTracker {
/// Returns the active mouse cursor for a device.
///
/// The return value is the last [MouseCursor] activated onto this device, even
/// if the activation failed.
///
/// This function is only active when asserts are enabled. In release builds,
/// it always returns null.
@visibleForTesting
MouseCursor? debugDeviceActiveCursor(int device) {
return _mouseCursorMixin.debugDeviceActiveCursor(device);
}
// Handles device update and dispatches mouse event callbacks.
static void _handleDeviceUpdateMouseEvents(MouseTrackerUpdateDetails details) {
static void _handleDeviceUpdateMouseEvents(_MouseTrackerUpdateDetails details) {
final PointerEvent latestEvent = details.latestEvent;
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = details.lastAnnotations;
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = details.nextAnnotations;
// Order is important for mouse event callbacks. The `findAnnotations`
// returns annotations in the visual order from front to back. We call
// it the "visual order", and the opposite one "reverse visual order".
// The algorithm here is explained in
// https://github.com/flutter/flutter/issues/41420
// Order is important for mouse event callbacks. The
// `_hitTestResultToAnnotations` returns annotations in the visual order
// from front to back, called the "hit-test order". The algorithm here is
// explained in https://github.com/flutter/flutter/issues/41420
// Send exit events to annotations that are in last but not in next, in
// visual order.
// hit-test order.
final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent);
lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
if (!nextAnnotations.containsKey(annotation))
......@@ -502,7 +396,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
});
// Send enter events to annotations that are not in last but in next, in
// reverse visual order.
// reverse hit-test order.
final List<MouseTrackerAnnotation> enteringAnnotations = nextAnnotations.keys.where(
(MouseTrackerAnnotation annotation) => !lastAnnotations.containsKey(annotation),
).toList();
......@@ -512,35 +406,4 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
}
}
@protected
@override
void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
super.handleDeviceUpdate(details);
_handleDeviceUpdateMouseEvents(details);
}
}
/// Tracks the relationship between mouse devices and annotations, and
/// triggers mouse events and cursor changes accordingly.
///
/// The [MouseTracker] tracks the relationship between mouse devices and
/// [MouseTrackerAnnotation]s, and when such relationship changes, triggers
/// the following changes if applicable:
///
/// * Dispatches mouse-related pointer events (pointer enter, hover, and exit).
/// * Notifies changes of [mouseIsConnected].
/// * Changes mouse cursors.
///
/// An instance of [MouseTracker] is owned by the global singleton of
/// [RendererBinding].
///
/// This class is a [ChangeNotifier] that notifies its listeners if the value of
/// [mouseIsConnected] changes.
///
/// See also:
///
/// * [BaseMouseTracker], which introduces more details about the timing of
/// device updates.
class MouseTracker extends BaseMouseTracker with MouseTrackerCursorMixin, _MouseTrackerEventMixin {
}
......@@ -11,8 +11,7 @@ import 'package:flutter/services.dart';
import 'box.dart';
import 'layer.dart';
import 'mouse_cursor.dart';
import 'mouse_tracking.dart';
import 'mouse_tracker.dart';
import 'object.dart';
......
......@@ -8,14 +8,14 @@ import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'layer.dart';
import 'layout_helper.dart';
import 'mouse_cursor.dart';
import 'mouse_tracking.dart';
import 'mouse_tracker.dart';
import 'object.dart';
export 'package:flutter/gestures.dart' show
......
......@@ -4,17 +4,28 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'mouse_tracking.dart';
import 'system_channels.dart';
/// A mixin for [BaseMouseTracker] that sets the mouse pointer's cursors
/// on device update.
/// Maintains the state of mouse cursors and manages how cursors are searched
/// for.
///
/// See also:
///
/// * [MouseTracker], which uses this mixin.
mixin MouseTrackerCursorMixin on BaseMouseTracker {
/// This is typically created as a global singleton and owned by [MouseTracker].
class MouseCursorManager {
/// Create a [MouseCursorManager] by specifying the fallback cursor.
///
/// The `fallbackMouseCursor` must not be [MouseCursor.defer] (typically
/// [SystemMouseCursors.basic]).
MouseCursorManager(this.fallbackMouseCursor)
: assert(fallbackMouseCursor != MouseCursor.defer);
/// The mouse cursor to use if all cursor candidates choose to defer.
///
/// See also:
///
/// * [MouseCursor.defer], the mouse cursor object to use to defer.
final MouseCursor fallbackMouseCursor;
/// Returns the active mouse cursor of a device.
///
/// The return value is the last [MouseCursor] activated onto this
......@@ -22,7 +33,6 @@ mixin MouseTrackerCursorMixin on BaseMouseTracker {
///
/// Only valid when asserts are enabled. In release builds, always returns
/// null.
@visibleForTesting
MouseCursor? debugDeviceActiveCursor(int device) {
MouseCursor? result;
assert(() {
......@@ -32,38 +42,30 @@ mixin MouseTrackerCursorMixin on BaseMouseTracker {
return result;
}
@protected
@override
void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
super.handleDeviceUpdate(details);
_handleDeviceUpdateMouseCursor(details);
}
final Map<int, MouseCursorSession> _lastSession = <int, MouseCursorSession>{};
// 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(Iterable<MouseTrackerAnnotation> annotations) {
return _DeferringMouseCursor.firstNonDeferred(
annotations.map((MouseTrackerAnnotation annotation) => annotation.cursor),
) ?? SystemMouseCursors.basic;
}
// Handles device update and changes mouse cursors.
void _handleDeviceUpdateMouseCursor(MouseTrackerUpdateDetails details) {
final int device = details.device;
if (details.triggeringEvent is PointerRemovedEvent) {
/// Handles the changes that cause a pointer device to have a new list of mouse
/// cursor candidates.
///
/// This change can be caused by a pointer event, in which case
/// `triggeringEvent` should not be null, or by other changes, such as when a
/// widget has moved under a still mouse, which is detected after the current
/// frame is complete. In either case, `cursorCandidates` should be the list of
/// cursors at the location of the mouse in hit-test order.
void handleDeviceCursorUpdate(
int device,
PointerEvent? triggeringEvent,
Iterable<MouseCursor> cursorCandidates,
) {
if (triggeringEvent is PointerRemovedEvent) {
_lastSession.remove(device);
return;
}
final MouseCursorSession? lastSession = _lastSession[device];
final MouseCursor nextCursor = _findFirstCursor(details.nextAnnotations.keys);
final MouseCursor nextCursor = _DeferringMouseCursor.firstNonDeferred(cursorCandidates)
?? fallbackMouseCursor;
assert(nextCursor is! _DeferringMouseCursor);
if (lastSession?.cursor == nextCursor)
return;
......@@ -191,9 +193,9 @@ abstract class MouseCursorSession {
/// a cursor, and defines the states and behaviors of the cursor. Every mouse
/// cursor class usually has a corresponding [MouseCursorSession] class.
///
/// [MouseTrackerCursorMixin] is a mixin that adds the feature of changing
/// cursors to [BaseMouseTracker], which tracks the relationship between mouse
/// devices and annotations. [MouseTrackerCursorMixin] is usually used as a part
/// [MouseCursorManager] is a class that adds the feature of changing
/// cursors to [MouseTracker], which tracks the relationship between mouse
/// devices and annotations. [MouseCursorManager] is usually used as a part
/// of [MouseTracker].
@immutable
abstract class MouseCursor with Diagnosticable {
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'mouse_cursor.dart';
/// Signature for listening to [PointerEnterEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerEnterEventListener = void Function(PointerEnterEvent event);
/// Signature for listening to [PointerExitEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerExitEventListener = void Function(PointerExitEvent event);
/// Signature for listening to [PointerHoverEvent] events.
///
/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
/// The annotation object used to annotate regions that are interested in mouse
/// movements.
///
/// To use an annotation, return this object as a [HitTestEntry] in a hit test.
/// Typically this is implemented by making a [RenderBox] implement this class
/// (see [RenderMouseRegion]).
///
/// [MouseTracker] uses this class as a label to filter the hit test results. Hit
/// test entries that are also [MouseTrackerAnnotation]s are considered as valid
/// targets in terms of computing mouse related effects, such as enter events,
/// exit events, and mouse cursor events.
///
/// See also:
///
/// * [MouseTracker], 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.onExit,
this.cursor = MouseCursor.defer,
this.validForMouseTracker = true,
}) : assert(cursor != null);
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// entered the region and [validForMouseTracker] is true.
///
/// This callback is triggered when the pointer has started to be contained by
/// the region, either due to a pointer event, or due to the movement or
/// disappearance of the region. This method is always matched by a later
/// [onExit].
///
/// See also:
///
/// * [onExit], which is triggered when a mouse pointer exits the region.
/// * [MouseRegion.onEnter], which uses this callback.
final PointerEnterEventListener? onEnter;
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// exited the region and [validForMouseTracker] is true.
///
/// This callback is triggered when the pointer has stopped being contained
/// by the region, either due to a pointer event, or due to the movement or
/// disappearance of the region. This method always matches an earlier
/// [onEnter].
///
/// See also:
///
/// * [onEnter], which is triggered when a mouse pointer enters the region.
/// * [MouseRegion.onExit], which uses this callback, but is not triggered in
/// certain cases and does not always match its earlier [MouseRegion.onEnter].
final PointerExitEventListener? onExit;
/// 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.
///
/// Defaults to [MouseCursor.defer], deferring the choice of cursor to the next
/// region behind it in hit-test order.
///
/// See also:
///
/// * [MouseRegion.cursor], which provide values to this field.
final MouseCursor cursor;
/// Whether this is included when [MouseTracker] collects the list of
/// annotations.
///
/// If [validForMouseTracker] is false, this object is excluded from the
/// current annotation list even if it's included in the hit test, affecting
/// mouse-related behavior such as enter events, exit events, and mouse
/// cursors. The [validForMouseTracker] does not affect hit testing.
///
/// The [validForMouseTracker] is true for [MouseTrackerAnnotation]s built by
/// the constructor.
final bool validForMouseTracker;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagsSummary<Function?>(
'callbacks',
<String, Function?> {
'enter': onEnter,
'exit': onExit,
},
ifEmpty: '<none>',
));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
}
}
......@@ -41,6 +41,8 @@ export 'package:flutter/rendering.dart' show
LayerLink,
MainAxisAlignment,
MainAxisSize,
MouseCursor,
SystemMouseCursors,
MultiChildLayoutDelegate,
Overflow,
PaintingContext,
......
......@@ -1494,7 +1494,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
/// interaction type.
///
/// The initial value of [highlightMode] depends upon the value of
/// [defaultTargetPlatform] and [BaseMouseTracker.mouseIsConnected] of
/// [defaultTargetPlatform] and [MouseTracker.mouseIsConnected] of
/// [RendererBinding.mouseTracker], making a guess about which interaction is
/// most appropriate for the initial interaction mode.
///
......
......@@ -10,7 +10,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import '../flutter_test_alternative.dart';
import './mouse_tracking_test_utils.dart';
import 'mouse_tracker_test_utils.dart';
typedef MethodCallHandler = Future<dynamic> Function(MethodCall call);
typedef SimpleAnnotationFinder = Iterable<HitTestTarget> Function(Offset offset);
......
......@@ -5,12 +5,11 @@
import 'dart:ui' as ui;
import 'dart:ui' show PointerChange;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart';
import '../flutter_test_alternative.dart';
import './mouse_tracking_test_utils.dart';
import 'mouse_tracker_test_utils.dart';
MouseTracker get _mouseTracker => RendererBinding.instance!.mouseTracker;
......@@ -76,32 +75,6 @@ void main() {
final Matrix4 translate10by20 = Matrix4.translationValues(10, 20, 0);
test('MouseTrackerAnnotation has correct toString', () {
final MouseTrackerAnnotation annotation1 = MouseTrackerAnnotation(
onEnter: (_) {},
onExit: (_) {},
);
expect(
annotation1.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation1)}(callbacks: [enter, exit])'),
);
const MouseTrackerAnnotation annotation2 = MouseTrackerAnnotation();
expect(
annotation2.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation2)}(callbacks: <none>)'),
);
final MouseTrackerAnnotation annotation3 = MouseTrackerAnnotation(
onEnter: (_) {},
cursor: SystemMouseCursors.grab,
);
expect(
annotation3.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation3)}(callbacks: [enter], cursor: SystemMouseCursor(grab))'),
);
});
test('should detect enter, hover, and exit from Added, Hover, and Removed events', () {
final List<PointerEvent> events = <PointerEvent>[];
_setUpWithOneAnnotation(logEvents: events);
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('MouseTrackerAnnotation has correct toString', () {
final MouseTrackerAnnotation annotation1 = MouseTrackerAnnotation(
onEnter: (_) {},
onExit: (_) {},
);
expect(
annotation1.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation1)}(callbacks: [enter, exit])'),
);
const MouseTrackerAnnotation annotation2 = MouseTrackerAnnotation();
expect(
annotation2.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation2)}(callbacks: <none>)'),
);
final MouseTrackerAnnotation annotation3 = MouseTrackerAnnotation(
onEnter: (_) {},
cursor: SystemMouseCursors.grab,
);
expect(
annotation3.toString(),
equals('MouseTrackerAnnotation#${shortHash(annotation3)}(callbacks: [enter], cursor: SystemMouseCursor(grab))'),
);
});
}
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