pointer_router.dart 5.04 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

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

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

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

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

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

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
  /// 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.
  void addGlobalRoute(PointerRoute route) {
    assert(!_globalRoutes.contains(route));
    _globalRoutes.add(route);
  }

  /// 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) {
    assert(_globalRoutes.contains(route));
    _globalRoutes.remove(route);
  }

  void _dispatch(PointerEvent event, PointerRoute route) {
    try {
      route(event);
    } catch (exception, stack) {
      FlutterError.reportError(new FlutterErrorDetailsForPointerRouter(
        exception: exception,
        stack: stack,
        library: 'gesture library',
        context: 'while routing a pointer event',
        router: this,
        route: route,
        event: event,
        informationCollector: (StringBuffer information) {
          information.writeln('Event:');
          information.write('  $event');
        }
      ));
    }
  }

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

/// 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,
    String context,
    this.router,
    this.route,
    this.event,
130
    InformationCollector informationCollector,
131
    bool silent: false
132 133 134 135 136 137 138 139 140 141
  }) : super(
    exception: exception,
    stack: stack,
    library: library,
    context: context,
    informationCollector: informationCollector,
    silent: silent
  );

  /// The pointer router that caught the exception.
142 143 144
  ///
  /// In a typical application, this is the value of [GestureBinding.pointerRouter] on
  /// the binding ([GestureBinding.instance]).
145 146 147 148 149 150 151 152
  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;
}