button.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package widget
  3. import (
  4. "image"
  5. "time"
  6. "gioui.org/gesture"
  7. "gioui.org/io/event"
  8. "gioui.org/io/key"
  9. "gioui.org/io/pointer"
  10. "gioui.org/io/semantic"
  11. "gioui.org/layout"
  12. "gioui.org/op"
  13. "gioui.org/op/clip"
  14. )
  15. // Clickable represents a clickable area.
  16. type Clickable struct {
  17. click gesture.Click
  18. history []Press
  19. requestClicks int
  20. pressedKey key.Name
  21. }
  22. // Click represents a click.
  23. type Click struct {
  24. Modifiers key.Modifiers
  25. NumClicks int
  26. }
  27. // Press represents a past pointer press.
  28. type Press struct {
  29. // Position of the press.
  30. Position image.Point
  31. // Start is when the press began.
  32. Start time.Time
  33. // End is when the press was ended by a release or cancel.
  34. // A zero End means it hasn't ended yet.
  35. End time.Time
  36. // Cancelled is true for cancelled presses.
  37. Cancelled bool
  38. }
  39. // Click executes a simple programmatic click.
  40. func (b *Clickable) Click() {
  41. b.requestClicks++
  42. }
  43. // Clicked calls Update and reports whether a click was registered.
  44. func (b *Clickable) Clicked(gtx layout.Context) bool {
  45. return b.clicked(b, gtx)
  46. }
  47. func (b *Clickable) clicked(t event.Tag, gtx layout.Context) bool {
  48. _, clicked := b.update(t, gtx)
  49. return clicked
  50. }
  51. // Hovered reports whether a pointer is over the element.
  52. func (b *Clickable) Hovered() bool {
  53. return b.click.Hovered()
  54. }
  55. // Pressed reports whether a pointer is pressing the element.
  56. func (b *Clickable) Pressed() bool {
  57. return b.click.Pressed()
  58. }
  59. // History is the past pointer presses useful for drawing markers.
  60. // History is retained for a short duration (about a second).
  61. func (b *Clickable) History() []Press {
  62. return b.history
  63. }
  64. // Layout and update the button state.
  65. func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
  66. return b.layout(b, gtx, w)
  67. }
  68. func (b *Clickable) layout(t event.Tag, gtx layout.Context, w layout.Widget) layout.Dimensions {
  69. for {
  70. _, ok := b.update(t, gtx)
  71. if !ok {
  72. break
  73. }
  74. }
  75. m := op.Record(gtx.Ops)
  76. dims := w(gtx)
  77. c := m.Stop()
  78. defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
  79. semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
  80. b.click.Add(gtx.Ops)
  81. event.Op(gtx.Ops, t)
  82. c.Add(gtx.Ops)
  83. return dims
  84. }
  85. // Update the button state by processing events, and return the next
  86. // click, if any.
  87. func (b *Clickable) Update(gtx layout.Context) (Click, bool) {
  88. return b.update(b, gtx)
  89. }
  90. func (b *Clickable) update(t event.Tag, gtx layout.Context) (Click, bool) {
  91. for len(b.history) > 0 {
  92. c := b.history[0]
  93. if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
  94. break
  95. }
  96. n := copy(b.history, b.history[1:])
  97. b.history = b.history[:n]
  98. }
  99. if c := b.requestClicks; c > 0 {
  100. b.requestClicks = 0
  101. return Click{
  102. NumClicks: c,
  103. }, true
  104. }
  105. for {
  106. e, ok := b.click.Update(gtx.Source)
  107. if !ok {
  108. break
  109. }
  110. switch e.Kind {
  111. case gesture.KindClick:
  112. if l := len(b.history); l > 0 {
  113. b.history[l-1].End = gtx.Now
  114. }
  115. return Click{
  116. Modifiers: e.Modifiers,
  117. NumClicks: e.NumClicks,
  118. }, true
  119. case gesture.KindCancel:
  120. for i := range b.history {
  121. b.history[i].Cancelled = true
  122. if b.history[i].End.IsZero() {
  123. b.history[i].End = gtx.Now
  124. }
  125. }
  126. case gesture.KindPress:
  127. if e.Source == pointer.Mouse {
  128. gtx.Execute(key.FocusCmd{Tag: t})
  129. }
  130. b.history = append(b.history, Press{
  131. Position: e.Position,
  132. Start: gtx.Now,
  133. })
  134. }
  135. }
  136. for {
  137. e, ok := gtx.Event(
  138. key.FocusFilter{Target: t},
  139. key.Filter{Focus: t, Name: key.NameReturn},
  140. key.Filter{Focus: t, Name: key.NameSpace},
  141. )
  142. if !ok {
  143. break
  144. }
  145. switch e := e.(type) {
  146. case key.FocusEvent:
  147. if e.Focus {
  148. b.pressedKey = ""
  149. }
  150. case key.Event:
  151. if !gtx.Focused(t) {
  152. break
  153. }
  154. if e.Name != key.NameReturn && e.Name != key.NameSpace {
  155. break
  156. }
  157. switch e.State {
  158. case key.Press:
  159. b.pressedKey = e.Name
  160. case key.Release:
  161. if b.pressedKey != e.Name {
  162. break
  163. }
  164. // only register a key as a click if the key was pressed and released while this button was focused
  165. b.pressedKey = ""
  166. return Click{
  167. Modifiers: e.Modifiers,
  168. NumClicks: 1,
  169. }, true
  170. }
  171. }
  172. }
  173. return Click{}, false
  174. }