Commit a46b83da authored by Adam Barth's avatar Adam Barth

Merge pull request #912 from abarth/tap

Add tap, show press, and long press gestures and use tap in IconButton
parents d2e32b80 95e80663
Sky Gestures
============
Code for recognizing and representing user interface gestures.
Dependencies
------------
* [`package:sky/base`](../base)
...@@ -7,14 +7,26 @@ enum GestureDisposition { ...@@ -7,14 +7,26 @@ enum GestureDisposition {
rejected 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 { 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); 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); 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 { class GestureArenaEntry {
GestureArenaEntry._(this._arena, this._key, this._member); GestureArenaEntry._(this._arena, this._key, this._member);
...@@ -23,6 +35,8 @@ class GestureArenaEntry { ...@@ -23,6 +35,8 @@ class GestureArenaEntry {
final GestureArenaMember _member; final GestureArenaMember _member;
/// Call this member to claim victory (with accepted) or admit defeat (with rejected). /// 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) { void resolve(GestureDisposition disposition) {
_arena._resolve(_key, _member, disposition); _arena._resolve(_key, _member, disposition);
} }
...@@ -32,6 +46,8 @@ class GestureArenaEntry { ...@@ -32,6 +46,8 @@ class GestureArenaEntry {
class GestureArena { class GestureArena {
final Map<Object, List<GestureArenaMember>> _arenas = new Map<Object, List<GestureArenaMember>>(); final Map<Object, List<GestureArenaMember>> _arenas = new Map<Object, List<GestureArenaMember>>();
static final GestureArena instance = new GestureArena();
GestureArenaEntry add(Object key, GestureArenaMember member) { GestureArenaEntry add(Object key, GestureArenaMember member) {
List<GestureArenaMember> members = _arenas.putIfAbsent(key, () => new List<GestureArenaMember>()); List<GestureArenaMember> members = _arenas.putIfAbsent(key, () => new List<GestureArenaMember>());
members.add(member); members.add(member);
...@@ -40,10 +56,13 @@ class GestureArena { ...@@ -40,10 +56,13 @@ class GestureArena {
void _resolve(Object key, GestureArenaMember member, GestureDisposition disposition) { void _resolve(Object key, GestureArenaMember member, GestureDisposition disposition) {
List<GestureArenaMember> members = _arenas[key]; List<GestureArenaMember> members = _arenas[key];
if (members == null)
return; // This arena has already resolved.
assert(members != null); assert(members != null);
assert(members.contains(member)); assert(members.contains(member));
if (disposition == GestureDisposition.rejected) { if (disposition == GestureDisposition.rejected) {
members.remove(member); members.remove(member);
member.rejectGesture(key);
if (members.length == 1) { if (members.length == 1) {
_arenas.remove(key); _arenas.remove(key);
members.first.acceptGesture(key); members.first.acceptGesture(key);
...@@ -52,7 +71,6 @@ class GestureArena { ...@@ -52,7 +71,6 @@ class GestureArena {
} }
} else { } else {
assert(disposition == GestureDisposition.accepted); assert(disposition == GestureDisposition.accepted);
List<GestureArenaMember> members = _arenas[key];
_arenas.remove(key); _arenas.remove(key);
for (GestureArenaMember rejectedMember in members) { for (GestureArenaMember rejectedMember in members) {
if (rejectedMember != member) if (rejectedMember != member)
......
// 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?
// 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);
}
}
// 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;
}
}
// 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);
}
}
// 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; ...@@ -21,19 +21,3 @@ const double kScrollbarFadeDuration = 250.0;
const double kScrollbarFadeDelay = 300.0; const double kScrollbarFadeDelay = 300.0;
const double kFadingEdgeLength = 12.0; const double kFadingEdgeLength = 12.0;
const double kPressedStateDuration = 64.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;
// 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; ...@@ -7,6 +7,7 @@ import 'dart:sky' as sky;
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/icon.dart'; import 'package:sky/widgets/icon.dart';
import 'package:sky/widgets/framework.dart'; import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/gesture_detector.dart';
class IconButton extends Component { class IconButton extends Component {
...@@ -25,14 +26,14 @@ class IconButton extends Component { ...@@ -25,14 +26,14 @@ class IconButton extends Component {
child: child child: child
); );
} }
return new Listener( return new GestureDetector(
child: new Padding( onTap: () {
child: child,
padding: const EdgeDims.all(8.0)),
onGestureTap: (_) {
if (onPressed != null) if (onPressed != null)
onPressed(); onPressed();
} },
child: new Padding(
child: child,
padding: const EdgeDims.all(8.0))
); );
} }
......
...@@ -12,9 +12,9 @@ import 'package:sky/animation/animation_performance.dart'; ...@@ -12,9 +12,9 @@ import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/animated_value.dart'; import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/curves.dart'; import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart'; import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/gestures/constants.dart';
import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/viewport.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/basic.dart';
import 'package:sky/widgets/framework.dart'; import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/mixed_viewport.dart'; import 'package:sky/widgets/mixed_viewport.dart';
...@@ -24,8 +24,8 @@ export 'package:sky/widgets/mixed_viewport.dart' show MixedViewportLayoutState; ...@@ -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 // The GestureEvent velocity properties are pixels/second, config min,max limits are pixels/ms
const double _kMillisecondsPerSecond = 1000.0; const double _kMillisecondsPerSecond = 1000.0;
const double _kMinFlingVelocity = -config.kMaxFlingVelocity * _kMillisecondsPerSecond; const double _kMinFlingVelocity = -kMaxFlingVelocity * _kMillisecondsPerSecond;
const double _kMaxFlingVelocity = config.kMaxFlingVelocity * _kMillisecondsPerSecond; const double _kMaxFlingVelocity = kMaxFlingVelocity * _kMillisecondsPerSecond;
typedef void ScrollListener(); typedef void ScrollListener();
......
...@@ -4,11 +4,7 @@ import 'package:sky/base/hit_test.dart'; ...@@ -4,11 +4,7 @@ import 'package:sky/base/hit_test.dart';
import 'package:sky/base/pointer_router.dart'; import 'package:sky/base/pointer_router.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
class TestPointerEvent extends sky.PointerEvent { import '../engine/mock_events.dart';
TestPointerEvent({ this.pointer });
final int pointer;
}
void main() { void main() {
test('Should route pointers', () { test('Should route pointers', () {
......
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;
}
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();
});
}
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();
});
}
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();
});
}
import 'package:quiver/testing/async.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:quiver/testing/async.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
......
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
import 'package:sky/base/scheduler.dart' as scheduler; import 'package:sky/base/scheduler.dart' as scheduler;
import '../engine/mock_events.dart';
typedef Widget WidgetBuilder(); typedef Widget WidgetBuilder();
class TestApp extends App { class TestApp extends App {
...@@ -22,86 +25,6 @@ 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 { class WidgetTester {
WidgetTester() { WidgetTester() {
_app = new TestApp(); _app = new TestApp();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment