package dialogs import ( "image" "log" "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/widgets" ) type QuickSearch struct { widget.BaseWidget OnDismiss func() OnNavigateTo func(mediaprovider.ContentType, string) mp mediaprovider.MediaProvider im *backend.ImageManager resultsMutex sync.RWMutex searchResults []*mediaprovider.SearchResult list *widget.List selectedIndex int content *fyne.Container } func NewQuickSearch(mp mediaprovider.MediaProvider, im *backend.ImageManager) *QuickSearch { q := &QuickSearch{ mp: mp, im: im, } q.ExtendBaseWidget(q) searchEntry := widgets.NewSearchEntry() searchEntry.OnChanged = q.onSearched q.list = widget.NewList( func() int { q.resultsMutex.RLock() defer q.resultsMutex.RUnlock() return len(q.searchResults) }, func() fyne.CanvasObject { return newQuickSearchResult(im) }, func(lii widget.ListItemID, co fyne.CanvasObject) { var result *mediaprovider.SearchResult q.resultsMutex.RLock() if len(q.searchResults) > lii { result = q.searchResults[lii] } q.resultsMutex.RUnlock() co.(*quickSearchResult).Update(result) }, ) title := widget.NewRichText(&widget.TextSegment{Text: "Quick Search", Style: boldStyle}) title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter q.content = container.NewBorder( container.NewVBox(title, searchEntry), nil, nil, nil, q.list) return q } func (q *QuickSearch) onSearched(query string) { var results []*mediaprovider.SearchResult if query != "" { if res, err := q.mp.SearchAll(query, 20); err != nil { log.Printf("Error searching: %s", err.Error()) } else { results = res } } q.resultsMutex.Lock() q.searchResults = results q.resultsMutex.Unlock() q.list.Refresh() } func (q *QuickSearch) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(q.content) } func (q *QuickSearch) MinSize() fyne.Size { return fyne.NewSize(500, 350) } type quickSearchResult struct { widget.BaseWidget // parent *QuickSearch id string contentType mediaprovider.ContentType imageLoader backend.ThumbnailLoader image *widgets.ImagePlaceholder title *widget.Label secondary *widget.RichText selection *canvas.Rectangle content *fyne.Container } func newQuickSearchResult(im *backend.ImageManager) *quickSearchResult { qs := &quickSearchResult{ image: widgets.NewImagePlaceholder(myTheme.AlbumIcon, 50), title: widget.NewLabel(""), secondary: widget.NewRichText(), } qs.ExtendBaseWidget(qs) qs.imageLoader = im.NewThumbnailLoader(func(im image.Image) { qs.image.SetImage(im, false) }) qs.imageLoader.OnBeforeLoad = func() { qs.image.SetImage(nil, false) } return qs } func (q *quickSearchResult) Update(result *mediaprovider.SearchResult) { if result == nil { return } if q.contentType == result.Type && q.id == result.ID { return // nothing to do } q.id = result.ID q.contentType = result.Type q.image.CenterIcon = placeholderIconForContentType(result.Type) q.imageLoader.Load(result.CoverID) q.title.SetText(result.Name) //TODO: q.secondary.Segments = } func (q *quickSearchResult) CreateRenderer() fyne.WidgetRenderer { if q.selection == nil { q.selection = canvas.NewRectangle(theme.SelectionColor()) } if q.content == nil { q.content = container.NewMax( q.selection, container.NewBorder(nil, nil, q.image, nil, container.NewVBox( q.title, q.secondary, )), ) } return widget.NewSimpleRenderer(q.content) } func (q *quickSearchResult) Refresh() { q.BaseWidget.Refresh() } func placeholderIconForContentType(c mediaprovider.ContentType) fyne.Resource { switch c { case mediaprovider.ContentTypeAlbum: return myTheme.AlbumIcon case mediaprovider.ContentTypeArtist: return myTheme.ArtistIcon case mediaprovider.ContentTypeTrack: return myTheme.TracksIcon case mediaprovider.ContentTypeGenre: return myTheme.GenreIcon case mediaprovider.ContentTypePlaylist: return myTheme.PlaylistIcon default: return theme.WarningIcon() // unreached } }