Skip to content

Commit

Permalink
fix focus next/previous bug
Browse files Browse the repository at this point in the history
With the move of focus next/previous to FocusManager the action
lost the ability to notify the Focusable.
This commit restores it and adds appropriate tests.
  • Loading branch information
toaster committed Sep 12, 2020
1 parent a3be02b commit 83b4f9e
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 27 deletions.
39 changes: 21 additions & 18 deletions internal/app/focus.go
Expand Up @@ -24,22 +24,7 @@ func NewFocusManager(c fyne.CanvasObject) *FocusManager {
func (f *FocusManager) Focus(obj fyne.Focusable) {
f.Lock()
defer f.Unlock()

if f.focused == obj {
return
}

if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() {
obj = nil
}

if f.focused != nil {
f.focused.FocusLost()
}
f.focused = obj
if obj != nil {
obj.FocusGained()
}
f.focus(obj)
}

// Focused returns the currently focused object or nil if none.
Expand All @@ -54,15 +39,33 @@ func (f *FocusManager) Focused() fyne.Focusable {
func (f *FocusManager) FocusNext() {
f.Lock()
defer f.Unlock()
f.focused = f.nextInChain(f.focused)
f.focus(f.nextInChain(f.focused))
}

// FocusPrevious will find the item before the current that can be focused and focus it.
// If current is nil then the last focusable item in the canvas will be focused.
func (f *FocusManager) FocusPrevious() {
f.Lock()
defer f.Unlock()
f.focused = f.previousInChain(f.focused)
f.focus(f.previousInChain(f.focused))
}

func (f *FocusManager) focus(obj fyne.Focusable) {
if f.focused == obj {
return
}

if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() {
obj = nil
}

if f.focused != nil {
f.focused.FocusLost()
}
f.focused = obj
if obj != nil {
obj.FocusGained()
}
}

func (f *FocusManager) nextInChain(current fyne.Focusable) fyne.Focusable {
Expand Down
54 changes: 45 additions & 9 deletions internal/app/focus_test.go
Expand Up @@ -10,79 +10,115 @@ import (
)

func TestFocusManager_Focus(t *testing.T) {
entry1 := widget.NewEntry()
entry1 := &focusable{}
hidden := widget.NewCheck("test", func(bool) {})
hidden.Hide()
entry2 := widget.NewEntry()
entry2 := &focusable{}
disabled := widget.NewCheck("test", func(bool) {})
disabled.Disable()
entry3 := widget.NewEntry()
entry3 := &focusable{}
c := widget.NewVBox(entry1, hidden, entry2, disabled, entry3)

manager := app.NewFocusManager(c)
assert.Nil(t, manager.Focused())

manager.Focus(entry2)
assert.Equal(t, entry2, manager.Focused())
assert.True(t, entry2.focused)

manager.Focus(entry1)
assert.Equal(t, entry1, manager.Focused())
assert.True(t, entry1.focused)
assert.False(t, entry2.focused)

manager.Focus(entry3)
assert.Equal(t, entry3, manager.Focused())
assert.True(t, entry3.focused)
assert.False(t, entry1.focused)

manager.Focus(nil)
assert.Nil(t, manager.Focused())
assert.False(t, entry3.focused)
}

func TestFocusManager_FocusNext(t *testing.T) {
entry1 := widget.NewEntry()
entry1 := &focusable{}
hidden := widget.NewCheck("test", func(bool) {})
hidden.Hide()
entry2 := widget.NewEntry()
entry2 := &focusable{}
disabled := widget.NewCheck("test", func(bool) {})
disabled.Disable()
entry3 := widget.NewEntry()
entry3 := &focusable{}
c := widget.NewVBox(entry1, hidden, entry2, disabled, entry3)

manager := app.NewFocusManager(c)
assert.Nil(t, manager.Focused())

manager.FocusNext()
assert.Equal(t, entry1, manager.Focused())
assert.True(t, entry1.focused)

manager.FocusNext()
assert.Equal(t, entry2, manager.Focused())
assert.True(t, entry2.focused)
assert.False(t, entry1.focused)

manager.FocusNext()
assert.Equal(t, entry3, manager.Focused())
assert.True(t, entry3.focused)
assert.False(t, entry2.focused)

manager.FocusNext()
assert.Equal(t, entry1, manager.Focused())
assert.True(t, entry1.focused)
assert.False(t, entry3.focused)
}

func TestFocusManager_FocusPrevious(t *testing.T) {
entry1 := widget.NewEntry()
entry1 := &focusable{}
hidden := widget.NewCheck("test", func(bool) {})
hidden.Hide()
entry2 := widget.NewEntry()
entry2 := &focusable{}
disabled := widget.NewCheck("test", func(bool) {})
disabled.Disable()
entry3 := widget.NewEntry()
entry3 := &focusable{}
c := widget.NewVBox(entry1, hidden, entry2, disabled, entry3)

manager := app.NewFocusManager(c)
assert.Nil(t, manager.Focused())

manager.FocusPrevious()
assert.Equal(t, entry3, manager.Focused())
assert.True(t, entry3.focused)

manager.FocusPrevious()
assert.Equal(t, entry2, manager.Focused())
assert.True(t, entry2.focused)
assert.False(t, entry3.focused)

manager.FocusPrevious()
assert.Equal(t, entry1, manager.Focused())
assert.True(t, entry1.focused)
assert.False(t, entry2.focused)

manager.FocusPrevious()
assert.Equal(t, entry3, manager.Focused())
assert.True(t, entry3.focused)
assert.False(t, entry1.focused)
}

type focusable struct {
widget.Entry
focused bool
}

func (f *focusable) FocusGained() {
if f.Disabled() {
return
}
f.focused = true
}

func (f *focusable) FocusLost() {
f.focused = false
}

0 comments on commit 83b4f9e

Please sign in to comment.