diff --git a/go.mod b/go.mod index f9014c27..f5a48278 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,9 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/cenkalti/dominantcolor v1.0.3 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 - github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe + github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1 github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 + github.com/dweymouth/fyne-tooltip v0.2.0 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf github.com/dweymouth/go-subsonic v0.0.0-20240726004217-2e8e348ad417 @@ -53,4 +54,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139 +replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e diff --git a/go.sum b/go.sum index 42740745..95d5df33 100644 --- a/go.sum +++ b/go.sum @@ -75,12 +75,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= -github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe h1:owGwqph+Y+PqjDiWjZjOFOhlo8QsLs+LHrHojaaBo34= -github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o= +github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1 h1:NTiWy47NaM2WoJ4Rjlx/4QmtHpNLl6ujptmIirDJ9vk= +github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139 h1:JNOd+yJlurYrdAuOiibR6/EYl+/+idFkk+wwyXiNwIQ= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc= +github.com/dweymouth/fyne-tooltip v0.2.0 h1:6Zy3gryctuPoQfYf8Xp3tjenioebMt11NBGW/QXIvxE= +github.com/dweymouth/fyne-tooltip v0.2.0/go.mod h1:zEgy7p9tSVIuy2GufFbOCoK3Q04zhyDPOotlU4G3Ma4= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e h1:FSTLNY9xV0+4/x9jKPXqUpwPZfhkAMz5ZnzLBkRirMw= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf h1:QVjXWx7XGkSPOCEu5EL9QVY3fkdDSnG5KEkok5o1svM= diff --git a/res/translations/en.json b/res/translations/en.json index df03d218..08d9b692 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -28,6 +28,7 @@ "Authentication failed": "Authentication failed", "Auto": "Auto", "Autoselect device": "Autoselect device", + "Back": "Back", "Bit rate": "Bit rate", "BPM": "BPM", "Broadcast": "Broadcast", @@ -73,7 +74,9 @@ "Field Recording": "Field Recording", "File path": "File path", "File size": "File size", + "Filter albums": "Filter albums", "Filter genres": "Filter genres", + "Forward": "Forward", "Frequently Played": "Frequently Played", "General": "General", "Genre": "Genre", @@ -81,6 +84,7 @@ "Github page": "Github page", "Go to release page": "Go to release page", "Hide": "Hide", + "Home": "Home", "Home Page": "Home Page", "hr": "hr", "hrs": "hrs", @@ -95,9 +99,11 @@ "Login to Server": "Login to Server", "Lyrics": "Lyrics", "Lyrics not available": "Lyrics not available", + "Menu": "Menu", "min": "min", "minutes of track have been played": "minutes of track have been played", "Mixtape": "Mixtape", + "Mute": "Mute", "My Server": "My Server", "Name": "Name", "Name (A-Z)": "Name (A-Z)", @@ -139,6 +145,7 @@ "Recently Added": "Recently Added", "Recently Played": "Recently Played", "Related": "Related", + "Reload": "Reload", "Remix": "Remix", "Remove from playlist": "Remove from playlist", "ReplayGain mode": "ReplayGain mode", @@ -163,6 +170,7 @@ "Show": "Show", "Show info": "Show info", "Show notification on track change": "Show notification on track change", + "Show play queue": "Show play queue", "Show year in album grid cards": "Show year in album grid cards", "Shuffle": "Shuffle", "Shuffle albums": "Shuffle albums", diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 9c135bec..105f58b2 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -3,6 +3,7 @@ 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" @@ -11,6 +12,8 @@ import ( "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,14 +63,14 @@ type BrowsingPane struct { curPage Page - home *widget.Button - forward *widget.Button - back *widget.Button - reload *widget.Button + home *ttwidget.Button + forward *ttwidget.Button + back *ttwidget.Button + reload *ttwidget.Button history []SavedPage historyIdx int - settingsBtn *widget.Button + settingsBtn *ttwidget.Button settingsMenu *fyne.Menu navBtnsContainer *fyne.Container pageContainer *fyne.Container @@ -78,22 +81,28 @@ type BrowsingPane struct { func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome func()) *BrowsingPane { b := &BrowsingPane{app: app} b.ExtendBaseWidget(b) - b.home = widget.NewButtonWithIcon("", theme.HomeIcon(), onGoHome) - b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack) - b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) - b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload) + 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) bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground) b.pageContainer = container.NewStack(bkgrnd, layout.NewSpacer()) - b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() { + 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())) }) - quickSearchBtn := widget.NewButtonWithIcon("", theme.SearchIcon(), contr.ShowQuickSearch) + 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{} @@ -146,10 +155,11 @@ func (b *BrowsingPane) AddSettingsMenuSeparator() { fyne.NewMenuItemSeparator()) } -func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *widget.Button { +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 := widget.NewButtonWithIcon("", browsingPaneIcon, action) + btn := ttwidget.NewButtonWithIcon("", browsingPaneIcon, action) + btn.SetToolTip(lang.L(pageName.String())) b.navBtnsContainer.Add(btn) b.navBtnsPageMap[pageName] = browsingPaneIcon return btn @@ -157,13 +167,13 @@ func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controll func (b *BrowsingPane) DisableNavigationButtons() { for _, obj := range b.navBtnsContainer.Objects { - obj.(*widget.Button).Disable() + obj.(fyne.Disableable).Disable() } } func (b *BrowsingPane) EnableNavigationButtons() { for _, obj := range b.navBtnsContainer.Objects { - obj.(*widget.Button).Enable() + obj.(fyne.Disableable).Enable() } } diff --git a/ui/controller/controller.go b/ui/controller/controller.go index b4dd7430..f911a6dc 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -14,6 +14,7 @@ import ( "sync" "time" + fynetooltip "github.com/dweymouth/fyne-tooltip" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/player" @@ -49,7 +50,8 @@ type Controller struct { RefreshPageFunc func() popUpQueueMutex sync.Mutex - popUpQueue *widgets.PlayQueueList + popUpQueue *widget.PopUp + popUpQueueList *widgets.PlayQueueList popUpQueueLastUsed int64 escapablePopUp *widget.PopUp haveModal bool @@ -64,18 +66,22 @@ func New(app *backend.App, appVersion string, mainWindow fyne.Window) *Controlle } c.initVisualizations() c.App.PlaybackManager.OnQueueChange(func() { + c.popUpQueueMutex.Lock() + defer c.popUpQueueMutex.Unlock() if c.popUpQueue != nil { - c.popUpQueue.SetItems(c.App.PlaybackManager.GetPlayQueue()) + c.popUpQueueList.SetItems(c.App.PlaybackManager.GetPlayQueue()) } }) c.App.PlaybackManager.OnSongChange(func(track mediaprovider.MediaItem, _ *mediaprovider.Track) { + c.popUpQueueMutex.Lock() + defer c.popUpQueueMutex.Unlock() if c.popUpQueue == nil { return } if track == nil { - c.popUpQueue.SetNowPlaying("") + c.popUpQueueList.SetNowPlaying("") } else { - c.popUpQueue.SetNowPlaying(track.Metadata().ID) + c.popUpQueueList.SetNowPlaying(track.Metadata().ID) } }) return c @@ -115,10 +121,28 @@ func (m *Controller) HaveModal() bool { func (m *Controller) ShowPopUpPlayQueue() { m.popUpQueueMutex.Lock() if m.popUpQueue == nil { - m.popUpQueue = widgets.NewPlayQueueList(m.App.ImageManager, false) - m.popUpQueue.Reorderable = true - m.popUpQueue.SetItems(m.App.PlaybackManager.GetPlayQueue()) - m.ConnectPlayQueuelistActions(m.popUpQueue) + m.popUpQueueList = widgets.NewPlayQueueList(m.App.ImageManager, false) + m.popUpQueueList.Reorderable = true + m.popUpQueueList.SetItems(m.App.PlaybackManager.GetPlayQueue()) + m.ConnectPlayQueuelistActions(m.popUpQueueList) + + title := widget.NewRichTextWithText(lang.L("Play Queue")) + title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter + title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true + ctr := container.NewBorder(title, nil, nil, nil, + container.NewPadded(m.popUpQueueList), + ) + m.popUpQueue = widget.NewPopUp(ctr, m.MainWindow.Canvas()) + fynetooltip.AddPopUpToolTipLayer(m.popUpQueue) + + container.NewThemeOverride(m.popUpQueue, myTheme.WithColorTransformOverride( + theme.ColorNameOverlayBackground, + func(c color.Color) color.Color { + c_ := c.(color.NRGBA) + c_.A = 245 + return c_ + }, + )) // free popUpQueue if it hasn't been used in awhile go func() { @@ -127,7 +151,9 @@ func (m *Controller) ShowPopUpPlayQueue() { m.popUpQueueMutex.Lock() now := time.Now().UnixMilli() if m.popUpQueueLastUsed < now-120_000 /*2 min*/ { + fynetooltip.DestroyPopUpToolTipLayer(m.popUpQueue) m.popUpQueue = nil + m.popUpQueueList = nil m.popUpQueueLastUsed = 0 m.popUpQueueMutex.Unlock() t.Stop() @@ -138,31 +164,17 @@ func (m *Controller) ShowPopUpPlayQueue() { }() } m.popUpQueueLastUsed = time.Now().UnixMilli() - popUpQueue := m.popUpQueue + popUpQueueList := m.popUpQueueList + pop := m.popUpQueue m.popUpQueueMutex.Unlock() npID := "" if np := m.App.PlaybackManager.NowPlaying(); np != nil { npID = np.Metadata().ID } - popUpQueue.SetNowPlaying(npID) - popUpQueue.UnselectAll() - - title := widget.NewRichTextWithText(lang.L("Play Queue")) - title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter - title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true - ctr := container.NewBorder(title, nil, nil, nil, - container.NewPadded(m.popUpQueue), - ) - pop := widget.NewPopUp(ctr, m.MainWindow.Canvas()) - container.NewThemeOverride(pop, myTheme.WithColorTransformOverride( - theme.ColorNameOverlayBackground, - func(c color.Color) color.Color { - c_ := c.(color.NRGBA) - c_.A = 245 - return c_ - }, - )) + popUpQueueList.SetNowPlaying(npID) + popUpQueueList.UnselectAll() + m.ClosePopUpOnEscape(pop) minSize := fyne.NewSize(300, 400) maxSize := fyne.NewSize(800, 1000) @@ -171,7 +183,7 @@ func (m *Controller) ShowPopUpPlayQueue() { fyne.NewSize(canvasSize.Width*0.4, canvasSize.Height*0.5), )) pop.Resize(size) - popUpQueue.ScrollToNowPlaying() // must come after resize + popUpQueueList.ScrollToNowPlaying() // must come after resize pop.ShowAtPosition(fyne.NewPos( canvasSize.Width-size.Width-10, canvasSize.Height-size.Height-100, @@ -528,8 +540,10 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map } dlg.OnPageNeedsRefresh = c.RefreshPageFunc pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas()) + fynetooltip.AddPopUpToolTipLayer(pop) dlg.OnDismiss = func() { pop.Hide() + fynetooltip.DestroyPopUpToolTipLayer(pop) c.doModalClosed() c.App.SaveConfigFile() } diff --git a/ui/controller/routes.go b/ui/controller/routes.go index 83b9e229..e3e62f6b 100644 --- a/ui/controller/routes.go +++ b/ui/controller/routes.go @@ -18,6 +18,37 @@ const ( Radios ) +func (p PageName) String() string { + switch p { + case Album: + return "Album" + case Albums: + return "Albums" + case Artist: + return "Artist" + case Artists: + return "Artists" + case Genre: + return "Genre" + case Genres: + return "Genres" + case Favorites: + return "Favorites" + case NowPlaying: + return "Now Playing" + case Playlist: + return "Playlist" + case Playlists: + return "Playlists" + case Tracks: + return "All Tracks" + case Radios: + return "Internet Radio Stations" + default: + return "" + } +} + type Route struct { Page PageName Arg string diff --git a/ui/dialogs/graphicequalizer.go b/ui/dialogs/graphicequalizer.go index 9b7059f0..c2b1579b 100644 --- a/ui/dialogs/graphicequalizer.go +++ b/ui/dialogs/graphicequalizer.go @@ -1,11 +1,14 @@ package dialogs import ( + "fmt" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/ui/layouts" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" @@ -17,7 +20,8 @@ type GraphicEqualizer struct { OnChanged func(band int, gain float64) OnPreampChanged func(gain float64) - container *fyne.Container + bandSliders []*eqSlider + container *fyne.Container } func NewGraphicEqualizer(preamp float64, bandFreqs []string, bandGains []float64) *GraphicEqualizer { @@ -36,7 +40,8 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain layout.NewSpacer(), newCaptionTextSizeLabel("-12", fyne.TextAlignTrailing), ) - bandSliders := container.New(layouts.NewGridLayoutWithColumnsAndPadding(len(bands)+2, -16)) + g.bandSliders = make([]*eqSlider, len(bands)) + bandSlidersCtr := container.New(layouts.NewGridLayoutWithColumnsAndPadding(len(bands)+2, -16)) pre := newCaptionTextSizeLabel("Pre", fyne.TextAlignCenter) preampSlider := newEQSlider() preampSlider.SetValue(preamp) @@ -44,24 +49,28 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain if g.OnPreampChanged != nil { g.OnPreampChanged(f) } + preampSlider.UpdateToolTip() } - bandSliders.Add(container.NewBorder(nil, pre, nil, nil, preampSlider)) - bandSliders.Add(container.NewBorder(nil, widget.NewLabel(""), nil, nil, rng)) + preampSlider.UpdateToolTip() + bandSlidersCtr.Add(container.NewBorder(nil, pre, nil, nil, preampSlider)) + bandSlidersCtr.Add(container.NewBorder(nil, widget.NewLabel(""), nil, nil, rng)) for i, band := range bands { s := newEQSlider() if i < len(bandGains) { s.SetValue(bandGains[i]) + s.UpdateToolTip() } - s.OnChanged = func(i int) func(float64) { - return func(f float64) { - if g.OnChanged != nil { - g.OnChanged(i, f) - } + _i := i + s.OnChanged = func(f float64) { + if g.OnChanged != nil { + g.OnChanged(_i, f) } - }(i) + g.bandSliders[_i].UpdateToolTip() + } l := newCaptionTextSizeLabel(band, fyne.TextAlignCenter) c := container.NewBorder(nil, l, nil, nil, s) - bandSliders.Add(c) + bandSlidersCtr.Add(c) + g.bandSliders[i] = s } g.container = container.NewStack( container.NewBorder(nil, widget.NewLabel(""), nil, nil, @@ -73,7 +82,7 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain ), ), ), - bandSliders, + bandSlidersCtr, ) } @@ -90,23 +99,29 @@ func (g *GraphicEqualizer) CreateRenderer() fyne.WidgetRenderer { } type eqSlider struct { - widget.Slider - tappedAt int64 + ttwidget.Slider } func newEQSlider() *eqSlider { s := &eqSlider{ - Slider: widget.Slider{ - Orientation: widget.Vertical, - Min: -12, - Max: 12, - Step: 0.1, + Slider: ttwidget.Slider{ + Slider: widget.Slider{ + Orientation: widget.Vertical, + Min: -12, + Max: 12, + Step: 0.1, + }, }, } + s.UpdateToolTip() s.ExtendBaseWidget(s) return s } +func (s *eqSlider) UpdateToolTip() { + s.SetToolTip(fmt.Sprintf("%0.1f dB", s.Value)) +} + // We implement our own double tapping so that the Tapped behavior // can be triggered instantly. func (s *eqSlider) DoubleTapped(e *fyne.PointEvent) { diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 2d17e44a..05683b5c 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -7,6 +7,7 @@ import ( "strings" "time" + fynetooltip "github.com/dweymouth/fyne-tooltip" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" @@ -40,7 +41,7 @@ type MainWindow struct { container *fyne.Container // needs to bes shown/hidden when switching between servers based on whether they support radio - radioBtn *widget.Button + radioBtn fyne.CanvasObject } func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, app *backend.App) MainWindow { @@ -49,6 +50,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, Window: fyneApp.NewWindow(displayAppName), theme: theme.NewMyTheme(&app.Config.Theme, app.ThemesDir()), } + fynetooltip.SetToolTipTextSizeName(theme.SizeNameSubText) m.theme.NormalFont = app.Config.Application.FontNormalTTF m.theme.BoldFont = app.Config.Application.FontBoldTTF @@ -80,7 +82,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller) m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane) - m.Window.SetContent(m.container) + m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.container, m.Window.Canvas())) m.setInitialSize() app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) { if item == nil { @@ -191,8 +193,11 @@ func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName } _, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider) - m.radioBtn.Hidden = !supportsRadio - m.radioBtn.Refresh() + if supportsRadio { + m.radioBtn.Show() + } else { + m.radioBtn.Hide() + } m.App.SaveConfigFile() diff --git a/ui/util/util.go b/ui/util/util.go index e99a3469..8b2c964b 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -12,10 +12,12 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "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" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/sharedutil" @@ -252,6 +254,18 @@ func NewTruncatingLabel() *widget.Label { return rt } +func NewTruncatingTooltipRichText() *ttwidget.RichText { + rt := ttwidget.NewRichTextWithText("") + rt.Truncation = fyne.TextTruncateEllipsis + return rt +} + +func NewTruncatingTooltipLabel() *ttwidget.Label { + rt := ttwidget.NewLabel("") + rt.Truncation = fyne.TextTruncateEllipsis + return rt +} + func NewTrailingAlignLabel() *widget.Label { rt := widget.NewLabel("") rt.Alignment = fyne.TextAlignTrailing @@ -269,6 +283,14 @@ func LocalizeSlice(s []string) []string { return sharedutil.MapSlice(s, func(s string) string { return lang.L(s) }) } +type ToolTipRichText struct { + ttwidget.RichText + + OnMouseIn func(e *desktop.MouseEvent) + OnMouseOut func() + OnTapped func(e *fyne.PointEvent) +} + type HSpace struct { widget.BaseWidget diff --git a/ui/widgets/albumfilterbutton.go b/ui/widgets/albumfilterbutton.go index ef92b03d..35628688 100644 --- a/ui/widgets/albumfilterbutton.go +++ b/ui/widgets/albumfilterbutton.go @@ -15,6 +15,7 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" myTheme "github.com/dweymouth/supersonic/ui/theme" @@ -22,7 +23,7 @@ import ( ) type AlbumFilterButton struct { - widget.Button + ttwidget.Button OnChanged func() GenreDisabled bool @@ -37,10 +38,13 @@ type AlbumFilterButton struct { func NewAlbumFilterButton(filter mediaprovider.AlbumFilter, fetchGenresFunc func() ([]*mediaprovider.Genre, error)) *AlbumFilterButton { a := &AlbumFilterButton{ filter: filter, - Button: widget.Button{ - Icon: theme.NewThemedResource(myTheme.FilterIcon), + Button: ttwidget.Button{ + Button: widget.Button{ + Icon: theme.NewThemedResource(myTheme.FilterIcon), + }, }, } + a.SetToolTip(lang.L("Filter albums")) a.OnTapped = a.showFilterDialog a.ExtendBaseWidget(a) a.genreListChan = make(chan []string) diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 16fc38b8..ee119ded 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -3,6 +3,7 @@ package widgets 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" @@ -31,7 +32,9 @@ func NewAuxControls(initialVolume int) *AuxControls { showQueue: NewIconButton(myTheme.PlayQueueIcon, nil), } a.loop.IconSize = IconButtonSizeSmaller + a.loop.SetToolTip(lang.L("Shuffle")) a.showQueue.IconSize = IconButtonSizeSmaller + a.showQueue.SetToolTip(lang.L("Show play queue")) a.container = container.NewHBox( layout.NewSpacer(), container.NewVBox( @@ -125,6 +128,7 @@ func NewVolumeControl(initialVol int) *VolumeControl { v := &VolumeControl{} v.ExtendBaseWidget(v) v.icon = NewIconButton(theme.VolumeUpIcon(), v.toggleMute) + v.icon.SetToolTip(lang.L("Mute")) v.icon.IconSize = IconButtonSizeSmaller v.slider = NewVolumeSlider(100) v.lastVol = initialVol diff --git a/ui/widgets/focuslist.go b/ui/widgets/focuslist.go index 0a32eda9..e4f13307 100644 --- a/ui/widgets/focuslist.go +++ b/ui/widgets/focuslist.go @@ -1,12 +1,14 @@ package widgets import ( + "image/color" "sync" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -72,6 +74,7 @@ func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) { var _ fyne.Tappable = (*FocusListRowBase)(nil) var _ fyne.Widget = (*FocusListRowBase)(nil) var _ fyne.Focusable = (*FocusListRowBase)(nil) +var _ desktop.Hoverable = (*FocusListRowBase)(nil) // Base type used for all list rows in widgets such as Tracklist, etc. type FocusListRowBase struct { @@ -81,14 +84,16 @@ type FocusListRowBase struct { Content fyne.CanvasObject Selected bool Focused bool + hovered bool OnTapped func() OnDoubleTapped func() OnFocusNeighbor func(up bool) //TODO: func(up, selecting bool) tappedAt int64 // unixMillis - focusedRect *canvas.Rectangle - selectionRect *canvas.Rectangle + focusedRect canvas.Rectangle + selectionRect canvas.Rectangle + hoverRect canvas.Rectangle } func (l *FocusListRowBase) SetOnTapped(f func()) { @@ -139,12 +144,14 @@ func (l *FocusListRowBase) Tapped(*fyne.PointEvent) { func (l *FocusListRowBase) FocusGained() { l.Focused = true - l.Refresh() + l.focusedRect.FillColor = theme.HoverColor() + l.focusedRect.Refresh() } func (l *FocusListRowBase) FocusLost() { l.Focused = false - l.Refresh() + l.focusedRect.FillColor = color.Transparent + l.focusedRect.Refresh() } func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) { @@ -180,26 +187,51 @@ func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) { func (l *FocusListRowBase) TypedRune(r rune) { } +func (l *FocusListRowBase) MouseIn(e *desktop.MouseEvent) { + l.hovered = true + l.hoverRect.FillColor = theme.HoverColor() + l.hoverRect.Refresh() +} + +func (l *FocusListRowBase) MouseMoved(e *desktop.MouseEvent) { +} + +func (l *FocusListRowBase) MouseOut() { + l.hovered = false + l.hoverRect.FillColor = color.Transparent + l.hoverRect.Refresh() +} + func (l *FocusListRowBase) Refresh() { - l.focusedRect.FillColor = theme.HoverColor() - l.focusedRect.Hidden = !l.Focused - l.selectionRect.FillColor = theme.SelectionColor() - l.selectionRect.Hidden = !l.Selected + l.updateBackgroundRendering() l.BaseWidget.Refresh() } -func (l *FocusListRowBase) CreateRenderer() fyne.WidgetRenderer { - if l.selectionRect == nil { - l.selectionRect = canvas.NewRectangle(theme.SelectionColor()) - l.selectionRect.CornerRadius = theme.SelectionRadiusSize() - l.selectionRect.Hidden = !l.Selected +func (l *FocusListRowBase) updateBackgroundRendering() { + if l.Selected { + l.selectionRect.FillColor = theme.SelectionColor() + } else { + l.selectionRect.FillColor = color.Transparent } - if l.focusedRect == nil { - l.focusedRect = canvas.NewRectangle(theme.HoverColor()) - l.focusedRect.CornerRadius = theme.SelectionRadiusSize() - l.focusedRect.Hidden = !l.Focused + if l.Focused { + l.focusedRect.FillColor = theme.HoverColor() + } else { + l.focusedRect.FillColor = color.Transparent + } + if l.hovered { + l.hoverRect.FillColor = theme.HoverColor() + } else { + l.hoverRect.FillColor = color.Transparent } +} + +func (l *FocusListRowBase) CreateRenderer() fyne.WidgetRenderer { + l.selectionRect.CornerRadius = theme.SelectionRadiusSize() + l.focusedRect.CornerRadius = theme.SelectionRadiusSize() + l.hoverRect.CornerRadius = theme.SelectionRadiusSize() + l.updateBackgroundRendering() + return widget.NewSimpleRenderer( - container.NewStack(l.selectionRect, l.focusedRect, l.Content), + container.NewStack(&l.selectionRect, &l.focusedRect, &l.hoverRect, l.Content), ) } diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index ef7c85ce..b09669a5 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -13,6 +13,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/res" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" @@ -142,7 +143,7 @@ type GridViewItem struct { itemID string secondaryIDs []string - primaryText *widget.Hyperlink + primaryText *ttwidget.Hyperlink secondaryText *MultiHyperlink suffix string container *fyne.Container @@ -165,7 +166,7 @@ type GridViewItem struct { func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { g := &GridViewItem{ - primaryText: widget.NewHyperlink("", nil), + primaryText: ttwidget.NewHyperlink("", nil), secondaryText: NewMultiHyperlink(), Cover: newCoverImage(placeholderResource), } @@ -220,6 +221,7 @@ func (g *GridViewItem) Update(model GridViewItemModel) { g.itemID = model.ID g.secondaryIDs = model.SecondaryIDs g.primaryText.SetText(model.Name) + g.primaryText.SetToolTip(model.Name) g.secondaryText.BuildSegments(model.Secondary, model.SecondaryIDs) if g.ShowSuffix { g.secondaryText.Suffix = model.Suffix diff --git a/ui/widgets/iconbutton.go b/ui/widgets/iconbutton.go index 075aea98..cb9750d3 100644 --- a/ui/widgets/iconbutton.go +++ b/ui/widgets/iconbutton.go @@ -7,6 +7,7 @@ import ( "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" myTheme "github.com/dweymouth/supersonic/ui/theme" ) @@ -20,7 +21,7 @@ const ( ) type IconButton struct { - widget.BaseWidget + ttwidget.ToolTipWidget Highlighted bool IconSize IconButtonSize @@ -84,7 +85,8 @@ func (i *IconButton) TypedKey(e *fyne.KeyEvent) { func (i *IconButton) TypedRune(r rune) { } -func (i *IconButton) MouseIn(*desktop.MouseEvent) { +func (i *IconButton) MouseIn(e *desktop.MouseEvent) { + i.ToolTipWidget.MouseIn(e) if !i.hovered { defer i.Refresh() } @@ -92,13 +94,15 @@ func (i *IconButton) MouseIn(*desktop.MouseEvent) { } func (i *IconButton) MouseOut() { + i.ToolTipWidget.MouseOut() if i.hovered { defer i.Refresh() } i.hovered = false } -func (i *IconButton) MouseMoved(*desktop.MouseEvent) { +func (i *IconButton) MouseMoved(e *desktop.MouseEvent) { + i.ToolTipWidget.MouseMoved(e) } func (i *IconButton) MinSize() fyne.Size { diff --git a/ui/widgets/multihyperlink.go b/ui/widgets/multihyperlink.go index a76968dc..0e9ff2a6 100644 --- a/ui/widgets/multihyperlink.go +++ b/ui/widgets/multihyperlink.go @@ -3,8 +3,10 @@ package widgets import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" ) type MultiHyperlink struct { @@ -16,7 +18,9 @@ type MultiHyperlink struct { // only if there is enough room Suffix string - OnTapped func(string) + OnMouseIn func(*desktop.MouseEvent) + OnMouseOut func() + OnTapped func(string) SizeName fyne.ThemeSizeName SuffixSizeName fyne.ThemeSizeName @@ -30,7 +34,7 @@ type MultiHyperlink struct { //provider *widget.RichText objects []fyne.CanvasObject - suffixLabel *widget.RichText + suffixLabel *ttwidget.RichText content *fyne.Container } @@ -136,7 +140,9 @@ func (c *MultiHyperlink) layoutObjects() { c.content.Objects = c.objects[:2*i-1] if i == len(c.Segments) && c.Suffix != "" { if c.suffixLabel == nil { - c.suffixLabel = widget.NewRichTextWithText("· " + c.Suffix) + c.suffixLabel = ttwidget.NewRichTextWithText("· " + c.Suffix) + c.suffixLabel.OnMouseIn = c.callOnMouseIn + c.suffixLabel.OnMouseOut = c.callOnMouseOut } else { c.suffixLabel.Segments[0].(*widget.TextSegment).Text = "· " + c.Suffix } @@ -157,14 +163,18 @@ func (c *MultiHyperlink) layoutObjects() { func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string) fyne.CanvasObject { if obj != nil { - if label, ok := obj.(*widget.RichText); ok { + if label, ok := obj.(*ttwidget.RichText); ok { ts := label.Segments[0].(*widget.TextSegment) ts.Text = text ts.Style.SizeName = c.sizeName() + label.SetToolTip(text) return label } } - l := widget.NewRichTextWithText(text) + l := ttwidget.NewRichTextWithText(text) + l.SetToolTip(text) + l.OnMouseIn = c.callOnMouseIn + l.OnMouseOut = c.callOnMouseOut l.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName() l.Truncation = fyne.TextTruncateEllipsis return l @@ -172,20 +182,34 @@ func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text, link string) fyne.CanvasObject { if obj != nil { - if l, ok := obj.(*widget.Hyperlink); ok { + if l, ok := obj.(*ttwidget.Hyperlink); ok { l.Text = text + l.SetToolTip(text) l.SizeName = c.sizeName() l.OnTapped = func() { c.onSegmentTapped(link) } return l } } - l := widget.NewHyperlink(text, nil) + l := ttwidget.NewHyperlink(text, nil) + l.SetToolTip(text) l.SizeName = c.sizeName() l.Truncation = fyne.TextTruncateEllipsis l.OnTapped = func() { c.onSegmentTapped(link) } return l } +func (c *MultiHyperlink) callOnMouseIn(e *desktop.MouseEvent) { + if f := c.OnMouseIn; f != nil { + f(e) + } +} + +func (c *MultiHyperlink) callOnMouseOut() { + if f := c.OnMouseOut; f != nil { + f() + } +} + func (c *MultiHyperlink) newSeparatorLabel() *widget.RichText { rt := widget.NewRichTextWithText(", ") rt.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName() diff --git a/ui/widgets/nowplayingcard.go b/ui/widgets/nowplayingcard.go index 823f3618..5618b06d 100644 --- a/ui/widgets/nowplayingcard.go +++ b/ui/widgets/nowplayingcard.go @@ -15,6 +15,7 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" ) // Shows the current album art, track name, artist name, and album name @@ -26,7 +27,7 @@ type NowPlayingCard struct { trackName *OptionHyperlink artistName *MultiHyperlink - albumName *widget.Hyperlink + albumName *ttwidget.Hyperlink cover *ImagePlaceholder menu *widget.PopUpMenu ratingMenu *fyne.MenuItem @@ -46,7 +47,7 @@ func NewNowPlayingCard() *NowPlayingCard { n := &NowPlayingCard{ trackName: NewOptionHyperlink(), artistName: NewMultiHyperlink(), - albumName: widget.NewHyperlink("", nil), + albumName: ttwidget.NewHyperlink("", nil), } n.ExtendBaseWidget(n) n.cover = NewImagePlaceholder(myTheme.TracksIcon, 85) @@ -135,20 +136,23 @@ func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer { func (n *NowPlayingCard) Update(track mediaprovider.MediaItem) { if track == nil { - n.trackName.SetText("") + n.trackName.SetTextAndToolTip("") n.artistName.BuildSegments([]string{}, []string{}) n.albumName.SetText("") + n.albumName.SetToolTip("") n.cover.Hidden = true } else { n.cover.Hidden = false - n.trackName.SetText(track.Metadata().Name) + n.trackName.SetTextAndToolTip(track.Metadata().Name) if tr, ok := track.(*mediaprovider.Track); ok { n.artistName.BuildSegments(tr.ArtistNames, tr.ArtistIDs) n.albumName.SetText(tr.Album) + n.albumName.SetToolTip(tr.Album) n.cover.PlaceholderIcon = myTheme.TracksIcon } else { n.artistName.BuildSegments([]string{}, []string{}) n.albumName.SetText("") + n.albumName.SetToolTip("") n.cover.PlaceholderIcon = myTheme.RadioIcon } } diff --git a/ui/widgets/optionhyperlink.go b/ui/widgets/optionhyperlink.go index 1fdda1a9..32e64839 100644 --- a/ui/widgets/optionhyperlink.go +++ b/ui/widgets/optionhyperlink.go @@ -5,13 +5,14 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" ) // OptionHyperlink is a widget that displays a "vertical dots"/"more" button // to the right of the text, which can show a menu. type OptionHyperlink struct { widget.BaseWidget - h *widget.Hyperlink + h *ttwidget.Hyperlink b *IconButton OnShowMenu func(btnPos fyne.Position) @@ -21,7 +22,7 @@ type OptionHyperlink struct { func NewOptionHyperlink() *OptionHyperlink { o := &OptionHyperlink{ - h: widget.NewHyperlink("", nil), + h: ttwidget.NewHyperlink("", nil), } o.h.Truncation = fyne.TextTruncateEllipsis o.b = NewIconButton(theme.MoreVerticalIcon(), func() { @@ -34,8 +35,9 @@ func NewOptionHyperlink() *OptionHyperlink { return o } -func (o *OptionHyperlink) SetText(text string) { +func (o *OptionHyperlink) SetTextAndToolTip(text string) { o.h.SetText(text) + o.h.SetToolTip(text) o.updatePreferredWidth() o.BaseWidget.Refresh() } diff --git a/ui/widgets/playercontrols.go b/ui/widgets/playercontrols.go index 83076b3c..619aece4 100644 --- a/ui/widgets/playercontrols.go +++ b/ui/widgets/playercontrols.go @@ -5,6 +5,7 @@ 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" @@ -128,8 +129,11 @@ func NewPlayerControls() *PlayerControls { } pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {}) + pc.prev.SetToolTip(lang.L("Previous")) pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {}) + pc.next.SetToolTip(lang.L("Next")) pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {}) + pc.playpause.SetToolTip(lang.L("Play")) pc.playpause.IconSize = IconButtonSizeBigger buttons := container.NewHBox(layout.NewSpacer(), pc.prev, pc.playpause, pc.next, layout.NewSpacer()) @@ -165,8 +169,10 @@ func (pc *PlayerControls) OnPlayPause(f func()) { func (pc *PlayerControls) SetPlaying(playing bool) { if playing { pc.playpause.SetIcon(theme.MediaPauseIcon()) + pc.playpause.SetToolTip(lang.L("Pause")) } else { pc.playpause.SetIcon(theme.MediaPlayIcon()) + pc.playpause.SetToolTip(lang.L("Play")) } } diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index 75400f55..4571b44b 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -15,6 +15,7 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" @@ -436,7 +437,7 @@ type PlayQueueListRow struct { playingIcon fyne.CanvasObject num *widget.Label cover *ImagePlaceholder - title *widget.Label + title *ttwidget.Label artist *MultiHyperlink time *widget.Label } @@ -447,14 +448,18 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager, playQueueList: playQueueList, num: widget.NewLabel(""), cover: NewImagePlaceholder(myTheme.TracksIcon, playQueueListThumbnailSize), - title: util.NewTruncatingLabel(), + title: util.NewTruncatingTooltipLabel(), artist: NewMultiHyperlink(), time: util.NewTrailingAlignLabel(), } p.ExtendBaseWidget(p) p.cover.ScaleMode = canvas.ImageScaleFastest + p.title.OnMouseIn = p.MouseIn + p.title.OnMouseOut = p.MouseOut p.artist.OnTapped = playQueueList.onArtistTapped + p.artist.OnMouseIn = p.MouseIn + p.artist.OnMouseOut = p.MouseOut p.OnDoubleTapped = func() { playQueueList.onPlayTrackAt(p.ItemID()) } @@ -490,15 +495,12 @@ func (p *PlayQueueListRow) TappedSecondary(e *fyne.PointEvent) { } func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) { - changed := false if tm.Selected != p.Selected { p.Selected = tm.Selected - changed = true } if num := strconv.Itoa(rowNum); p.num.Text != num { p.num.Text = num - changed = true } // Update info that can change if this row is bound to @@ -514,9 +516,9 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) { p.EnsureUnfocused() p.trackID = meta.ID p.title.Text = meta.Name + p.title.SetToolTip(meta.Name) p.artist.BuildSegments(meta.Artists, meta.ArtistIDs) p.time.Text = util.SecondsToMMSS(float64(meta.Duration)) - changed = true } // Render whether track is playing or not @@ -529,10 +531,11 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) { } else { p.Content.(*fyne.Container).Objects[0] = container.NewCenter(p.num) } - changed = true } - if changed { - p.Refresh() - } + // we always need to refresh in case of light/dark change + // even if no info changed in the update, since the + // PlayQueueList is used in the pop up queue which may be + // hidden and re-shown after a theme variant change + p.Refresh() } diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index 9e9920a4..96623891 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -13,6 +13,7 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" "github.com/dweymouth/supersonic/backend" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" @@ -151,7 +152,7 @@ type tracklistRowBase struct { playCount int num *widget.Label - name *widget.RichText // for bold support + name *ttwidget.RichText artist *MultiHyperlink album *MultiHyperlink // for disabled support, if albumID is "" composer *MultiHyperlink @@ -161,9 +162,9 @@ type tracklistRowBase struct { rating *StarRating bitrate *widget.Label plays *widget.Label - comment *widget.Label + comment *ttwidget.Label size *widget.Label - path *widget.Label + path *ttwidget.Label // must be injected by extending widget setColVisibility func(int, bool) bool @@ -274,13 +275,21 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) func (t *tracklistRowBase) create(tracklist *Tracklist) { t.tracklist = tracklist t.num = util.NewTrailingAlignLabel() - t.name = util.NewTruncatingRichText() + t.name = util.NewTruncatingTooltipRichText() + t.name.OnMouseIn = t.MouseIn + t.name.OnMouseOut = t.MouseOut t.artist = NewMultiHyperlink() t.artist.OnTapped = tracklist.onArtistTapped + t.artist.OnMouseIn = t.MouseIn + t.artist.OnMouseOut = t.MouseOut t.album = NewMultiHyperlink() t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) } + t.album.OnMouseIn = t.MouseIn + t.album.OnMouseOut = t.MouseOut t.composer = NewMultiHyperlink() t.composer.OnTapped = func(id string) { tracklist.onArtistTapped(id) } + t.composer.OnMouseIn = t.MouseIn + t.composer.OnMouseOut = t.MouseOut t.dur = util.NewTrailingAlignLabel() t.year = util.NewTrailingAlignLabel() favorite := NewFavoriteIcon() @@ -291,10 +300,14 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.rating.StarSize = 16 t.rating.OnRatingChanged = t.setTrackRating t.plays = util.NewTrailingAlignLabel() - t.comment = util.NewTruncatingLabel() + t.comment = util.NewTruncatingTooltipLabel() + t.comment.OnMouseIn = t.MouseIn + t.comment.OnMouseOut = t.MouseOut t.bitrate = util.NewTrailingAlignLabel() t.size = util.NewTrailingAlignLabel() - t.path = util.NewTruncatingLabel() + t.path = util.NewTruncatingTooltipLabel() + t.path.OnMouseIn = t.MouseIn + t.path.OnMouseOut = t.MouseOut } func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) { @@ -319,6 +332,7 @@ func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int) { t.trackID = id t.name.Segments[0].(*widget.TextSegment).Text = tr.Title + t.name.SetToolTip(tr.Title) t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs) t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID}) t.composer.BuildSegments(tr.ComposerNames, tr.ComposerIDs) @@ -326,9 +340,11 @@ func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int) { t.year.Text = strconv.Itoa(tr.Year) t.plays.Text = strconv.Itoa(int(tr.PlayCount)) t.comment.Text = tr.Comment + t.comment.SetToolTip(tr.Comment) t.bitrate.Text = strconv.Itoa(tr.BitRate) t.size.Text = util.BytesToSizeString(tr.Size) t.path.Text = tr.FilePath + t.path.SetToolTip(tr.FilePath) changed = true }