Merge pull request #26 from dweymouth/add-navigation-buttons

Add Artists, Genres pages + buttons in navigation bar
This commit is contained in:
Drew Weymouth
2023-01-15 13:42:31 -08:00
committed by GitHub
38 changed files with 540 additions and 66 deletions
+4 -4
View File
@@ -3,9 +3,9 @@ A lightweight desktop client for Subsonic music servers. This is in early develo
On first startup, the app will prompt you for your Subsonic server connection. The app currently has a searchable albums grid view, an artist discography view, and individual album view with tracklist. On first startup, the app will prompt you for your Subsonic server connection. The app currently has a searchable albums grid view, an artist discography view, and individual album view with tracklist.
<p align="center"> <p align="center">
<img src="https://raw.github.com/dweymouth/supersonic/main/screenshots/albums-view.png" scale="50%"/><br/> <img src="https://raw.github.com/dweymouth/supersonic/main/res/screenshots/albums-view.png" scale="50%"/><br/>
Supersonic running against the Navidrome demo server<br/> Supersonic running against the Navidrome demo server<br/>
<img src="https://raw.github.com/dweymouth/supersonic/main/screenshots/album-view.png" scale="50%"/> <img src="https://raw.github.com/dweymouth/supersonic/main/res/screenshots/album-view.png" scale="50%"/>
</p> </p>
## Features ## Features
@@ -15,8 +15,8 @@ Supersonic running against the Navidrome demo server<br/>
* [x] Scrobble plays to server * [x] Scrobble plays to server
* [x] Browse, search, and play albums * [x] Browse, search, and play albums
* [x] Album view with tracklist * [x] Album view with tracklist
* [x] Browse by genre (partial; full support coming soon) * [x] Browse by genre
* [x] Browse by artist (partial; full support coming soon) * [x] Browse by artist
* [ ] View and edit play queue (coming soon) * [ ] View and edit play queue (coming soon)
* [ ] Set/unset favorite and browse by favorites (coming soon) * [ ] Set/unset favorite and browse by favorites (coming soon)
* [ ] Browse, create, and edit playlists (coming soon) * [ ] Browse, create, and edit playlists (coming soon)
+1 -1
View File
@@ -37,7 +37,7 @@ func main() {
// This makes it much less likely to occur (not seen on dozens of startups) // This makes it much less likely to occur (not seen on dozens of startups)
// but is a hacky "solution"! // but is a hacky "solution"!
go func() { go func() {
time.Sleep(75 * time.Millisecond) time.Sleep(250 * time.Millisecond)
defaultServer := myApp.Config.GetDefaultServer() defaultServer := myApp.Config.GetDefaultServer()
if defaultServer == nil { if defaultServer == nil {
mainWindow.PromptForFirstServer(func(nick, host, user, pass string) { mainWindow.PromptForFirstServer(func(nick, host, user, pass string) {
+117
View File
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
#!/bin/sh
fyne bundle -package res -prefix Res icons/disc.png > bundled.go
fyne bundle -append -prefix Res icons/disc-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/headphones.png >> bundled.go
fyne bundle -append -prefix Res icons/headphones-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/heart-filled.png >> bundled.go
fyne bundle -append -prefix Res icons/heart-filled-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/heart-outline.png >> bundled.go
fyne bundle -append -prefix Res icons/heart-outline-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/musicnotes.png >> bundled.go
fyne bundle -append -prefix Res icons/musicnotes-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/people.png >> bundled.go
fyne bundle -append -prefix Res icons/people-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/playlist.png >> bundled.go
fyne bundle -append -prefix Res icons/playlist-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/podcast.png >> bundled.go
fyne bundle -append -prefix Res icons/podcast-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/star-filled.png >> bundled.go
fyne bundle -append -prefix Res icons/star-filled-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/star-outline.png >> bundled.go
fyne bundle -append -prefix Res icons/star-outline-invert.png >> bundled.go
fyne bundle -append -prefix Res icons/theatermasks.png >> bundled.go
fyne bundle -append -prefix Res icons/theatermasks-invert.png >> bundled.go
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+1
View File
@@ -0,0 +1 @@
<a href="https://www.flaticon.com/free-icons/user" title="user icons">User icons created by Freepik - Flaticon</a>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

Before

Width:  |  Height:  |  Size: 746 KiB

After

Width:  |  Height:  |  Size: 746 KiB

+2 -40
View File
@@ -6,53 +6,15 @@ import (
"supersonic/backend" "supersonic/backend"
"supersonic/player" "supersonic/player"
"supersonic/ui/browsing" "supersonic/ui/browsing"
"supersonic/ui/layouts"
"supersonic/ui/widgets" "supersonic/ui/widgets"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic" "github.com/dweymouth/go-subsonic"
) )
type bottomPanelLayout struct {
left, middle, right fyne.CanvasObject
middleWidth float32
hbox fyne.Layout
}
func newBottomPanelLayout(midWidth float32, left, middle, right fyne.CanvasObject) *bottomPanelLayout {
return &bottomPanelLayout{
left: left,
middle: middle,
right: right,
middleWidth: midWidth,
hbox: layout.NewHBoxLayout(),
}
}
func (b *bottomPanelLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
hboxSize := b.hbox.MinSize(objects)
return fyne.Size{
Height: hboxSize.Height,
Width: hboxSize.Width + fyne.Max(0, b.middleWidth-b.middle.MinSize().Width),
}
}
func (b *bottomPanelLayout) Layout(_ []fyne.CanvasObject, size fyne.Size) {
midW := fyne.Max(b.middleWidth, b.middle.MinSize().Width)
lrW := (size.Width - midW - theme.Padding()*4) / 2
b.left.Resize(fyne.NewSize(lrW, size.Height))
b.left.Move(fyne.NewPos(theme.Padding(), 0))
b.middle.Resize(fyne.NewSize(midW, size.Height))
b.middle.Move(fyne.NewPos(lrW+theme.Padding()*2, 0))
if b.right != nil {
b.right.Resize(fyne.NewSize(lrW, size.Height))
b.right.Move(fyne.NewPos(lrW+midW+theme.Padding()*3, 0))
}
}
type BottomPanel struct { type BottomPanel struct {
widget.BaseWidget widget.BaseWidget
@@ -106,7 +68,7 @@ func NewBottomPanel(p *player.Player, nav func(browsing.Route)) *BottomPanel {
_ = p.SetVolume(v) _ = p.SetVolume(v)
} }
bp.container = container.New(newBottomPanelLayout(500, bp.NowPlaying, bp.Controls, bp.AuxControls), bp.container = container.New(layouts.NewLeftMiddleRightLayout(500),
bp.NowPlaying, bp.Controls, bp.AuxControls) bp.NowPlaying, bp.Controls, bp.AuxControls)
return bp return bp
} }
+140
View File
@@ -0,0 +1,140 @@
package browsing
import (
"log"
"supersonic/backend"
"supersonic/ui/layouts"
"supersonic/ui/widgets"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic"
)
var _ fyne.Widget = (*ArtistPage)(nil)
type ArtistsGenresPage struct {
widget.BaseWidget
isGenresPage bool
sm *backend.ServerManager
nav func(Route)
titleDisp *widget.RichText
container *fyne.Container
list *widgets.ArtistGenrePlaylist
}
func NewArtistsGenresPage(isGenresPage bool, sm *backend.ServerManager, nav func(Route)) *ArtistsGenresPage {
title := "Artists"
if isGenresPage {
title = "Genres"
}
a := &ArtistsGenresPage{
isGenresPage: isGenresPage,
sm: sm,
nav: nav,
titleDisp: widget.NewRichTextWithText(title),
}
a.ExtendBaseWidget(a)
a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText
a.list = widgets.NewArtistGenrePlaylist(nil)
a.list.ShowAlbumCount = true
a.list.ShowTrackCount = isGenresPage
a.list.OnNavTo = func(id string) {
if a.isGenresPage {
nav(GenreRoute(id))
} else {
nav(ArtistRoute(id))
}
}
a.buildContainer()
go a.loadAsync()
return a
}
func (a *ArtistsGenresPage) loadAsync() {
if a.isGenresPage {
genres, err := a.sm.Server.GetGenres()
if err != nil {
log.Printf("error loading genres: %v", err.Error())
}
a.list.Items = a.buildGenresListModel(genres)
} else {
artists, err := a.sm.Server.GetArtists(nil)
if err != nil {
log.Printf("error loading artists: %v", err.Error())
}
a.list.Items = a.buildArtistListModel(artists)
}
a.Refresh()
}
func (a *ArtistsGenresPage) Route() Route {
if a.isGenresPage {
return GenresRoute()
}
return ArtistsRoute()
}
func (a *ArtistsGenresPage) Reload() {
go a.loadAsync()
}
func (a *ArtistsGenresPage) Save() SavedPage {
return &savedArtistsGenresPage{
isGenresPage: a.isGenresPage,
sm: a.sm,
nav: a.nav,
}
}
type savedArtistsGenresPage struct {
isGenresPage bool
sm *backend.ServerManager
nav func(Route)
}
func (s *savedArtistsGenresPage) Restore() Page {
return NewArtistsGenresPage(s.isGenresPage, s.sm, s.nav)
}
func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) []widgets.ArtistGenrePlaylistItemModel {
model := make([]widgets.ArtistGenrePlaylistItemModel, 0)
for _, idx := range artists.Index {
for _, artist := range idx.Artist {
model = append(model, widgets.ArtistGenrePlaylistItemModel{
ID: artist.ID,
Name: artist.Name,
AlbumCount: artist.AlbumCount,
Favorite: artist.Starred != time.Time{},
})
}
}
return model
}
func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []widgets.ArtistGenrePlaylistItemModel {
model := make([]widgets.ArtistGenrePlaylistItemModel, 0)
for _, genre := range genres {
model = append(model, widgets.ArtistGenrePlaylistItemModel{
ID: genre.Name,
Name: genre.Name,
AlbumCount: genre.AlbumCount,
TrackCount: genre.SongCount,
Favorite: false,
})
}
return model
}
func (a *ArtistsGenresPage) buildContainer() {
a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15},
container.NewBorder(a.titleDisp, nil, nil, nil, a.list))
}
func (a *ArtistsGenresPage) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container)
}
+11 -14
View File
@@ -3,6 +3,7 @@ package browsing
import ( import (
"image/color" "image/color"
"supersonic/backend" "supersonic/backend"
"supersonic/ui/layouts"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
@@ -38,26 +39,22 @@ type BrowsingPane struct {
app *backend.App app *backend.App
// Invoked when the home button is clicked
OnGoHome func()
curPage Page curPage Page
home *widget.Button
forward *widget.Button forward *widget.Button
back *widget.Button back *widget.Button
reload *widget.Button reload *widget.Button
history []SavedPage history []SavedPage
historyIdx int historyIdx int
pageContainer *fyne.Container navBtnsContainer *fyne.Container
container *fyne.Container pageContainer *fyne.Container
container *fyne.Container
} }
func NewBrowsingPane(app *backend.App) *BrowsingPane { func NewBrowsingPane(app *backend.App) *BrowsingPane {
b := &BrowsingPane{app: app} b := &BrowsingPane{app: app}
b.ExtendBaseWidget(b) b.ExtendBaseWidget(b)
b.home = widget.NewButtonWithIcon("", theme.HomeIcon(), b.goHome)
b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack) b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack)
b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload) b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload)
@@ -65,8 +62,10 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane {
b.pageContainer = container.NewMax( b.pageContainer = container.NewMax(
canvas.NewRectangle(color.RGBA{R: 24, G: 24, B: 24, A: 255}), canvas.NewRectangle(color.RGBA{R: 24, G: 24, B: 24, A: 255}),
layout.NewSpacer()) layout.NewSpacer())
b.navBtnsContainer = container.NewHBox()
b.container = container.NewBorder( b.container = container.NewBorder(
container.NewHBox(b.home, b.back, b.forward, b.reload), container.New(layouts.NewLeftMiddleRightLayout(0),
container.NewHBox(b.back, b.forward, b.reload), b.navBtnsContainer, layout.NewSpacer()),
nil, nil, nil, b.pageContainer) nil, nil, nil, b.pageContainer)
return b return b
} }
@@ -78,6 +77,10 @@ func (b *BrowsingPane) SetPage(p Page) {
} }
} }
func (b *BrowsingPane) AddNavigationButton(iconRes fyne.Resource, action func()) {
b.navBtnsContainer.Add(widget.NewButtonWithIcon("", iconRes, action))
}
func (b *BrowsingPane) doSetPage(p Page) bool { func (b *BrowsingPane) doSetPage(p Page) bool {
if b.curPage != nil && b.curPage.Route() == p.Route() { if b.curPage != nil && b.curPage.Route() == p.Route() {
return false return false
@@ -121,12 +124,6 @@ func (b *BrowsingPane) addPageToHistory(p Page, truncate bool) {
b.historyIdx++ b.historyIdx++
} }
func (b *BrowsingPane) goHome() {
if b.OnGoHome != nil {
b.OnGoHome()
}
}
func (b *BrowsingPane) GoBack() { func (b *BrowsingPane) GoBack() {
if b.historyIdx > 0 { if b.historyIdx > 0 {
b.addPageToHistory(b.curPage, false) b.addPageToHistory(b.curPage, false)
+12
View File
@@ -39,6 +39,14 @@ func GenreRoute(genre string) Route {
return Route{Page: Genre, Arg: genre} return Route{Page: Genre, Arg: genre}
} }
func GenresRoute() Route {
return Route{Page: Genres}
}
func ArtistsRoute() Route {
return Route{Page: Artists}
}
type NavigationHandler interface { type NavigationHandler interface {
SetPage(Page) SetPage(Page)
} }
@@ -63,8 +71,12 @@ func (r Router) CreatePage(rte Route) Page {
return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
case Artist: case Artist:
return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute) return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute)
case Artists:
return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute)
case Genre: case Genre:
return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute)
case Genres:
return NewArtistsGenresPage(true, r.App.ServerManager, r.OpenRoute)
} }
return nil return nil
} }
+40
View File
@@ -0,0 +1,40 @@
package layouts
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
)
type LeftMiddleRightLayout struct {
middleWidth float32
hbox fyne.Layout
}
func NewLeftMiddleRightLayout(middleWidth float32) *LeftMiddleRightLayout {
return &LeftMiddleRightLayout{
middleWidth: middleWidth,
hbox: layout.NewHBoxLayout(),
}
}
func (b *LeftMiddleRightLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
hboxSize := b.hbox.MinSize(objects)
return fyne.Size{
Height: hboxSize.Height,
Width: hboxSize.Width + fyne.Max(0, b.middleWidth-objects[1].MinSize().Width),
}
}
func (b *LeftMiddleRightLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
midW := fyne.Max(b.middleWidth, objects[1].MinSize().Width)
lrW := (size.Width - midW - theme.Padding()*4) / 2
objects[0].Resize(fyne.NewSize(lrW, size.Height))
objects[0].Move(fyne.NewPos(theme.Padding(), 0))
objects[1].Resize(fyne.NewSize(midW, size.Height))
objects[1].Move(fyne.NewPos(lrW+theme.Padding()*2, 0))
if objects[2] != nil {
objects[2].Resize(fyne.NewSize(lrW, size.Height))
objects[2].Move(fyne.NewPos(lrW+midW+theme.Padding()*3, 0))
}
}
+14 -3
View File
@@ -2,6 +2,7 @@ package ui
import ( import (
"supersonic/backend" "supersonic/backend"
"supersonic/res"
"supersonic/ui/browsing" "supersonic/ui/browsing"
"supersonic/ui/widgets" "supersonic/ui/widgets"
@@ -32,9 +33,6 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo
} }
m.Router = browsing.NewRouter(app, m.BrowsingPane) m.Router = browsing.NewRouter(app, m.BrowsingPane)
m.BrowsingPane.OnGoHome = func() {
m.Router.OpenRoute(HomePage)
}
m.BottomPanel = NewBottomPanel(app.Player, m.Router.OpenRoute) m.BottomPanel = NewBottomPanel(app.Player, m.Router.OpenRoute)
m.BottomPanel.SetPlaybackManager(app.PlaybackManager) m.BottomPanel.SetPlaybackManager(app.PlaybackManager)
m.BottomPanel.ImageManager = app.ImageManager m.BottomPanel.ImageManager = app.ImageManager
@@ -51,6 +49,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App) MainWindo
app.ServerManager.OnServerConnected(func() { app.ServerManager.OnServerConnected(func() {
m.Router.OpenRoute(HomePage) m.Router.OpenRoute(HomePage)
}) })
m.addNavigationButtons()
return m return m
} }
@@ -64,6 +63,18 @@ func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string
pop.Show() pop.Show()
} }
func (m *MainWindow) addNavigationButtons() {
m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() {
m.Router.OpenRoute(browsing.AlbumsRoute(backend.AlbumSortRecentlyAdded))
})
m.BrowsingPane.AddNavigationButton(res.ResPeopleInvertPng, func() {
m.Router.OpenRoute(browsing.ArtistsRoute())
})
m.BrowsingPane.AddNavigationButton(res.ResTheatermasksInvertPng, func() {
m.Router.OpenRoute(browsing.GenresRoute())
})
}
func (m *MainWindow) Show() { func (m *MainWindow) Show() {
m.Window.Show() m.Window.Show()
} }
+111
View File
@@ -0,0 +1,111 @@
package widgets
import (
"strconv"
"supersonic/ui/layouts"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
type ArtistGenrePlaylistItemModel struct {
ID string
Name string
AlbumCount int
TrackCount int
Favorite bool
}
type ArtistGenrePlaylist struct {
widget.BaseWidget
Items []ArtistGenrePlaylistItemModel
ShowAlbumCount bool
ShowTrackCount bool
OnNavTo func(string)
columnsLayout *layouts.ColumnsLayout
hdr *ListHeader
list *widget.List
container *fyne.Container
}
type ArtistGenrePlaylistRow struct {
widget.BaseWidget
Item ArtistGenrePlaylistItemModel
OnTapped func()
nameLabel *widget.Label
albumCountLabel *widget.Label
trackCountLabel *widget.Label
container *fyne.Container
}
func NewArtistGenrePlaylistRow(layout *layouts.ColumnsLayout) *ArtistGenrePlaylistRow {
a := &ArtistGenrePlaylistRow{
nameLabel: widget.NewLabel(""),
albumCountLabel: widget.NewLabel("alCount"),
trackCountLabel: widget.NewLabel(""),
}
a.ExtendBaseWidget(a)
a.container = container.New(layout, a.nameLabel, a.albumCountLabel, a.trackCountLabel)
return a
}
func NewArtistGenrePlaylist(items []ArtistGenrePlaylistItemModel) *ArtistGenrePlaylist {
a := &ArtistGenrePlaylist{
Items: items,
columnsLayout: layouts.NewColumnsLayout([]float32{-1, 125, 125}),
}
a.ExtendBaseWidget(a)
a.hdr = NewListHeader([]string{"Name", "Album Count", "Track Count"}, a.columnsLayout)
a.list = widget.NewList(
func() int { return len(a.Items) },
func() fyne.CanvasObject {
r := NewArtistGenrePlaylistRow(a.columnsLayout)
r.OnTapped = func() { a.onRowDoubleTapped(r.Item) }
return r
},
func(id widget.ListItemID, item fyne.CanvasObject) {
row := item.(*ArtistGenrePlaylistRow)
row.Item = a.Items[id]
row.albumCountLabel.Hidden = !a.ShowAlbumCount
row.trackCountLabel.Hidden = !a.ShowTrackCount
row.nameLabel.Text = row.Item.Name
row.albumCountLabel.Text = strconv.Itoa(row.Item.AlbumCount)
row.trackCountLabel.Text = strconv.Itoa(row.Item.TrackCount)
row.Refresh()
},
)
a.container = container.NewBorder(a.hdr, nil, nil, nil, a.list)
return a
}
func (a *ArtistGenrePlaylist) Refresh() {
a.hdr.SetColumnVisible(1, a.ShowAlbumCount)
a.hdr.SetColumnVisible(2, a.ShowTrackCount)
a.BaseWidget.Refresh()
}
func (a *ArtistGenrePlaylist) onRowDoubleTapped(item ArtistGenrePlaylistItemModel) {
if a.OnNavTo != nil {
a.OnNavTo(item.ID)
}
}
func (a *ArtistGenrePlaylistRow) Tapped(*fyne.PointEvent) {
if a.OnTapped != nil {
a.OnTapped()
}
}
func (a *ArtistGenrePlaylist) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container)
}
func (a *ArtistGenrePlaylistRow) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container)
}
+53
View File
@@ -0,0 +1,53 @@
package widgets
import (
"supersonic/ui/layouts"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
type ListHeader struct {
widget.BaseWidget
columns []string
columnsLayout *layouts.ColumnsLayout
columnsContainer *fyne.Container
container *fyne.Container
}
func NewListHeader(cols []string, layout *layouts.ColumnsLayout) *ListHeader {
l := &ListHeader{
columns: cols,
columnsLayout: layout,
columnsContainer: container.New(layout),
}
l.container = container.NewMax(canvas.NewRectangle(theme.BackgroundColor()), l.columnsContainer)
l.ExtendBaseWidget(l)
l.buildColumns()
return l
}
func (l *ListHeader) SetColumnVisible(colNum int, visible bool) {
if visible {
l.columnsContainer.Objects[colNum].Show()
} else {
l.columnsContainer.Objects[colNum].Hide()
}
}
func (l *ListHeader) buildColumns() {
for _, c := range l.columns {
t := widget.NewRichTextWithText(c)
t.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
l.columnsContainer.Add(t)
}
}
func (l *ListHeader) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(l.container)
}
+10 -4
View File
@@ -30,7 +30,7 @@ type TrackRow struct {
var _ fyne.DoubleTappable = (*TrackRow)(nil) var _ fyne.DoubleTappable = (*TrackRow)(nil)
func NewTrackRow() *TrackRow { func NewTrackRow(layout *layouts.ColumnsLayout) *TrackRow {
t := &TrackRow{} t := &TrackRow{}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
t.num = widget.NewRichTextWithText("") t.num = widget.NewRichTextWithText("")
@@ -40,7 +40,7 @@ func NewTrackRow() *TrackRow {
t.artist.Wrapping = fyne.TextTruncate t.artist.Wrapping = fyne.TextTruncate
t.dur = widget.NewRichTextWithText("") t.dur = widget.NewRichTextWithText("")
t.container = container.New(layouts.NewColumnsLayout([]float32{35, -1, -1, 55}), t.container = container.New(layout,
t.num, t.name, t.artist, t.dur) t.num, t.name, t.artist, t.dur)
return t return t
} }
@@ -82,20 +82,26 @@ type Tracklist struct {
OnPlayTrackAt func(int) OnPlayTrackAt func(int)
nowPlayingIdx int nowPlayingIdx int
colLayout *layouts.ColumnsLayout
hdr *ListHeader
list *widget.List list *widget.List
container *fyne.Container
} }
func NewTracklist(tracks []*subsonic.Child) *Tracklist { func NewTracklist(tracks []*subsonic.Child) *Tracklist {
t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1} t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, 55})
t.hdr = NewListHeader([]string{"#", "Title", "Artist", "Time"}, t.colLayout)
t.list = widget.NewList( t.list = widget.NewList(
func() int { return len(t.Tracks) }, func() int { return len(t.Tracks) },
func() fyne.CanvasObject { return NewTrackRow() }, func() fyne.CanvasObject { return NewTrackRow(t.colLayout) },
func(itemID widget.ListItemID, item fyne.CanvasObject) { func(itemID widget.ListItemID, item fyne.CanvasObject) {
tr := item.(*TrackRow) tr := item.(*TrackRow)
tr.OnDoubleTapped = func() { t.onPlayTrackAt(itemID) } tr.OnDoubleTapped = func() { t.onPlayTrackAt(itemID) }
tr.Update(t.Tracks[itemID], itemID == t.nowPlayingIdx) tr.Update(t.Tracks[itemID], itemID == t.nowPlayingIdx)
}) })
t.container = container.NewBorder(t.hdr, nil, nil, nil, t.list)
return t return t
} }
@@ -111,7 +117,7 @@ func (t *Tracklist) SetNowPlaying(trackID string) {
} }
func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer { func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(t.list) return widget.NewSimpleRenderer(t.container)
} }
func (t *Tracklist) onPlayTrackAt(idx int) { func (t *Tracklist) onPlayTrackAt(idx int) {