|  | // 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 | 
|  | } |