mostly working now, except for track result action, and results ranking
This commit is contained in:
@@ -506,7 +506,10 @@ func (c *Controller) ShowQuickSearch() {
|
|||||||
c.NavigateTo(GenreRoute(id))
|
c.NavigateTo(GenreRoute(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.ClosePopUpOnEscape(pop)
|
||||||
|
c.haveModal = true
|
||||||
pop.Show()
|
pop.Show()
|
||||||
|
c.MainWindow.Canvas().Focus(qs.SearchEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) trySetPasswordAndConnectToServer(server *backend.ServerConfig, password string) error {
|
func (c *Controller) trySetPasswordAndConnectToServer(server *backend.ServerConfig, password string) error {
|
||||||
|
|||||||
+107
-23
@@ -6,12 +6,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
|
"github.com/dweymouth/supersonic/ui/layouts"
|
||||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
"github.com/dweymouth/supersonic/ui/widgets"
|
"github.com/dweymouth/supersonic/ui/widgets"
|
||||||
)
|
)
|
||||||
@@ -22,6 +23,8 @@ type QuickSearch struct {
|
|||||||
OnDismiss func()
|
OnDismiss func()
|
||||||
OnNavigateTo func(mediaprovider.ContentType, string)
|
OnNavigateTo func(mediaprovider.ContentType, string)
|
||||||
|
|
||||||
|
SearchEntry fyne.Focusable // exported so it can be focused by the Controller
|
||||||
|
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
|
|
||||||
@@ -40,15 +43,36 @@ func NewQuickSearch(mp mediaprovider.MediaProvider, im *backend.ImageManager) *Q
|
|||||||
}
|
}
|
||||||
q.ExtendBaseWidget(q)
|
q.ExtendBaseWidget(q)
|
||||||
|
|
||||||
searchEntry := widgets.NewSearchEntry()
|
se := newQuickSearchEntry()
|
||||||
searchEntry.OnChanged = q.onSearched
|
se.OnSearched = q.onSearched
|
||||||
|
se.OnSubmitted = func(_ string) {
|
||||||
|
q.onSelected(q.selectedIndex)
|
||||||
|
}
|
||||||
|
se.OnTypedDown = func() {
|
||||||
|
q.resultsMutex.RLock()
|
||||||
|
if q.selectedIndex < len(q.searchResults)-1 {
|
||||||
|
q.selectedIndex++
|
||||||
|
}
|
||||||
|
q.resultsMutex.RUnlock()
|
||||||
|
q.list.Select(q.selectedIndex)
|
||||||
|
}
|
||||||
|
se.OnTypedUp = func() {
|
||||||
|
q.resultsMutex.RLock()
|
||||||
|
if q.selectedIndex > 0 {
|
||||||
|
q.selectedIndex--
|
||||||
|
}
|
||||||
|
q.resultsMutex.RUnlock()
|
||||||
|
q.list.Select(q.selectedIndex)
|
||||||
|
}
|
||||||
|
se.OnTypedEscape = q.onDismiss
|
||||||
|
q.SearchEntry = se
|
||||||
q.list = widget.NewList(
|
q.list = widget.NewList(
|
||||||
func() int {
|
func() int {
|
||||||
q.resultsMutex.RLock()
|
q.resultsMutex.RLock()
|
||||||
defer q.resultsMutex.RUnlock()
|
defer q.resultsMutex.RUnlock()
|
||||||
return len(q.searchResults)
|
return len(q.searchResults)
|
||||||
},
|
},
|
||||||
func() fyne.CanvasObject { return newQuickSearchResult(im) },
|
func() fyne.CanvasObject { return newQuickSearchResult(q) },
|
||||||
func(lii widget.ListItemID, co fyne.CanvasObject) {
|
func(lii widget.ListItemID, co fyne.CanvasObject) {
|
||||||
var result *mediaprovider.SearchResult
|
var result *mediaprovider.SearchResult
|
||||||
q.resultsMutex.RLock()
|
q.resultsMutex.RLock()
|
||||||
@@ -56,16 +80,43 @@ func NewQuickSearch(mp mediaprovider.MediaProvider, im *backend.ImageManager) *Q
|
|||||||
result = q.searchResults[lii]
|
result = q.searchResults[lii]
|
||||||
}
|
}
|
||||||
q.resultsMutex.RUnlock()
|
q.resultsMutex.RUnlock()
|
||||||
co.(*quickSearchResult).Update(result)
|
qs := co.(*quickSearchResult)
|
||||||
|
qs.index = lii
|
||||||
|
qs.Update(result)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dismissBtn := widget.NewButton("Close", q.onDismiss)
|
||||||
title := widget.NewRichText(&widget.TextSegment{Text: "Quick Search", Style: boldStyle})
|
title := widget.NewRichText(&widget.TextSegment{Text: "Quick Search", Style: boldStyle})
|
||||||
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
|
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
|
||||||
q.content = container.NewBorder(
|
q.content = container.NewBorder(
|
||||||
container.NewVBox(title, searchEntry), nil, nil, nil, q.list)
|
container.NewVBox(title, se),
|
||||||
|
container.NewVBox(widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), dismissBtn)),
|
||||||
|
nil, nil, q.list)
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *QuickSearch) onDismiss() {
|
||||||
|
if q.OnDismiss != nil {
|
||||||
|
q.OnDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QuickSearch) onSelected(idx int) {
|
||||||
|
if q.OnNavigateTo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q.resultsMutex.RLock()
|
||||||
|
if len(q.searchResults) <= idx {
|
||||||
|
q.resultsMutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := q.searchResults[idx].ID
|
||||||
|
typ := q.searchResults[idx].Type
|
||||||
|
q.resultsMutex.RUnlock()
|
||||||
|
q.OnNavigateTo(typ, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *QuickSearch) onSearched(query string) {
|
func (q *QuickSearch) onSearched(query string) {
|
||||||
var results []*mediaprovider.SearchResult
|
var results []*mediaprovider.SearchResult
|
||||||
if query != "" {
|
if query != "" {
|
||||||
@@ -79,6 +130,8 @@ func (q *QuickSearch) onSearched(query string) {
|
|||||||
q.searchResults = results
|
q.searchResults = results
|
||||||
q.resultsMutex.Unlock()
|
q.resultsMutex.Unlock()
|
||||||
q.list.Refresh()
|
q.list.Refresh()
|
||||||
|
q.list.ScrollToTop()
|
||||||
|
q.list.Select(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuickSearch) CreateRenderer() fyne.WidgetRenderer {
|
func (q *QuickSearch) CreateRenderer() fyne.WidgetRenderer {
|
||||||
@@ -86,15 +139,16 @@ func (q *QuickSearch) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuickSearch) MinSize() fyne.Size {
|
func (q *QuickSearch) MinSize() fyne.Size {
|
||||||
return fyne.NewSize(500, 350)
|
return fyne.NewSize(400, 350)
|
||||||
}
|
}
|
||||||
|
|
||||||
type quickSearchResult struct {
|
type quickSearchResult struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
// parent *QuickSearch
|
parent *QuickSearch
|
||||||
|
|
||||||
id string
|
id string
|
||||||
|
index int
|
||||||
contentType mediaprovider.ContentType
|
contentType mediaprovider.ContentType
|
||||||
|
|
||||||
imageLoader backend.ThumbnailLoader
|
imageLoader backend.ThumbnailLoader
|
||||||
@@ -102,19 +156,21 @@ type quickSearchResult struct {
|
|||||||
image *widgets.ImagePlaceholder
|
image *widgets.ImagePlaceholder
|
||||||
title *widget.Label
|
title *widget.Label
|
||||||
secondary *widget.RichText
|
secondary *widget.RichText
|
||||||
selection *canvas.Rectangle
|
|
||||||
|
|
||||||
content *fyne.Container
|
content *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func newQuickSearchResult(im *backend.ImageManager) *quickSearchResult {
|
func newQuickSearchResult(parent *QuickSearch) *quickSearchResult {
|
||||||
qs := &quickSearchResult{
|
qs := &quickSearchResult{
|
||||||
|
parent: parent,
|
||||||
image: widgets.NewImagePlaceholder(myTheme.AlbumIcon, 50),
|
image: widgets.NewImagePlaceholder(myTheme.AlbumIcon, 50),
|
||||||
title: widget.NewLabel(""),
|
title: widget.NewLabel(""),
|
||||||
secondary: widget.NewRichText(),
|
secondary: widget.NewRichText(),
|
||||||
}
|
}
|
||||||
|
qs.title.Wrapping = fyne.TextTruncate
|
||||||
|
qs.secondary.Wrapping = fyne.TextTruncate
|
||||||
qs.ExtendBaseWidget(qs)
|
qs.ExtendBaseWidget(qs)
|
||||||
qs.imageLoader = im.NewThumbnailLoader(func(im image.Image) {
|
qs.imageLoader = parent.im.NewThumbnailLoader(func(im image.Image) {
|
||||||
qs.image.SetImage(im, false)
|
qs.image.SetImage(im, false)
|
||||||
})
|
})
|
||||||
qs.imageLoader.OnBeforeLoad = func() {
|
qs.imageLoader.OnBeforeLoad = func() {
|
||||||
@@ -136,22 +192,21 @@ func (q *quickSearchResult) Update(result *mediaprovider.SearchResult) {
|
|||||||
q.image.CenterIcon = placeholderIconForContentType(result.Type)
|
q.image.CenterIcon = placeholderIconForContentType(result.Type)
|
||||||
q.imageLoader.Load(result.CoverID)
|
q.imageLoader.Load(result.CoverID)
|
||||||
q.title.SetText(result.Name)
|
q.title.SetText(result.Name)
|
||||||
//TODO: q.secondary.Segments =
|
q.secondary.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: result.Type.String()}}
|
||||||
|
q.secondary.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quickSearchResult) Tapped(_ *fyne.PointEvent) {
|
||||||
|
q.parent.onSelected(q.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *quickSearchResult) CreateRenderer() fyne.WidgetRenderer {
|
func (q *quickSearchResult) CreateRenderer() fyne.WidgetRenderer {
|
||||||
if q.selection == nil {
|
|
||||||
q.selection = canvas.NewRectangle(theme.SelectionColor())
|
|
||||||
}
|
|
||||||
if q.content == nil {
|
if q.content == nil {
|
||||||
q.content = container.NewMax(
|
q.content = container.NewBorder(nil, nil, container.NewCenter(q.image), nil,
|
||||||
q.selection,
|
container.New(&layouts.VboxCustomPadding{ExtraPad: -15},
|
||||||
container.NewBorder(nil, nil, q.image, nil,
|
q.title,
|
||||||
container.NewVBox(
|
q.secondary,
|
||||||
q.title,
|
))
|
||||||
q.secondary,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return widget.NewSimpleRenderer(q.content)
|
return widget.NewSimpleRenderer(q.content)
|
||||||
}
|
}
|
||||||
@@ -160,6 +215,35 @@ func (q *quickSearchResult) Refresh() {
|
|||||||
q.BaseWidget.Refresh()
|
q.BaseWidget.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type quickSearchEntry struct {
|
||||||
|
widgets.SearchEntry
|
||||||
|
|
||||||
|
OnTypedUp func()
|
||||||
|
OnTypedDown func()
|
||||||
|
OnTypedEscape func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newQuickSearchEntry() *quickSearchEntry {
|
||||||
|
q := &quickSearchEntry{}
|
||||||
|
q.ExtendBaseWidget(q)
|
||||||
|
q.SearchEntry.Init()
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quickSearchEntry) TypedKey(e *fyne.KeyEvent) {
|
||||||
|
switch {
|
||||||
|
case e.Name == fyne.KeyUp && q.OnTypedUp != nil:
|
||||||
|
q.OnTypedUp()
|
||||||
|
case e.Name == fyne.KeyDown && q.OnTypedDown != nil:
|
||||||
|
q.OnTypedDown()
|
||||||
|
case e.Name == fyne.KeyEscape && q.OnTypedEscape != nil:
|
||||||
|
q.OnTypedEscape()
|
||||||
|
default:
|
||||||
|
q.SearchEntry.TypedKey(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func placeholderIconForContentType(c mediaprovider.ContentType) fyne.Resource {
|
func placeholderIconForContentType(c mediaprovider.ContentType) fyne.Resource {
|
||||||
switch c {
|
switch c {
|
||||||
case mediaprovider.ContentTypeAlbum:
|
case mediaprovider.ContentTypeAlbum:
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ type SearchEntry struct {
|
|||||||
func NewSearchEntry() *SearchEntry {
|
func NewSearchEntry() *SearchEntry {
|
||||||
sf := &SearchEntry{}
|
sf := &SearchEntry{}
|
||||||
sf.ExtendBaseWidget(sf)
|
sf.ExtendBaseWidget(sf)
|
||||||
|
sf.Init()
|
||||||
|
return sf
|
||||||
|
}
|
||||||
|
|
||||||
|
// For use only by extending widgets
|
||||||
|
func (sf *SearchEntry) Init() {
|
||||||
sf.PlaceHolder = "Search"
|
sf.PlaceHolder = "Search"
|
||||||
sf.ActionItem = NewClearTextButton(func() {
|
sf.ActionItem = NewClearTextButton(func() {
|
||||||
sf.SetText("")
|
sf.SetText("")
|
||||||
@@ -34,7 +40,6 @@ func NewSearchEntry() *SearchEntry {
|
|||||||
}
|
}
|
||||||
debounceFunc()
|
debounceFunc()
|
||||||
}
|
}
|
||||||
return sf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SearchEntry) TypedKey(e *fyne.KeyEvent) {
|
func (s *SearchEntry) TypedKey(e *fyne.KeyEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user