196 lines
4.6 KiB
Go
196 lines
4.6 KiB
Go
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),
|
|
)
|
|
}
|