Commit 95e80663 authored by Adam Barth's avatar Adam Barth

Add tap, show press, and long press gestures and use tap in IconButton

These gestures use the new gesture detection system.
parent 01e7c696
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)
......
// 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;
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;
// 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', () {
......
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:test/test.dart';
import 'package:quiver/testing/async.dart';
import 'widget_tester.dart';
......
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();
......
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