forked from golang/exp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gesture.go
326 lines (276 loc) · 8.37 KB
/
gesture.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gesture provides gesture events such as long presses and drags.
// These are higher level than underlying mouse and touch events.
package gesture
import (
"fmt"
"time"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/mouse"
)
// TODO: handle touch events, not just mouse events.
//
// TODO: multi-button / multi-touch gestures such as pinch, rotate and tilt?
const (
// TODO: use a resolution-independent unit such as DIPs or Millimetres?
dragThreshold = 10 // Pixels.
doublePressThreshold = 300 * time.Millisecond
longPressThreshold = 500 * time.Millisecond
)
// Type describes the type of a touch event.
type Type uint8
const (
// TypeStart and TypeEnd are the start and end of a gesture. A gesture
// spans multiple events.
TypeStart Type = 0
TypeEnd Type = 1
// TypeIsXxx is when the gesture is recognized as a long press, double
// press or drag. For example, a mouse button press won't generate a
// TypeIsLongPress immediately, but if a threshold duration passes without
// the corresponding mouse button release, a TypeIsLongPress event is sent.
//
// Once a TypeIsXxx event is sent, the corresponding Event.Xxx bool field
// is set for this and subsequent events. For example, a TypeTap event by
// itself doesn't say whether or not it is a single tap or the first tap of
// a double tap. If the app needs to distinguish these two sorts of taps,
// it can wait until a TypeEnd or TypeIsDoublePress event is seen. If a
// TypeEnd is seen before TypeIsDoublePress, or equivalently, if the
// TypeEnd event's DoublePress field is false, the gesture is a single tap.
//
// These attributes aren't exclusive. A long press drag is perfectly valid.
//
// The uncommon "double press" instead of "double tap" terminology is
// because, in this package, taps are associated with button releases, not
// button presses. Note also that "double" really means "at least two".
TypeIsLongPress Type = 10
TypeIsDoublePress Type = 11
TypeIsDrag Type = 12
// TypeTap and TypeDrag are tap and drag events.
//
// For 'flinging' drags, to simulate inertia, look to the Velocity field of
// the TypeEnd event.
//
// TODO: implement velocity.
TypeTap Type = 20
TypeDrag Type = 21
// All internal types are >= typeInternal.
typeInternal Type = 100
// The typeXxxSchedule and typeXxxResolve constants are used for the two
// step process for sending an event after a timeout, in a separate
// goroutine. There are two steps so that the spawned goroutine is
// guaranteed to execute only after any other EventDeque.SendFirst calls
// are made for the one underlying mouse or touch event.
typeDoublePressSchedule Type = 100
typeDoublePressResolve Type = 101
typeLongPressSchedule Type = 110
typeLongPressResolve Type = 111
)
func (t Type) String() string {
switch t {
case TypeStart:
return "Start"
case TypeEnd:
return "End"
case TypeIsLongPress:
return "IsLongPress"
case TypeIsDoublePress:
return "IsDoublePress"
case TypeIsDrag:
return "IsDrag"
case TypeTap:
return "Tap"
case TypeDrag:
return "Drag"
default:
return fmt.Sprintf("gesture.Type(%d)", t)
}
}
// Point is a mouse or touch location, in pixels.
type Point struct {
X, Y float32
}
// Event is a gesture event.
type Event struct {
// Type is the gesture type.
Type Type
// Drag, LongPress and DoublePress are set when the gesture is recognized as
// a drag, etc.
//
// Note that these status fields can be lost during a gesture's events over
// time: LongPress can be set for the first press of a double press, but
// unset on the second press.
Drag bool
LongPress bool
DoublePress bool
// InitialPos is the initial position of the button press or touch that
// started this gesture.
InitialPos Point
// CurrentPos is the current position of the button or touch event.
CurrentPos Point
// TODO: a "Velocity Point" field. See
// - frameworks/native/libs/input/VelocityTracker.cpp in AOSP, or
// - https://chromium.googlesource.com/chromium/src/+/master/ui/events/gesture_detection/velocity_tracker.cc in Chromium,
// for some velocity tracking implementations.
// Time is the event's time.
Time time.Time
// TODO: include the mouse Button and key Modifiers?
}
type internalEvent struct {
eventFilter *EventFilter
typ Type
x, y float32
// pressCounter is the EventFilter.pressCounter value at the time this
// internal event was scheduled to be delivered after a timeout. It detects
// whether there have been other button presses and releases during that
// timeout, and hence whether this internalEvent is obsolete.
pressCounter uint32
}
// EventFilter generates gesture events from lower level mouse and touch
// events.
type EventFilter struct {
EventDeque screen.EventDeque
inProgress bool
drag bool
longPress bool
doublePress bool
// initialPos is the initial position of the button press or touch that
// started this gesture.
initialPos Point
// pressButton is the initial button that started this gesture. If
// button.None, no gesture is in progress.
pressButton mouse.Button
// pressCounter is incremented on every button press and release.
pressCounter uint32
}
func (f *EventFilter) sendFirst(t Type, x, y float32, now time.Time) {
if t >= typeInternal {
f.EventDeque.SendFirst(internalEvent{
eventFilter: f,
typ: t,
x: x,
y: y,
pressCounter: f.pressCounter,
})
return
}
f.EventDeque.SendFirst(Event{
Type: t,
Drag: f.drag,
LongPress: f.longPress,
DoublePress: f.doublePress,
InitialPos: f.initialPos,
CurrentPos: Point{
X: x,
Y: y,
},
// TODO: Velocity.
Time: now,
})
}
func (f *EventFilter) sendAfter(e internalEvent, sleep time.Duration) {
time.Sleep(sleep)
f.EventDeque.SendFirst(e)
}
func (f *EventFilter) end(x, y float32, now time.Time) {
f.sendFirst(TypeEnd, x, y, now)
f.inProgress = false
f.drag = false
f.longPress = false
f.doublePress = false
f.initialPos = Point{}
f.pressButton = mouse.ButtonNone
}
// Filter filters the event. It can return e, a different event, or nil to
// consume the event. It can also trigger side effects such as pushing new
// events onto its EventDeque.
func (f *EventFilter) Filter(e interface{}) interface{} {
switch e := e.(type) {
case internalEvent:
if e.eventFilter != f {
break
}
now := time.Now()
switch e.typ {
case typeDoublePressSchedule:
e.typ = typeDoublePressResolve
go f.sendAfter(e, doublePressThreshold)
case typeDoublePressResolve:
if e.pressCounter == f.pressCounter {
// It's a single press only.
f.end(e.x, e.y, now)
}
case typeLongPressSchedule:
e.typ = typeLongPressResolve
go f.sendAfter(e, longPressThreshold)
case typeLongPressResolve:
if e.pressCounter == f.pressCounter && !f.drag {
f.longPress = true
f.sendFirst(TypeIsLongPress, e.x, e.y, now)
}
}
return nil
case mouse.Event:
now := time.Now()
switch e.Direction {
case mouse.DirNone:
if f.pressButton == mouse.ButtonNone {
break
}
startDrag := false
if !f.drag &&
(abs(e.X-f.initialPos.X) > dragThreshold || abs(e.Y-f.initialPos.Y) > dragThreshold) {
f.drag = true
startDrag = true
}
if f.drag {
f.sendFirst(TypeDrag, e.X, e.Y, now)
}
if startDrag {
f.sendFirst(TypeIsDrag, e.X, e.Y, now)
}
case mouse.DirPress:
if f.pressButton != mouse.ButtonNone {
break
}
oldInProgress := f.inProgress
oldDoublePress := f.doublePress
f.drag = false
f.longPress = false
f.doublePress = f.inProgress
f.initialPos = Point{e.X, e.Y}
f.pressButton = e.Button
f.pressCounter++
f.inProgress = true
f.sendFirst(typeLongPressSchedule, e.X, e.Y, now)
if !oldDoublePress && f.doublePress {
f.sendFirst(TypeIsDoublePress, e.X, e.Y, now)
}
if !oldInProgress {
f.sendFirst(TypeStart, e.X, e.Y, now)
}
case mouse.DirRelease:
if f.pressButton != e.Button {
break
}
f.pressButton = mouse.ButtonNone
f.pressCounter++
if f.drag {
f.end(e.X, e.Y, now)
break
}
f.sendFirst(typeDoublePressSchedule, e.X, e.Y, now)
f.sendFirst(TypeTap, e.X, e.Y, now)
}
}
return e
}
func abs(x float32) float32 {
if x < 0 {
return -x
} else if x == 0 {
return 0 // Handle floating point negative zero.
}
return x
}