Files
gio-patched/widget/enum.go
T
Elias Naur 380f96b3fc io/key: [API] implement key event propagation
Before this change, every Event would be passed to the focused InputOp
tag, making it impossible to implement, say, program-wide shortcuts.
This change implements key.Event routing similar to how pointer.Events
are routed: every InputOp describes the set of keys it can handle, and
the router use that information to deliver an Event to the matching
handler.

This is an API change, because every InputOp must now include a filter
matching the keys it wants to handle.

Fixes: https://todo.sr.ht/~eliasnaur/gio/395
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-14 19:09:00 +02:00

133 lines
2.5 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package widget
import (
"image"
"gioui.org/gesture"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/semantic"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
)
type Enum struct {
Value string
hovered string
hovering bool
focus string
focused bool
changed bool
keys []*enumKey
}
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 nil
}
// Changed reports whether Value has changed by user interaction since the last
// call to Changed.
func (e *Enum) Changed() bool {
changed := e.changed
e.changed = false
return changed
}
// Hovered returns the key that is highlighted, or false if none are.
func (e *Enum) Hovered() (string, bool) {
return e.hovered, e.hovering
}
// 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()
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
}
}
}
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.Release {
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, Keys: "⏎|Space"}.Add(gtx.Ops)
} else if e.focus == k {
e.focused = false
}
semantic.SelectedOp(k == e.Value).Add(gtx.Ops)
semantic.DisabledOp(disabled).Add(gtx.Ops)
c.Add(gtx.Ops)
return dims
}