diff --git a/widget/bool.go b/widget/bool.go index 28992c98..ec8efaa9 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -23,16 +23,21 @@ func (b *Bool) Changed() bool { return changed } -// Hovered returns whether pointer is over the element. +// Hovered reports whether pointer is over the element. func (b *Bool) Hovered() bool { return b.clk.Hovered() } -// Pressed returns whether pointer is pressing the element. +// Pressed reports whether pointer is pressing the element. 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() } diff --git a/widget/button.go b/widget/button.go index b8aa30c3..99ef8904 100644 --- a/widget/button.go +++ b/widget/button.go @@ -9,6 +9,7 @@ import ( "gioui.org/f32" "gioui.org/gesture" "gioui.org/io/key" + "gioui.org/io/pointer" "gioui.org/io/semantic" "gioui.org/layout" "gioui.org/op" @@ -24,6 +25,9 @@ type Clickable struct { // clicks bounded. prevClicks int history []Press + + keyTag struct{} + focused bool } // Click represents a click. @@ -67,16 +71,21 @@ func (b *Clickable) Clicked() bool { return true } -// Hovered returns whether pointer is over the element. +// Hovered reports whether a pointer is over the element. func (b *Clickable) Hovered() bool { return b.click.Hovered() } -// Pressed returns whether pointer is pressing the element. +// Pressed reports whether a pointer is pressing the element. func (b *Clickable) Pressed() bool { return b.click.Pressed() } +// Focused reports whether b has focus. +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 @@ -98,8 +107,12 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension dims := w(gtx) c := m.Stop() defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() - semantic.DisabledOp(gtx.Queue == nil).Add(gtx.Ops) + disabled := gtx.Queue == nil + semantic.DisabledOp(disabled).Add(gtx.Ops) b.click.Add(gtx.Ops) + if !disabled { + key.InputOp{Tag: &b.keyTag}.Add(gtx.Ops) + } c.Add(gtx.Ops) for len(b.history) > 0 { c := b.history[0] @@ -137,10 +150,30 @@ func (b *Clickable) update(gtx layout.Context) { } } case gesture.TypePress: + if e.Source == pointer.Mouse { + key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops) + } b.history = append(b.history, Press{ Position: e.Position, Start: gtx.Now, }) } } + for _, e := range gtx.Events(&b.keyTag) { + switch e := e.(type) { + case key.FocusEvent: + b.focused = e.Focus + case key.Event: + if e.State != key.Press { + break + } + if e.Name != key.NameReturn && e.Name != key.NameSpace { + break + } + b.clicks = append(b.clicks, Click{ + Modifiers: e.Modifiers, + NumClicks: 1, + }) + } + } } diff --git a/widget/enum.go b/widget/enum.go index 9dba0db8..49ff21da 100644 --- a/widget/enum.go +++ b/widget/enum.go @@ -6,6 +6,8 @@ import ( "image" "gioui.org/gesture" + "gioui.org/io/key" + "gioui.org/io/pointer" "gioui.org/io/semantic" "gioui.org/layout" "gioui.org/op" @@ -17,19 +19,27 @@ type Enum struct { hovered string hovering bool + focus string + focused bool + changed bool - clicks []gesture.Click - values []string + keys []*enumKey } -func index(vs []string, t string) int { - for i, v := range vs { - if v == t { - return i +type enumKey struct { + key string + click gesture.Click + tag struct{} +} + +func (e *Enum) index(k string) *enumKey { + for _, v := range e.keys { + if v.key == k { + return v } } - return -1 + return nil } // Changed reports whether Value has changed by user interaction since the last @@ -45,40 +55,75 @@ func (e *Enum) Hovered() (string, bool) { return e.hovered, e.hovering } -// Layout adds the event handler for key. -func (e *Enum) Layout(gtx layout.Context, key string, content layout.Widget) layout.Dimensions { +// Focused reports the focused key, or false if no key is focused. +func (e *Enum) Focused() (string, bool) { + return e.focus, e.focused +} + +// Layout adds the event handler for the key k. +func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layout.Dimensions { m := op.Record(gtx.Ops) dims := content(gtx) c := m.Stop() defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() - if index(e.values, key) == -1 { - e.values = append(e.values, key) - e.clicks = append(e.clicks, gesture.Click{}) - e.clicks[len(e.clicks)-1].Add(gtx.Ops) - } else { - idx := index(e.values, key) - clk := &e.clicks[idx] - for _, ev := range clk.Events(gtx) { - switch ev.Type { - case gesture.TypeClick: - if new := e.values[idx]; new != e.Value { - e.Value = new - e.changed = true - } + state := e.index(k) + if state == nil { + state = &enumKey{ + key: k, + } + e.keys = append(e.keys, state) + } + clk := &state.click + for _, ev := range clk.Events(gtx) { + switch ev.Type { + case gesture.TypePress: + if ev.Source == pointer.Mouse { + key.FocusOp{Tag: &state.tag}.Add(gtx.Ops) + } + case gesture.TypeClick: + if state.key != e.Value { + e.Value = state.key + e.changed = true } } - if e.hovering && e.hovered == key { - e.hovering = false - } - if clk.Hovered() { - e.hovered = key - e.hovering = true - } - clk.Add(gtx.Ops) } - semantic.SelectedOp(key == e.Value).Add(gtx.Ops) - semantic.DisabledOp(gtx.Queue == nil).Add(gtx.Ops) + for _, ev := range gtx.Events(&state.tag) { + switch ev := ev.(type) { + case key.FocusEvent: + if ev.Focus { + e.focused = true + e.focus = state.key + } else if state.key == e.focus { + e.focused = false + } + case key.Event: + if ev.State != key.Press { + break + } + if ev.Name != key.NameEnter && ev.Name != key.NameSpace { + break + } + if state.key != e.Value { + e.Value = state.key + e.changed = true + } + } + } + if clk.Hovered() { + e.hovered = k + e.hovering = true + } else if e.hovered == k { + e.hovering = false + } + + clk.Add(gtx.Ops) + disabled := gtx.Queue == nil + if !disabled { + key.InputOp{Tag: &state.tag}.Add(gtx.Ops) + } + semantic.SelectedOp(k == e.Value).Add(gtx.Ops) + semantic.DisabledOp(disabled).Add(gtx.Ops) c.Add(gtx.Ops) return dims diff --git a/widget/material/button.go b/widget/material/button.go index 4be4b21a..362fc73f 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -132,7 +132,7 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di switch { case gtx.Queue == nil: background = f32color.Disabled(b.Background) - case b.Button.Hovered(): + case b.Button.Hovered() || b.Button.Focused(): background = f32color.Hovered(b.Background) } paint.Fill(gtx.Ops, background) @@ -168,7 +168,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { switch { case gtx.Queue == nil: background = f32color.Disabled(b.Background) - case b.Button.Hovered(): + case b.Button.Hovered() || b.Button.Focused(): background = f32color.Hovered(b.Background) } paint.Fill(gtx.Ops, background) diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go index 76b7efb0..afd758db 100644 --- a/widget/material/checkbox.go +++ b/widget/material/checkbox.go @@ -34,6 +34,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()) + return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || c.CheckBox.Focused()) }) } diff --git a/widget/material/radiobutton.go b/widget/material/radiobutton.go index 84d976d1..5292bfd3 100644 --- a/widget/material/radiobutton.go +++ b/widget/material/radiobutton.go @@ -38,8 +38,10 @@ func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonSt // Layout updates enum and displays the radio button. func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions { hovered, hovering := r.Group.Hovered() + focus, focused := r.Group.Focused() return r.Group.Layout(gtx, r.Key, func(gtx layout.Context) layout.Dimensions { semantic.RadioButton.Add(gtx.Ops) - return r.layout(gtx, r.Group.Value == r.Key, hovering && hovered == r.Key) + highlight := hovering && hovered == r.Key || focused && focus == r.Key + return r.layout(gtx, r.Group.Value == r.Key, highlight) }) } diff --git a/widget/material/switch.go b/widget/material/switch.go index 76f469f1..682e758c 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -99,7 +99,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions { return clip.Ellipse(b).Op(gtx.Ops) } // Draw hover. - if s.Switch.Hovered() { + if s.Switch.Hovered() || s.Switch.Focused() { r := 1.7 * thumbRadius background := f32color.MulAlpha(s.Color.Enabled, 70) paint.FillShape(gtx.Ops, background, circle(thumbRadius, thumbRadius, r))