pointer_router.dart 4.87 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Adam Barth's avatar
Adam Barth committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6
import 'package:flutter/foundation.dart';
7

Kris Giesing's avatar
Kris Giesing committed
8
import 'events.dart';
Adam Barth's avatar
Adam Barth committed
9

10 11 12 13
export 'package:vector_math/vector_math_64.dart' show Matrix4;

export 'events.dart' show PointerEvent;

Ian Hickson's avatar
Ian Hickson committed
14
/// A callback that receives a [PointerEvent]
15
typedef PointerRoute = void Function(PointerEvent event);
Adam Barth's avatar
Adam Barth committed
16

Ian Hickson's avatar
Ian Hickson committed
17
/// A routing table for [PointerEvent] events.
18
class PointerRouter {
19 20
  final Map<int, Map<PointerRoute, Matrix4?>> _routeMap = <int, Map<PointerRoute, Matrix4?>>{};
  final Map<PointerRoute, Matrix4?> _globalRoutes = <PointerRoute, Matrix4?>{};
Adam Barth's avatar
Adam Barth committed
21

Ian Hickson's avatar
Ian Hickson committed
22
  /// Adds a route to the routing table.
Adam Barth's avatar
Adam Barth committed
23
  ///
Ian Hickson's avatar
Ian Hickson committed
24
  /// Whenever this object routes a [PointerEvent] corresponding to
Adam Barth's avatar
Adam Barth committed
25
  /// pointer, call route.
26 27 28
  ///
  /// Routes added reentrantly within [PointerRouter.route] will take effect when
  /// routing the next event.
29 30
  void addRoute(int pointer, PointerRoute route, [Matrix4? transform]) {
    final Map<PointerRoute, Matrix4?> routes = _routeMap.putIfAbsent(
31
      pointer,
32
      () => <PointerRoute, Matrix4?>{},
33 34 35
    );
    assert(!routes.containsKey(route));
    routes[route] = transform;
Adam Barth's avatar
Adam Barth committed
36 37
  }

Ian Hickson's avatar
Ian Hickson committed
38
  /// Removes a route from the routing table.
Adam Barth's avatar
Adam Barth committed
39
  ///
Ian Hickson's avatar
Ian Hickson committed
40
  /// No longer call route when routing a [PointerEvent] corresponding to
Adam Barth's avatar
Adam Barth committed
41
  /// pointer. Requires that this route was previously added to the router.
42 43 44
  ///
  /// Routes removed reentrantly within [PointerRouter.route] will take effect
  /// immediately.
Adam Barth's avatar
Adam Barth committed
45
  void removeRoute(int pointer, PointerRoute route) {
Adam Barth's avatar
Adam Barth committed
46
    assert(_routeMap.containsKey(pointer));
47
    final Map<PointerRoute, Matrix4?> routes = _routeMap[pointer]!;
48 49
    assert(routes.containsKey(route));
    routes.remove(route);
50
    if (routes.isEmpty) {
Adam Barth's avatar
Adam Barth committed
51
      _routeMap.remove(pointer);
52
    }
Adam Barth's avatar
Adam Barth committed
53 54
  }

55 56 57 58 59 60
  /// Adds a route to the global entry in the routing table.
  ///
  /// Whenever this object routes a [PointerEvent], call route.
  ///
  /// Routes added reentrantly within [PointerRouter.route] will take effect when
  /// routing the next event.
61
  void addGlobalRoute(PointerRoute route, [Matrix4? transform]) {
62 63
    assert(!_globalRoutes.containsKey(route));
    _globalRoutes[route] = transform;
64 65 66 67 68 69 70 71 72 73
  }

  /// Removes a route from the global entry in the routing table.
  ///
  /// No longer call route when routing a [PointerEvent]. Requires that this
  /// route was previously added via [addGlobalRoute].
  ///
  /// Routes removed reentrantly within [PointerRouter.route] will take effect
  /// immediately.
  void removeGlobalRoute(PointerRoute route) {
74 75
    assert(_globalRoutes.containsKey(route));
    _globalRoutes.remove(route);
76 77
  }

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
  /// The number of global routes that have been registered.
  ///
  /// This is valid in debug builds only. In release builds, this will throw an
  /// [UnsupportedError].
  int get debugGlobalRouteCount {
    int? count;
    assert(() {
      count = _globalRoutes.length;
      return true;
    }());
    if (count != null) {
      return count!;
    }
    throw UnsupportedError('debugGlobalRouteCount is not supported in release builds');
  }

94
  @pragma('vm:notify-debugger-on-exception')
95
  void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
96
    try {
97 98
      event = event.transformed(transform);
      route(event);
99
    } catch (exception, stack) {
100
      InformationCollector? collector;
101
      assert(() {
102 103 104 105 106
        collector = () => <DiagnosticsNode>[
          DiagnosticsProperty<PointerRouter>('router', this, level: DiagnosticLevel.debug),
          DiagnosticsProperty<PointerRoute>('route', route, level: DiagnosticLevel.debug),
          DiagnosticsProperty<PointerEvent>('event', event, level: DiagnosticLevel.debug),
        ];
107 108
        return true;
      }());
109
      FlutterError.reportError(FlutterErrorDetails(
110 111 112
        exception: exception,
        stack: stack,
        library: 'gesture library',
113
        context: ErrorDescription('while routing a pointer event'),
114
        informationCollector: collector,
115 116 117 118
      ));
    }
  }

119
  /// Calls the routes registered for this pointer event.
Adam Barth's avatar
Adam Barth committed
120
  ///
121 122
  /// Routes are called in the order in which they were added to the
  /// PointerRouter object.
Ian Hickson's avatar
Ian Hickson committed
123
  void route(PointerEvent event) {
124
    final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
125
    final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.of(_globalRoutes);
126
    if (routes != null) {
127 128 129
      _dispatchEventToRoutes(
        event,
        routes,
130
        Map<PointerRoute, Matrix4?>.of(routes),
131
      );
132
    }
133 134 135 136 137
    _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
  }

  void _dispatchEventToRoutes(
    PointerEvent event,
138 139
    Map<PointerRoute, Matrix4?> referenceRoutes,
    Map<PointerRoute, Matrix4?> copiedRoutes,
140
  ) {
141
    copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
142 143 144 145
      if (referenceRoutes.containsKey(route)) {
        _dispatch(event, route, transform);
      }
    });
Adam Barth's avatar
Adam Barth committed
146 147
  }
}