163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
|
package app
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/internal/driver"
|
||
|
)
|
||
|
|
||
|
// FocusManager represents a standard manager of input focus for a canvas
|
||
|
type FocusManager struct {
|
||
|
sync.RWMutex
|
||
|
|
||
|
content fyne.CanvasObject
|
||
|
focused fyne.Focusable
|
||
|
}
|
||
|
|
||
|
// NewFocusManager returns a new instance of the standard focus manager for a canvas.
|
||
|
func NewFocusManager(c fyne.CanvasObject) *FocusManager {
|
||
|
return &FocusManager{content: c}
|
||
|
}
|
||
|
|
||
|
// Focus focuses the given obj.
|
||
|
func (f *FocusManager) Focus(obj fyne.Focusable) bool {
|
||
|
f.Lock()
|
||
|
defer f.Unlock()
|
||
|
if obj != nil {
|
||
|
var hiddenAncestor fyne.CanvasObject
|
||
|
hidden := false
|
||
|
found := driver.WalkCompleteObjectTree(
|
||
|
f.content,
|
||
|
func(object fyne.CanvasObject, _, _ fyne.Position, _ fyne.Size) bool {
|
||
|
if hiddenAncestor == nil && !object.Visible() {
|
||
|
hiddenAncestor = object
|
||
|
}
|
||
|
if object == obj.(fyne.CanvasObject) {
|
||
|
hidden = hiddenAncestor != nil
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
},
|
||
|
func(object fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
|
||
|
if hiddenAncestor == object {
|
||
|
hiddenAncestor = nil
|
||
|
}
|
||
|
},
|
||
|
)
|
||
|
if !found {
|
||
|
return false
|
||
|
}
|
||
|
if hidden {
|
||
|
return true
|
||
|
}
|
||
|
if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() {
|
||
|
type selectableText interface {
|
||
|
SelectedText() string
|
||
|
}
|
||
|
if _, isSelectableText := obj.(selectableText); !isSelectableText || fyne.CurrentDevice().IsMobile() {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
f.focus(obj)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Focused returns the currently focused object or nil if none.
|
||
|
func (f *FocusManager) Focused() fyne.Focusable {
|
||
|
f.RLock()
|
||
|
defer f.RUnlock()
|
||
|
return f.focused
|
||
|
}
|
||
|
|
||
|
// FocusGained signals to the manager that its content got focus (due to window/overlay switch for instance).
|
||
|
func (f *FocusManager) FocusGained() {
|
||
|
if focused := f.Focused(); focused != nil {
|
||
|
focused.FocusGained()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FocusLost signals to the manager that its content lost focus (due to window/overlay switch for instance).
|
||
|
func (f *FocusManager) FocusLost() {
|
||
|
if focused := f.Focused(); focused != nil {
|
||
|
focused.FocusLost()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FocusNext will find the item after the current that can be focused and focus it.
|
||
|
// If current is nil then the first focusable item in the canvas will be focused.
|
||
|
func (f *FocusManager) FocusNext() {
|
||
|
f.Lock()
|
||
|
defer f.Unlock()
|
||
|
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.focus(f.previousInChain(f.focused))
|
||
|
}
|
||
|
|
||
|
func (f *FocusManager) focus(obj fyne.Focusable) {
|
||
|
if f.focused == obj {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if f.focused != nil {
|
||
|
f.focused.FocusLost()
|
||
|
}
|
||
|
f.focused = obj
|
||
|
if obj != nil {
|
||
|
obj.FocusGained()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (f *FocusManager) nextInChain(current fyne.Focusable) fyne.Focusable {
|
||
|
return f.nextWithWalker(current, driver.WalkVisibleObjectTree)
|
||
|
}
|
||
|
|
||
|
func (f *FocusManager) nextWithWalker(current fyne.Focusable, walker walkerFunc) fyne.Focusable {
|
||
|
var next fyne.Focusable
|
||
|
found := current == nil // if we have no starting point then pretend we matched already
|
||
|
walker(f.content, func(obj fyne.CanvasObject, _ fyne.Position, _ fyne.Position, _ fyne.Size) bool {
|
||
|
if w, ok := obj.(fyne.Disableable); ok && w.Disabled() {
|
||
|
// disabled widget cannot receive focus
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
focus, ok := obj.(fyne.Focusable)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if found {
|
||
|
next = focus
|
||
|
return true
|
||
|
}
|
||
|
if next == nil {
|
||
|
next = focus
|
||
|
}
|
||
|
|
||
|
if obj == current.(fyne.CanvasObject) {
|
||
|
found = true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}, nil)
|
||
|
|
||
|
return next
|
||
|
}
|
||
|
|
||
|
func (f *FocusManager) previousInChain(current fyne.Focusable) fyne.Focusable {
|
||
|
return f.nextWithWalker(current, driver.ReverseWalkVisibleObjectTree)
|
||
|
}
|
||
|
|
||
|
type walkerFunc func(
|
||
|
fyne.CanvasObject,
|
||
|
func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
|
||
|
func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
|
||
|
) bool
|