diff --git a/ui/bottompanel.go b/ui/bottompanel.go index e9b05d6..c6d0ec8 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -6,53 +6,15 @@ import ( "supersonic/backend" "supersonic/player" "supersonic/ui/browsing" + "supersonic/ui/layouts" "supersonic/ui/widgets" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "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 { widget.BaseWidget @@ -106,7 +68,7 @@ func NewBottomPanel(p *player.Player, nav func(browsing.Route)) *BottomPanel { _ = 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) return bp } diff --git a/ui/browsing/artistsgenrespage.go b/ui/browsing/artistsgenrespage.go new file mode 100644 index 0000000..c4346eb --- /dev/null +++ b/ui/browsing/artistsgenrespage.go @@ -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) +} diff --git a/ui/browsing/artistspage.go b/ui/browsing/artistspage.go deleted file mode 100644 index e8fe213..0000000 --- a/ui/browsing/artistspage.go +++ /dev/null @@ -1,101 +0,0 @@ -package browsing - -import ( - "log" - "supersonic/backend" - "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 ArtistsPage struct { - widget.BaseWidget - - sm *backend.ServerManager - nav func(Route) - titleDisp *widget.RichText - container *fyne.Container - list *widgets.ArtistGenrePlaylist -} - -func NewArtistsPage(sm *backend.ServerManager, nav func(Route)) *ArtistsPage { - a := &ArtistsPage{ - sm: sm, - nav: nav, - titleDisp: widget.NewRichTextWithText("Artists"), - } - 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 = false - a.list.OnNavTo = func(id string) { - nav(ArtistRoute(id)) - } - a.buildContainer() - go a.loadAsync() - return a -} - -func (a *ArtistsPage) loadAsync() { - artists, err := a.sm.Server.GetArtists(nil) - if err != nil { - log.Printf("error loading artists: %v", err.Error()) - } - a.list.Items = a.buildListModel(artists) - a.Refresh() -} - -func (a *ArtistsPage) Route() Route { - return ArtistsRoute() -} - -func (a *ArtistsPage) Reload() { - go a.loadAsync() -} - -func (a *ArtistsPage) Save() SavedPage { - return &savedArtistsPage{ - sm: a.sm, - nav: a.nav, - } -} - -type savedArtistsPage struct { - sm *backend.ServerManager - nav func(Route) -} - -func (s *savedArtistsPage) Restore() Page { - return NewArtistsPage(s.sm, s.nav) -} - -func (a *ArtistsPage) buildListModel(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 *ArtistsPage) buildContainer() { - a.container = container.NewBorder(a.titleDisp, nil, nil, nil, a.list) -} - -func (a *ArtistsPage) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(a.container) -} diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 6b638cc..8e9e322 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -3,6 +3,7 @@ package browsing import ( "image/color" "supersonic/backend" + "supersonic/ui/layouts" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -63,7 +64,8 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane { layout.NewSpacer()) b.navBtnsContainer = container.NewHBox() b.container = container.NewBorder( - container.NewHBox(b.back, b.forward, b.reload, layout.NewSpacer(), b.navBtnsContainer, layout.NewSpacer()), + container.New(layouts.NewLeftMiddleRightLayout(0), + container.NewHBox(b.back, b.forward, b.reload), b.navBtnsContainer, layout.NewSpacer()), nil, nil, nil, b.pageContainer) return b } diff --git a/ui/browsing/router.go b/ui/browsing/router.go index c297fd1..d87af84 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -39,6 +39,10 @@ func GenreRoute(genre string) Route { return Route{Page: Genre, Arg: genre} } +func GenresRoute() Route { + return Route{Page: Genres} +} + func ArtistsRoute() Route { return Route{Page: Artists} } @@ -68,9 +72,11 @@ func (r Router) CreatePage(rte Route) Page { case Artist: return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.OpenRoute) case Artists: - return NewArtistsPage(r.App.ServerManager, r.OpenRoute) + return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute) case Genre: return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + case Genres: + return NewArtistsGenresPage(true, r.App.ServerManager, r.OpenRoute) } return nil } diff --git a/ui/layouts/leftmiddlerightlayout.go b/ui/layouts/leftmiddlerightlayout.go new file mode 100644 index 0000000..f18da27 --- /dev/null +++ b/ui/layouts/leftmiddlerightlayout.go @@ -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)) + } +} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 3e29a57..744fa22 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -70,6 +70,9 @@ func (m *MainWindow) addNavigationButtons() { 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() {