Skip to content

Split pointer enter, exit, and hover callbacks from Listener #36085

@dkwingsmt

Description

@dkwingsmt

TL;DR

We propose that pointer enter, exit, and hover callbacks are split from Listener into a new class Mouse. It is mainly because mouse tracking is unable to support certain features provided by Listener, also because Listener is often used only for mouse events.

Problem

Unable to support HitTestBehavior.deferToChild

Although Listener is designed to be a all-in-one class for pointer-related callbacks, under the hood it consists of two subsystems: Mouse tracking, represented by onPointer{Enter,Exit,Hover} events, are implemented with layer-based hit testing, while pointer events listening, represented by onPointer{Down,Up,Move}, are implemented with render-box-based hit testing.

Listener also provides a few options, one of them being HitTestBehavior behavior, which controls the behavior of hit testing. However, this option is directly passed to RenderProxyBoxWithHitTestBehavior, therefore it can not affect layers (hence the layer-based callbacks). Having an option that only affects some of the callbacks is definitely confusing and undesired.

We can try to simulate the behavior in layers (which is planned), but it is technically impossible to simulate HitTestBehavior.deferToChild, since child information is related with render objects and is lost when converted to layers.

By splitting onPointer{Enter,Exit,Hover} into a separate class, we can provide a different type of behavior that has only two options to avoid this confusion.

It is often used alone

Listener.onPointer{Down,Up,Move} are rarely used directly, instead developers are supposed to use GestureDetector when possible, or to create a custom gesture recognizer with RawGestureDetector.

On the other hand, Listener.onPointer{Enter,Exit,Hover} are currently the only way to track mouse movement. This leads to a common code pattern as the following snippet, where Listener is only used for onPointer{Enter,Exit,Hover}:

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerEnter: _handlePointerEnter,
      onPointerExit: _handlePointerExit,
      child: GestureDetector(
        onTapDown: _handleTapDown,
        onTap: _handleTap,
        ...
        child: widget.child,
      ),
    );
  }

Although we squashed 2 separate subsystems into Listener, developers often utilize only one of them. Therefore it might be a better idea to split them for modularity.

More mouse features are coming

Flutter plans to add more features related to mouse (for example, pointer icons). For features that are built based on the MouseTracker, the new APIs will be added to the same place, i.e. Listener. By splitting the mouse tracking part out, we keep Listener clean for apps that don't plan to support mouse.

Proposal

Split the mouse tracking part of RenderPointerListener into a new class RenderMouseListener.

Split the mouse tracking part of Listener into a new class Mouse. Listener will keep onPointer{Down,Up,Move,Cancel,Signal} as well as behavior.

Rename the mouse tracking callbacks of Mouse and MouseTrackerAnnotation to onMouse{Enter,Exit,Hover} to emphasize the difference.

  • I understand that this change is more aggressive and less necessary. Comments are welcome.

Listener.onPointer{Enter,Exit,Hover} are preserved for backward compatibility and marked as deprecated.

A proposal PR is #36217.

Future plans

New behavior option for mouse tracking will be added in a separate PR after this change.

Revisions

Previously I proposed to split pointer events (up/down/move/cancel/signal) out while keeping enter/exit/hover in. The current proposal suggest the opposite.

Previously I proposed to call the new class MouseListener. The current proposal is calling Mouse to accommodate upcoming changes such as pointer icon.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: API breakBackwards-incompatible API changesc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterf: gesturesflutter/packages/flutter/gestures repository.frameworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions