mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #912 from abarth/tap
Add tap, show press, and long press gestures and use tap in IconButton
This commit is contained in:
commit
df5e2f7812
9
sky/packages/sky/lib/gestures/README.md
Normal file
9
sky/packages/sky/lib/gestures/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
Sky Gestures
|
||||
============
|
||||
|
||||
Code for recognizing and representing user interface gestures.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* [`package:sky/base`](../base)
|
||||
@ -7,14 +7,26 @@ enum GestureDisposition {
|
||||
rejected
|
||||
}
|
||||
|
||||
/// Represents an object participating in an arena.
|
||||
///
|
||||
/// Receives callbacks from the GestureArena to notify the object when it wins
|
||||
/// or loses a gesture negotiation. Exactly one of [acceptGesture] or
|
||||
/// [rejectGesture] will be called for each arena key this member was added to,
|
||||
/// regardless of what caused the arena to be resolved. For example, if a
|
||||
/// member resolves the arena itself, that member still receives an
|
||||
/// [acceptGesture] callback.
|
||||
abstract class GestureArenaMember {
|
||||
/// Called when this member wins the arena.
|
||||
/// Called when this member wins the arena for the given key.
|
||||
void acceptGesture(Object key);
|
||||
|
||||
/// Called when this member loses the arena.
|
||||
/// Called when this member loses the arena for the given key.
|
||||
void rejectGesture(Object key);
|
||||
}
|
||||
|
||||
/// An interface to information to an arena
|
||||
///
|
||||
/// A given [GestureArenaMember] can have multiple entries in multiple arenas
|
||||
/// with different keys.
|
||||
class GestureArenaEntry {
|
||||
GestureArenaEntry._(this._arena, this._key, this._member);
|
||||
|
||||
@ -23,6 +35,8 @@ class GestureArenaEntry {
|
||||
final GestureArenaMember _member;
|
||||
|
||||
/// Call this member to claim victory (with accepted) or admit defeat (with rejected).
|
||||
///
|
||||
/// It's fine to attempt to resolve an arena that is already resolved.
|
||||
void resolve(GestureDisposition disposition) {
|
||||
_arena._resolve(_key, _member, disposition);
|
||||
}
|
||||
@ -32,6 +46,8 @@ class GestureArenaEntry {
|
||||
class GestureArena {
|
||||
final Map<Object, List<GestureArenaMember>> _arenas = new Map<Object, List<GestureArenaMember>>();
|
||||
|
||||
static final GestureArena instance = new GestureArena();
|
||||
|
||||
GestureArenaEntry add(Object key, GestureArenaMember member) {
|
||||
List<GestureArenaMember> members = _arenas.putIfAbsent(key, () => new List<GestureArenaMember>());
|
||||
members.add(member);
|
||||
@ -40,10 +56,13 @@ class GestureArena {
|
||||
|
||||
void _resolve(Object key, GestureArenaMember member, GestureDisposition disposition) {
|
||||
List<GestureArenaMember> members = _arenas[key];
|
||||
if (members == null)
|
||||
return; // This arena has already resolved.
|
||||
assert(members != null);
|
||||
assert(members.contains(member));
|
||||
if (disposition == GestureDisposition.rejected) {
|
||||
members.remove(member);
|
||||
member.rejectGesture(key);
|
||||
if (members.length == 1) {
|
||||
_arenas.remove(key);
|
||||
members.first.acceptGesture(key);
|
||||
@ -52,7 +71,6 @@ class GestureArena {
|
||||
}
|
||||
} else {
|
||||
assert(disposition == GestureDisposition.accepted);
|
||||
List<GestureArenaMember> members = _arenas[key];
|
||||
_arenas.remove(key);
|
||||
for (GestureArenaMember rejectedMember in members) {
|
||||
if (rejectedMember != member)
|
||||
|
||||
23
sky/packages/sky/lib/gestures/constants.dart
Normal file
23
sky/packages/sky/lib/gestures/constants.dart
Normal file
@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
// Modeled after Android's ViewConfiguration:
|
||||
// https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/ViewConfiguration.java
|
||||
|
||||
const Duration kLongPressTimeout = const Duration(milliseconds: 500);
|
||||
const Duration kTapTimeout = const Duration(milliseconds: 100);
|
||||
const Duration kJumpTapTimeout = const Duration(milliseconds: 500);
|
||||
const Duration kDoubleTapTimeout = const Duration(milliseconds: 300);
|
||||
const Duration kDoubleTapMinTime = const Duration(milliseconds: 40);
|
||||
const Duration kHoverTapTimeout = const Duration(milliseconds: 150);
|
||||
const Duration kZoomControlsTimeout = const Duration(milliseconds: 3000);
|
||||
const double kHoverTapSlop = 20.0; // Logical pixels
|
||||
const double kEdgeSlop = 12.0; // Logical pixels
|
||||
const double kTouchSlop = 8.0; // Logical pixels
|
||||
const double kDoubleTapTouchSlop = kTouchSlop; // Logical pixels
|
||||
const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
|
||||
const double kDoubleTapSlop = 100.0; // Logical pixels
|
||||
const double kWindowTouchSlop = 16.0; // Logical pixels
|
||||
const double kMinFlingVelocity = 50.0; // TODO(abarth): Which units is this in?
|
||||
const double kMaxFlingVelocity = 8000.0; // TODO(abarth): Which units is this in?
|
||||
29
sky/packages/sky/lib/gestures/long_press.dart
Normal file
29
sky/packages/sky/lib/gestures/long_press.dart
Normal file
@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/arena.dart';
|
||||
import 'package:sky/gestures/constants.dart';
|
||||
import 'package:sky/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureLongPressListener();
|
||||
|
||||
class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
LongPressGestureRecognizer({ PointerRouter router, this.onLongPress })
|
||||
: super(router: router, deadline: kTapTimeout + kLongPressTimeout);
|
||||
|
||||
GestureLongPressListener onLongPress;
|
||||
|
||||
void didExceedDeadline() {
|
||||
resolve(GestureDisposition.accepted);
|
||||
onLongPress();
|
||||
}
|
||||
|
||||
void handlePrimaryPointer(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerup')
|
||||
resolve(GestureDisposition.rejected);
|
||||
}
|
||||
}
|
||||
148
sky/packages/sky/lib/gestures/recognizer.dart
Normal file
148
sky/packages/sky/lib/gestures/recognizer.dart
Normal file
@ -0,0 +1,148 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/arena.dart';
|
||||
import 'package:sky/gestures/constants.dart';
|
||||
|
||||
abstract class GestureRecognizer extends GestureArenaMember {
|
||||
GestureRecognizer({ PointerRouter router }) : _router = router;
|
||||
|
||||
PointerRouter _router;
|
||||
|
||||
final List<GestureArenaEntry> _entries = new List<GestureArenaEntry>();
|
||||
final Set<int> _trackedPointers = new Set<int>();
|
||||
|
||||
/// The primary entry point for users of this class.
|
||||
void addPointer(sky.PointerEvent event);
|
||||
|
||||
void handleEvent(sky.PointerEvent event);
|
||||
void acceptGesture(int key);
|
||||
void rejectGesture(int key);
|
||||
void didStopTrackingLastPointer();
|
||||
|
||||
void resolve(GestureDisposition disposition) {
|
||||
List<GestureArenaEntry> localEntries = new List.from(_entries);
|
||||
_entries.clear();
|
||||
for (GestureArenaEntry entry in localEntries)
|
||||
entry.resolve(disposition);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
resolve(GestureDisposition.rejected);
|
||||
for (int pointer in _trackedPointers)
|
||||
_router.removeRoute(pointer, handleEvent);
|
||||
_trackedPointers.clear();
|
||||
assert(_entries.isEmpty);
|
||||
_router = null;
|
||||
}
|
||||
|
||||
void startTrackingPointer(int pointer) {
|
||||
_router.addRoute(pointer, handleEvent);
|
||||
_trackedPointers.add(pointer);
|
||||
_entries.add(GestureArena.instance.add(pointer, this));
|
||||
}
|
||||
|
||||
void stopTrackingPointer(int pointer) {
|
||||
_router.removeRoute(pointer, handleEvent);
|
||||
_trackedPointers.remove(pointer);
|
||||
if (_trackedPointers.isEmpty)
|
||||
didStopTrackingLastPointer();
|
||||
}
|
||||
|
||||
void stopTrackingIfPointerNoLongerDown(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerup' || event.type == 'pointercancel')
|
||||
stopTrackingPointer(event.pointer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum GestureRecognizerState {
|
||||
ready,
|
||||
possible,
|
||||
defunct
|
||||
}
|
||||
|
||||
sky.Point _getPoint(sky.PointerEvent event) {
|
||||
return new sky.Point(event.x, event.y);
|
||||
}
|
||||
|
||||
abstract class PrimaryPointerGestureRecognizer extends GestureRecognizer {
|
||||
PrimaryPointerGestureRecognizer({ PointerRouter router, this.deadline })
|
||||
: super(router: router);
|
||||
|
||||
final Duration deadline;
|
||||
|
||||
GestureRecognizerState state = GestureRecognizerState.ready;
|
||||
int primaryPointer;
|
||||
sky.Point initialPosition;
|
||||
Timer _timer;
|
||||
|
||||
void addPointer(sky.PointerEvent event) {
|
||||
startTrackingPointer(event.pointer);
|
||||
if (state == GestureRecognizerState.ready) {
|
||||
state = GestureRecognizerState.possible;
|
||||
primaryPointer = event.pointer;
|
||||
initialPosition = _getPoint(event);
|
||||
if (deadline != null)
|
||||
_timer = new Timer(deadline, didExceedDeadline);
|
||||
}
|
||||
}
|
||||
|
||||
void handleEvent(sky.PointerEvent event) {
|
||||
assert(state != GestureRecognizerState.ready);
|
||||
if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
|
||||
// TODO(abarth): Maybe factor the slop handling out into a separate class?
|
||||
if (event.type == 'pointermove' && _getDistance(event) > kTouchSlop)
|
||||
resolve(GestureDisposition.rejected);
|
||||
else
|
||||
handlePrimaryPointer(event);
|
||||
}
|
||||
stopTrackingIfPointerNoLongerDown(event);
|
||||
}
|
||||
|
||||
/// Override to provide behavior for the primary pointer when the gesture is still possible.
|
||||
void handlePrimaryPointer(sky.PointerEvent event);
|
||||
|
||||
/// Override to be notified with [deadline] is exceeded.
|
||||
///
|
||||
/// You must override this function if you supply a [deadline].
|
||||
void didExceedDeadline() {
|
||||
assert(deadline == null);
|
||||
}
|
||||
|
||||
void acceptGesture(int pointer) {
|
||||
}
|
||||
|
||||
void rejectGesture(int pointer) {
|
||||
_stopTimer();
|
||||
if (pointer == primaryPointer)
|
||||
state = GestureRecognizerState.defunct;
|
||||
}
|
||||
|
||||
void didStopTrackingLastPointer() {
|
||||
state = GestureRecognizerState.ready;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_stopTimer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _stopTimer() {
|
||||
if (_timer != null) {
|
||||
_timer.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
double _getDistance(sky.PointerEvent event) {
|
||||
sky.Offset offset = _getPoint(event) - initialPosition;
|
||||
return offset.distance;
|
||||
}
|
||||
|
||||
}
|
||||
31
sky/packages/sky/lib/gestures/show_press.dart
Normal file
31
sky/packages/sky/lib/gestures/show_press.dart
Normal file
@ -0,0 +1,31 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/arena.dart';
|
||||
import 'package:sky/gestures/constants.dart';
|
||||
import 'package:sky/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureShowPressListener();
|
||||
|
||||
class ShowPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
ShowPressGestureRecognizer({ PointerRouter router, this.onShowPress })
|
||||
: super(router: router, deadline: kTapTimeout);
|
||||
|
||||
GestureShowPressListener onShowPress;
|
||||
|
||||
void didExceedDeadline() {
|
||||
// Show press isn't an exclusive gesture. We can recognize a show press
|
||||
// as well as another gesture, like a long press.
|
||||
resolve(GestureDisposition.rejected);
|
||||
onShowPress();
|
||||
}
|
||||
|
||||
void handlePrimaryPointer(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerup')
|
||||
resolve(GestureDisposition.rejected);
|
||||
}
|
||||
}
|
||||
25
sky/packages/sky/lib/gestures/tap.dart
Normal file
25
sky/packages/sky/lib/gestures/tap.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/arena.dart';
|
||||
import 'package:sky/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureTapListener();
|
||||
|
||||
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
TapGestureRecognizer({ PointerRouter router, this.onTap })
|
||||
: super(router: router);
|
||||
|
||||
GestureTapListener onTap;
|
||||
|
||||
void handlePrimaryPointer(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerup') {
|
||||
resolve(GestureDisposition.accepted);
|
||||
onTap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,19 +21,3 @@ const double kScrollbarFadeDuration = 250.0;
|
||||
const double kScrollbarFadeDelay = 300.0;
|
||||
const double kFadingEdgeLength = 12.0;
|
||||
const double kPressedStateDuration = 64.0;
|
||||
const double kDefaultLongPressTimeout = 500.0;
|
||||
const double kTapTimeout = 100.0;
|
||||
const double kJumpTapTimeout = 500.0;
|
||||
const double kDoubleTapTimeout = 300.0;
|
||||
const double kDoubleTapMinTime = 40.0;
|
||||
const double kHoverTapTimeout = 150.0;
|
||||
const double kHoverTapSlop = 20.0;
|
||||
const double kZoomControlsTimeout = 3000.0;
|
||||
const double kEdgeSlop = 12.0;
|
||||
const double kTouchSlop = 8.0;
|
||||
const double kDoubleTapTouchSlop = kTouchSlop;
|
||||
const double kPagingTouchSlop = kTouchSlop * 2.0;
|
||||
const double kDoubleTapSlop = 100.0;
|
||||
const double kWindowTouchSlop = 16.0;
|
||||
const double kMinFlingVelocity = 50.0;
|
||||
const double kMaxFlingVelocity = 8000.0;
|
||||
|
||||
121
sky/packages/sky/lib/widgets/gesture_detector.dart
Normal file
121
sky/packages/sky/lib/widgets/gesture_detector.dart
Normal file
@ -0,0 +1,121 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/long_press.dart';
|
||||
import 'package:sky/gestures/recognizer.dart';
|
||||
import 'package:sky/gestures/show_press.dart';
|
||||
import 'package:sky/gestures/tap.dart';
|
||||
import 'package:sky/rendering/sky_binding.dart';
|
||||
import 'package:sky/widgets/framework.dart';
|
||||
|
||||
class GestureDetector extends StatefulComponent {
|
||||
GestureDetector({
|
||||
Key key,
|
||||
this.child,
|
||||
this.onTap,
|
||||
this.onShowPress,
|
||||
this.onLongPress
|
||||
}) : super(key: key);
|
||||
|
||||
Widget child;
|
||||
GestureTapListener onTap;
|
||||
GestureShowPressListener onShowPress;
|
||||
GestureLongPressListener onLongPress;
|
||||
|
||||
void syncConstructorArguments(GestureDetector source) {
|
||||
child = source.child;
|
||||
onTap = source.onTap;
|
||||
onShowPress = source.onShowPress;
|
||||
onLongPress = source.onLongPress;
|
||||
_syncGestureListeners();
|
||||
}
|
||||
|
||||
final PointerRouter _router = SkyBinding.instance.pointerRouter;
|
||||
|
||||
TapGestureRecognizer _tap;
|
||||
TapGestureRecognizer _ensureTap() {
|
||||
if (_tap == null)
|
||||
_tap = new TapGestureRecognizer(router: _router);
|
||||
return _tap;
|
||||
}
|
||||
|
||||
ShowPressGestureRecognizer _showPress;
|
||||
ShowPressGestureRecognizer _ensureShowPress() {
|
||||
if (_showPress == null)
|
||||
_showPress = new ShowPressGestureRecognizer(router: _router);
|
||||
return _showPress;
|
||||
}
|
||||
|
||||
LongPressGestureRecognizer _longPress;
|
||||
LongPressGestureRecognizer _ensureLongPress() {
|
||||
if (_longPress == null)
|
||||
_longPress = new LongPressGestureRecognizer(router: _router);
|
||||
return _longPress;
|
||||
}
|
||||
|
||||
void didMount() {
|
||||
super.didMount();
|
||||
_syncGestureListeners();
|
||||
}
|
||||
|
||||
void didUnmount() {
|
||||
super.didUnmount();
|
||||
_tap = _ensureDisposed(_tap);
|
||||
_showPress = _ensureDisposed(_showPress);
|
||||
_longPress = _ensureDisposed(_longPress);
|
||||
}
|
||||
|
||||
void _syncGestureListeners() {
|
||||
_syncTap();
|
||||
_syncShowPress();
|
||||
_syncLongPress();
|
||||
}
|
||||
|
||||
void _syncTap() {
|
||||
if (onTap == null)
|
||||
_tap = _ensureDisposed(_tap);
|
||||
else
|
||||
_ensureTap().onTap = onTap;
|
||||
}
|
||||
|
||||
void _syncShowPress() {
|
||||
if (onShowPress == null)
|
||||
_showPress = _ensureDisposed(_showPress);
|
||||
else
|
||||
_ensureShowPress().onShowPress = onShowPress;
|
||||
}
|
||||
|
||||
void _syncLongPress() {
|
||||
if (onLongPress == null)
|
||||
_longPress = _ensureDisposed(_longPress);
|
||||
else
|
||||
_ensureLongPress().onLongPress = onLongPress;
|
||||
}
|
||||
|
||||
GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
|
||||
if (recognizer != null)
|
||||
recognizer.dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
EventDisposition _handlePointerDown(sky.PointerEvent event) {
|
||||
if (_tap != null)
|
||||
_tap.addPointer(event);
|
||||
if (_showPress != null)
|
||||
_showPress.addPointer(event);
|
||||
if (_longPress != null)
|
||||
_longPress.addPointer(event);
|
||||
return EventDisposition.processed;
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
return new Listener(
|
||||
onPointerDown: _handlePointerDown,
|
||||
child: child
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import 'dart:sky' as sky;
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/icon.dart';
|
||||
import 'package:sky/widgets/framework.dart';
|
||||
import 'package:sky/widgets/gesture_detector.dart';
|
||||
|
||||
class IconButton extends Component {
|
||||
|
||||
@ -25,14 +26,14 @@ class IconButton extends Component {
|
||||
child: child
|
||||
);
|
||||
}
|
||||
return new Listener(
|
||||
child: new Padding(
|
||||
child: child,
|
||||
padding: const EdgeDims.all(8.0)),
|
||||
onGestureTap: (_) {
|
||||
return new GestureDetector(
|
||||
onTap: () {
|
||||
if (onPressed != null)
|
||||
onPressed();
|
||||
}
|
||||
},
|
||||
child: new Padding(
|
||||
child: child,
|
||||
padding: const EdgeDims.all(8.0))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -12,9 +12,9 @@ import 'package:sky/animation/animation_performance.dart';
|
||||
import 'package:sky/animation/animated_value.dart';
|
||||
import 'package:sky/animation/curves.dart';
|
||||
import 'package:sky/animation/scroll_behavior.dart';
|
||||
import 'package:sky/gestures/constants.dart';
|
||||
import 'package:sky/rendering/box.dart';
|
||||
import 'package:sky/rendering/viewport.dart';
|
||||
import 'package:sky/theme/view_configuration.dart' as config;
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/framework.dart';
|
||||
import 'package:sky/widgets/mixed_viewport.dart';
|
||||
@ -24,8 +24,8 @@ export 'package:sky/widgets/mixed_viewport.dart' show MixedViewportLayoutState;
|
||||
|
||||
// The GestureEvent velocity properties are pixels/second, config min,max limits are pixels/ms
|
||||
const double _kMillisecondsPerSecond = 1000.0;
|
||||
const double _kMinFlingVelocity = -config.kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
const double _kMaxFlingVelocity = config.kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
const double _kMinFlingVelocity = -kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
const double _kMaxFlingVelocity = kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
|
||||
typedef void ScrollListener();
|
||||
|
||||
|
||||
@ -4,11 +4,7 @@ import 'package:sky/base/hit_test.dart';
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestPointerEvent extends sky.PointerEvent {
|
||||
TestPointerEvent({ this.pointer });
|
||||
|
||||
final int pointer;
|
||||
}
|
||||
import '../engine/mock_events.dart';
|
||||
|
||||
void main() {
|
||||
test('Should route pointers', () {
|
||||
|
||||
81
sky/unit/test/engine/mock_events.dart
Normal file
81
sky/unit/test/engine/mock_events.dart
Normal file
@ -0,0 +1,81 @@
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
class TestPointerEvent extends sky.PointerEvent {
|
||||
TestPointerEvent({
|
||||
this.type,
|
||||
this.pointer,
|
||||
this.kind,
|
||||
this.x,
|
||||
this.y,
|
||||
this.dx,
|
||||
this.dy,
|
||||
this.velocityX,
|
||||
this.velocityY,
|
||||
this.buttons,
|
||||
this.down,
|
||||
this.primary,
|
||||
this.obscured,
|
||||
this.pressure,
|
||||
this.pressureMin,
|
||||
this.pressureMax,
|
||||
this.distance,
|
||||
this.distanceMin,
|
||||
this.distanceMax,
|
||||
this.radiusMajor,
|
||||
this.radiusMinor,
|
||||
this.radiusMin,
|
||||
this.radiusMax,
|
||||
this.orientation,
|
||||
this.tilt
|
||||
});
|
||||
|
||||
// These are all of the PointerEvent members, but not all of Event.
|
||||
String type;
|
||||
int pointer;
|
||||
String kind;
|
||||
double x;
|
||||
double y;
|
||||
double dx;
|
||||
double dy;
|
||||
double velocityX;
|
||||
double velocityY;
|
||||
int buttons;
|
||||
bool down;
|
||||
bool primary;
|
||||
bool obscured;
|
||||
double pressure;
|
||||
double pressureMin;
|
||||
double pressureMax;
|
||||
double distance;
|
||||
double distanceMin;
|
||||
double distanceMax;
|
||||
double radiusMajor;
|
||||
double radiusMinor;
|
||||
double radiusMin;
|
||||
double radiusMax;
|
||||
double orientation;
|
||||
double tilt;
|
||||
}
|
||||
|
||||
class TestGestureEvent extends sky.GestureEvent {
|
||||
TestGestureEvent({
|
||||
this.type,
|
||||
this.primaryPointer,
|
||||
this.x,
|
||||
this.y,
|
||||
this.dx,
|
||||
this.dy,
|
||||
this.velocityX,
|
||||
this.velocityY
|
||||
});
|
||||
|
||||
// These are all of the GestureEvent members, but not all of Event.
|
||||
String type;
|
||||
int primaryPointer;
|
||||
double x;
|
||||
double y;
|
||||
double dx;
|
||||
double dy;
|
||||
double velocityX;
|
||||
double velocityY;
|
||||
}
|
||||
106
sky/unit/test/gestures/long_press_test.dart
Normal file
106
sky/unit/test/gestures/long_press_test.dart
Normal file
@ -0,0 +1,106 @@
|
||||
import 'package:quiver/testing/async.dart';
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/long_press.dart';
|
||||
import 'package:sky/gestures/show_press.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../engine/mock_events.dart';
|
||||
|
||||
final TestPointerEvent down = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerdown',
|
||||
x: 10.0,
|
||||
y: 10.0
|
||||
);
|
||||
|
||||
final TestPointerEvent up = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerup',
|
||||
x: 11.0,
|
||||
y: 9.0
|
||||
);
|
||||
|
||||
void main() {
|
||||
test('Should recognize long press', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer(router: router);
|
||||
|
||||
bool longPressRecognized = false;
|
||||
longPress.onLongPress = () {
|
||||
longPressRecognized = true;
|
||||
};
|
||||
|
||||
new FakeAsync().run((async) {
|
||||
longPress.addPointer(down);
|
||||
expect(longPressRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 300));
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 700));
|
||||
expect(longPressRecognized, isTrue);
|
||||
});
|
||||
|
||||
longPress.dispose();
|
||||
});
|
||||
|
||||
test('Up cancels long press', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer(router: router);
|
||||
|
||||
bool longPressRecognized = false;
|
||||
longPress.onLongPress = () {
|
||||
longPressRecognized = true;
|
||||
};
|
||||
|
||||
new FakeAsync().run((async) {
|
||||
longPress.addPointer(down);
|
||||
expect(longPressRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 300));
|
||||
expect(longPressRecognized, isFalse);
|
||||
router.handleEvent(up, null);
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(seconds: 1));
|
||||
expect(longPressRecognized, isFalse);
|
||||
});
|
||||
|
||||
longPress.dispose();
|
||||
});
|
||||
|
||||
test('Should recognize both show press and long press', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
ShowPressGestureRecognizer showPress = new ShowPressGestureRecognizer(router: router);
|
||||
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer(router: router);
|
||||
|
||||
bool showPressRecognized = false;
|
||||
showPress.onShowPress = () {
|
||||
showPressRecognized = true;
|
||||
};
|
||||
|
||||
bool longPressRecognized = false;
|
||||
longPress.onLongPress = () {
|
||||
longPressRecognized = true;
|
||||
};
|
||||
|
||||
new FakeAsync().run((async) {
|
||||
showPress.addPointer(down);
|
||||
longPress.addPointer(down);
|
||||
expect(showPressRecognized, isFalse);
|
||||
expect(longPressRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(showPressRecognized, isFalse);
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 300));
|
||||
expect(showPressRecognized, isTrue);
|
||||
expect(longPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 700));
|
||||
expect(showPressRecognized, isTrue);
|
||||
expect(longPressRecognized, isTrue);
|
||||
});
|
||||
|
||||
showPress.dispose();
|
||||
longPress.dispose();
|
||||
});
|
||||
}
|
||||
68
sky/unit/test/gestures/show_press_test.dart
Normal file
68
sky/unit/test/gestures/show_press_test.dart
Normal file
@ -0,0 +1,68 @@
|
||||
import 'package:quiver/testing/async.dart';
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/show_press.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../engine/mock_events.dart';
|
||||
|
||||
final TestPointerEvent down = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerdown',
|
||||
x: 10.0,
|
||||
y: 10.0
|
||||
);
|
||||
|
||||
final TestPointerEvent up = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerup',
|
||||
x: 11.0,
|
||||
y: 9.0
|
||||
);
|
||||
|
||||
void main() {
|
||||
test('Should recognize show press', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
ShowPressGestureRecognizer showPress = new ShowPressGestureRecognizer(router: router);
|
||||
|
||||
bool showPressRecognized = false;
|
||||
showPress.onShowPress = () {
|
||||
showPressRecognized = true;
|
||||
};
|
||||
|
||||
new FakeAsync().run((async) {
|
||||
showPress.addPointer(down);
|
||||
expect(showPressRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(showPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 300));
|
||||
expect(showPressRecognized, isTrue);
|
||||
});
|
||||
|
||||
showPress.dispose();
|
||||
});
|
||||
|
||||
test('Up cancels show press', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
ShowPressGestureRecognizer showPress = new ShowPressGestureRecognizer(router: router);
|
||||
|
||||
bool showPressRecognized = false;
|
||||
showPress.onShowPress = () {
|
||||
showPressRecognized = true;
|
||||
};
|
||||
|
||||
new FakeAsync().run((async) {
|
||||
showPress.addPointer(down);
|
||||
expect(showPressRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(showPressRecognized, isFalse);
|
||||
async.elapse(new Duration(milliseconds: 50));
|
||||
expect(showPressRecognized, isFalse);
|
||||
router.handleEvent(up, null);
|
||||
expect(showPressRecognized, isFalse);
|
||||
async.elapse(new Duration(seconds: 1));
|
||||
expect(showPressRecognized, isFalse);
|
||||
});
|
||||
|
||||
showPress.dispose();
|
||||
});
|
||||
}
|
||||
41
sky/unit/test/gestures/tap_test.dart
Normal file
41
sky/unit/test/gestures/tap_test.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:sky/base/pointer_router.dart';
|
||||
import 'package:sky/gestures/tap.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../engine/mock_events.dart';
|
||||
|
||||
void main() {
|
||||
test('Should recognize tap', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
TapGestureRecognizer tap = new TapGestureRecognizer(router: router);
|
||||
|
||||
bool tapRecognized = false;
|
||||
tap.onTap = () {
|
||||
tapRecognized = true;
|
||||
};
|
||||
|
||||
TestPointerEvent down = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerdown',
|
||||
x: 10.0,
|
||||
y: 10.0
|
||||
);
|
||||
|
||||
tap.addPointer(down);
|
||||
expect(tapRecognized, isFalse);
|
||||
router.handleEvent(down, null);
|
||||
expect(tapRecognized, isFalse);
|
||||
|
||||
TestPointerEvent up = new TestPointerEvent(
|
||||
pointer: 5,
|
||||
type: 'pointerup',
|
||||
x: 11.0,
|
||||
y: 9.0
|
||||
);
|
||||
|
||||
router.handleEvent(up, null);
|
||||
expect(tapRecognized, isTrue);
|
||||
|
||||
tap.dispose();
|
||||
});
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:quiver/testing/async.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
import 'widget_tester.dart';
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:sky/base/scheduler.dart' as scheduler;
|
||||
|
||||
import '../engine/mock_events.dart';
|
||||
|
||||
typedef Widget WidgetBuilder();
|
||||
|
||||
class TestApp extends App {
|
||||
@ -22,86 +25,6 @@ class TestApp extends App {
|
||||
}
|
||||
}
|
||||
|
||||
class TestPointerEvent extends sky.PointerEvent {
|
||||
TestPointerEvent({
|
||||
this.type,
|
||||
this.pointer,
|
||||
this.kind,
|
||||
this.x,
|
||||
this.y,
|
||||
this.dx,
|
||||
this.dy,
|
||||
this.velocityX,
|
||||
this.velocityY,
|
||||
this.buttons,
|
||||
this.down,
|
||||
this.primary,
|
||||
this.obscured,
|
||||
this.pressure,
|
||||
this.pressureMin,
|
||||
this.pressureMax,
|
||||
this.distance,
|
||||
this.distanceMin,
|
||||
this.distanceMax,
|
||||
this.radiusMajor,
|
||||
this.radiusMinor,
|
||||
this.radiusMin,
|
||||
this.radiusMax,
|
||||
this.orientation,
|
||||
this.tilt
|
||||
});
|
||||
|
||||
// These are all of the PointerEvent members, but not all of Event.
|
||||
String type;
|
||||
int pointer;
|
||||
String kind;
|
||||
double x;
|
||||
double y;
|
||||
double dx;
|
||||
double dy;
|
||||
double velocityX;
|
||||
double velocityY;
|
||||
int buttons;
|
||||
bool down;
|
||||
bool primary;
|
||||
bool obscured;
|
||||
double pressure;
|
||||
double pressureMin;
|
||||
double pressureMax;
|
||||
double distance;
|
||||
double distanceMin;
|
||||
double distanceMax;
|
||||
double radiusMajor;
|
||||
double radiusMinor;
|
||||
double radiusMin;
|
||||
double radiusMax;
|
||||
double orientation;
|
||||
double tilt;
|
||||
}
|
||||
|
||||
class TestGestureEvent extends sky.GestureEvent {
|
||||
TestGestureEvent({
|
||||
this.type,
|
||||
this.primaryPointer,
|
||||
this.x,
|
||||
this.y,
|
||||
this.dx,
|
||||
this.dy,
|
||||
this.velocityX,
|
||||
this.velocityY
|
||||
});
|
||||
|
||||
// These are all of the GestureEvent members, but not all of Event.
|
||||
String type;
|
||||
int primaryPointer;
|
||||
double x;
|
||||
double y;
|
||||
double dx;
|
||||
double dy;
|
||||
double velocityX;
|
||||
double velocityY;
|
||||
}
|
||||
|
||||
class WidgetTester {
|
||||
WidgetTester() {
|
||||
_app = new TestApp();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user