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

5 6
import 'dart:collection';

7
import 'package:flutter/foundation.dart';
8
import 'package:vector_math/vector_math_64.dart';
9

Kris Giesing's avatar
Kris Giesing committed
10
import 'events.dart';
Adam Barth's avatar
Adam Barth committed
11

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

Ian Hickson's avatar
Ian Hickson committed
15
/// A routing table for [PointerEvent] events.
16
class PointerRouter {
17 18
  final Map<int, LinkedHashSet<_RouteEntry>> _routeMap = <int, LinkedHashSet<_RouteEntry>>{};
  final LinkedHashSet<_RouteEntry> _globalRoutes = LinkedHashSet<_RouteEntry>();
Adam Barth's avatar
Adam Barth committed
19

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

Ian Hickson's avatar
Ian Hickson committed
33
  /// Removes a route from the routing table.
Adam Barth's avatar
Adam Barth committed
34
  ///
Ian Hickson's avatar
Ian Hickson committed
35
  /// No longer call route when routing a [PointerEvent] corresponding to
Adam Barth's avatar
Adam Barth committed
36
  /// pointer. Requires that this route was previously added to the router.
37 38 39
  ///
  /// Routes removed reentrantly within [PointerRouter.route] will take effect
  /// immediately.
Adam Barth's avatar
Adam Barth committed
40
  void removeRoute(int pointer, PointerRoute route) {
Adam Barth's avatar
Adam Barth committed
41
    assert(_routeMap.containsKey(pointer));
42 43 44
    final LinkedHashSet<_RouteEntry> routes = _routeMap[pointer];
    assert(routes.any(_RouteEntry.isRoutePredicate(route)));
    routes.removeWhere(_RouteEntry.isRoutePredicate(route));
Adam Barth's avatar
Adam Barth committed
45 46 47 48
    if (routes.isEmpty)
      _routeMap.remove(pointer);
  }

49 50 51 52 53 54
  /// 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.
55 56 57
  void addGlobalRoute(PointerRoute route, [Matrix4 transform]) {
    assert(!_globalRoutes.any(_RouteEntry.isRoutePredicate(route)));
    _globalRoutes.add(_RouteEntry(route: route, transform: transform));
58 59 60 61 62 63 64 65 66 67
  }

  /// 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) {
68 69
    assert(_globalRoutes.any(_RouteEntry.isRoutePredicate(route)));
    _globalRoutes.removeWhere(_RouteEntry.isRoutePredicate(route));
70 71
  }

72
  void _dispatch(PointerEvent event, _RouteEntry entry) {
73
    try {
74 75
      event = event.transformed(entry.transform);
      entry.route(event);
76
    } catch (exception, stack) {
77
      FlutterError.reportError(FlutterErrorDetailsForPointerRouter(
78 79 80
        exception: exception,
        stack: stack,
        library: 'gesture library',
81
        context: ErrorDescription('while routing a pointer event'),
82
        router: this,
83
        route: entry.route,
84
        event: event,
85 86
        informationCollector: () sync* {
          yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
87
        },
88 89 90 91
      ));
    }
  }

92
  /// Calls the routes registered for this pointer event.
Adam Barth's avatar
Adam Barth committed
93
  ///
94 95
  /// Routes are called in the order in which they were added to the
  /// PointerRouter object.
Ian Hickson's avatar
Ian Hickson committed
96
  void route(PointerEvent event) {
97 98
    final LinkedHashSet<_RouteEntry> routes = _routeMap[event.pointer];
    final List<_RouteEntry> globalRoutes = List<_RouteEntry>.from(_globalRoutes);
99
    if (routes != null) {
100 101 102
      for (_RouteEntry entry in List<_RouteEntry>.from(routes)) {
        if (routes.any(_RouteEntry.isRoutePredicate(entry.route)))
          _dispatch(event, entry);
103 104
      }
    }
105 106 107
    for (_RouteEntry entry in globalRoutes) {
      if (_globalRoutes.any(_RouteEntry.isRoutePredicate(entry.route)))
        _dispatch(event, entry);
108
    }
Adam Barth's avatar
Adam Barth committed
109 110
  }
}
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126

/// Variant of [FlutterErrorDetails] with extra fields for the gestures
/// library's pointer router ([PointerRouter]).
///
/// See also [FlutterErrorDetailsForPointerEventDispatcher], which is also used
/// by the gestures library.
class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails {
  /// Creates a [FlutterErrorDetailsForPointerRouter] object with the given
  /// arguments setting the object's properties.
  ///
  /// The gestures library calls this constructor when catching an exception
  /// that will subsequently be reported using [FlutterError.onError].
  const FlutterErrorDetailsForPointerRouter({
    dynamic exception,
    StackTrace stack,
    String library,
127
    DiagnosticsNode context,
128 129 130
    this.router,
    this.route,
    this.event,
131
    InformationCollector informationCollector,
132
    bool silent = false,
133 134 135 136 137 138 139 140 141 142
  }) : super(
    exception: exception,
    stack: stack,
    library: library,
    context: context,
    informationCollector: informationCollector,
    silent: silent
  );

  /// The pointer router that caught the exception.
143 144 145
  ///
  /// In a typical application, this is the value of [GestureBinding.pointerRouter] on
  /// the binding ([GestureBinding.instance]).
146 147 148 149 150 151 152 153
  final PointerRouter router;

  /// The callback that threw the exception.
  final PointerRoute route;

  /// The pointer event that was being routed when the exception was raised.
  final PointerEvent event;
}
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

typedef _RouteEntryPredicate = bool Function(_RouteEntry entry);

class _RouteEntry {
  const _RouteEntry({
    @required this.route,
    @required this.transform,
  });

  final PointerRoute route;
  final Matrix4 transform;

  static _RouteEntryPredicate isRoutePredicate(PointerRoute route) {
    return (_RouteEntry entry) => entry.route == route;
  }
}