gesture.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. /*
  3. Package gesture implements common pointer gestures.
  4. Gestures accept low level pointer Events from an event
  5. Queue and detect higher level actions such as clicks
  6. and scrolling.
  7. */
  8. package gesture
  9. import (
  10. "image"
  11. "math"
  12. "runtime"
  13. "time"
  14. "gioui.org/f32"
  15. "gioui.org/internal/fling"
  16. "gioui.org/io/event"
  17. "gioui.org/io/input"
  18. "gioui.org/io/key"
  19. "gioui.org/io/pointer"
  20. "gioui.org/op"
  21. "gioui.org/unit"
  22. )
  23. // The duration is somewhat arbitrary.
  24. const doubleClickDuration = 200 * time.Millisecond
  25. // Hover detects the hover gesture for a pointer area.
  26. type Hover struct {
  27. // entered tracks whether the pointer is inside the gesture.
  28. entered bool
  29. // pid is the pointer.ID.
  30. pid pointer.ID
  31. }
  32. // Add the gesture to detect hovering over the current pointer area.
  33. func (h *Hover) Add(ops *op.Ops) {
  34. event.Op(ops, h)
  35. }
  36. // Update state and report whether a pointer is inside the area.
  37. func (h *Hover) Update(q input.Source) bool {
  38. for {
  39. ev, ok := q.Event(pointer.Filter{
  40. Target: h,
  41. Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
  42. })
  43. if !ok {
  44. break
  45. }
  46. e, ok := ev.(pointer.Event)
  47. if !ok {
  48. continue
  49. }
  50. switch e.Kind {
  51. case pointer.Leave, pointer.Cancel:
  52. if h.entered && h.pid == e.PointerID {
  53. h.entered = false
  54. }
  55. case pointer.Enter:
  56. if !h.entered {
  57. h.pid = e.PointerID
  58. }
  59. if h.pid == e.PointerID {
  60. h.entered = true
  61. }
  62. }
  63. }
  64. return h.entered
  65. }
  66. // Click detects click gestures in the form
  67. // of ClickEvents.
  68. type Click struct {
  69. // clickedAt is the timestamp at which
  70. // the last click occurred.
  71. clickedAt time.Duration
  72. // clicks is incremented if successive clicks
  73. // are performed within a fixed duration.
  74. clicks int
  75. // pressed tracks whether the pointer is pressed.
  76. pressed bool
  77. // hovered tracks whether the pointer is inside the gesture.
  78. hovered bool
  79. // entered tracks whether an Enter event has been received.
  80. entered bool
  81. // pid is the pointer.ID.
  82. pid pointer.ID
  83. }
  84. // ClickEvent represent a click action, either a
  85. // KindPress for the beginning of a click or a
  86. // KindClick for a completed click.
  87. type ClickEvent struct {
  88. Kind ClickKind
  89. Position image.Point
  90. Source pointer.Source
  91. Modifiers key.Modifiers
  92. // NumClicks records successive clicks occurring
  93. // within a short duration of each other.
  94. NumClicks int
  95. }
  96. type ClickKind uint8
  97. // Drag detects drag gestures in the form of pointer.Drag events.
  98. type Drag struct {
  99. dragging bool
  100. pressed bool
  101. pid pointer.ID
  102. start f32.Point
  103. }
  104. // Scroll detects scroll gestures and reduces them to
  105. // scroll distances. Scroll recognizes mouse wheel
  106. // movements as well as drag and fling touch gestures.
  107. type Scroll struct {
  108. dragging bool
  109. estimator fling.Extrapolation
  110. flinger fling.Animation
  111. pid pointer.ID
  112. last int
  113. // Leftover scroll.
  114. scroll float32
  115. }
  116. type ScrollState uint8
  117. type Axis uint8
  118. const (
  119. Horizontal Axis = iota
  120. Vertical
  121. Both
  122. )
  123. const (
  124. // KindPress is reported for the first pointer
  125. // press.
  126. KindPress ClickKind = iota
  127. // KindClick is reported when a click action
  128. // is complete.
  129. KindClick
  130. // KindCancel is reported when the gesture is
  131. // cancelled.
  132. KindCancel
  133. )
  134. const (
  135. // StateIdle is the default scroll state.
  136. StateIdle ScrollState = iota
  137. // StateDragging is reported during drag gestures.
  138. StateDragging
  139. // StateFlinging is reported when a fling is
  140. // in progress.
  141. StateFlinging
  142. )
  143. const touchSlop = unit.Dp(3)
  144. // Add the handler to the operation list to receive click events.
  145. func (c *Click) Add(ops *op.Ops) {
  146. event.Op(ops, c)
  147. }
  148. // Hovered returns whether a pointer is inside the area.
  149. func (c *Click) Hovered() bool {
  150. return c.hovered
  151. }
  152. // Pressed returns whether a pointer is pressing.
  153. func (c *Click) Pressed() bool {
  154. return c.pressed
  155. }
  156. // Update state and return the next click events, if any.
  157. func (c *Click) Update(q input.Source) (ClickEvent, bool) {
  158. for {
  159. evt, ok := q.Event(pointer.Filter{
  160. Target: c,
  161. Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
  162. })
  163. if !ok {
  164. break
  165. }
  166. e, ok := evt.(pointer.Event)
  167. if !ok {
  168. continue
  169. }
  170. switch e.Kind {
  171. case pointer.Release:
  172. if !c.pressed || c.pid != e.PointerID {
  173. break
  174. }
  175. c.pressed = false
  176. if !c.entered || c.hovered {
  177. return ClickEvent{
  178. Kind: KindClick,
  179. Position: e.Position.Round(),
  180. Source: e.Source,
  181. Modifiers: e.Modifiers,
  182. NumClicks: c.clicks,
  183. }, true
  184. } else {
  185. return ClickEvent{Kind: KindCancel}, true
  186. }
  187. case pointer.Cancel:
  188. wasPressed := c.pressed
  189. c.pressed = false
  190. c.hovered = false
  191. c.entered = false
  192. if wasPressed {
  193. return ClickEvent{Kind: KindCancel}, true
  194. }
  195. case pointer.Press:
  196. if c.pressed {
  197. break
  198. }
  199. if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
  200. break
  201. }
  202. if !c.hovered {
  203. c.pid = e.PointerID
  204. }
  205. if c.pid != e.PointerID {
  206. break
  207. }
  208. c.pressed = true
  209. if e.Time-c.clickedAt < doubleClickDuration {
  210. c.clicks++
  211. } else {
  212. c.clicks = 1
  213. }
  214. c.clickedAt = e.Time
  215. return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
  216. case pointer.Leave:
  217. if !c.pressed {
  218. c.pid = e.PointerID
  219. }
  220. if c.pid == e.PointerID {
  221. c.hovered = false
  222. }
  223. case pointer.Enter:
  224. if !c.pressed {
  225. c.pid = e.PointerID
  226. }
  227. if c.pid == e.PointerID {
  228. c.hovered = true
  229. c.entered = true
  230. }
  231. }
  232. }
  233. return ClickEvent{}, false
  234. }
  235. func (ClickEvent) ImplementsEvent() {}
  236. // Add the handler to the operation list to receive scroll events.
  237. // The bounds variable refers to the scrolling boundaries
  238. // as defined in [pointer.Filter].
  239. func (s *Scroll) Add(ops *op.Ops) {
  240. event.Op(ops, s)
  241. }
  242. // Stop any remaining fling movement.
  243. func (s *Scroll) Stop() {
  244. s.flinger = fling.Animation{}
  245. }
  246. // Update state and report the scroll distance along axis.
  247. func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
  248. total := 0
  249. f := pointer.Filter{
  250. Target: s,
  251. Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
  252. ScrollX: scrollx,
  253. ScrollY: scrolly,
  254. }
  255. for {
  256. evt, ok := q.Event(f)
  257. if !ok {
  258. break
  259. }
  260. e, ok := evt.(pointer.Event)
  261. if !ok {
  262. continue
  263. }
  264. switch e.Kind {
  265. case pointer.Press:
  266. if s.dragging {
  267. break
  268. }
  269. // Only scroll on touch drags, or on Android where mice
  270. // drags also scroll by convention.
  271. if e.Source != pointer.Touch && runtime.GOOS != "android" {
  272. break
  273. }
  274. s.Stop()
  275. s.estimator = fling.Extrapolation{}
  276. v := s.val(axis, e.Position)
  277. s.last = int(math.Round(float64(v)))
  278. s.estimator.Sample(e.Time, v)
  279. s.dragging = true
  280. s.pid = e.PointerID
  281. case pointer.Release:
  282. if s.pid != e.PointerID {
  283. break
  284. }
  285. fling := s.estimator.Estimate()
  286. if slop, d := float32(cfg.Dp(touchSlop)), fling.Distance; d < -slop || d > slop {
  287. s.flinger.Start(cfg, t, fling.Velocity)
  288. }
  289. fallthrough
  290. case pointer.Cancel:
  291. s.dragging = false
  292. case pointer.Scroll:
  293. switch axis {
  294. case Horizontal:
  295. s.scroll += e.Scroll.X
  296. case Vertical:
  297. s.scroll += e.Scroll.Y
  298. }
  299. iscroll := int(s.scroll)
  300. s.scroll -= float32(iscroll)
  301. total += iscroll
  302. case pointer.Drag:
  303. if !s.dragging || s.pid != e.PointerID {
  304. continue
  305. }
  306. val := s.val(axis, e.Position)
  307. s.estimator.Sample(e.Time, val)
  308. v := int(math.Round(float64(val)))
  309. dist := s.last - v
  310. if e.Priority < pointer.Grabbed {
  311. slop := cfg.Dp(touchSlop)
  312. if dist := dist; dist >= slop || -slop >= dist {
  313. q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
  314. }
  315. } else {
  316. s.last = v
  317. total += dist
  318. }
  319. }
  320. }
  321. total += s.flinger.Tick(t)
  322. if s.flinger.Active() {
  323. q.Execute(op.InvalidateCmd{})
  324. }
  325. return total
  326. }
  327. func (s *Scroll) val(axis Axis, p f32.Point) float32 {
  328. if axis == Horizontal {
  329. return p.X
  330. } else {
  331. return p.Y
  332. }
  333. }
  334. // State reports the scroll state.
  335. func (s *Scroll) State() ScrollState {
  336. switch {
  337. case s.flinger.Active():
  338. return StateFlinging
  339. case s.dragging:
  340. return StateDragging
  341. default:
  342. return StateIdle
  343. }
  344. }
  345. // Add the handler to the operation list to receive drag events.
  346. func (d *Drag) Add(ops *op.Ops) {
  347. event.Op(ops, d)
  348. }
  349. // Update state and return the next drag event, if any.
  350. func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
  351. for {
  352. ev, ok := q.Event(pointer.Filter{
  353. Target: d,
  354. Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
  355. })
  356. if !ok {
  357. break
  358. }
  359. e, ok := ev.(pointer.Event)
  360. if !ok {
  361. continue
  362. }
  363. switch e.Kind {
  364. case pointer.Press:
  365. if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
  366. continue
  367. }
  368. d.pressed = true
  369. if d.dragging {
  370. continue
  371. }
  372. d.dragging = true
  373. d.pid = e.PointerID
  374. d.start = e.Position
  375. case pointer.Drag:
  376. if !d.dragging || e.PointerID != d.pid {
  377. continue
  378. }
  379. switch axis {
  380. case Horizontal:
  381. e.Position.Y = d.start.Y
  382. case Vertical:
  383. e.Position.X = d.start.X
  384. case Both:
  385. // Do nothing
  386. }
  387. if e.Priority < pointer.Grabbed {
  388. diff := e.Position.Sub(d.start)
  389. slop := cfg.Dp(touchSlop)
  390. if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
  391. q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
  392. }
  393. }
  394. case pointer.Release, pointer.Cancel:
  395. d.pressed = false
  396. if !d.dragging || e.PointerID != d.pid {
  397. continue
  398. }
  399. d.dragging = false
  400. }
  401. return e, true
  402. }
  403. return pointer.Event{}, false
  404. }
  405. // Dragging reports whether it is currently in use.
  406. func (d *Drag) Dragging() bool { return d.dragging }
  407. // Pressed returns whether a pointer is pressing.
  408. func (d *Drag) Pressed() bool { return d.pressed }
  409. func (a Axis) String() string {
  410. switch a {
  411. case Horizontal:
  412. return "Horizontal"
  413. case Vertical:
  414. return "Vertical"
  415. default:
  416. panic("invalid Axis")
  417. }
  418. }
  419. func (ct ClickKind) String() string {
  420. switch ct {
  421. case KindPress:
  422. return "KindPress"
  423. case KindClick:
  424. return "KindClick"
  425. case KindCancel:
  426. return "KindCancel"
  427. default:
  428. panic("invalid ClickKind")
  429. }
  430. }
  431. func (s ScrollState) String() string {
  432. switch s {
  433. case StateIdle:
  434. return "StateIdle"
  435. case StateDragging:
  436. return "StateDragging"
  437. case StateFlinging:
  438. return "StateFlinging"
  439. default:
  440. panic("unreachable")
  441. }
  442. }