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