binding.dart 23 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson 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 'dart:async';
import 'dart:collection';
7
import 'dart:ui' as ui show PointerDataPacket;
Ian Hickson's avatar
Ian Hickson committed
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/scheduler.dart';
Ian Hickson's avatar
Ian Hickson committed
11 12 13

import 'arena.dart';
import 'converter.dart';
14
import 'debug.dart';
Ian Hickson's avatar
Ian Hickson committed
15 16 17
import 'events.dart';
import 'hit_test.dart';
import 'pointer_router.dart';
18
import 'pointer_signal_resolver.dart';
19 20
import 'resampler.dart';

21 22 23 24 25 26
export 'dart:ui' show Offset;

export 'package:flutter/foundation.dart' show DiagnosticsNode, InformationCollector;

export 'arena.dart' show GestureArenaManager;
export 'events.dart' show PointerEvent;
27
export 'hit_test.dart' show HitTestEntry, HitTestResult, HitTestTarget;
28 29 30
export 'pointer_router.dart' show PointerRouter;
export 'pointer_signal_resolver.dart' show PointerSignalResolver;

31
typedef _HandleSampleTimeChangedCallback = void Function();
32

33 34 35 36 37 38
/// Class that implements clock used for sampling.
class SamplingClock {
  /// Returns current time.
  DateTime now() => DateTime.now();

  /// Returns a new stopwatch that uses the current time as reported by `this`.
39 40 41 42 43 44 45
  ///
  /// See also:
  ///
  ///   * [GestureBinding.debugSamplingClock], which is used in tests and
  ///     debug builds to observe [FakeAsync].
  Stopwatch stopwatch() => Stopwatch(); // flutter_ignore: stopwatch (see analyze.dart)
  // Ignore context: This is replaced by debugSampling clock in the test binding.
46 47
}

48 49 50
// Class that handles resampling of touch events for multiple pointer
// devices.
//
51 52
// The `samplingInterval` is used to determine the approximate next
// time for resampling.
53 54 55
// SchedulerBinding's `currentSystemFrameTimeStamp` is used to determine
// sample time.
class _Resampler {
56
  _Resampler(this._handlePointerEvent, this._handleSampleTimeChanged, this._samplingInterval);
57 58 59 60 61 62 63

  // Resamplers used to filter incoming pointer events.
  final Map<int, PointerEventResampler> _resamplers = <int, PointerEventResampler>{};

  // Flag to track if a frame callback has been scheduled.
  bool _frameCallbackScheduled = false;

64
  // Last frame time for resampling.
65 66
  Duration _frameTime = Duration.zero;

67
  // Time since `_frameTime` was updated.
68 69
  Stopwatch _frameTimeAge = Stopwatch(); // flutter_ignore: stopwatch (see analyze.dart)
  // Ignore context: This is tested safely outside of FakeAsync.
70

71 72 73 74 75 76 77 78 79 80
  // Last sample time and time stamp of last event.
  //
  // Only used for debugPrint of resampling margin.
  Duration _lastSampleTime = Duration.zero;
  Duration _lastEventTime = Duration.zero;

  // Callback used to handle pointer events.
  final HandleEventCallback _handlePointerEvent;

  // Callback used to handle sample time changes.
81
  final _HandleSampleTimeChangedCallback _handleSampleTimeChanged;
82

83 84 85 86 87 88
  // Interval used for sampling.
  final Duration _samplingInterval;

  // Timer used to schedule resampling.
  Timer? _timer;

89
  // Add `event` for resampling or dispatch it directly if
90
  // not a touch event.
91
  void addOrDispatch(PointerEvent event) {
92
    // Add touch event to resampler or dispatch pointer event directly.
93 94 95 96 97 98 99 100 101 102 103
    if (event.kind == PointerDeviceKind.touch) {
      // Save last event time for debugPrint of resampling margin.
      _lastEventTime = event.timeStamp;

      final PointerEventResampler resampler = _resamplers.putIfAbsent(
        event.device,
        () => PointerEventResampler(),
      );
      resampler.addEvent(event);
    } else {
      _handlePointerEvent(event);
104 105 106 107 108
    }
  }

  // Sample and dispatch events.
  //
109
  // The `samplingOffset` is relative to the current frame time, which
110
  // can be in the past when we're not actively resampling.
111
  //
112 113
  // The `samplingClock` is the clock used to determine frame time age.
  void sample(Duration samplingOffset, SamplingClock clock) {
114
    final SchedulerBinding scheduler = SchedulerBinding.instance;
115

116 117 118 119 120 121 122 123 124
    // Initialize `_frameTime` if needed. This will be used for periodic
    // sampling when frame callbacks are not received.
    if (_frameTime == Duration.zero) {
      _frameTime = Duration(milliseconds: clock.now().millisecondsSinceEpoch);
      _frameTimeAge = clock.stopwatch()..start();
    }

    // Schedule periodic resampling if `_timer` is not already active.
    if (_timer?.isActive != true) {
125
      _timer = Timer.periodic(_samplingInterval, (_) => _onSampleTimeChanged());
126 127 128 129 130 131 132 133 134 135 136
    }

    // Calculate the effective frame time by taking the number
    // of sampling intervals since last time `_frameTime` was
    // updated into account. This allows us to advance sample
    // time without having to receive frame callbacks.
    final int samplingIntervalUs = _samplingInterval.inMicroseconds;
    final int elapsedIntervals = _frameTimeAge.elapsedMicroseconds ~/ samplingIntervalUs;
    final int elapsedUs = elapsedIntervals * samplingIntervalUs;
    final Duration frameTime = _frameTime + Duration(microseconds: elapsedUs);

137 138 139 140
    // Determine sample time by adding the offset to the current
    // frame time. This is expected to be in the past and not
    // result in any dispatched events unless we're actively
    // resampling events.
141
    final Duration sampleTime = frameTime + samplingOffset;
142

143 144
    // Determine next sample time by adding the sampling interval
    // to the current sample time.
145
    final Duration nextSampleTime = sampleTime + _samplingInterval;
146

147 148 149
    // Iterate over active resamplers and sample pointer events for
    // current sample time.
    for (final PointerEventResampler resampler in _resamplers.values) {
150
      resampler.sample(sampleTime, nextSampleTime, _handlePointerEvent);
151 152 153 154 155 156 157 158 159 160
    }

    // Remove inactive resamplers.
    _resamplers.removeWhere((int key, PointerEventResampler resampler) {
      return !resampler.hasPendingEvents && !resampler.isDown;
    });

    // Save last sample time for debugPrint of resampling margin.
    _lastSampleTime = sampleTime;

161 162 163 164 165 166
    // Early out if another call to `sample` isn't needed.
    if (_resamplers.isEmpty) {
      _timer!.cancel();
      return;
    }

167
    // Schedule a frame callback if another call to `sample` is needed.
168
    if (!_frameCallbackScheduled) {
169
      _frameCallbackScheduled = true;
170 171 172
      // Add a post frame callback as this avoids producing unnecessary
      // frames but ensures that sampling phase is adjusted to frame
      // time when frames are produced.
173
      scheduler.addPostFrameCallback((_) {
174 175 176 177 178
        _frameCallbackScheduled = false;
        // We use `currentSystemFrameTimeStamp` here as it's critical that
        // sample time is in the same clock as the event time stamps, and
        // never adjusted or scaled like `currentFrameTimeStamp`.
        _frameTime = scheduler.currentSystemFrameTimeStamp;
179 180 181 182 183 184
        _frameTimeAge.reset();
        // Reset timer to match phase of latest frame callback.
        _timer?.cancel();
        _timer = Timer.periodic(_samplingInterval, (_) => _onSampleTimeChanged());
        // Trigger an immediate sample time change.
        _onSampleTimeChanged();
185
      }, debugLabel: 'Resampler.startTimer');
186 187 188 189 190 191 192 193 194
    }
  }

  // Stop all resampling and dispatched any queued events.
  void stop() {
    for (final PointerEventResampler resampler in _resamplers.values) {
      resampler.stop(_handlePointerEvent);
    }
    _resamplers.clear();
195
    _frameTime = Duration.zero;
196
    _timer?.cancel();
197 198 199 200 201 202
  }

  void _onSampleTimeChanged() {
    assert(() {
      if (debugPrintResamplingMargin) {
        final Duration resamplingMargin = _lastEventTime - _lastSampleTime;
203
        debugPrint('$resamplingMargin');
204 205 206 207
      }
      return true;
    }());
    _handleSampleTimeChanged();
208 209 210 211 212 213 214 215 216 217
  }
}

// The default sampling offset.
//
// Sampling offset is relative to presentation time. If we produce frames
// 16.667 ms before presentation and input rate is ~60hz, worst case latency
// is 33.334 ms. This however assumes zero latency from the input driver.
// 4.666 ms margin is added for this.
const Duration _defaultSamplingOffset = Duration(milliseconds: -38);
Ian Hickson's avatar
Ian Hickson committed
218

219 220 221
// The sampling interval.
//
// Sampling interval is used to determine the approximate time for subsequent
222 223
// sampling. This is used to sample events when frame callbacks are not
// being received and decide if early processing of up and removed events
224 225 226
// is appropriate. 16667 us for 60hz sampling interval.
const Duration _samplingInterval = Duration(microseconds: 16667);

227
/// A binding for the gesture subsystem.
228 229 230 231 232 233
///
/// ## Lifecycle of pointer events and the gesture arena
///
/// ### [PointerDownEvent]
///
/// When a [PointerDownEvent] is received by the [GestureBinding] (from
234
/// [dart:ui.PlatformDispatcher.onPointerDataPacket], as interpreted by the
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
/// [PointerEventConverter]), a [hitTest] is performed to determine which
/// [HitTestTarget] nodes are affected. (Other bindings are expected to
/// implement [hitTest] to defer to [HitTestable] objects. For example, the
/// rendering layer defers to the [RenderView] and the rest of the render object
/// hierarchy.)
///
/// The affected nodes then are given the event to handle ([dispatchEvent] calls
/// [HitTestTarget.handleEvent] for each affected node). If any have relevant
/// [GestureRecognizer]s, they provide the event to them using
/// [GestureRecognizer.addPointer]. This typically causes the recognizer to
/// register with the [PointerRouter] to receive notifications regarding the
/// pointer in question.
///
/// Once the hit test and dispatching logic is complete, the event is then
/// passed to the aforementioned [PointerRouter], which passes it to any objects
/// that have registered interest in that event.
///
/// Finally, the [gestureArena] is closed for the given pointer
/// ([GestureArenaManager.close]), which begins the process of selecting a
/// gesture to win that pointer.
///
/// ### Other events
///
/// A pointer that is [PointerEvent.down] may send further events, such as
/// [PointerMoveEvent], [PointerUpEvent], or [PointerCancelEvent]. These are
260 261 262
/// sent to the same [HitTestTarget] nodes as were found when the
/// [PointerDownEvent] was received (even if they have since been disposed; it is
/// the responsibility of those objects to be aware of that possibility).
263 264 265 266 267 268
///
/// Then, the events are routed to any still-registered entrants in the
/// [PointerRouter]'s table for that pointer.
///
/// When a [PointerUpEvent] is received, the [GestureArenaManager.sweep] method
/// is invoked to force the gesture arena logic to terminate if necessary.
269
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
270
  @override
Ian Hickson's avatar
Ian Hickson committed
271 272 273
  void initInstances() {
    super.initInstances();
    _instance = this;
274
    platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
Ian Hickson's avatar
Ian Hickson committed
275 276
  }

277 278 279 280 281 282 283 284
  /// The singleton instance of this object.
  ///
  /// Provides access to the features exposed by this mixin. The binding must
  /// be initialized before using this getter; this is typically done by calling
  /// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
  static GestureBinding get instance => BindingBase.checkInstance(_instance);
  static GestureBinding? _instance;

285 286 287 288 289 290
  @override
  void unlocked() {
    super.unlocked();
    _flushPointerEventQueue();
  }

291
  final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
292

293
  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
294 295
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
296
    try {
297
      _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, _devicePixelRatioForView));
298 299 300 301 302 303 304 305 306 307
      if (!locked) {
        _flushPointerEventQueue();
      }
    } catch (error, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: error,
        stack: stack,
        library: 'gestures library',
        context: ErrorDescription('while handling a pointer data packet'),
      ));
308
    }
309 310
  }

311 312 313 314
  double? _devicePixelRatioForView(int viewId) {
    return platformDispatcher.view(id: viewId)?.devicePixelRatio;
  }

315 316
  /// Dispatch a [PointerCancelEvent] for the given pointer soon.
  ///
317
  /// The pointer event will be dispatched before the next pointer event and
318 319
  /// before the end of the microtask but not within this function call.
  void cancelPointer(int pointer) {
320
    if (_pendingPointerEvents.isEmpty && !locked) {
321
      scheduleMicrotask(_flushPointerEventQueue);
322
    }
323
    _pendingPointerEvents.addFirst(PointerCancelEvent(pointer: pointer));
Ian Hickson's avatar
Ian Hickson committed
324 325
  }

326 327
  void _flushPointerEventQueue() {
    assert(!locked);
328

329
    while (_pendingPointerEvents.isNotEmpty) {
330
      handlePointerEvent(_pendingPointerEvents.removeFirst());
331
    }
332 333
  }

Ian Hickson's avatar
Ian Hickson committed
334
  /// A router that routes all pointer events received from the engine.
335
  final PointerRouter pointerRouter = PointerRouter();
Ian Hickson's avatar
Ian Hickson committed
336 337 338

  /// The gesture arenas used for disambiguating the meaning of sequences of
  /// pointer events.
339
  final GestureArenaManager gestureArena = GestureArenaManager();
Ian Hickson's avatar
Ian Hickson committed
340

341 342
  /// The resolver used for determining which widget handles a
  /// [PointerSignalEvent].
343 344
  final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();

Ian Hickson's avatar
Ian Hickson committed
345 346
  /// State for all pointers which are currently down.
  ///
347 348 349 350 351 352 353 354 355 356 357 358
  /// This map caches the hit test result done when the pointer goes down
  /// ([PointerDownEvent] and [PointerPanZoomStartEvent]). This hit test result
  /// will be used throughout the entire pointer interaction; that is, the
  /// pointer is seen as pointing to the same place even if it has moved away
  /// until pointer goes up ([PointerUpEvent] and [PointerPanZoomEndEvent]).
  /// This matches the expected gesture interaction with a button, and allows
  /// devices that don't support hovering to perform as few hit tests as
  /// possible.
  ///
  /// On the other hand, hovering requires hit testing on almost every frame.
  /// This is handled in [RendererBinding] and [MouseTracker], and will ignore
  /// the results cached here.
359
  final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
Ian Hickson's avatar
Ian Hickson committed
360

361 362 363 364 365 366 367 368 369 370 371
  /// Dispatch an event to the targets found by a hit test on its position.
  ///
  /// This method sends the given event to [dispatchEvent] based on event types:
  ///
  ///  * [PointerDownEvent]s and [PointerSignalEvent]s are dispatched to the
  ///    result of a new [hitTest].
  ///  * [PointerUpEvent]s and [PointerMoveEvent]s are dispatched to the result of hit test of the
  ///    preceding [PointerDownEvent]s.
  ///  * [PointerHoverEvent]s, [PointerAddedEvent]s, and [PointerRemovedEvent]s
  ///    are dispatched without a hit test result.
  void handlePointerEvent(PointerEvent event) {
372
    assert(!locked);
373 374 375

    if (resamplingEnabled) {
      _resampler.addOrDispatch(event);
376
      _resampler.sample(samplingOffset, samplingClock);
377 378 379 380 381 382 383 384 385 386
      return;
    }

    // Stop resampler if resampling is not enabled. This is a no-op if
    // resampling was never enabled.
    _resampler.stop();
    _handlePointerEventImmediately(event);
  }

  void _handlePointerEventImmediately(PointerEvent event) {
387
    HitTestResult? hitTestResult;
388
    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
389
      assert(!_hitTests.containsKey(event.pointer), 'Pointer of ${event.toString(minLevel: DiagnosticLevel.debug)} unexpectedly has a HitTestResult associated with it.');
390
      hitTestResult = HitTestResult();
391
      hitTestInView(hitTestResult, event.position, event.viewId);
392
      if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
393 394
        _hitTests[event.pointer] = hitTestResult;
      }
395
      assert(() {
396
        if (debugPrintHitTestResults) {
397
          debugPrint('${event.toString(minLevel: DiagnosticLevel.debug)}: $hitTestResult');
398
        }
399
        return true;
400
      }());
401
    } else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
402
      hitTestResult = _hitTests.remove(event.pointer);
403
    } else if (event.down || event is PointerPanZoomUpdateEvent) {
404
      // Because events that occur with the pointer down (like
405
      // [PointerMoveEvent]s) should be dispatched to the same place that their
406 407 408 409 410 411
      // initial PointerDownEvent was, we want to re-use the path we found when
      // the pointer went down, rather than do hit detection each time we get
      // such an event.
      hitTestResult = _hitTests[event.pointer];
    }
    assert(() {
412
      if (debugPrintMouseHoverEvents && event is PointerHoverEvent) {
413
        debugPrint('$event');
414
      }
415 416 417 418 419 420
      return true;
    }());
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      dispatchEvent(event, hitTestResult);
Ian Hickson's avatar
Ian Hickson committed
421 422 423
    }
  }

424 425
  /// Determine which [HitTestTarget] objects are located at a given position in
  /// the specified view.
426
  @override // from HitTestable
427
  void hitTestInView(HitTestResult result, Offset position, int viewId) {
428
    result.add(HitTestEntry(this));
Ian Hickson's avatar
Ian Hickson committed
429 430
  }

431 432 433 434 435 436 437 438 439
  @override // from HitTestable
  @Deprecated(
    'Use hitTestInView and specify the view to hit test. '
    'This feature was deprecated after v3.11.0-20.0.pre.',
  )
  void hitTest(HitTestResult result, Offset position) {
    hitTestInView(result, position, platformDispatcher.implicitView!.viewId);
  }

440
  /// Dispatch an event to [pointerRouter] and the path of a hit test result.
441
  ///
442 443 444 445
  /// The `event` is routed to [pointerRouter]. If the `hitTestResult` is not
  /// null, the event is also sent to every [HitTestTarget] in the entries of the
  /// given [HitTestResult]. Any exceptions from the handlers are caught.
  ///
446 447
  /// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or
  /// [PointerRemovedEvent]s.
448
  @override // from HitTestDispatcher
449
  @pragma('vm:notify-debugger-on-exception')
450
  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
451
    assert(!locked);
452 453 454
    // No hit test information implies that this is a [PointerAddedEvent] or
    // [PointerRemovedEvent]. These events are specially routed here; other
    // events will be routed through the `handleEvent` below.
455
    if (hitTestResult == null) {
456
      assert(event is PointerAddedEvent || event is PointerRemovedEvent);
457 458 459 460 461 462 463
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
          exception: exception,
          stack: stack,
          library: 'gesture library',
464
          context: ErrorDescription('while dispatching a non-hit-tested pointer event'),
465
          event: event,
466 467 468
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty),
          ],
469 470 471 472
        ));
      }
      return;
    }
473
    for (final HitTestEntry entry in hitTestResult.path) {
474
      try {
475
        entry.target.handleEvent(event.transformed(entry.transform), entry);
476
      } catch (exception, stack) {
477
        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
478 479 480
          exception: exception,
          stack: stack,
          library: 'gesture library',
481
          context: ErrorDescription('while dispatching a pointer event'),
482 483
          event: event,
          hitTestEntry: entry,
484 485 486 487
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty),
            DiagnosticsProperty<HitTestTarget>('Target', entry.target, style: DiagnosticsTreeStyle.errorProperty),
          ],
488
        ));
489 490
      }
    }
Ian Hickson's avatar
Ian Hickson committed
491 492
  }

493
  @override // from HitTestTarget
Ian Hickson's avatar
Ian Hickson committed
494 495
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
496
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
Ian Hickson's avatar
Ian Hickson committed
497
      gestureArena.close(event.pointer);
498
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
Ian Hickson's avatar
Ian Hickson committed
499
      gestureArena.sweep(event.pointer);
500 501
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
Ian Hickson's avatar
Ian Hickson committed
502 503
    }
  }
504

505 506 507 508 509 510 511 512 513 514
  /// Reset states of [GestureBinding].
  ///
  /// This clears the hit test records.
  ///
  /// This is typically called between tests.
  @protected
  void resetGestureBinding() {
    _hitTests.clear();
  }

515 516
  void _handleSampleTimeChanged() {
    if (!locked) {
517
      if (resamplingEnabled) {
518
        _resampler.sample(samplingOffset, samplingClock);
519 520 521 522
      }
      else {
        _resampler.stop();
      }
523 524 525
    }
  }

526 527 528 529 530 531 532 533 534 535 536 537
  /// Overrides the sampling clock for debugging and testing.
  ///
  /// This value is ignored in non-debug builds.
  @protected
  SamplingClock? get debugSamplingClock => null;

  /// Provides access to the current [DateTime] and `StopWatch` objects for
  /// sampling.
  ///
  /// Overridden by [debugSamplingClock] for debug builds and testing. Using
  /// this object under test will maintain synchronization with [FakeAsync].
  SamplingClock get samplingClock {
538 539 540
    SamplingClock value = SamplingClock();
    assert(() {
      final SamplingClock? debugValue = debugSamplingClock;
541
      if (debugValue != null) {
542
        value = debugValue;
543
      }
544 545 546 547 548
      return true;
    }());
    return value;
  }

549 550 551
  // Resampler used to filter incoming pointer events when resampling
  // is enabled.
  late final _Resampler _resampler = _Resampler(
552
    _handlePointerEventImmediately,
553
    _handleSampleTimeChanged,
554
    _samplingInterval,
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  );

  /// Enable pointer event resampling for touch devices by setting
  /// this to true.
  ///
  /// Resampling results in smoother touch event processing at the
  /// cost of some added latency. Devices with low frequency sensors
  /// or when the frequency is not a multiple of the display frequency
  /// (e.g., 120Hz input and 90Hz display) benefit from this.
  ///
  /// This is typically set during application initialization but
  /// can be adjusted dynamically in case the application only
  /// wants resampling for some period of time.
  bool resamplingEnabled = false;

  /// Offset relative to current frame time that should be used for
  /// resampling. The [samplingOffset] is expected to be negative.
  /// Non-negative [samplingOffset] is allowed but will effectively
  /// disable resampling.
  Duration samplingOffset = _defaultSamplingOffset;
Ian Hickson's avatar
Ian Hickson committed
575
}
576 577

/// Variant of [FlutterErrorDetails] with extra fields for the gesture
578
/// library's binding's pointer event dispatcher ([GestureBinding.dispatchEvent]).
579 580 581 582 583 584 585
class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
  /// Creates a [FlutterErrorDetailsForPointerEventDispatcher] object with the given
  /// arguments setting the object's properties.
  ///
  /// The gesture library calls this constructor when catching an exception
  /// that will subsequently be reported using [FlutterError.onError].
  const FlutterErrorDetailsForPointerEventDispatcher({
586 587 588 589
    required super.exception,
    super.stack,
    super.library,
    super.context,
590 591
    this.event,
    this.hitTestEntry,
592 593 594
    super.informationCollector,
    super.silent,
  });
595 596

  /// The pointer event that was being routed when the exception was raised.
597
  final PointerEvent? event;
598 599

  /// The hit test result entry for the object whose handleEvent method threw
600
  /// the exception. May be null if no hit test entry is associated with the
601 602
  /// event (e.g. [PointerHoverEvent]s, [PointerAddedEvent]s, and
  /// [PointerRemovedEvent]s).
603
  ///
604 605
  /// The target object itself is given by the [HitTestEntry.target] property of
  /// the hitTestEntry object.
606
  final HitTestEntry? hitTestEntry;
607
}