refactor and add focus navigation to genres list
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// FocusList extends List to be disabled so that the focus manager
|
||||
// considers it unfocusable, and adds utilities for handling our
|
||||
// own focus navigation on the rows directly (with FocusListRow
|
||||
type FocusList struct {
|
||||
widget.List
|
||||
|
||||
mutex sync.Mutex
|
||||
itemForIndex map[widget.ListItemID]FocusListRow
|
||||
}
|
||||
|
||||
type FocusListRow interface {
|
||||
fyne.Focusable
|
||||
ItemID() widget.ListItemID
|
||||
}
|
||||
|
||||
func NewFocusList(len func() int, create func() fyne.CanvasObject, update func(widget.GridWrapItemID, fyne.CanvasObject)) *FocusList {
|
||||
g := &FocusList{
|
||||
List: widget.List{
|
||||
Length: len,
|
||||
CreateItem: create,
|
||||
UpdateItem: update,
|
||||
},
|
||||
itemForIndex: make(map[int]FocusListRow),
|
||||
}
|
||||
g.ExtendBaseWidget(g)
|
||||
return g
|
||||
}
|
||||
|
||||
var _ fyne.Disableable = (*FocusList)(nil)
|
||||
|
||||
func (g *FocusList) Disabled() bool { return true }
|
||||
|
||||
func (g *FocusList) Disable() {}
|
||||
|
||||
func (g *FocusList) Enable() {}
|
||||
|
||||
// MUST be called *before* updating the ListItemID field to the
|
||||
// new ItemID this row will be bound to.
|
||||
func (g *FocusList) SetItemForID(id widget.ListItemID, item FocusListRow) {
|
||||
g.mutex.Lock()
|
||||
if other, ok := g.itemForIndex[id]; ok && other == item {
|
||||
delete(g.itemForIndex, other.ItemID())
|
||||
}
|
||||
g.itemForIndex[id] = item
|
||||
g.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (g *FocusList) ClearItemForIDMap() {
|
||||
g.itemForIndex = make(map[int]FocusListRow)
|
||||
}
|
||||
|
||||
func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) {
|
||||
focusIdx := curItem + 1
|
||||
if up {
|
||||
focusIdx = curItem - 1
|
||||
}
|
||||
if focusIdx >= 0 && focusIdx < g.Length() {
|
||||
g.ScrollTo(focusIdx)
|
||||
}
|
||||
g.mutex.Lock()
|
||||
other, ok := g.itemForIndex[focusIdx]
|
||||
g.mutex.Unlock()
|
||||
if ok {
|
||||
fyne.CurrentApp().Driver().CanvasForObject(g).Focus(other)
|
||||
}
|
||||
}
|
||||
|
||||
var _ fyne.Tappable = (*FocusListRowBase)(nil)
|
||||
var _ fyne.Widget = (*FocusListRowBase)(nil)
|
||||
var _ fyne.Focusable = (*FocusListRowBase)(nil)
|
||||
|
||||
// Base type used for all list rows in widgets such as Tracklist, etc.
|
||||
type FocusListRowBase struct {
|
||||
widget.BaseWidget
|
||||
|
||||
ListItemID widget.ListItemID
|
||||
Content fyne.CanvasObject
|
||||
Selected bool
|
||||
Focused bool
|
||||
|
||||
OnTapped func()
|
||||
OnDoubleTapped func()
|
||||
OnFocusNeighbor func(up bool) //TODO: func(up, selecting bool)
|
||||
|
||||
tappedAt int64 // unixMillis
|
||||
focusedRect *canvas.Rectangle
|
||||
selectionRect *canvas.Rectangle
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) ItemID() widget.ListItemID {
|
||||
return l.ListItemID
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) EnsureUnfocused() {
|
||||
if l.Focused {
|
||||
fyne.CurrentApp().Driver().CanvasForObject(l).Unfocus()
|
||||
}
|
||||
l.Focused = false
|
||||
}
|
||||
|
||||
// We implement our own double tapping so that the Tapped behavior
|
||||
// can be triggered instantly.
|
||||
func (l *FocusListRowBase) Tapped(*fyne.PointEvent) {
|
||||
prevTap := l.tappedAt
|
||||
l.tappedAt = time.Now().UnixMilli()
|
||||
if l.tappedAt-prevTap < 300 {
|
||||
if l.OnDoubleTapped != nil {
|
||||
l.OnDoubleTapped()
|
||||
}
|
||||
} else {
|
||||
if l.OnTapped != nil {
|
||||
l.OnTapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) FocusGained() {
|
||||
l.Focused = true
|
||||
l.Refresh()
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) FocusLost() {
|
||||
l.Focused = false
|
||||
l.Refresh()
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) {
|
||||
/**
|
||||
// TODO: enable shift+arrows for selection, but it's complicated to implement in the widgets
|
||||
desktop, ok := fyne.CurrentApp().Driver().(desktop.Driver)
|
||||
isSelecting := func() bool {
|
||||
return ok && desktop.CurrentKeyModifiers()&fyne.KeyModifierShift != 0
|
||||
}
|
||||
*/
|
||||
switch {
|
||||
case e.Name == fyne.KeyUp:
|
||||
if l.OnFocusNeighbor != nil {
|
||||
l.OnFocusNeighbor(true)
|
||||
}
|
||||
case e.Name == fyne.KeyDown:
|
||||
if l.OnFocusNeighbor != nil {
|
||||
l.OnFocusNeighbor(false)
|
||||
}
|
||||
case e.Name == fyne.KeySpace:
|
||||
if l.OnTapped != nil {
|
||||
l.OnTapped()
|
||||
}
|
||||
case e.Name == fyne.KeyReturn || e.Name == fyne.KeyEnter:
|
||||
if l.OnDoubleTapped != nil {
|
||||
l.OnDoubleTapped()
|
||||
} else if l.OnTapped != nil {
|
||||
l.OnTapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) TypedRune(r rune) {
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) Refresh() {
|
||||
l.focusedRect.FillColor = theme.HoverColor()
|
||||
l.focusedRect.Hidden = !l.Focused
|
||||
l.selectionRect.FillColor = theme.SelectionColor()
|
||||
l.selectionRect.Hidden = !l.Selected
|
||||
l.BaseWidget.Refresh()
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) CreateRenderer() fyne.WidgetRenderer {
|
||||
if l.selectionRect == nil {
|
||||
l.selectionRect = canvas.NewRectangle(theme.SelectionColor())
|
||||
l.selectionRect.CornerRadius = theme.SelectionRadiusSize()
|
||||
l.selectionRect.Hidden = !l.Selected
|
||||
}
|
||||
if l.focusedRect == nil {
|
||||
l.focusedRect = canvas.NewRectangle(theme.HoverColor())
|
||||
l.focusedRect.CornerRadius = theme.SelectionRadiusSize()
|
||||
l.focusedRect.Hidden = !l.Focused
|
||||
}
|
||||
return widget.NewSimpleRenderer(
|
||||
container.NewStack(l.selectionRect, l.focusedRect, l.Content),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user