diff --git a/widget/bool.go b/widget/bool.go index 55045dc7..740ce5b3 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -43,11 +43,11 @@ func (b *Bool) History() []Press { } func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { + for b.clk.Clicked(gtx) { + b.Value = !b.Value + b.changed = true + } dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - for b.clk.Clicked() { - b.Value = !b.Value - b.changed = true - } semantic.SelectedOp(b.Value).Add(gtx.Ops) semantic.EnabledOp(gtx.Queue != nil).Add(gtx.Ops) return w(gtx) diff --git a/widget/button.go b/widget/button.go index 38ba483c..da3c0acb 100644 --- a/widget/button.go +++ b/widget/button.go @@ -17,18 +17,16 @@ import ( // Clickable represents a clickable area. type Clickable struct { - click gesture.Click - clicks []Click - // prevClicks is the index into clicks that marks the clicks - // from the most recent Layout call. prevClicks is used to keep - // clicks bounded. - prevClicks int - history []Press + click gesture.Click + // clicks is for saved clicks to support Clicked. + clicks []Click + history []Press - keyTag struct{} - requestFocus bool - focused bool - pressedKey string + keyTag struct{} + requestFocus bool + requestClicks int + focused bool + pressedKey string } // Click represents a click. @@ -50,26 +48,24 @@ type Press struct { Cancelled bool } -// Click executes a simple programmatic click +// Click executes a simple programmatic click. func (b *Clickable) Click() { - b.clicks = append(b.clicks, Click{ - Modifiers: 0, - NumClicks: 1, - }) + b.requestClicks++ } // Clicked reports whether there are pending clicks as would be // reported by Clicks. If so, Clicked removes the earliest click. -func (b *Clickable) Clicked() bool { - if len(b.clicks) == 0 { - return false +func (b *Clickable) Clicked(gtx layout.Context) bool { + if len(b.clicks) > 0 { + b.clicks = b.clicks[1:] + return true } - n := copy(b.clicks, b.clicks[1:]) - b.clicks = b.clicks[:n] - if b.prevClicks > 0 { - b.prevClicks-- + b.clicks = b.Update(gtx) + if len(b.clicks) > 0 { + b.clicks = b.clicks[1:] + return true } - return true + return false } // Hovered reports whether a pointer is over the element. @@ -92,23 +88,15 @@ func (b *Clickable) Focused() bool { return b.focused } -// Clicks returns and clear the clicks since the last call to Clicks. -func (b *Clickable) Clicks() []Click { - clicks := b.clicks - b.clicks = nil - b.prevClicks = 0 - return clicks -} - // History is the past pointer presses useful for drawing markers. // History is retained for a short duration (about a second). func (b *Clickable) History() []Press { return b.history } -// Layout and update the button state +// Layout and update the button state. func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { - b.update(gtx) + b.Update(gtx) m := op.Record(gtx.Ops) dims := w(gtx) c := m.Stop() @@ -122,14 +110,22 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension keys = "" } key.InputOp{Tag: &b.keyTag, Keys: keys}.Add(gtx.Ops) - if b.requestFocus { - key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops) - b.requestFocus = false - } - } else { - b.focused = false } c.Add(gtx.Ops) + return dims +} + +// Update the button state by processing events, and return the resulting +// clicks, if any. +func (b *Clickable) Update(gtx layout.Context) []Click { + b.clicks = nil + if gtx.Queue == nil { + b.focused = false + } + if b.requestFocus { + key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops) + b.requestFocus = false + } for len(b.history) > 0 { c := b.history[0] if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second { @@ -138,26 +134,23 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension n := copy(b.history, b.history[1:]) b.history = b.history[:n] } - return dims -} - -// update the button state by processing events. -func (b *Clickable) update(gtx layout.Context) { - // Flush clicks from before the last update. - n := copy(b.clicks, b.clicks[b.prevClicks:]) - b.clicks = b.clicks[:n] - b.prevClicks = n - + var clicks []Click + if c := b.requestClicks; c > 0 { + b.requestClicks = 0 + clicks = append(clicks, Click{ + NumClicks: c, + }) + } for _, e := range b.click.Events(gtx) { switch e.Kind { case gesture.KindClick: - b.clicks = append(b.clicks, Click{ - Modifiers: e.Modifiers, - NumClicks: e.NumClicks, - }) if l := len(b.history); l > 0 { b.history[l-1].End = gtx.Now } + clicks = append(clicks, Click{ + Modifiers: e.Modifiers, + NumClicks: e.NumClicks, + }) case gesture.KindCancel: for i := range b.history { b.history[i].Cancelled = true @@ -198,11 +191,12 @@ func (b *Clickable) update(gtx layout.Context) { } // only register a key as a click if the key was pressed and released while this button was focused b.pressedKey = "" - b.clicks = append(b.clicks, Click{ + clicks = append(clicks, Click{ Modifiers: e.Modifiers, NumClicks: 1, }) } } } + return clicks } diff --git a/widget/button_test.go b/widget/button_test.go index 9bb712f5..49f3a5ee 100644 --- a/widget/button_test.go +++ b/widget/button_test.go @@ -48,6 +48,7 @@ func TestClickable(t *testing.T) { t.Error("button 2 should not have focus") } // frame: press & release return + frame() r.Queue( key.Event{ Name: key.NameReturn, @@ -58,11 +59,10 @@ func TestClickable(t *testing.T) { State: key.Release, }, ) - frame() - if !b1.Clicked() { + if !b1.Clicked(gtx) { t.Error("button 1 did not get clicked when it got return press & release") } - if b2.Clicked() { + if b2.Clicked(gtx) { t.Error("button 2 got clicked when it did not have focus") } // frame: press return down @@ -73,7 +73,7 @@ func TestClickable(t *testing.T) { }, ) frame() - if b1.Clicked() { + if b1.Clicked(gtx) { t.Error("button 1 got clicked, even if it only got return press") } // frame: request focus for button 2 @@ -95,10 +95,10 @@ func TestClickable(t *testing.T) { }, ) frame() - if b1.Clicked() { + if b1.Clicked(gtx) { t.Error("button 1 got clicked, even if it had lost focus") } - if b2.Clicked() { + if b2.Clicked(gtx) { t.Error("button 2 should not have been clicked, as it only got return release") } } diff --git a/widget/decorations.go b/widget/decorations.go index 38457825..184c38d9 100644 --- a/widget/decorations.go +++ b/widget/decorations.go @@ -30,7 +30,7 @@ func (d *Decorations) LayoutMove(gtx layout.Context, w layout.Widget) layout.Dim } // Clickable returns the clickable for the given single action. -func (d *Decorations) Clickable(action system.Action) *Clickable { +func (d *Decorations) Clickable(gtx layout.Context, action system.Action) *Clickable { if bits.OnesCount(uint(action)) != 1 { panic(fmt.Errorf("not a single action")) } @@ -39,7 +39,7 @@ func (d *Decorations) Clickable(action system.Action) *Clickable { d.clicks = append(d.clicks, make([]Clickable, n+1)...) } click := &d.clicks[idx] - if click.Clicked() { + if click.Clicked(gtx) { if action == system.ActionMaximize { if d.maximized { d.maximized = false diff --git a/widget/example_test.go b/widget/example_test.go index 131936f7..2bfdbf8e 100644 --- a/widget/example_test.go +++ b/widget/example_test.go @@ -57,13 +57,11 @@ func ExampleClickable_passthrough() { Position: f32.Pt(50, 50), }, ) - // The second layout ensures that the click event is registered by the buttons. - widget() - if button1.Clicked() { + if button1.Clicked(gtx) { fmt.Println("button1 clicked!") } - if button2.Clicked() { + if button2.Clicked(gtx) { fmt.Println("button2 clicked!") } diff --git a/widget/material/decorations.go b/widget/material/decorations.go index d327dbbc..6e67f6fb 100644 --- a/widget/material/decorations.go +++ b/widget/material/decorations.go @@ -85,7 +85,7 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension default: continue } - cl := d.Decorations.Clickable(a) + cl := d.Decorations.Clickable(gtx, a) dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) return layout.Stack{Alignment: layout.Center}.Layout(gtx,