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