simplify SearchEntry and extract a debouncer util func

This commit is contained in:
Drew Weymouth
2023-05-10 21:05:17 -07:00
parent b94ef8da97
commit 5634dfcfc1
8 changed files with 58 additions and 85 deletions
+5 -5
View File
@@ -26,7 +26,7 @@ type AlbumsPage struct {
lm *backend.LibraryManager
grid *widgets.GridView
searchGrid *widgets.GridView
searcher *widgets.Searcher
searcher *widgets.SearchEntry
searchText string
titleDisp *widget.RichText
sortOrder *selectWidget
@@ -74,7 +74,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected))
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im)
contr.ConnectAlbumGridActions(a.grid)
a.searcher = widgets.NewSearcher()
a.searcher = widgets.NewSearchEntry()
a.searcher.OnSearched = a.OnSearched
a.createContainer(false)
@@ -82,7 +82,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
}
func (a *AlbumsPage) createContainer(searchgrid bool) {
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
g := a.grid
if searchgrid {
@@ -115,7 +115,7 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
a.sortOrder.Selected = saved.sortOrder
a.sortOrder.OnChanged = a.onSortOrderChanged
a.grid = widgets.NewGridViewFromState(saved.gridState)
a.searcher = widgets.NewSearcher()
a.searcher = widgets.NewSearchEntry()
a.searcher.OnSearched = a.OnSearched
a.searcher.Entry.Text = saved.searchText
a.searchText = saved.searchText
@@ -147,7 +147,7 @@ func (a *AlbumsPage) Route() controller.Route {
var _ Searchable = (*AlbumsPage)(nil)
func (a *AlbumsPage) SearchWidget() fyne.Focusable {
return a.searcher.Entry
return a.searcher
}
func (a *AlbumsPage) Reload() {
+4 -4
View File
@@ -33,7 +33,7 @@ type ArtistsGenresPage struct {
titleDisp *widget.RichText
container *fyne.Container
searcher *widgets.Searcher
searcher *widgets.SearchEntry
}
func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager) *ArtistsGenresPage {
@@ -63,7 +63,7 @@ func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b
a.contr.NavigateTo(controller.ArtistRoute(id))
}
}
a.searcher = widgets.NewSearcher()
a.searcher = widgets.NewSearchEntry()
a.searcher.OnSearched = a.onSearched
a.searcher.Entry.Text = searchText
a.buildContainer()
@@ -111,7 +111,7 @@ func (a *ArtistsGenresPage) onSearched(query string) {
var _ Searchable = (*ArtistsGenresPage)(nil)
func (a *ArtistsGenresPage) SearchWidget() fyne.Focusable {
return a.searcher.Entry
return a.searcher
}
func (a *ArtistsGenresPage) Route() controller.Route {
@@ -175,7 +175,7 @@ func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []wid
}
func (a *ArtistsGenresPage) buildContainer() {
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15},
container.NewBorder(
container.New(&layouts.MaxPadLayout{PadLeft: -5},
+4 -4
View File
@@ -38,7 +38,7 @@ type FavoritesPage struct {
searchGrid *widgets.GridView
artistListCtr *fyne.Container
tracklistCtr *fyne.Container
searcher *widgets.Searcher
searcher *widgets.SearchEntry
titleDisp *widget.RichText
toggleBtns *widgets.ToggleButtonGroup
container *fyne.Container
@@ -78,13 +78,13 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) {
widget.NewButtonWithIcon("", myTheme.AlbumIcon, a.onShowFavoriteAlbums),
widget.NewButtonWithIcon("", myTheme.ArtistIcon, a.onShowFavoriteArtists),
widget.NewButtonWithIcon("", myTheme.TracksIcon, a.onShowFavoriteSongs))
a.searcher = widgets.NewSearcher()
a.searcher = widgets.NewSearchEntry()
a.searcher.OnSearched = a.OnSearched
a.searcher.Entry.Text = searchText
}
func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) {
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
a.container = container.NewBorder(
container.NewHBox(util.NewHSpace(9), a.titleDisp, container.NewCenter(a.toggleBtns), layout.NewSpacer(), searchVbox, util.NewHSpace(15)),
nil, nil, nil, initialView)
@@ -182,7 +182,7 @@ func (a *FavoritesPage) Save() SavedPage {
var _ Searchable = (*FavoritesPage)(nil)
func (a *FavoritesPage) SearchWidget() fyne.Focusable {
return a.searcher.Entry
return a.searcher
}
func (a *FavoritesPage) OnSearched(query string) {
+5 -5
View File
@@ -27,7 +27,7 @@ type GenrePage struct {
lm *backend.LibraryManager
grid *widgets.GridView
searchGrid *widgets.GridView
searcher *widgets.Searcher
searcher *widgets.SearchEntry
searchText string
titleDisp *widget.RichText
playRandom *widget.Button
@@ -55,7 +55,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
iter := g.lm.GenreIter(g.genre)
g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.grid)
g.searcher = widgets.NewSearcher()
g.searcher = widgets.NewSearchEntry()
g.searcher.OnSearched = g.OnSearched
g.createContainer(false)
@@ -63,7 +63,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
}
func (g *GenrePage) createContainer(searchGrid bool) {
searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher, layout.NewSpacer())
gr := g.grid
if searchGrid {
gr = g.searchGrid
@@ -94,7 +94,7 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage {
}
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
g.grid = widgets.NewGridViewFromState(saved.gridState)
g.searcher = widgets.NewSearcher()
g.searcher = widgets.NewSearchEntry()
g.searcher.OnSearched = g.OnSearched
g.searcher.Entry.Text = saved.searchText
g.searchText = saved.searchText
@@ -142,7 +142,7 @@ func (g *GenrePage) Save() SavedPage {
var _ Searchable = (*AlbumsPage)(nil)
func (g *GenrePage) SearchWidget() fyne.Focusable {
return g.searcher.Entry
return g.searcher
}
func (g *GenrePage) OnSearched(query string) {
+4 -4
View File
@@ -32,7 +32,7 @@ type PlaylistsPage struct {
searchedPlaylists []*subsonic.Playlist
viewToggle *widgets.ToggleButtonGroup
searcher *widgets.Searcher
searcher *widgets.SearchEntry
titleDisp *widget.RichText
container *fyne.Container
listView *PlaylistList
@@ -56,7 +56,7 @@ func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageCo
}
a.ExtendBaseWidget(a)
a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText
a.searcher = widgets.NewSearcher()
a.searcher = widgets.NewSearchEntry()
a.searcher.OnSearched = a.onSearched
a.searcher.Entry.Text = searchText
a.viewToggle = widgets.NewToggleButtonGroup(0,
@@ -199,7 +199,7 @@ func (a *PlaylistsPage) refreshView(playlists []*subsonic.Playlist) {
var _ Searchable = (*PlaylistsPage)(nil)
func (a *PlaylistsPage) SearchWidget() fyne.Focusable {
return a.searcher.Entry
return a.searcher
}
func (a *PlaylistsPage) Route() controller.Route {
@@ -233,7 +233,7 @@ func (s *savedPlaylistsPage) Restore() Page {
}
func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) {
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15},
container.NewBorder(
container.NewHBox(a.titleDisp, container.NewCenter(a.viewToggle), layout.NewSpacer(), searchVbox),
+4 -4
View File
@@ -24,7 +24,7 @@ type TracksPage struct {
nowPlayingID string
title *widget.RichText
searcher *widgets.Searcher
searcher *widgets.SearchEntry
tracklist *widgets.Tracklist
loader widgets.TracklistLoader
searchTracklist *widgets.Tracklist
@@ -58,7 +58,7 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
t.title = widget.NewRichTextWithText("All Tracks")
t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
t.playRandom = widget.NewButtonWithIcon("Play random", theme.ShuffleIcon, t.playRandomSongs)
t.searcher = widgets.NewSearcher()
t.searcher = widgets.NewSearchEntry()
t.searcher.OnSearched = t.OnSearched
t.createContainer()
t.Reload()
@@ -67,7 +67,7 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
func (t *TracksPage) createContainer() {
playRandomVbox := container.NewVBox(layout.NewSpacer(), t.playRandom, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), t.searcher.Entry, layout.NewSpacer())
searchVbox := container.NewVBox(layout.NewSpacer(), t.searcher, layout.NewSpacer())
topRow := container.NewHBox(t.title, playRandomVbox, layout.NewSpacer(), searchVbox)
t.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15},
container.NewBorder(topRow, nil, nil, nil, t.tracklist))
@@ -100,7 +100,7 @@ func (t *TracksPage) OnSongChange(track *subsonic.Child, lastScrobbledIfAny *sub
var _ Searchable = (*TracksPage)(nil)
func (t *TracksPage) SearchWidget() fyne.Focusable {
return t.searcher.Entry
return t.searcher
}
func (t *TracksPage) OnSearched(query string) {
+18
View File
@@ -6,6 +6,8 @@ import (
"math"
"strconv"
"strings"
"sync"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/layout"
@@ -60,6 +62,22 @@ func ImageAspect(im image.Image) float32 {
return float32(b.Max.X-b.Min.X) / float32(b.Max.Y-b.Min.Y)
}
// Debouncer returns a function that will call callOnDone when
// it has not been invoked since the last dur interval.
func NewDebouncer(dur time.Duration, callOnDone func()) func() {
var mu sync.Mutex
var timer *time.Timer
return func() {
mu.Lock()
defer mu.Unlock()
if timer != nil {
timer.Stop()
}
timer = time.AfterFunc(dur, callOnDone)
}
}
func RichTextSegsFromHTMLString(s string) []widget.RichTextSegment {
tokr := html.NewTokenizer(strings.NewReader(s))
var segs []widget.RichTextSegment
@@ -1,76 +1,21 @@
package widgets
import (
"sync"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/ui/util"
)
// Searcher is a search entry widget that will issue a search command
// SearchEntry is a search entry widget that will issue a search command
// (aka call OnSearched) when a short span of time has elapsed since
// the user typed into the widget.
type Searcher struct {
Entry *SearchEntry
OnSearched func(string)
searchGoroutine bool
pendingSearch bool
pendingSearchLock sync.Mutex
}
func NewSearcher() *Searcher {
s := &Searcher{
Entry: NewSearchEntry(),
}
s.Entry.OnChanged = s.onSearchTextChanged
return s
}
func (s *Searcher) onSearchTextChanged(text string) {
if text == "" {
s.sendSearch("")
return
}
s.pendingSearchLock.Lock()
defer s.pendingSearchLock.Unlock()
s.pendingSearch = true
if !s.searchGoroutine {
go s.waitAndSearch()
s.searchGoroutine = true
}
}
func (s *Searcher) waitAndSearch() {
t := time.NewTicker(200 * time.Millisecond)
var getReadyToSearch bool
var done bool
for !done {
<-t.C
s.pendingSearchLock.Lock()
if s.pendingSearch {
getReadyToSearch = true
s.pendingSearch = false
} else if getReadyToSearch {
s.sendSearch(s.Entry.Text)
t.Stop()
s.searchGoroutine = false
done = true
}
s.pendingSearchLock.Unlock()
}
}
func (s *Searcher) sendSearch(text string) {
if s.OnSearched != nil {
s.OnSearched(text)
}
}
type SearchEntry struct {
widget.Entry
OnSearched func(string)
}
func NewSearchEntry() *SearchEntry {
@@ -80,6 +25,10 @@ func NewSearchEntry() *SearchEntry {
sf.ActionItem = NewClearTextButton(func() {
sf.SetText("")
})
debounceFunc := util.NewDebouncer(200*time.Millisecond, func() {
sf.sendSearch(sf.Entry.Text)
})
sf.Entry.OnChanged = func(_ string) { debounceFunc() }
return sf
}
@@ -104,6 +53,12 @@ type clearTextButton struct {
OnTapped func()
}
func (s *SearchEntry) sendSearch(text string) {
if s.OnSearched != nil {
s.OnSearched(text)
}
}
func NewClearTextButton(onTapped func()) *clearTextButton {
c := &clearTextButton{OnTapped: onTapped}
c.ExtendBaseWidget(c)