option to use blurred cover art for Now Playing page background

This commit is contained in:
Drew Weymouth
2025-11-08 14:22:35 -08:00
parent f48b2ba210
commit 828b5ffc56
6 changed files with 81 additions and 38 deletions
+4 -2
View File
@@ -111,7 +111,8 @@ type TracksPageConfig struct {
}
type NowPlayingPageConfig struct {
InitialView string
InitialView string
UseBackgroundImage bool
}
type PlaybackConfig struct {
@@ -242,7 +243,8 @@ func DefaultConfig(appVersionTag string) *Config {
InitialView: "List",
},
NowPlayingConfig: NowPlayingPageConfig{
InitialView: "Play Queue",
InitialView: "Play Queue",
UseBackgroundImage: true,
},
TracksPage: TracksPageConfig{
TracklistColumns: []string{"Album", "Time", "Plays"},
+4 -3
View File
@@ -1,11 +1,12 @@
module github.com/dweymouth/supersonic
go 1.23.0
go 1.24.0
require (
fyne.io/fyne/v2 v2.6.1
github.com/20after4/configdir v0.1.1
github.com/Microsoft/go-winio v0.6.2
github.com/boxes-ltd/imaging v1.7.1
github.com/cenkalti/dominantcolor v1.0.3
github.com/charlievieth/strcase v0.0.5
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
@@ -27,7 +28,7 @@ require (
golang.org/x/net v0.38.0
golang.org/x/sys v0.31.0
golang.org/x/term v0.30.0
golang.org/x/text v0.23.0
golang.org/x/text v0.30.0
)
require (
@@ -64,7 +65,7 @@ require (
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/yuin/goldmark v1.7.8 // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/image v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+6 -4
View File
@@ -8,6 +8,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/boxes-ltd/imaging v1.7.1 h1:JlfPQkr9sowLuPY24OLNw1Wa0UjriPVo2BN0YyxdCf0=
github.com/boxes-ltd/imaging v1.7.1/go.mod h1:m89K2cMB8PXek+mUi/9SAha2A86+ZgfAOKjGypjMuDk=
github.com/cenkalti/dominantcolor v1.0.3 h1:Pt0vfRZ8enkZh1n22RvoboA53SMM/v2aEwNQTZKSqww=
github.com/cenkalti/dominantcolor v1.0.3/go.mod h1:mGpFMbWUnyXaGN48Zbf9bU9HJP1eCCD7dnsscb4lyR4=
github.com/charlievieth/strcase v0.0.5 h1:gV4iXVyD6eI5KdfOV+/vIVCKXZwtCWOmDMcu7Uy00Rs=
@@ -142,16 +144,16 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+2
View File
@@ -254,7 +254,9 @@
"Unable to play artist radio": "Unable to play artist radio",
"Unable to play random tracks": "Unable to play random tracks",
"URL": "URL",
"Use blurred album cover for Now Playing page background": "Use blurred album cover for Now Playing page background",
"Use legacy authentication": "Use legacy authentication",
"Use waveform seekbar": "Use waveform seekbar",
"Username": "Username",
"version": "version",
"Visualizations": "Visualizations",
+62 -29
View File
@@ -10,6 +10,7 @@ import (
"slices"
"strings"
"github.com/boxes-ltd/imaging"
"github.com/cenkalti/dominantcolor"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
@@ -49,16 +50,18 @@ type NowPlayingPage struct {
alreadyLoaded bool
// widgets for render
background *canvas.LinearGradient
queueList *widgets.PlayQueueList
relatedList *widgets.PlayQueueList
lyricsViewer *widgets.LyricsViewer
card *widgets.LargeNowPlayingCard
statusLabel *widget.Label
tabs *container.AppTabs
lyricsLoading *widgets.LoadingDots
relatedLoading *widgets.LoadingDots
container *fyne.Container
backgroundImgA *canvas.Image
backgroundImgB *canvas.Image
backgroundGradient *canvas.LinearGradient
queueList *widgets.PlayQueueList
relatedList *widgets.PlayQueueList
lyricsViewer *widgets.LyricsViewer
card *widgets.LargeNowPlayingCard
statusLabel *widget.Label
tabs *container.AppTabs
lyricsLoading *widgets.LoadingDots
relatedLoading *widgets.LoadingDots
container *fyne.Container
// cancel funcs for background fetch tasks
imageLoadCancel context.CancelFunc
@@ -232,7 +235,9 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
a.updateRelatedList()
}
c := theme.Color(myTheme.ColorNamePageBackground)
a.background = canvas.NewLinearGradient(c, c, 0)
a.backgroundGradient = canvas.NewLinearGradient(c, c, 0)
a.backgroundImgA = canvas.NewImageFromImage(nil)
a.backgroundImgB = canvas.NewImageFromImage(nil)
mainContent := container.NewGridWithColumns(2,
container.New(paddedLayout, a.card),
@@ -240,7 +245,9 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
util.AddHeaderBackgroundWithColorName(
a.tabs, myTheme.ColorNameNowPlayingPanel)))
a.container = container.NewStack(
a.background,
a.backgroundImgA,
a.backgroundImgB,
a.backgroundGradient,
mainContent,
container.NewVBox(
layout.NewSpacer(),
@@ -318,20 +325,46 @@ func (a *NowPlayingPage) onImageLoaded(img image.Image, err error) {
return
}
c := dominantcolor.Find(img)
if c == a.background.StartColor {
return
}
// Fyne animation starting is currently thread-safe,
// despite not being marked as such
// TODO: if this changes, use fyne.Do
anim := canvas.NewColorRGBAAnimation(
a.background.StartColor, c, myTheme.AnimationDurationMedium, func(c color.Color) {
a.background.StartColor = c
a.background.Refresh()
if a.conf.UseBackgroundImage {
resized := imaging.Resize(img, 300, 0, imaging.NearestNeighbor)
blurred := imaging.Blur(resized, 10.0)
fyne.Do(func() {
if a.backgroundGradient.StartColor != color.Transparent {
a.backgroundGradient.StartColor = color.Transparent
a.backgroundGradient.Refresh()
}
a.backgroundImgA.Hidden = false
a.backgroundImgB.Hidden = false
a.backgroundImgA.Image = a.backgroundImgB.Image
a.backgroundImgB.Image = blurred
fyne.NewAnimation(myTheme.AnimationDurationMedium, func(f float32) {
a.backgroundImgA.Translucency = float64(f)
a.backgroundImgB.Translucency = float64(1 - f)
a.backgroundImgA.Refresh()
a.backgroundImgB.Refresh()
}).Start()
})
anim.Start()
} else {
c := dominantcolor.Find(img)
if c == a.backgroundGradient.StartColor {
return
}
if !a.backgroundImgA.Hidden {
a.backgroundImgA.Hide()
}
if !a.backgroundImgB.Hidden {
a.backgroundImgB.Hide()
}
// Fyne animation starting is currently thread-safe,
// despite not being marked as such
// TODO: if this changes, use fyne.Do
anim := canvas.NewColorRGBAAnimation(
a.backgroundGradient.StartColor, c, myTheme.AnimationDurationMedium, func(c color.Color) {
a.backgroundGradient.StartColor = c
a.backgroundGradient.Refresh()
})
anim.Start()
}
}
func (a *NowPlayingPage) updateLyrics() {
@@ -486,11 +519,11 @@ func (a *NowPlayingPage) UnselectAll() {
}
func (a *NowPlayingPage) Refresh() {
if a.background != nil {
if a.backgroundGradient != nil {
c := theme.Color(myTheme.ColorNamePageBackground)
if c != a.background.EndColor {
a.background.EndColor = c
a.background.Refresh()
if c != a.backgroundGradient.EndColor {
a.backgroundGradient.EndColor = c
a.backgroundGradient.Refresh()
}
}
a.BaseWidget.Refresh()
+3
View File
@@ -571,6 +571,8 @@ func (s *SettingsDialog) createAppearanceTab(window fyne.Window) *container.TabI
})
useWaveformSeekbar.Checked = s.config.Playback.UseWaveformSeekbar
nowPlayingBackground := widget.NewCheckWithData(lang.L("Use blurred album cover for Now Playing page background"), binding.BindBool(&s.config.NowPlayingConfig.UseBackgroundImage))
return container.NewTabItem(lang.L("Appearance"), container.NewVBox(
util.NewHSpace(0), // insert a theme.Padding amount of space at top
container.NewBorder(nil, nil, widget.NewLabel(lang.L("Theme")), /*left*/
@@ -583,6 +585,7 @@ func (s *SettingsDialog) createAppearanceTab(window fyne.Window) *container.TabI
disableDPI,
s.newSectionSeparator(),
useWaveformSeekbar,
nowPlayingBackground,
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: lang.L("Application font"), Style: util.BoldRichTextStyle}),
container.New(layout.NewFormLayout(),