diff --git a/backend/config.go b/backend/config.go
index 29566bb..15dbdf7 100644
--- a/backend/config.go
+++ b/backend/config.go
@@ -57,6 +57,8 @@ type AppConfig struct {
EnableAutoUpdateChecker bool
RequestTimeoutSeconds int
EnableOSMediaPlayerAPIs bool
+ ShowSidebar bool
+ SidebarWidthFraction float64
FontNormalTTF string
FontBoldTTF string
@@ -207,6 +209,7 @@ func DefaultConfig(appVersionTag string) *Config {
EnableAutoUpdateChecker: true,
RequestTimeoutSeconds: 15,
EnableOSMediaPlayerAPIs: true,
+ SidebarWidthFraction: 0.8,
},
AlbumPage: AlbumPageConfig{
TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite", "Rating"},
diff --git a/res/bundled.go b/res/bundled.go
index 4956f1a..f3660ab 100644
--- a/res/bundled.go
+++ b/res/bundled.go
@@ -80,6 +80,11 @@ var ResPlayqueueSvg = &fyne.StaticResource{
StaticContent: []byte(
"\n\n"),
}
+var ResSidebarSvg = &fyne.StaticResource{
+ StaticName: "sidebar.svg",
+ StaticContent: []byte(
+ "\r\n"),
+}
var ResStarOutlineSvg = &fyne.StaticResource{
StaticName: "star-outline.svg",
StaticContent: []byte(
diff --git a/res/bundled_gen.sh b/res/bundled_gen.sh
index 2792568..c367529 100755
--- a/res/bundled_gen.sh
+++ b/res/bundled_gen.sh
@@ -15,6 +15,7 @@ fyne bundle -append -prefix Res icons/publicdomain/oscilloscope.svg >> bundled.g
fyne bundle -append -prefix Res icons/publicdomain/people.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/playlist.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/playqueue.svg >> bundled.go
+fyne bundle -append -prefix Res icons/publicdomain/sidebar.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/star-outline.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/star-filled.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/theatermasks.svg >> bundled.go
diff --git a/res/icons/publicdomain/sidebar.svg b/res/icons/publicdomain/sidebar.svg
new file mode 100644
index 0000000..0f35df0
--- /dev/null
+++ b/res/icons/publicdomain/sidebar.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/res/translations/en.json b/res/translations/en.json
index 48ee900..0c27198 100644
--- a/res/translations/en.json
+++ b/res/translations/en.json
@@ -236,6 +236,7 @@
"Time": "Time",
"Title": "Title",
"Title (A-Z)": "Title (A-Z)",
+ "Toggle sidebar": "Toggle sidebar",
"Top Tracks": "Top Tracks",
"Total time": "Total time",
"Track": "Track",
diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go
index deecf88..f7db871 100644
--- a/ui/browsing/browsingpane.go
+++ b/ui/browsing/browsingpane.go
@@ -3,17 +3,12 @@ package browsing
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
- "fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
- "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/controller"
- "github.com/dweymouth/supersonic/ui/layouts"
myTheme "github.com/dweymouth/supersonic/ui/theme"
-
- ttwidget "github.com/dweymouth/fyne-tooltip/widget"
)
type Page interface {
@@ -60,60 +55,25 @@ type CanShowPlayQueue interface {
type BrowsingPane struct {
widget.BaseWidget
- app *backend.App
+ OnHistoryChanged func()
- curPage Page
+ playbackManager *backend.PlaybackManager
- home *ttwidget.Button
- forward *ttwidget.Button
- back *ttwidget.Button
- reload *ttwidget.Button
+ curPage Page
history []SavedPage
historyIdx int
- settingsBtn *ttwidget.Button
- settingsMenu *fyne.Menu
- navBtnsContainer *fyne.Container
- pageContainer *fyne.Container
- container *fyne.Container
- navBtnsPageMap map[controller.PageName]fyne.Resource
+ pageContainer *fyne.Container
}
-func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome func()) *BrowsingPane {
- b := &BrowsingPane{app: app}
+func NewBrowsingPane(app *backend.PlaybackManager, contr *controller.Controller, onGoHome func()) *BrowsingPane {
+ b := &BrowsingPane{playbackManager: app}
b.ExtendBaseWidget(b)
- b.home = ttwidget.NewButtonWithIcon("", theme.HomeIcon(), onGoHome)
- b.home.SetToolTip(lang.L("Home"))
- b.back = ttwidget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack)
- b.back.SetToolTip(lang.L("Back"))
- b.forward = ttwidget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
- b.forward.SetToolTip(lang.L("Forward"))
- b.reload = ttwidget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload)
- b.reload.SetToolTip(lang.L("Reload"))
- b.app.PlaybackManager.OnSongChange(b.onSongChange)
- b.app.PlaybackManager.OnPlayTimeUpdate(b.onPlayTimeUpdate)
- b.app.PlaybackManager.OnQueueChange(b.onQueueChange)
+ b.playbackManager.OnSongChange(b.onSongChange)
+ b.playbackManager.OnPlayTimeUpdate(b.onPlayTimeUpdate)
+ b.playbackManager.OnQueueChange(b.onQueueChange)
bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground)
b.pageContainer = container.NewStack(bkgrnd, layout.NewSpacer())
- b.settingsBtn = ttwidget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
- p := widget.NewPopUpMenu(b.settingsMenu,
- fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn))
- p.ShowAtPosition(fyne.NewPos(b.Size().Width-p.MinSize().Width+4,
- b.navBtnsContainer.MinSize().Height+theme.Padding()))
- })
- b.settingsBtn.SetToolTip(lang.L("Menu"))
- quickSearchBtn := ttwidget.NewButtonWithIcon("", theme.SearchIcon(), contr.ShowQuickSearch)
- quickSearchBtn.SetToolTip(lang.L("Search Everywhere"))
- b.settingsMenu = fyne.NewMenu("")
- b.navBtnsContainer = container.NewHBox()
- b.navBtnsPageMap = map[controller.PageName]fyne.Resource{}
- b.container = container.NewBorder(container.New(
- &layout.CustomPaddedLayout{LeftPadding: -5, RightPadding: -5},
- container.New(layouts.NewLeftMiddleRightLayout(0, 0),
- container.NewHBox(b.home, b.back, b.forward, b.reload), b.navBtnsContainer,
- container.NewHBox(layout.NewSpacer(), quickSearchBtn, b.settingsBtn))),
- nil, nil, nil, b.pageContainer)
- b.updateHistoryButtons()
return b
}
@@ -124,77 +84,19 @@ func (b *BrowsingPane) SetPage(p Page) {
b.pageContainer.Objects[1] = layout.NewSpacer()
b.curPage = nil
b.pageContainer.Refresh()
- b.updateNavBtnsColor(p)
} else {
oldPage := b.curPage
if b.doSetPage(p) && oldPage != nil {
b.addPageToHistory(oldPage, true)
- b.updateHistoryButtons()
}
}
+ b.onHistoryChanged()
}
func (b *BrowsingPane) ClearHistory() {
b.history = nil
b.historyIdx = 0
- b.updateHistoryButtons()
-}
-
-func (b *BrowsingPane) AddSettingsMenuItem(label string, icon fyne.Resource, action func()) {
- item := fyne.NewMenuItem(label, action)
- item.Icon = icon
- b.settingsMenu.Items = append(b.settingsMenu.Items, item)
-}
-
-func (b *BrowsingPane) AddSettingsSubmenu(label string, icon fyne.Resource, menu *fyne.Menu) {
- item := fyne.NewMenuItem(label, nil)
- item.ChildMenu = menu
- item.Icon = icon
- b.settingsMenu.Items = append(b.settingsMenu.Items, item)
-}
-
-func (b *BrowsingPane) SetSubmenuForMenuItem(label string, submenu *fyne.Menu) {
- for _, item := range b.settingsMenu.Items {
- if item.Label == label {
- item.ChildMenu = submenu
- }
- }
-}
-
-func (b *BrowsingPane) AddSettingsMenuSeparator() {
- b.settingsMenu.Items = append(b.settingsMenu.Items,
- fyne.NewMenuItemSeparator())
-}
-
-func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *ttwidget.Button {
- // make a copy of the icon, because it can change the color
- browsingPaneIcon := theme.NewThemedResource(icon)
- btn := ttwidget.NewButtonWithIcon("", browsingPaneIcon, action)
- btn.SetToolTip(lang.L(pageName.String()))
- b.navBtnsContainer.Add(btn)
- b.navBtnsPageMap[pageName] = browsingPaneIcon
- return btn
-}
-
-func (b *BrowsingPane) DisableNavigationButtons() {
- for _, obj := range b.navBtnsContainer.Objects {
- obj.(fyne.Disableable).Disable()
- }
-}
-
-func (b *BrowsingPane) EnableNavigationButtons() {
- for _, obj := range b.navBtnsContainer.Objects {
- obj.(fyne.Disableable).Enable()
- }
-}
-
-func (b *BrowsingPane) ActivateNavigationButton(num int) {
- if num < len(b.navBtnsContainer.Objects) {
- btn := b.navBtnsContainer.Objects[num].(*widget.Button)
- if !btn.Disabled() && !btn.Hidden {
- btn.OnTapped()
- }
- }
+ b.onHistoryChanged()
}
func (b *BrowsingPane) GetSearchBarIfAny() fyne.Focusable {
@@ -255,15 +157,20 @@ func (b *BrowsingPane) doSetPage(p Page) bool {
b.curPage = p
if np, ok := p.(CanShowNowPlaying); ok {
// inform page of currently playing track
- np.OnSongChange(b.app.PlaybackManager.NowPlaying(), nil)
+ np.OnSongChange(b.playbackManager.NowPlaying(), nil)
}
b.pageContainer.Remove(b.curPage)
b.pageContainer.Objects[1] = p
- b.updateNavBtnsColor(p)
b.Refresh()
return true
}
+func (b *BrowsingPane) onHistoryChanged() {
+ if b.OnHistoryChanged != nil {
+ b.OnHistoryChanged()
+ }
+}
+
func (b *BrowsingPane) onSongChange(song mediaprovider.MediaItem, lastScrobbledIfAny *mediaprovider.Track) {
fyne.Do(func() {
if b.curPage == nil {
@@ -313,17 +220,12 @@ func (b *BrowsingPane) addPageToHistory(p Page, truncate bool) {
b.historyIdx++
}
-func (b *BrowsingPane) updateHistoryButtons() {
- if b.historyIdx > 0 {
- b.back.Enable()
- } else {
- b.back.Disable()
- }
- if b.historyIdx < len(b.history)-1 {
- b.forward.Enable()
- } else {
- b.forward.Disable()
- }
+func (b *BrowsingPane) CanGoBack() bool {
+ return b.historyIdx > 0
+}
+
+func (b *BrowsingPane) CanGoForward() bool {
+ return b.historyIdx < len(b.history)-1
}
func (b *BrowsingPane) GoBack() {
@@ -334,7 +236,7 @@ func (b *BrowsingPane) GoBack() {
b.addPageToHistory(b.curPage, false)
b.historyIdx -= 2
b.doSetPage(p)
- b.updateHistoryButtons()
+ b.onHistoryChanged()
}
}
@@ -343,7 +245,7 @@ func (b *BrowsingPane) GoForward() {
p := b.history[b.historyIdx+1].Restore()
b.addPageToHistory(b.curPage, false)
b.doSetPage(p)
- b.updateHistoryButtons()
+ b.onHistoryChanged()
}
}
@@ -361,15 +263,5 @@ func (b *BrowsingPane) CurrentPage() controller.Route {
}
func (b *BrowsingPane) CreateRenderer() fyne.WidgetRenderer {
- return widget.NewSimpleRenderer(b.container)
-}
-
-func (b *BrowsingPane) updateNavBtnsColor(p Page) {
- for pageName, icon := range b.navBtnsPageMap {
- if p != nil && pageName == p.Route().Page {
- icon.(*theme.ThemedResource).ColorName = theme.ColorNamePrimary
- } else {
- icon.(*theme.ThemedResource).ColorName = theme.ColorNameForeground
- }
- }
+ return widget.NewSimpleRenderer(b.pageContainer)
}
diff --git a/ui/container/split.go b/ui/container/split.go
new file mode 100644
index 0000000..13abe9c
--- /dev/null
+++ b/ui/container/split.go
@@ -0,0 +1,422 @@
+// Copied from Fyne develop codebase, to get improvements
+// to split container ahead of the Fyne 2.7 release
+package container
+
+import (
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/canvas"
+ "fyne.io/fyne/v2/driver/desktop"
+ "fyne.io/fyne/v2/theme"
+ "fyne.io/fyne/v2/widget"
+)
+
+// Declare conformity with CanvasObject interface
+var _ fyne.CanvasObject = (*Split)(nil)
+
+// Split defines a container whose size is split between two children.
+//
+// Since: 1.4
+type Split struct {
+ widget.BaseWidget
+ Offset float64
+ Horizontal bool
+ Leading fyne.CanvasObject
+ Trailing fyne.CanvasObject
+
+ // to communicate to the renderer that the next refresh
+ // is just an offset update (ie a resize and move only)
+ // cleared by renderer in Refresh()
+ offsetUpdated bool
+}
+
+// NewHSplit creates a horizontally arranged container with the specified leading and trailing elements.
+// A vertical split bar that can be dragged will be added between the elements.
+//
+// Since: 1.4
+func NewHSplit(leading, trailing fyne.CanvasObject) *Split {
+ return newSplitContainer(true, leading, trailing)
+}
+
+// NewVSplit creates a vertically arranged container with the specified top and bottom elements.
+// A horizontal split bar that can be dragged will be added between the elements.
+//
+// Since: 1.4
+func NewVSplit(top, bottom fyne.CanvasObject) *Split {
+ return newSplitContainer(false, top, bottom)
+}
+
+func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Split {
+ s := &Split{
+ Offset: 0.5, // Sensible default, can be overridden with SetOffset
+ Horizontal: horizontal,
+ Leading: leading,
+ Trailing: trailing,
+ }
+ s.BaseWidget.ExtendBaseWidget(s)
+ return s
+}
+
+// CreateRenderer is a private method to Fyne which links this widget to its renderer
+func (s *Split) CreateRenderer() fyne.WidgetRenderer {
+ s.BaseWidget.ExtendBaseWidget(s)
+ d := newDivider(s)
+ return &splitContainerRenderer{
+ split: s,
+ divider: d,
+ objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
+ }
+}
+
+// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
+//
+// Deprecated: Support for extending containers is being removed
+func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
+ s.BaseWidget.ExtendBaseWidget(wid)
+}
+
+// SetOffset sets the offset (0.0 to 1.0) of the Split divider.
+// 0.0 - Leading is min size, Trailing uses all remaining space.
+// 0.5 - Leading & Trailing equally share the available space.
+// 1.0 - Trailing is min size, Leading uses all remaining space.
+func (s *Split) SetOffset(offset float64) {
+ if s.Offset == offset {
+ return
+ }
+ s.Offset = offset
+ s.offsetUpdated = true
+ s.Refresh()
+}
+
+var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
+
+type splitContainerRenderer struct {
+ split *Split
+ divider *divider
+ objects []fyne.CanvasObject
+}
+
+func (r *splitContainerRenderer) Destroy() {
+}
+
+func (r *splitContainerRenderer) Layout(size fyne.Size) {
+ var dividerPos, leadingPos, trailingPos fyne.Position
+ var dividerSize, leadingSize, trailingSize fyne.Size
+
+ dividerVisible := r.split.Leading.Visible() && r.split.Trailing.Visible()
+ if !r.split.Leading.Visible() {
+ trailingPos = fyne.NewPos(0, 0)
+ trailingSize = size
+ } else if !r.split.Trailing.Visible() {
+ leadingPos = fyne.NewPos(0, 0)
+ leadingSize = size
+ } else if dividerVisible {
+ if r.split.Horizontal {
+ lw, tw := r.computeSplitLengths(size.Width, r.minLeadingWidth(), r.minTrailingWidth())
+ leadingPos.X = 0
+ leadingSize.Width = lw
+ leadingSize.Height = size.Height
+ dividerPos.X = lw
+ dividerSize.Width = dividerThickness(r.divider)
+ dividerSize.Height = size.Height
+ trailingPos.X = lw + dividerSize.Width
+ trailingSize.Width = tw
+ trailingSize.Height = size.Height
+ } else {
+ lh, th := r.computeSplitLengths(size.Height, r.minLeadingHeight(), r.minTrailingHeight())
+ leadingPos.Y = 0
+ leadingSize.Width = size.Width
+ leadingSize.Height = lh
+ dividerPos.Y = lh
+ dividerSize.Width = size.Width
+ dividerSize.Height = dividerThickness(r.divider)
+ trailingPos.Y = lh + dividerSize.Height
+ trailingSize.Width = size.Width
+ trailingSize.Height = th
+ }
+ }
+
+ r.divider.Move(dividerPos)
+ r.divider.Resize(dividerSize)
+ r.divider.Hidden = !dividerVisible
+
+ r.split.Leading.Move(leadingPos)
+ r.split.Leading.Resize(leadingSize)
+ r.split.Trailing.Move(trailingPos)
+ r.split.Trailing.Resize(trailingSize)
+ canvas.Refresh(r.divider)
+}
+
+func (r *splitContainerRenderer) MinSize() fyne.Size {
+ s := fyne.NewSize(0, 0)
+ dividerVisible := r.split.Leading.Visible() && r.split.Trailing.Visible()
+ for i, o := range r.objects {
+ if (i == 1 /*divider*/ && !dividerVisible) || (i != 1 && !o.Visible()) {
+ continue
+ }
+ min := o.MinSize()
+ if r.split.Horizontal {
+ s.Width += min.Width
+ s.Height = fyne.Max(s.Height, min.Height)
+ } else {
+ s.Width = fyne.Max(s.Width, min.Width)
+ s.Height += min.Height
+ }
+ }
+ return s
+}
+
+func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
+ return r.objects
+}
+
+func (r *splitContainerRenderer) Refresh() {
+ if r.split.offsetUpdated {
+ r.Layout(r.split.Size())
+ r.split.offsetUpdated = false
+ return
+ }
+
+ r.objects[0] = r.split.Leading
+ // [1] is divider which doesn't change
+ r.objects[2] = r.split.Trailing
+ r.Layout(r.split.Size())
+
+ r.split.Leading.Refresh()
+ r.divider.Refresh()
+ r.split.Trailing.Refresh()
+ canvas.Refresh(r.split)
+}
+
+func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
+ available := float64(total - dividerThickness(r.divider))
+ if available <= 0 {
+ return 0, 0
+ }
+ ld := float64(lMin)
+ tr := float64(tMin)
+ offset := r.split.Offset
+
+ min := ld / available
+ max := 1 - tr/available
+ if min <= max {
+ if offset < min {
+ offset = min
+ }
+ if offset > max {
+ offset = max
+ }
+ } else {
+ offset = ld / (ld + tr)
+ }
+
+ ld = offset * available
+ tr = available - ld
+ return float32(ld), float32(tr)
+}
+
+func (r *splitContainerRenderer) minLeadingWidth() float32 {
+ if r.split.Leading.Visible() {
+ return r.split.Leading.MinSize().Width
+ }
+ return 0
+}
+
+func (r *splitContainerRenderer) minLeadingHeight() float32 {
+ if r.split.Leading.Visible() {
+ return r.split.Leading.MinSize().Height
+ }
+ return 0
+}
+
+func (r *splitContainerRenderer) minTrailingWidth() float32 {
+ if r.split.Trailing.Visible() {
+ return r.split.Trailing.MinSize().Width
+ }
+ return 0
+}
+
+func (r *splitContainerRenderer) minTrailingHeight() float32 {
+ if r.split.Trailing.Visible() {
+ return r.split.Trailing.MinSize().Height
+ }
+ return 0
+}
+
+// Declare conformity with interfaces
+var (
+ _ fyne.CanvasObject = (*divider)(nil)
+ _ fyne.Draggable = (*divider)(nil)
+ _ desktop.Cursorable = (*divider)(nil)
+ _ desktop.Hoverable = (*divider)(nil)
+)
+
+type divider struct {
+ widget.BaseWidget
+ split *Split
+ hovered bool
+ startDragOff *fyne.Position
+ currentDragPos fyne.Position
+}
+
+func newDivider(split *Split) *divider {
+ d := ÷r{
+ split: split,
+ }
+ d.ExtendBaseWidget(d)
+ return d
+}
+
+// CreateRenderer is a private method to Fyne which links this widget to its renderer
+func (d *divider) CreateRenderer() fyne.WidgetRenderer {
+ d.ExtendBaseWidget(d)
+ th := d.Theme()
+ v := fyne.CurrentApp().Settings().ThemeVariant()
+
+ background := canvas.NewRectangle(th.Color(theme.ColorNameShadow, v))
+ foreground := canvas.NewRectangle(th.Color(theme.ColorNameForeground, v))
+ return ÷rRenderer{
+ divider: d,
+ background: background,
+ foreground: foreground,
+ objects: []fyne.CanvasObject{background, foreground},
+ }
+}
+
+func (d *divider) Cursor() desktop.Cursor {
+ if d.split.Horizontal {
+ return desktop.HResizeCursor
+ }
+ return desktop.VResizeCursor
+}
+
+func (d *divider) DragEnd() {
+ d.startDragOff = nil
+}
+
+func (d *divider) Dragged(e *fyne.DragEvent) {
+ if d.startDragOff == nil {
+ d.currentDragPos = d.Position().Add(e.Position)
+ start := e.Position.Subtract(e.Dragged)
+ d.startDragOff = &start
+ } else {
+ d.currentDragPos = d.currentDragPos.Add(e.Dragged)
+ }
+
+ x, y := d.currentDragPos.Components()
+ var offset, leadingRatio, trailingRatio float64
+ if d.split.Horizontal {
+ widthFree := float64(d.split.Size().Width - dividerThickness(d))
+ leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
+ trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
+ offset = float64(x-d.startDragOff.X) / widthFree
+ } else {
+ heightFree := float64(d.split.Size().Height - dividerThickness(d))
+ leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
+ trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
+ offset = float64(y-d.startDragOff.Y) / heightFree
+ }
+
+ if offset < leadingRatio {
+ offset = leadingRatio
+ }
+ if offset > trailingRatio {
+ offset = trailingRatio
+ }
+ d.split.SetOffset(offset)
+}
+
+func (d *divider) MouseIn(event *desktop.MouseEvent) {
+ d.hovered = true
+ d.Refresh()
+}
+
+func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
+
+func (d *divider) MouseOut() {
+ d.hovered = false
+ d.Refresh()
+}
+
+var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
+
+type dividerRenderer struct {
+ divider *divider
+ background *canvas.Rectangle
+ foreground *canvas.Rectangle
+ objects []fyne.CanvasObject
+}
+
+func (r *dividerRenderer) Destroy() {
+}
+
+func (r *dividerRenderer) Layout(size fyne.Size) {
+ r.background.Resize(size)
+ var x, y, w, h float32
+ if r.divider.split.Horizontal {
+ x = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
+ y = (size.Height - handleLength(r.divider)) / 2
+ w = handleThickness(r.divider)
+ h = handleLength(r.divider)
+ } else {
+ x = (size.Width - handleLength(r.divider)) / 2
+ y = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
+ w = handleLength(r.divider)
+ h = handleThickness(r.divider)
+ }
+ r.foreground.Move(fyne.NewPos(x, y))
+ r.foreground.Resize(fyne.NewSize(w, h))
+}
+
+func (r *dividerRenderer) MinSize() fyne.Size {
+ if r.divider.split.Horizontal {
+ return fyne.NewSize(dividerThickness(r.divider), dividerLength(r.divider))
+ }
+ return fyne.NewSize(dividerLength(r.divider), dividerThickness(r.divider))
+}
+
+func (r *dividerRenderer) Objects() []fyne.CanvasObject {
+ return r.objects
+}
+
+func (r *dividerRenderer) Refresh() {
+ th := r.divider.Theme()
+ v := fyne.CurrentApp().Settings().ThemeVariant()
+
+ if r.divider.hovered {
+ r.background.FillColor = th.Color(theme.ColorNameHover, v)
+ } else {
+ r.background.FillColor = th.Color(theme.ColorNameShadow, v)
+ }
+ r.background.Refresh()
+ r.foreground.FillColor = th.Color(theme.ColorNameForeground, v)
+ r.foreground.Refresh()
+ r.Layout(r.divider.Size())
+}
+
+func dividerTheme(d *divider) fyne.Theme {
+ if d == nil {
+ return theme.Current()
+ }
+
+ return d.Theme()
+}
+
+func dividerThickness(d *divider) float32 {
+ th := dividerTheme(d)
+ return th.Size(theme.SizeNamePadding) * 2
+}
+
+func dividerLength(d *divider) float32 {
+ th := dividerTheme(d)
+ return th.Size(theme.SizeNamePadding) * 6
+}
+
+func handleThickness(d *divider) float32 {
+ th := dividerTheme(d)
+ return th.Size(theme.SizeNamePadding) / 2
+}
+
+func handleLength(d *divider) float32 {
+ th := dividerTheme(d)
+ return th.Size(theme.SizeNamePadding) * 4
+}
diff --git a/ui/mainwindow.go b/ui/mainwindow.go
index ef21795..62ea750 100644
--- a/ui/mainwindow.go
+++ b/ui/mainwindow.go
@@ -13,6 +13,7 @@ import (
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/ui/browsing"
+ uicontainer "github.com/dweymouth/supersonic/ui/container"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/dialogs"
"github.com/dweymouth/supersonic/ui/shortcuts"
@@ -24,6 +25,7 @@ import (
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/lang"
+ "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
@@ -35,17 +37,17 @@ type MainWindow struct {
Router browsing.Router
Controller *controller.Controller
BrowsingPane *browsing.BrowsingPane
+ Sidebar *Sidebar
+ Toolbar *Toolbar
BottomPanel *BottomPanel
ToastOverlay *ToastOverlay
+ splitContainer *uicontainer.Split
theme *myTheme.MyTheme
haveSystemTray bool
alreadyConnected bool // tracks if we have already connected to a server before
content *mainWindowContent
- // needs to be shown/hidden when switching between servers based on whether they support radio
- radioBtn fyne.CanvasObject
-
// updated when changing servers or libraries
librarySubmenu *fyne.Menu
}
@@ -66,9 +68,13 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
m.SetupSystemTrayMenu(displayAppName, fyneApp)
}
m.Controller = controller.New(app, appVersion, m.Window)
- m.BrowsingPane = browsing.NewBrowsingPane(app, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
+ m.BrowsingPane = browsing.NewBrowsingPane(app.PlaybackManager, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
+ m.Sidebar = NewSidebar(m.Controller, m.App.ImageManager)
m.ToastOverlay = NewToastOverlay()
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
+ goHomeFn := func() { m.Router.NavigateTo(m.StartupPage()) }
+ m.Toolbar = NewToolbar(m.BrowsingPane, m.Router.NavigateTo, goHomeFn, m.Controller.ShowQuickSearch, m.toggleSidebar)
+
// inject controller dependencies
m.Controller.NavHandler = m.Router.NavigateTo
m.Controller.ReloadFunc = m.BrowsingPane.Reload
@@ -94,27 +100,30 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
fyne.Do(func() { m.UpdateOnTrackChange(item) })
})
+ app.PlaybackManager.OnQueueChange(func() {
+ fyne.Do(func() { m.Sidebar.SetQueueTracks(app.PlaybackManager.GetPlayQueue()) })
+ })
app.ServerManager.OnServerConnected(func(conf *backend.ServerConfig) {
go m.RunOnServerConnectedTasks(conf, app, displayAppName)
})
app.ServerManager.OnLogout(func() {
- m.BrowsingPane.DisableNavigationButtons()
+ m.Toolbar.DisableNavigationButtons()
m.BrowsingPane.SetPage(nil)
m.BrowsingPane.ClearHistory()
m.Controller.PromptForLoginAndConnect()
})
- m.BrowsingPane.AddSettingsMenuItem(lang.L("Log Out"), theme.LogoutIcon(), func() { app.ServerManager.Logout(true) })
- m.BrowsingPane.AddSettingsMenuItem(lang.L("Switch Servers"), theme.LoginIcon(), func() { app.ServerManager.Logout(false) })
- m.BrowsingPane.AddSettingsSubmenu(lang.L("Select Library"), myTheme.LibraryIcon, fyne.NewMenu("",
+ m.Toolbar.AddSettingsMenuItem(lang.L("Log Out"), theme.LogoutIcon(), func() { app.ServerManager.Logout(true) })
+ m.Toolbar.AddSettingsMenuItem(lang.L("Switch Servers"), theme.LoginIcon(), func() { app.ServerManager.Logout(false) })
+ m.Toolbar.AddSettingsSubmenu(lang.L("Select Library"), myTheme.LibraryIcon, fyne.NewMenu("",
fyne.NewMenuItem(lang.L("All Libraries"), func() { /* dummy - will get replaced on server login */ })))
- m.BrowsingPane.AddSettingsMenuItem(lang.L("Rescan Library"), theme.ViewRefreshIcon(), func() { app.ServerManager.Server.RescanLibrary() })
- m.BrowsingPane.AddSettingsMenuSeparator()
- m.BrowsingPane.AddSettingsSubmenu(lang.L("Visualizations"), myTheme.VisualizationIcon,
+ m.Toolbar.AddSettingsMenuItem(lang.L("Rescan Library"), theme.ViewRefreshIcon(), func() { app.ServerManager.Server.RescanLibrary() })
+ m.Toolbar.AddSettingsMenuSeparator()
+ m.Toolbar.AddSettingsSubmenu(lang.L("Visualizations"), myTheme.VisualizationIcon,
fyne.NewMenu("", []*fyne.MenuItem{
fyne.NewMenuItem(lang.L("Peak Meter"), m.Controller.ShowPeakMeter),
}...))
- m.BrowsingPane.AddSettingsMenuSeparator()
- m.BrowsingPane.AddSettingsMenuItem(lang.L("Check for Updates"), theme.DownloadIcon(), func() {
+ m.Toolbar.AddSettingsMenuSeparator()
+ m.Toolbar.AddSettingsMenuItem(lang.L("Check for Updates"), theme.DownloadIcon(), func() {
go func() {
if t := app.UpdateChecker.CheckLatestVersionTag(); t != "" && t != app.VersionTag() {
fyne.Do(func() { m.ShowNewVersionDialog(displayAppName, t) })
@@ -127,20 +136,24 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
}
}()
})
- m.BrowsingPane.AddSettingsMenuItem(lang.L("Settings")+"...", theme.SettingsIcon(), m.showSettingsDialog)
- m.BrowsingPane.AddSettingsMenuItem(lang.L("About")+"...", theme.InfoIcon(), m.Controller.ShowAboutDialog)
- m.addNavigationButtons()
- m.BrowsingPane.DisableNavigationButtons()
+ m.Toolbar.AddSettingsMenuItem(lang.L("Settings")+"...", theme.SettingsIcon(), m.showSettingsDialog)
+ m.Toolbar.AddSettingsMenuItem(lang.L("About")+"...", theme.InfoIcon(), m.Controller.ShowAboutDialog)
+ m.Toolbar.DisableNavigationButtons()
m.addShortcuts()
- center := container.NewStack(m.BrowsingPane, m.ToastOverlay)
- m.content = newMainWindowContent(container.NewBorder(nil, m.BottomPanel, nil, nil, center),
+ m.splitContainer = uicontainer.NewHSplit(m.BrowsingPane, m.Sidebar)
+ m.splitContainer.Offset = min(1.0, max(0.0, m.App.Config.Application.SidebarWidthFraction))
+ m.Sidebar.Hidden = !m.App.Config.Application.ShowSidebar
+ center := container.NewStack(m.splitContainer, m.ToastOverlay)
+ toolbarWrapper := container.New(
+ &layout.CustomPaddedLayout{LeftPadding: -theme.Padding(), RightPadding: -theme.Padding()}, m.Toolbar)
+ m.content = newMainWindowContent(container.NewBorder(toolbarWrapper, m.BottomPanel, nil, nil, center),
m.Controller.UnselectAll)
m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.content, m.Window.Canvas()))
m.setInitialSize()
m.Window.SetCloseIntercept(func() {
- m.SaveWindowSize()
+ m.SaveWindowSettings()
if app.Config.Application.CloseToSystemTray && m.HaveSystemTray() {
m.Window.Hide()
} else {
@@ -154,9 +167,12 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
func (m *MainWindow) UpdateOnTrackChange(item mediaprovider.MediaItem) {
if item == nil {
m.Window.SetTitle(res.DisplayName)
+ m.Sidebar.SetNowPlaying("")
return
}
+
meta := item.Metadata()
+ m.Sidebar.SetNowPlaying(meta.ID)
artistDisp := ""
if tr, ok := item.(*mediaprovider.Track); ok {
artistDisp = " – " + strings.Join(tr.ArtistNames, ", ")
@@ -285,24 +301,20 @@ func (m *MainWindow) RunOnServerConnectedTasks(serverConf *backend.ServerConfig,
}
m.librarySubmenu = libraryMenu
m.librarySubmenu.Items[initialLibraryMenuIdx].Checked = true
- m.BrowsingPane.SetSubmenuForMenuItem(lang.L("Select Library"), libraryMenu)
+ m.Toolbar.SetSubmenuForMenuItem(lang.L("Select Library"), libraryMenu)
if initialLibraryID != "" {
m.App.ServerManager.Server.SetLibrary(initialLibraryID)
}
fyne.Do(func() {
- m.BrowsingPane.EnableNavigationButtons()
+ m.Toolbar.EnableNavigationButtons()
m.Router.NavigateTo(m.StartupPage())
_, canRate := m.App.ServerManager.Server.(mediaprovider.SupportsRating)
m.BottomPanel.NowPlaying.DisableRating = !canRate
_, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider)
- if supportsRadio {
- m.radioBtn.Show()
- } else {
- m.radioBtn.Hide()
- }
+ m.Toolbar.SetRadioButtonVisible(supportsRadio)
})
m.App.SaveConfigFile()
@@ -395,31 +407,13 @@ func (m *MainWindow) ShowWhatsNewDialog() {
dialog.ShowCustom("What's new in "+res.AppVersion, lang.L("Close"), dialogs.NewWhatsNewDialog(), m.Window)
}
-func (m *MainWindow) addNavigationButtons() {
- m.BrowsingPane.AddNavigationButton(myTheme.NowPlayingIcon, controller.NowPlaying, func() {
- m.Router.NavigateTo(controller.NowPlayingRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.FavoriteIcon, controller.Favorites, func() {
- m.Router.NavigateTo(controller.FavoritesRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.AlbumIcon, controller.Albums, func() {
- m.Router.NavigateTo(controller.AlbumsRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.ArtistIcon, controller.Artists, func() {
- m.Router.NavigateTo(controller.ArtistsRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.GenreIcon, controller.Genres, func() {
- m.Router.NavigateTo(controller.GenresRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.PlaylistIcon, controller.Playlists, func() {
- m.Router.NavigateTo(controller.PlaylistsRoute())
- })
- m.BrowsingPane.AddNavigationButton(myTheme.TracksIcon, controller.Tracks, func() {
- m.Router.NavigateTo(controller.TracksRoute())
- })
- m.radioBtn = m.BrowsingPane.AddNavigationButton(myTheme.RadioIcon, controller.Radios, func() {
- m.Router.NavigateTo(controller.RadiosRoute())
- })
+func (m *MainWindow) toggleSidebar() {
+ if m.Sidebar.Visible() {
+ m.Sidebar.Hide()
+ } else {
+ m.Sidebar.Show()
+ }
+ m.splitContainer.Refresh()
}
func (m *MainWindow) addShortcuts() {
@@ -473,7 +467,7 @@ func (m *MainWindow) addShortcuts() {
for i, ns := range shortcuts.NavShortcuts {
m.Canvas().AddShortcut(&ns, func(i int) func(fyne.Shortcut) {
return func(fyne.Shortcut) {
- m.BrowsingPane.ActivateNavigationButton(i)
+ m.Toolbar.ActivateNavigationButton(i)
}
}(i))
}
@@ -525,14 +519,16 @@ func (m *MainWindow) SetContent(c fyne.CanvasObject) {
}
func (m *MainWindow) Quit() {
- m.SaveWindowSize()
+ m.SaveWindowSettings()
fyne.CurrentApp().Quit()
}
-func (m *MainWindow) SaveWindowSize() {
+func (m *MainWindow) SaveWindowSettings() {
util.SaveWindowSize(m.Window,
&m.App.Config.Application.WindowWidth,
&m.App.Config.Application.WindowHeight)
+ m.App.Config.Application.ShowSidebar = !m.Sidebar.Hidden
+ m.App.Config.Application.SidebarWidthFraction = m.splitContainer.Offset
}
// widget just so we can catch a tap event that doesn't land anywhere else
diff --git a/ui/sidebar.go b/ui/sidebar.go
new file mode 100644
index 0000000..f3fa7a9
--- /dev/null
+++ b/ui/sidebar.go
@@ -0,0 +1,46 @@
+package ui
+
+import (
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/container"
+ "fyne.io/fyne/v2/lang"
+ "fyne.io/fyne/v2/widget"
+ "github.com/dweymouth/supersonic/backend"
+ "github.com/dweymouth/supersonic/backend/mediaprovider"
+ "github.com/dweymouth/supersonic/ui/controller"
+ "github.com/dweymouth/supersonic/ui/theme"
+ "github.com/dweymouth/supersonic/ui/widgets"
+)
+
+type Sidebar struct {
+ widget.BaseWidget
+
+ queueList *widgets.PlayQueueList
+}
+
+func NewSidebar(contr *controller.Controller, im *backend.ImageManager) *Sidebar {
+ s := &Sidebar{
+ queueList: widgets.NewPlayQueueList(im, false),
+ }
+ s.queueList.Reorderable = true
+ contr.ConnectPlayQueuelistActions(s.queueList)
+ s.ExtendBaseWidget(s)
+ return s
+}
+
+func (s *Sidebar) SetQueueTracks(items []mediaprovider.MediaItem) {
+ s.queueList.SetItems(items)
+}
+
+func (s *Sidebar) SetNowPlaying(itemID string) {
+ s.queueList.SetNowPlaying(itemID)
+}
+
+func (s *Sidebar) CreateRenderer() fyne.WidgetRenderer {
+ return widget.NewSimpleRenderer(container.NewStack(
+ theme.NewThemedRectangle(theme.ColorNamePageBackground),
+ container.NewAppTabs(
+ container.NewTabItem(lang.L("Play Queue"), container.NewPadded(s.queueList)),
+ ),
+ ))
+}
diff --git a/ui/theme/theme.go b/ui/theme/theme.go
index 43e6095..a05f30e 100644
--- a/ui/theme/theme.go
+++ b/ui/theme/theme.go
@@ -60,6 +60,7 @@ var (
FilterIcon fyne.Resource = theme.NewThemedResource(res.ResFilterSvg)
RepeatIcon fyne.Resource = theme.NewThemedResource(res.ResRepeatSvg)
RepeatOneIcon fyne.Resource = theme.NewThemedResource(res.ResRepeatoneSvg)
+ SidebarIcon fyne.Resource = theme.NewThemedResource(res.ResSidebarSvg)
SortIcon fyne.Resource = theme.NewThemedResource(res.ResUpdownarrowSvg)
VisualizationIcon fyne.Resource = theme.NewThemedResource(res.ResOscilloscopeSvg)
LibraryIcon fyne.Resource = theme.NewThemedResource(res.ResLibrarySvg)
diff --git a/ui/toolbar.go b/ui/toolbar.go
new file mode 100644
index 0000000..1ba87ac
--- /dev/null
+++ b/ui/toolbar.go
@@ -0,0 +1,206 @@
+package ui
+
+import (
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/container"
+ "fyne.io/fyne/v2/lang"
+ "fyne.io/fyne/v2/layout"
+ "fyne.io/fyne/v2/theme"
+ "fyne.io/fyne/v2/widget"
+ "github.com/dweymouth/supersonic/ui/browsing"
+ "github.com/dweymouth/supersonic/ui/controller"
+ "github.com/dweymouth/supersonic/ui/layouts"
+ myTheme "github.com/dweymouth/supersonic/ui/theme"
+
+ ttwidget "github.com/dweymouth/fyne-tooltip/widget"
+)
+
+type Toolbar struct {
+ widget.BaseWidget
+
+ browsingPane *browsing.BrowsingPane
+
+ home *ttwidget.Button
+ forward *ttwidget.Button
+ back *ttwidget.Button
+ reload *ttwidget.Button
+
+ navBtnsContainer *fyne.Container
+ navBtnsPageMap map[controller.PageName]fyne.Resource
+ radioBtn fyne.CanvasObject
+
+ quickSearchBtn *ttwidget.Button
+ sidebarBtn *ttwidget.Button
+ settingsBtn *ttwidget.Button
+ settingsMenu *fyne.Menu
+}
+
+func NewToolbar(browsingPane *browsing.BrowsingPane, navigateFn func(controller.Route), homeFunc, showSearchFn, toggleSidebarFn func()) *Toolbar {
+ t := &Toolbar{
+ browsingPane: browsingPane,
+ navBtnsContainer: container.NewHBox(),
+ navBtnsPageMap: make(map[controller.PageName]fyne.Resource),
+ settingsMenu: fyne.NewMenu(""),
+ }
+ t.ExtendBaseWidget(t)
+
+ browsingPane.OnHistoryChanged = t.updateNavButtons
+
+ t.home = ttwidget.NewButtonWithIcon("", theme.HomeIcon(), homeFunc)
+ t.home.SetToolTip(lang.L("Home"))
+ t.back = ttwidget.NewButtonWithIcon("", theme.NavigateBackIcon(), browsingPane.GoBack)
+ t.back.SetToolTip(lang.L("Back"))
+ t.forward = ttwidget.NewButtonWithIcon("", theme.NavigateNextIcon(), browsingPane.GoForward)
+ t.forward.SetToolTip(lang.L("Forward"))
+ t.reload = ttwidget.NewButtonWithIcon("", theme.ViewRefreshIcon(), browsingPane.Reload)
+ t.reload.SetToolTip(lang.L("Reload"))
+
+ t.setupNavigationButtons(navigateFn)
+
+ t.quickSearchBtn = ttwidget.NewButtonWithIcon("", theme.SearchIcon(), showSearchFn)
+ t.quickSearchBtn.SetToolTip(lang.L("Search Everywhere"))
+ t.sidebarBtn = ttwidget.NewButtonWithIcon("", myTheme.SidebarIcon, toggleSidebarFn)
+ t.sidebarBtn.SetToolTip(lang.L("Toggle sidebar"))
+ t.settingsBtn = ttwidget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
+ p := widget.NewPopUpMenu(t.settingsMenu,
+ fyne.CurrentApp().Driver().CanvasForObject(t.settingsBtn))
+ p.ShowAtPosition(fyne.NewPos(t.Size().Width-p.MinSize().Width+4,
+ t.navBtnsContainer.MinSize().Height+theme.Padding()))
+ })
+ t.settingsBtn.SetToolTip(lang.L("Menu"))
+
+ t.updateNavButtons()
+ return t
+}
+
+// SetRadioButtonVisible sets whether the radio button is visible
+func (t *Toolbar) SetRadioButtonVisible(vis bool) {
+ if vis {
+ t.radioBtn.Show()
+ } else {
+ t.radioBtn.Hide()
+ }
+}
+
+// AddSettingsMenuItem adds an item to the Settings menu
+func (t *Toolbar) AddSettingsMenuItem(label string, icon fyne.Resource, action func()) {
+ item := fyne.NewMenuItem(label, action)
+ item.Icon = icon
+ t.settingsMenu.Items = append(t.settingsMenu.Items, item)
+}
+
+// AddSettingsSubmenu adds a submenu to the Settings menu
+func (t *Toolbar) AddSettingsSubmenu(label string, icon fyne.Resource, menu *fyne.Menu) {
+ item := fyne.NewMenuItem(label, nil)
+ item.ChildMenu = menu
+ item.Icon = icon
+ t.settingsMenu.Items = append(t.settingsMenu.Items, item)
+}
+
+// AddSettingsMenuSeparator adds a separator to the settings menu
+func (t *Toolbar) AddSettingsMenuSeparator() {
+ t.settingsMenu.Items = append(t.settingsMenu.Items,
+ fyne.NewMenuItemSeparator())
+}
+
+// SetSubmenuForMenuItem sets (changes) the submenu for the first menu item matching the label
+func (t *Toolbar) SetSubmenuForMenuItem(label string, submenu *fyne.Menu) {
+ for _, item := range t.settingsMenu.Items {
+ if item.Label == label {
+ item.ChildMenu = submenu
+ return
+ }
+ }
+}
+
+// DisableNavigationButtons disables all navigation buttons
+func (t *Toolbar) DisableNavigationButtons() {
+ for _, obj := range t.navBtnsContainer.Objects {
+ obj.(fyne.Disableable).Disable()
+ }
+}
+
+// EnableNavigationButtons enables all navigation buttons
+func (t *Toolbar) EnableNavigationButtons() {
+ for _, obj := range t.navBtnsContainer.Objects {
+ obj.(fyne.Disableable).Enable()
+ }
+}
+
+// ActivateNavigationButton executes the button action for the nth navigation button
+func (t *Toolbar) ActivateNavigationButton(n int) {
+ if n < len(t.navBtnsContainer.Objects) {
+ btn := t.navBtnsContainer.Objects[n].(*ttwidget.Button)
+ if !btn.Disabled() && !btn.Hidden {
+ btn.OnTapped()
+ }
+ }
+}
+
+func (t *Toolbar) CreateRenderer() fyne.WidgetRenderer {
+ content := container.New(layouts.NewLeftMiddleRightLayout(0, 0),
+ container.NewHBox(t.home, t.back, t.forward, t.reload),
+ t.navBtnsContainer,
+ container.NewHBox(layout.NewSpacer(), t.quickSearchBtn, t.sidebarBtn, t.settingsBtn))
+ return widget.NewSimpleRenderer(content)
+}
+
+func (t *Toolbar) setupNavigationButtons(navigateFn func(controller.Route)) {
+ t.addNavigationButton(myTheme.NowPlayingIcon, controller.NowPlaying, func() {
+ navigateFn(controller.NowPlayingRoute())
+ })
+ t.addNavigationButton(myTheme.FavoriteIcon, controller.Favorites, func() {
+ navigateFn(controller.FavoritesRoute())
+ })
+ t.addNavigationButton(myTheme.AlbumIcon, controller.Albums, func() {
+ navigateFn(controller.AlbumsRoute())
+ })
+ t.addNavigationButton(myTheme.ArtistIcon, controller.Artists, func() {
+ navigateFn(controller.ArtistsRoute())
+ })
+ t.addNavigationButton(myTheme.GenreIcon, controller.Genres, func() {
+ navigateFn(controller.GenresRoute())
+ })
+ t.addNavigationButton(myTheme.PlaylistIcon, controller.Playlists, func() {
+ navigateFn(controller.PlaylistsRoute())
+ })
+ t.addNavigationButton(myTheme.TracksIcon, controller.Tracks, func() {
+ navigateFn(controller.TracksRoute())
+ })
+ t.radioBtn = t.addNavigationButton(myTheme.RadioIcon, controller.Radios, func() {
+ navigateFn(controller.RadiosRoute())
+ })
+}
+
+func (t *Toolbar) addNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *ttwidget.Button {
+ // make a copy of the icon, because it can change the color
+ browsingPaneIcon := theme.NewThemedResource(icon)
+ btn := ttwidget.NewButtonWithIcon("", browsingPaneIcon, action)
+ btn.SetToolTip(lang.L(pageName.String()))
+ t.navBtnsContainer.Add(btn)
+ t.navBtnsPageMap[pageName] = browsingPaneIcon
+ return btn
+}
+
+func (t *Toolbar) updateNavButtons() {
+ if t.browsingPane.CanGoBack() {
+ t.back.Enable()
+ } else {
+ t.back.Disable()
+ }
+ if t.browsingPane.CanGoForward() {
+ t.forward.Enable()
+ } else {
+ t.forward.Disable()
+ }
+
+ p := t.browsingPane.CurrentPage()
+ for pageName, icon := range t.navBtnsPageMap {
+ if pageName == p.Page {
+ icon.(*theme.ThemedResource).ColorName = theme.ColorNamePrimary
+ } else {
+ icon.(*theme.ThemedResource).ColorName = theme.ColorNameForeground
+ }
+ }
+ t.Refresh()
+}