| // 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. |
| |
| // @dart = 2.8 |
| |
| import 'dart:collection' show LinkedHashSet; |
| import 'dart:ui'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/scheduler.dart'; |
| |
| 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.onHover, |
| this.onExit, |
| this.cursor = MouseCursor.defer, |
| }) : assert(cursor != null); |
| |
| /// Triggered when a mouse pointer, with or without buttons pressed, has |
| /// entered the region. |
| /// |
| /// 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 has moved onto or within the region without |
| /// buttons pressed. |
| /// |
| /// This callback is not triggered by the movement of an annotation. |
| /// |
| /// See also: |
| /// |
| /// * [MouseRegion.onHover], which uses this callback. |
| final PointerHoverEventListener onHover; |
| |
| /// Triggered when a mouse pointer, with or without buttons pressed, has |
| /// exited the region. |
| /// |
| /// 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; |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(FlagsSummary<Function>( |
| 'callbacks', |
| <String, Function> { |
| 'enter': onEnter, |
| 'hover': onHover, |
| 'exit': onExit, |
| }, |
| ifEmpty: '<none>', |
| )); |
| properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer)); |
| } |
| } |
| |
| /// Signature for searching for [MouseTrackerAnnotation]s at the given offset. |
| /// |
| /// It is used by the [BaseMouseTracker] to fetch annotations for the mouse |
| /// position. |
| typedef MouseDetectorAnnotationFinder = Iterable<MouseTrackerAnnotation> Function(Offset offset); |
| |
| // Various states of a connected mouse device used by [BaseMouseTracker]. |
| class _MouseState { |
| _MouseState({ |
| @required PointerEvent initialEvent, |
| }) : assert(initialEvent != null), |
| _latestEvent = initialEvent; |
| |
| // The list of annotations that contains this device. |
| // |
| // It uses [LinkedHashSet] to keep the insertion order. |
| LinkedHashSet<MouseTrackerAnnotation> get annotations => _annotations; |
| LinkedHashSet<MouseTrackerAnnotation> _annotations = LinkedHashSet<MouseTrackerAnnotation>(); |
| |
| LinkedHashSet<MouseTrackerAnnotation> replaceAnnotations(LinkedHashSet<MouseTrackerAnnotation> value) { |
| assert(value != null); |
| final LinkedHashSet<MouseTrackerAnnotation> previous = _annotations; |
| _annotations = value; |
| return previous; |
| } |
| |
| // The most recently processed mouse event observed from this device. |
| PointerEvent get latestEvent => _latestEvent; |
| PointerEvent _latestEvent; |
| |
| PointerEvent replaceLatestEvent(PointerEvent value) { |
| assert(value != null); |
| assert(value.device == _latestEvent.device); |
| final PointerEvent previous = _latestEvent; |
| _latestEvent = value; |
| return previous; |
| } |
| |
| int get device => latestEvent.device; |
| |
| @override |
| String toString() { |
| String describeEvent(PointerEvent event) { |
| return event == null ? 'null' : describeIdentity(event); |
| } |
| final String describeLatestEvent = 'latestEvent: ${describeEvent(latestEvent)}'; |
| final String describeAnnotations = 'annotations: [list of ${annotations.length}]'; |
| return '${describeIdentity(this)}($describeLatestEvent, $describeAnnotations)'; |
| } |
| } |
| |
| /// 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. |
| @immutable |
| class MouseTrackerUpdateDetails with Diagnosticable { |
| /// When device update is triggered by a new frame. |
| /// |
| /// All parameters are required. |
| const MouseTrackerUpdateDetails.byNewFrame({ |
| @required this.lastAnnotations, |
| @required this.nextAnnotations, |
| @required this.previousEvent, |
| }) : assert(previousEvent != null), |
| assert(lastAnnotations != null), |
| assert(nextAnnotations != null), |
| triggeringEvent = null; |
| |
| /// When device update is triggered by a pointer event. |
| /// |
| /// The [lastAnnotations], [nextAnnotations], and [triggeringEvent] are |
| /// required. |
| const MouseTrackerUpdateDetails.byPointerEvent({ |
| @required this.lastAnnotations, |
| @required this.nextAnnotations, |
| this.previousEvent, |
| @required this.triggeringEvent, |
| }) : assert(triggeringEvent != null), |
| assert(lastAnnotations != null), |
| assert(nextAnnotations != null); |
| |
| /// The annotations that the device is hovering before the update. |
| /// |
| /// It is never null. |
| final LinkedHashSet<MouseTrackerAnnotation> lastAnnotations; |
| |
| /// The annotations that the device is hovering after the update. |
| /// |
| /// It is never null. |
| final LinkedHashSet<MouseTrackerAnnotation> nextAnnotations; |
| |
| /// The last event that the device observed before the update. |
| /// |
| /// If the update is triggered by a frame, the [previousEvent] is never null, |
| /// since the pointer must have been added before. |
| /// |
| /// If the update is triggered by a pointer event, the [previousEvent] is not |
| /// null except for cases where the event is the first event observed by the |
| /// pointer (which is not necessarily a [PointerAddedEvent]). |
| final PointerEvent previousEvent; |
| |
| /// The event that triggered this update. |
| /// |
| /// It is non-null if and only if the update is triggered by a pointer event. |
| final PointerEvent triggeringEvent; |
| |
| /// The pointing device of this update. |
| int get device { |
| final int result = (previousEvent ?? triggeringEvent).device; |
| assert(result != null); |
| return result; |
| } |
| |
| /// The last event that the device observed after the update. |
| /// |
| /// The [latestEvent] is never null. |
| PointerEvent get latestEvent { |
| final PointerEvent result = triggeringEvent ?? previousEvent; |
| assert(result != null); |
| return result; |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(IntProperty('device', device)); |
| properties.add(DiagnosticsProperty<PointerEvent>('previousEvent', previousEvent)); |
| properties.add(DiagnosticsProperty<PointerEvent>('triggeringEvent', triggeringEvent)); |
| properties.add(DiagnosticsProperty<Set<MouseTrackerAnnotation>>('lastAnnotations', lastAnnotations)); |
| properties.add(DiagnosticsProperty<Set<MouseTrackerAnnotation>>('nextAnnotations', nextAnnotations)); |
| } |
| } |
| |
| /// A base class that tracks the relationship between mouse devices and |
| /// [MouseTrackerAnnotation]s. |
| /// |
| /// A _device update_ is defined as an event that changes the relationship |
| /// between mouse devices and [MouseTrackerAnnotation]s. Subclasses should |
| /// override [handleDeviceUpdate] to process the updates. |
| /// |
| /// This class is a [ChangeNotifier] that notifies its listeners if the value of |
| /// [mouseIsConnected] changes. |
| /// |
| /// ### States and device updates |
| /// |
| /// The state of [BaseMouseTracker] consists of two parts: |
| /// |
| /// * The mouse devices that are connected. |
| /// * In which annotations each device is contained. |
| /// |
| /// The states remain stable most of the time, and are only changed at the |
| /// following moments: |
| /// |
| /// * An eligible [PointerEvent] has been observed, e.g. a device is added, |
| /// removed, or moved. In this case, the state related to this device will |
| /// be immediately updated, and triggers [handleDeviceUpdate] on this device. |
| /// * A frame has been painted. In this case, a callback will be scheduled for |
| /// the upcoming post-frame phase to update all devices, and triggers |
| /// [handleDeviceUpdate] on each device separately. |
| /// |
| /// See also: |
| /// |
| /// * [MouseTracker], which is a subclass of [BaseMouseTracker] with definition |
| /// of how to process mouse event callbacks and mouse cursors. |
| /// * [MouseCursorMixin], which is a mixin for [BaseMouseTracker] that defines |
| /// how to process mouse cursors. |
| class BaseMouseTracker extends ChangeNotifier { |
| /// Creates a [BaseMouseTracker] to keep track of mouse locations. |
| /// |
| /// The first parameter is a [PointerRouter], which [BaseMouseTracker] will |
| /// subscribe to and receive events from. Usually it is the global singleton |
| /// instance [GestureBinding.pointerRouter]. |
| /// |
| /// The second parameter is a function with which the [BaseMouseTracker] can |
| /// search for [MouseTrackerAnnotation]s at a given position. |
| /// Usually it is [Layer.findAllAnnotations] of the root layer. |
| /// |
| /// All of the parameters must be non-null. |
| BaseMouseTracker(this._router, this.annotationFinder) |
| : assert(_router != null), |
| assert(annotationFinder != null) { |
| _router.addGlobalRoute(_handleEvent); |
| } |
| |
| @override |
| void dispose() { |
| super.dispose(); |
| _router.removeGlobalRoute(_handleEvent); |
| } |
| |
| /// Find annotations at a given offset in global logical coordinate space |
| /// in visual order from front to back. |
| /// |
| /// [BaseMouseTracker] uses this callback to know which annotations are |
| /// affected by each device. |
| /// |
| /// The annotations should be returned in visual order from front to |
| /// back, so that the callbacks are called in an correct order. |
| final MouseDetectorAnnotationFinder annotationFinder; |
| |
| // The pointer router that the mouse tracker listens to, and receives new |
| // mouse events from. |
| final PointerRouter _router; |
| |
| bool _hasScheduledPostFrameCheck = false; |
| /// Mark all devices as dirty, and schedule a callback that is executed in the |
| /// upcoming post-frame phase to check their updates. |
| /// |
| /// Checking a device means to collect the annotations that the pointer |
| /// hovers, and triggers necessary callbacks accordingly. |
| /// |
| /// Although the actual callback belongs to the scheduler's post-frame phase, |
| /// this method must be called in persistent callback phase to ensure that |
| /// the callback is scheduled after every frame, since every frame can change |
| /// the position of annotations. Typically the method is called by |
| /// [RendererBinding]'s drawing method. |
| void schedulePostFrameCheck() { |
| assert(SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks); |
| assert(!_debugDuringDeviceUpdate); |
| if (!mouseIsConnected) |
| return; |
| if (!_hasScheduledPostFrameCheck) { |
| _hasScheduledPostFrameCheck = true; |
| SchedulerBinding.instance.addPostFrameCallback((Duration duration) { |
| assert(_hasScheduledPostFrameCheck); |
| _hasScheduledPostFrameCheck = false; |
| _updateAllDevices(); |
| }); |
| } |
| } |
| |
| /// Whether or not at least one mouse is connected and has produced events. |
| bool get mouseIsConnected => _mouseStates.isNotEmpty; |
| |
| // Tracks the state of connected mouse devices. |
| // |
| // It is the source of truth for the list of connected mouse devices. |
| final Map<int, _MouseState> _mouseStates = <int, _MouseState>{}; |
| |
| // Used to wrap any procedure that might change `mouseIsConnected`. |
| // |
| // This method records `mouseIsConnected`, runs `task`, and calls |
| // [notifyListeners] at the end if the `mouseIsConnected` has changed. |
| void _monitorMouseConnection(VoidCallback task) { |
| final bool mouseWasConnected = mouseIsConnected; |
| task(); |
| if (mouseWasConnected != mouseIsConnected) |
| notifyListeners(); |
| } |
| |
| bool _debugDuringDeviceUpdate = false; |
| // Used to wrap any procedure that might call [handleDeviceUpdate]. |
| // |
| // In debug mode, this method uses `_debugDuringDeviceUpdate` to prevent |
| // `_deviceUpdatePhase` being recursively called. |
| void _deviceUpdatePhase(VoidCallback task) { |
| assert(!_debugDuringDeviceUpdate); |
| assert(() { |
| _debugDuringDeviceUpdate = true; |
| return true; |
| }()); |
| task(); |
| assert(() { |
| _debugDuringDeviceUpdate = false; |
| return true; |
| }()); |
| } |
| |
| // Whether an observed event might update a device. |
| static bool _shouldMarkStateDirty(_MouseState state, PointerEvent event) { |
| if (state == null) |
| return true; |
| assert(event != null); |
| final PointerEvent lastEvent = state.latestEvent; |
| assert(event.device == lastEvent.device); |
| // An Added can only follow a Removed, and a Removed can only be followed |
| // by an Added. |
| assert((event is PointerAddedEvent) == (lastEvent is PointerRemovedEvent)); |
| |
| // Ignore events that are unrelated to mouse tracking. |
| if (event is PointerSignalEvent) |
| return false; |
| return lastEvent is PointerAddedEvent |
| || event is PointerRemovedEvent |
| || lastEvent.position != event.position; |
| } |
| |
| // Find the annotations that is hovered by the device of the `state`. |
| // |
| // 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)) |
| ? LinkedHashSet<MouseTrackerAnnotation>.from(annotationFinder(globalPosition)) |
| : <MouseTrackerAnnotation>{} as LinkedHashSet<MouseTrackerAnnotation>; |
| } |
| |
| /// A callback that is called on the update of a device. |
| /// |
| /// This method should be called only by [BaseMouseTracker]. |
| /// |
| /// Override this method to receive updates when the relationship between a |
| /// device and annotations have changed. 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. |
| /// |
| /// This method is not triggered if the [MouseTrackerAnnotation] is mutated. |
| /// |
| /// Calling of this method must be wrapped in `_deviceUpdatePhase`. |
| @protected |
| @mustCallSuper |
| void handleDeviceUpdate(MouseTrackerUpdateDetails details) { |
| assert(_debugDuringDeviceUpdate); |
| } |
| |
| // Handler for events coming from the PointerRouter. |
| // |
| // If the event marks the device dirty, update the device immediately. |
| void _handleEvent(PointerEvent event) { |
| if (event.kind != PointerDeviceKind.mouse) |
| return; |
| if (event is PointerSignalEvent) |
| return; |
| final int device = event.device; |
| final _MouseState existingState = _mouseStates[device]; |
| if (!_shouldMarkStateDirty(existingState, event)) |
| return; |
| |
| _monitorMouseConnection(() { |
| _deviceUpdatePhase(() { |
| // Update mouseState to the latest devices that have not been removed, |
| // so that [mouseIsConnected], which is decided by `_mouseStates`, is |
| // correct during the callbacks. |
| if (existingState == null) { |
| _mouseStates[device] = _MouseState(initialEvent: event); |
| } else { |
| assert(event is! PointerAddedEvent); |
| if (event is PointerRemovedEvent) |
| _mouseStates.remove(event.device); |
| } |
| final _MouseState targetState = _mouseStates[device] ?? existingState; |
| |
| final PointerEvent lastEvent = targetState.replaceLatestEvent(event); |
| final LinkedHashSet<MouseTrackerAnnotation> nextAnnotations = _findAnnotations(targetState); |
| final LinkedHashSet<MouseTrackerAnnotation> lastAnnotations = targetState.replaceAnnotations(nextAnnotations); |
| |
| handleDeviceUpdate(MouseTrackerUpdateDetails.byPointerEvent( |
| lastAnnotations: lastAnnotations, |
| nextAnnotations: nextAnnotations, |
| previousEvent: lastEvent, |
| triggeringEvent: event, |
| )); |
| }); |
| }); |
| } |
| |
| // Update all devices, despite observing no new events. |
| // |
| // This is called after a new frame, since annotations can be moved after |
| // every frame. |
| void _updateAllDevices() { |
| _deviceUpdatePhase(() { |
| for (final _MouseState dirtyState in _mouseStates.values) { |
| final PointerEvent lastEvent = dirtyState.latestEvent; |
| final LinkedHashSet<MouseTrackerAnnotation> nextAnnotations = _findAnnotations(dirtyState); |
| final LinkedHashSet<MouseTrackerAnnotation> lastAnnotations = dirtyState.replaceAnnotations(nextAnnotations); |
| |
| handleDeviceUpdate(MouseTrackerUpdateDetails.byNewFrame( |
| lastAnnotations: lastAnnotations, |
| nextAnnotations: nextAnnotations, |
| previousEvent: lastEvent, |
| )); |
| } |
| }); |
| } |
| } |
| |
| // A mixin for [BaseMouseTracker] that dispatches mouse events on device update. |
| // |
| // See also: |
| // |
| // * [MouseTracker], which uses this mixin. |
| mixin _MouseTrackerEventMixin on BaseMouseTracker { |
| // Handles device update and dispatches mouse event callbacks. |
| static void _handleDeviceUpdateMouseEvents(MouseTrackerUpdateDetails details) { |
| final PointerEvent previousEvent = details.previousEvent; |
| final PointerEvent triggeringEvent = details.triggeringEvent; |
| final PointerEvent latestEvent = details.latestEvent; |
| |
| final LinkedHashSet<MouseTrackerAnnotation> lastAnnotations = details.lastAnnotations; |
| final LinkedHashSet<MouseTrackerAnnotation> 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 |
| |
| // Send exit events to annotations that are in last but not in next, in |
| // visual order. |
| final Iterable<MouseTrackerAnnotation> exitingAnnotations = lastAnnotations.difference(nextAnnotations); |
| for (final MouseTrackerAnnotation annotation in exitingAnnotations) { |
| if (annotation.onExit != null) |
| annotation.onExit(PointerExitEvent.fromMouseEvent(latestEvent)); |
| } |
| |
| // 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) { |
| if (annotation.onEnter != null) |
| annotation.onEnter(PointerEnterEvent.fromMouseEvent(latestEvent)); |
| } |
| |
| // 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 (triggeringEvent is PointerHoverEvent) { |
| final Offset hoverPositionBeforeUpdate = previousEvent is PointerHoverEvent ? previousEvent.position : null; |
| final bool pointerHasMoved = hoverPositionBeforeUpdate == null || hoverPositionBeforeUpdate != triggeringEvent.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) { |
| if (annotation.onHover != null) { |
| annotation.onHover(triggeringEvent); |
| } |
| } |
| } |
| } |
| |
| @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 { |
| /// Creates a [MouseTracker] to keep track of mouse locations. |
| /// |
| /// The first parameter is a [PointerRouter], which [MouseTracker] will |
| /// subscribe to and receive events from. Usually it is the global singleton |
| /// instance [GestureBinding.pointerRouter]. |
| /// |
| /// The second parameter is a function with which the [MouseTracker] can |
| /// search for [MouseTrackerAnnotation]s at a given position. |
| /// Usually it is [Layer.findAllAnnotations] of the root layer. |
| /// |
| /// All of the parameters must be non-null. |
| MouseTracker( |
| PointerRouter router, |
| MouseDetectorAnnotationFinder annotationFinder, |
| ) : super(router, annotationFinder); |
| } |