io/input,widget: [API] replace per-widget Focused with Source.Focused

Widgets have themselves as tags, by convention, and so it's possible to
replace the per-widget Focused methods with a general-purpose Source.
Focused query.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-28 10:35:51 -06:00
parent c3f2abebca
commit e59f91dfd0
9 changed files with 65 additions and 68 deletions
+2 -2
View File
@@ -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)
}
}
+9
View File
@@ -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() {
+2 -7
View File
@@ -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)
+21 -19
View File
@@ -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 {
+4 -4
View File
@@ -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(
+4 -13
View File
@@ -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,7 +576,6 @@ 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()
@@ -603,7 +595,6 @@ func (e *Editor) Update(gtx layout.Context) {
}
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)
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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))
})
}
+1 -1
View File
@@ -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))