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