| // 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:async'; |
| import 'dart:collection' show LinkedHashSet; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/services.dart'; |
| |
| import 'mouse_tracking.dart'; |
| |
| /// A mixin for [BaseMouseTracker] that sets the mouse pointer's cursors |
| /// on device update. |
| /// |
| /// See also: |
| /// |
| /// * [MouseTracker], which uses this mixin. |
| mixin MouseTrackerCursorMixin on BaseMouseTracker { |
| /// Returns the active mouse cursor of a device. |
| /// |
| /// The return value is the last [MouseCursor] activated onto this |
| /// device, even if the activation failed. |
| /// |
| /// Only valid when asserts are enabled. In release builds, always returns |
| /// null. |
| @visibleForTesting |
| MouseCursor debugDeviceActiveCursor(int device) { |
| MouseCursor result; |
| assert(() { |
| result = _lastSession[device]?.cursor; |
| return true; |
| }()); |
| 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(LinkedHashSet<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) { |
| _lastSession.remove(device); |
| return; |
| } |
| |
| final MouseCursorSession lastSession = _lastSession[device]; |
| final MouseCursor nextCursor = _findFirstCursor(details.nextAnnotations); |
| if (lastSession?.cursor == nextCursor) |
| return; |
| |
| final MouseCursorSession nextSession = nextCursor.createSession(device); |
| _lastSession[device] = nextSession; |
| |
| lastSession?.dispose(); |
| nextSession.activate(); |
| } |
| } |
| |
| /// Manages the duration that a pointing device should display a specific mouse |
| /// cursor. |
| /// |
| /// While [MouseCursor] classes describe the kind of cursors, [MouseCursorSession] |
| /// classes represents a continuous use of the cursor on a pointing device. The |
| /// [MouseCursorSession] classes can be stateful. For example, a cursor that |
| /// needs to load resources might want to set a temporary cursor first, then |
| /// switch to the correct cursor after the load is completed. |
| /// |
| /// A [MouseCursorSession] has the following lifecycle: |
| /// |
| /// * When a pointing device should start displaying a cursor, [MouseTracker] |
| /// creates a session by calling [MouseCursor.createSession] on the target |
| /// cursor, and stores it in a table associated with the device. |
| /// * [MouseTracker] then immediately calls the session's [activate], where the |
| /// session should fetch resources and make system calls. |
| /// * When the pointing device should start displaying a different cursor, |
| /// [MouseTracker] calls [dispose] on this session. After [dispose], this session |
| /// will no longer be used in the future. |
| abstract class MouseCursorSession { |
| /// Create a session. |
| /// |
| /// All arguments must be non-null. |
| MouseCursorSession(this.cursor, this.device) |
| : assert(cursor != null), |
| assert(device != null); |
| |
| /// The cursor that created this session. |
| final MouseCursor cursor; |
| |
| /// The device ID of the pointing device. |
| final int device; |
| |
| /// Override this method to do the work of changing the cursor of the device. |
| /// |
| /// Called right after this session is created. |
| /// |
| /// This method has full control over the cursor until the [dispose] call, and |
| /// can make system calls to change the pointer cursor as many times as |
| /// necessary (usually through [SystemChannels.mouseCursor]). It can also |
| /// collect resources, and store the result in this object. |
| @protected |
| Future<void> activate(); |
| |
| /// Called when device stops displaying the cursor. |
| /// |
| /// After this call, this session instance will no longer be used in the |
| /// future. |
| /// |
| /// When implementing this method in subclasses, you should release resources |
| /// and prevent [activate] from causing side effects after disposal. |
| @protected |
| void dispose(); |
| } |
| |
| /// An interface for mouse cursor definitions. |
| /// |
| /// A mouse cursor is a graphical image on the screen that echoes the movement |
| /// of a pointing device, such as a mouse or a stylus. A [MouseCursor] object |
| /// defines a kind of mouse cursor, such as an arrow, a pointing hand, or an |
| /// I-beam. |
| /// |
| /// During the painting phase, [MouseCursor] objects are assigned to regions on |
| /// the screen via annotations. Later during a device update (e.g. when a mouse |
| /// moves), [MouseTracker] finds the _active cursor_ of each mouse device, which |
| /// is the front-most region associated with the position of each mouse cursor, |
| /// or defaults to [SystemMouseCursors.basic] if no cursors are associated with |
| /// the position. [MouseTracker] changes the cursor of the pointer if the new |
| /// active cursor is different from the previous active cursor, whose effect is |
| /// defined by the session created by [createSession]. |
| /// |
| /// ## Cursor classes |
| /// |
| /// A [SystemMouseCursor] is a cursor that is natively supported by the |
| /// platform that the program is running on. All supported system mouse cursors |
| /// are enumerated in [SystemMouseCursors]. |
| /// |
| /// ## Using cursors |
| /// |
| /// A [MouseCursor] object is used by being assigned to a [MouseRegion] or |
| /// another widget that exposes the [MouseRegion] API, such as |
| /// [InkWell.mouseCursor]. |
| /// |
| /// {@tool snippet --template=stateless_widget_material} |
| /// This sample creates a rectangular region that is wrapped by a [MouseRegion] |
| /// with a system mouse cursor. The mouse pointer becomes an I-beam when |
| /// hovering over the region. |
| /// |
| /// ```dart |
| /// Widget build(BuildContext context) { |
| /// return Center( |
| /// child: MouseRegion( |
| /// cursor: SystemMouseCursors.text, |
| /// child: Container( |
| /// width: 200, |
| /// height: 100, |
| /// decoration: BoxDecoration( |
| /// color: Colors.blue, |
| /// border: Border.all(color: Colors.yellow), |
| /// ), |
| /// ), |
| /// ), |
| /// ); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// Assigning regions with mouse cursors on platforms that do not support mouse |
| /// cursors, or when there are no mice connected, will have no effect. |
| /// |
| /// ## Related classes |
| /// |
| /// [MouseCursorSession] represents the duration when a pointing device displays |
| /// 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 |
| /// of [MouseTracker]. |
| @immutable |
| abstract class MouseCursor with Diagnosticable { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const MouseCursor(); |
| |
| /// Associate a pointing device to this cursor. |
| /// |
| /// A mouse cursor class usually has a corresponding [MouseCursorSession] |
| /// class, and instantiates such class in this method. |
| /// |
| /// This method is called each time a pointing device starts displaying this |
| /// cursor. A given cursor can be displayed by multiple devices at the same |
| /// time, in which case this method will be called separately for each device. |
| @protected |
| @factory |
| MouseCursorSession createSession(int device); |
| |
| /// A very short description of the mouse cursor. |
| /// |
| /// The [debugDescription] should 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 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 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 { |
| _NoopMouseCursorSession(_NoopMouseCursor cursor, int device) |
| : super(cursor, device); |
| |
| @override |
| Future<void> activate() async { /* Nothing */ } |
| |
| @override |
| void dispose() { /* Nothing */ } |
| } |
| |
| /// A mouse cursor that doesn't change the cursor when activated. |
| /// |
| /// 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 [MouseCursors.uncontrolled]. |
| /// |
| /// 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 |
| // instance is accessible at [SystemMouseCursors.releaseControl]. |
| const _NoopMouseCursor._(); |
| |
| @override |
| @protected |
| _NoopMouseCursorSession createSession(int device) => _NoopMouseCursorSession(this, device); |
| |
| @override |
| String get debugDescription => 'uncontrolled'; |
| } |
| |
| class _SystemMouseCursorSession extends MouseCursorSession { |
| _SystemMouseCursorSession(SystemMouseCursor cursor, int device) |
| : super(cursor, device); |
| |
| @override |
| SystemMouseCursor get cursor => super.cursor as SystemMouseCursor; |
| |
| @override |
| Future<void> activate() { |
| return SystemChannels.mouseCursor.invokeMethod<void>( |
| 'activateSystemCursor', |
| <String, dynamic>{ |
| 'device': device, |
| 'kind': cursor.kind, |
| }, |
| ); |
| } |
| |
| @override |
| void dispose() { /* Nothing */ } |
| } |
| |
| /// A mouse cursor that is natively supported on the platform that the |
| /// application is running on. |
| /// |
| /// System cursors can be used without external resources, and their appearances |
| /// match the experience of native apps. Examples of system cursors are a |
| /// pointing arrow, a pointing hand, a double arrow for resizing, or a text |
| /// I-beam, etc. |
| /// |
| /// An instance of [SystemMouseCursor] refers to one cursor from each platform |
| /// that represents the same concept, such as being text text, being clickable, |
| /// or being a forbidden operation. Since the set of system cursors supported by |
| /// each platform varies, multiple instances can correspond to the same system |
| /// cursor. |
| /// |
| /// [SystemMouseCursors] enumerates the complete set of system cursors supported |
| /// by Flutter, which are hard-coded in the engine. Therefore, manually |
| /// instantiating this class is not supported. |
| class SystemMouseCursor extends MouseCursor { |
| // Application code shouldn't directly instantiate system mouse cursors, since |
| // the supported system cursors are enumerated in [SystemMouseCursors]. |
| const SystemMouseCursor._({ |
| @required this.kind, |
| }) : assert(kind != null); |
| |
| /// A string that identifies the kind of the cursor. |
| /// |
| /// The interpretation of [kind] is platform-dependent. |
| final String kind; |
| |
| @override |
| String get debugDescription => '$runtimeType($kind)'; |
| |
| @override |
| @protected |
| _SystemMouseCursorSession createSession(int device) => _SystemMouseCursorSession(this, device); |
| |
| @override |
| bool operator ==(dynamic other) { |
| if (other.runtimeType != runtimeType) |
| return false; |
| return other is SystemMouseCursor |
| && other.kind == kind; |
| } |
| |
| @override |
| int get hashCode => kind.hashCode; |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(DiagnosticsProperty<String>('kind', kind, level: DiagnosticLevel.debug)); |
| } |
| } |
| |
| /// A collection of system [MouseCursor]s. |
| /// |
| /// System cursors are standard mouse cursors that are provided by the current |
| /// platform. They don't require external resources. |
| /// |
| /// [SystemMouseCursors] is a superset of the system cursors of every platform |
| /// that Flutter supports, therefore some of these objects might map to the same |
| /// result, or fallback to the basic arrow. This mapping is defined by the |
| /// Flutter engine. |
| /// |
| /// The cursor names are chosen to reflect the cursors' use cases instead of |
| /// their shapes, because different platforms might (although not commonly) use |
| /// different shapes for the same use case. |
| class SystemMouseCursors { |
| // This class only contains static members, and should not be instantiated or |
| // extended. |
| factory SystemMouseCursors._() => null; |
| |
| /// Hide the cursor. |
| /// |
| /// Any cursor other than [none] or [uncontrolled] unhides the cursor. |
| static const SystemMouseCursor none = SystemMouseCursor._(kind: 'none'); |
| |
| /// The platform-dependent basic cursor. |
| /// |
| /// Typically the shape of an arrow. |
| static const SystemMouseCursor basic = SystemMouseCursor._(kind: 'basic'); |
| |
| /// A cursor that indicates a user interface element that is clickable, such as a hyperlink. |
| /// |
| /// Typically the shape of a pointing hand. |
| static const SystemMouseCursor click = SystemMouseCursor._(kind: 'click'); |
| |
| /// A cursor that indicates selectable text. |
| /// |
| /// Typically the shape of a capital I. |
| static const SystemMouseCursor text = SystemMouseCursor._(kind: 'text'); |
| |
| /// A cursor that indicates a forbidden action. |
| /// |
| /// Typically the shape of a circle with a diagonal line. |
| static const SystemMouseCursor forbidden = SystemMouseCursor._(kind: 'forbidden'); |
| |
| /// A cursor that indicates something that can be dragged. |
| /// |
| /// Typically the shape of an open hand. |
| static const SystemMouseCursor grab = SystemMouseCursor._(kind: 'grab'); |
| |
| /// A cursor that indicates something that is being dragged. |
| /// |
| /// Typically the shape of a closed hand. |
| static const SystemMouseCursor grabbing = SystemMouseCursor._(kind: 'grabbing'); |
| |
| /// A cursor in the shape of a horizontal double-arrow. |
| /// |
| /// This cursor is typically used when moving or resizing an object, and the |
| /// object can be moved left or right. |
| static const SystemMouseCursor horizontalDoubleArrow = SystemMouseCursor._(kind: 'horizontalDoubleArrow'); |
| |
| /// A cursor in the shape of a vertical double-arrow. |
| /// |
| /// This cursor is typically used when moving or resizing an object, and the |
| /// object can be moved up or down. |
| static const SystemMouseCursor verticalDoubleArrow = SystemMouseCursor._(kind: 'verticalDoubleArrow'); |
| } |