diff --git a/io/input/key_test.go b/io/input/key_test.go index 34c5fcc8..71034c90 100644 --- a/io/input/key_test.go +++ b/io/input/key_test.go @@ -318,8 +318,8 @@ func TestKeyRouting(t *testing.T) { func assertFocus(t *testing.T, router *Router, expected event.Tag) { t.Helper() - if got := router.state().focus; got != expected { - t.Errorf("expected %v to be focused, got %v", expected, got) + if !router.Source().Focused(expected) { + t.Errorf("expected %v to be focused", expected) } } diff --git a/io/input/router.go b/io/input/router.go index 6f69fac2..c8299da9 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -184,6 +184,15 @@ func (s Source) Enabled() bool { return s.r != nil } +// Focused reports whether tag is focused, according to the most recent +// [key.FocusEvent] delivered. +func (s Source) Focused(tag event.Tag) bool { + if !s.Enabled() { + return false + } + return s.r.state().keyState.focus == tag +} + // Event returns the next event that matches at least one of filters. func (s Source) Event(filters ...event.Filter) (event.Event, bool) { if !s.Enabled() { diff --git a/widget/bool.go b/widget/bool.go index 00c559a1..66fcb06f 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -16,7 +16,7 @@ type Bool struct { // Update the widget state and report whether Value was changed. func (b *Bool) Update(gtx layout.Context) bool { changed := false - for b.clk.Clicked(gtx) { + for b.clk.clicked(b, gtx) { b.Value = !b.Value changed = true } @@ -33,18 +33,13 @@ func (b *Bool) Pressed() bool { return b.clk.Pressed() } -// Focused reports whether b has focus. -func (b *Bool) Focused() bool { - return b.clk.Focused() -} - func (b *Bool) History() []Press { return b.clk.History() } func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { b.Update(gtx) - dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + dims := b.clk.layout(b, gtx, func(gtx layout.Context) layout.Dimensions { semantic.SelectedOp(b.Value).Add(gtx.Ops) semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops) return w(gtx) diff --git a/widget/button.go b/widget/button.go index 85233759..4f3cb840 100644 --- a/widget/button.go +++ b/widget/button.go @@ -22,7 +22,6 @@ type Clickable struct { history []Press requestClicks int - focused bool pressedKey key.Name } @@ -52,7 +51,11 @@ func (b *Clickable) Click() { // Clicked calls Update and reports whether a click was registered. func (b *Clickable) Clicked(gtx layout.Context) bool { - _, clicked := b.Update(gtx) + return b.clicked(b, gtx) +} + +func (b *Clickable) clicked(t event.Tag, gtx layout.Context) bool { + _, clicked := b.update(t, gtx) return clicked } @@ -66,11 +69,6 @@ func (b *Clickable) Pressed() bool { return b.click.Pressed() } -// Focused reports whether b has focus. -func (b *Clickable) Focused() bool { - return b.focused -} - // 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 { @@ -79,8 +77,12 @@ func (b *Clickable) History() []Press { // Layout and update the button state. func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { + return b.layout(b, gtx, w) +} + +func (b *Clickable) layout(t event.Tag, gtx layout.Context, w layout.Widget) layout.Dimensions { for { - _, ok := b.Update(gtx) + _, ok := b.update(t, gtx) if !ok { break } @@ -91,7 +93,7 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops) b.click.Add(gtx.Ops) - event.InputOp(gtx.Ops, b) + event.InputOp(gtx.Ops, t) c.Add(gtx.Ops) return dims } @@ -99,9 +101,10 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension // Update the button state by processing events, and return the next // click, if any. func (b *Clickable) Update(gtx layout.Context) (Click, bool) { - if !gtx.Enabled() { - b.focused = false - } + return b.update(b, gtx) +} + +func (b *Clickable) update(t event.Tag, gtx layout.Context) (Click, bool) { for len(b.history) > 0 { c := b.history[0] if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second { @@ -139,7 +142,7 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) { } case gesture.KindPress: if e.Source == pointer.Mouse { - gtx.Execute(key.FocusCmd{Tag: b}) + gtx.Execute(key.FocusCmd{Tag: t}) } b.history = append(b.history, Press{ Position: e.Position, @@ -149,21 +152,20 @@ func (b *Clickable) Update(gtx layout.Context) (Click, bool) { } for { e, ok := gtx.Event( - key.FocusFilter{Target: b}, - key.Filter{Focus: b, Name: key.NameReturn}, - key.Filter{Focus: b, Name: key.NameSpace}, + key.FocusFilter{Target: t}, + key.Filter{Focus: t, Name: key.NameReturn}, + key.Filter{Focus: t, Name: key.NameSpace}, ) if !ok { break } switch e := e.(type) { case key.FocusEvent: - b.focused = e.Focus - if !b.focused { + if e.Focus { b.pressedKey = "" } case key.Event: - if !b.focused { + if !gtx.Focused(t) { break } if e.Name != key.NameReturn && e.Name != key.NameSpace { diff --git a/widget/button_test.go b/widget/button_test.go index a86c1536..5818bdb0 100644 --- a/widget/button_test.go +++ b/widget/button_test.go @@ -39,10 +39,10 @@ func TestClickable(t *testing.T) { } gtx.Execute(key.FocusCmd{Tag: &b1}) frame() - if !b1.Focused() { + if !gtx.Focused(&b1) { t.Error("button 1 did not gain focus") } - if b2.Focused() { + if gtx.Focused(&b2) { t.Error("button 2 should not have focus") } r.Queue( @@ -73,10 +73,10 @@ func TestClickable(t *testing.T) { frame() gtx.Execute(key.FocusCmd{Tag: &b2}) frame() - if b1.Focused() { + if gtx.Focused(&b1) { t.Error("button 1 should not have focus") } - if !b2.Focused() { + if !gtx.Focused(&b2) { t.Error("button 2 did not gain focus") } r.Queue( diff --git a/widget/editor.go b/widget/editor.go index d02e35db..2d89d77d 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -72,7 +72,6 @@ type Editor struct { // from the textView. scratch []byte blinkStart time.Time - focused bool // ime tracks the state relevant to input methods. ime struct { @@ -385,14 +384,13 @@ func (e *Editor) processKey(gtx layout.Context) { e.blinkStart = gtx.Now switch ke := ke.(type) { case key.FocusEvent: - e.focused = ke.Focus // Reset IME state. e.ime.imeState = imeState{} if ke.Focus { gtx.Execute(key.SoftKeyboardCmd{Show: true}) } case key.Event: - if !e.focused || ke.State != key.Press { + if !gtx.Focused(e) || ke.State != key.Press { break } if !e.ReadOnly && e.Submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) { @@ -558,11 +556,6 @@ func (e *Editor) command(gtx layout.Context, k key.Event) { } } -// Focused returns whether the editor is focused or not. -func (e *Editor) Focused() bool { - return e.focused -} - // initBuffer should be invoked first in every exported function that accesses // text state. It ensures that the underlying text widget is both ready to use // and has its fields synced with the editor. @@ -583,27 +576,25 @@ func (e *Editor) initBuffer() { func (e *Editor) Update(gtx layout.Context) { e.initBuffer() e.processEvents(gtx) - if e.focused { - // Notify IME of selection if it changed. - newSel := e.ime.selection - start, end := e.text.Selection() - newSel.rng = key.Range{ - Start: start, - End: end, - } - caretPos, carAsc, carDesc := e.text.CaretInfo() - newSel.caret = key.Caret{ - Pos: layout.FPt(caretPos), - Ascent: float32(carAsc), - Descent: float32(carDesc), - } - if newSel != e.ime.selection { - e.ime.selection = newSel - gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret}) - } - - e.updateSnippet(gtx, e.ime.start, e.ime.end) + // Notify IME of selection if it changed. + newSel := e.ime.selection + start, end := e.text.Selection() + newSel.rng = key.Range{ + Start: start, + End: end, } + caretPos, carAsc, carDesc := e.text.CaretInfo() + newSel.caret = key.Caret{ + Pos: layout.FPt(caretPos), + Ascent: float32(carAsc), + Descent: float32(carDesc), + } + if newSel != e.ime.selection { + e.ime.selection = newSel + gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret}) + } + + e.updateSnippet(gtx, e.ime.start, e.ime.end) } // Layout lays out the editor using the provided textMaterial as the paint material @@ -679,7 +670,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call e.clicker.Add(gtx.Ops) e.dragger.Add(gtx.Ops) e.showCaret = false - if e.focused { + if gtx.Focused(e) { now := gtx.Now dt := now.Sub(e.blinkStart) blinking := dt < maxBlinkDuration @@ -688,7 +679,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call if blinking { gtx.Execute(op.InvalidateCmd{At: nextBlink}) } - e.showCaret = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2) + e.showCaret = !blinking || dt%timePerBlink < timePerBlink/2 } semantic.Editor.Add(gtx.Ops) if e.Len() > 0 { @@ -705,7 +696,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call // material to set the painting material for the selection. func (e *Editor) paintSelection(gtx layout.Context, material op.CallOp) { e.initBuffer() - if !e.focused { + if !gtx.Focused(e) { return } e.text.PaintSelection(gtx, material) diff --git a/widget/material/button.go b/widget/material/button.go index 8221d6b9..b403f4d8 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -96,7 +96,7 @@ func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) la return layout.Background{}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() - if button.Hovered() || button.Focused() { + if button.Hovered() || gtx.Focused(button) { paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{})) } for _, c := range button.History() { @@ -135,7 +135,7 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di switch { case !gtx.Enabled(): background = f32color.Disabled(b.Background) - case b.Button.Hovered() || b.Button.Focused(): + case b.Button.Hovered() || gtx.Focused(b.Button): background = f32color.Hovered(b.Background) } paint.Fill(gtx.Ops, background) @@ -167,7 +167,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { switch { case !gtx.Enabled(): background = f32color.Disabled(b.Background) - case b.Button.Hovered() || b.Button.Focused(): + case b.Button.Hovered() || gtx.Focused(b.Button): background = f32color.Hovered(b.Background) } paint.Fill(gtx.Ops, background) diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go index 74261074..1525a73e 100644 --- a/widget/material/checkbox.go +++ b/widget/material/checkbox.go @@ -35,6 +35,6 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle { func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions { return c.CheckBox.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.CheckBox.Add(gtx.Ops) - return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || c.CheckBox.Focused()) + return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || gtx.Focused(c.CheckBox)) }) } diff --git a/widget/material/switch.go b/widget/material/switch.go index 7bf18e77..2463a6fe 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -98,7 +98,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions { return clip.Ellipse(b).Op(gtx.Ops) } // Draw hover. - if s.Switch.Hovered() || s.Switch.Focused() { + if s.Switch.Hovered() || gtx.Focused(s.Switch) { r := thumbRadius * 10 / 17 background := f32color.MulAlpha(s.Color.Enabled, 70) paint.FillShape(gtx.Ops, background, circle(thumbRadius, thumbRadius, r))