diff --git a/go.mod b/go.mod index 71dc9f6..a200804 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( fyne.io/fyne/v2 v2.5.0 github.com/20after4/configdir v0.1.1 + github.com/cenkalti/dominantcolor v1.0.2 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 @@ -26,6 +27,7 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/disintegration/imaging v1.6.2 // indirect github.com/fredbi/uri v1.1.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect diff --git a/go.sum b/go.sum index 918a5d4..2eff5bf 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/cenkalti/dominantcolor v1.0.2 h1:nP1qLG2sD4vu+mGjvEcp3zMaiT7OvcRDtp+wE0YEtfg= +github.com/cenkalti/dominantcolor v1.0.2/go.mod h1:HvN7ziRLPAes3UkUrLDDRADCPTFsKUzZx5ZAQx8KECc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -78,6 +80,8 @@ 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 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.20240528001024-2e495ffd5a3d h1:8obdXU9J1UEkESdcSF6HnAYwvLosqSsDtTNBOgnRfDk= @@ -389,6 +393,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 5fd124c..5b09e9e 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -4,11 +4,14 @@ import ( "context" "fmt" "image" + "image/color" "log" "slices" "strings" "sync" + "time" + "github.com/cenkalti/dominantcolor" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/player" @@ -21,6 +24,7 @@ import ( "github.com/dweymouth/supersonic/ui/widgets" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" @@ -46,6 +50,7 @@ type NowPlayingPage struct { relatedLock sync.Mutex // widgets for render + background *canvas.LinearGradient queueList *widgets.PlayQueueList relatedList *widgets.PlayQueueList lyricsViewer *widgets.LyricsViewer @@ -201,13 +206,18 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer { } else if initialTab == 2 /*related*/ { a.updateRelatedList() } + c := theme.Color(myTheme.ColorNamePageBackground) + a.background = canvas.NewLinearGradient(c, c, 0) + mainContent := container.NewGridWithColumns(2, container.New(paddedLayout, a.card), container.New(paddedLayout, container.NewStack( - util.AddHeaderBackground(a.tabs), + util.AddHeaderBackgroundWithColorName( + a.tabs, myTheme.ColorNameNowPlayingPanel), container.NewCenter(a.tabLoading)))) a.container = container.NewStack( + a.background, mainContent, container.NewVBox( layout.NewSpacer(), @@ -248,13 +258,7 @@ func (a *NowPlayingPage) OnSongChange(song, _ *mediaprovider.Track) { if song == nil { a.card.SetCoverImage(nil) } else { - a.imageLoadCancel = a.im.GetFullSizeCoverArtAsync(song.CoverArtID, func(img image.Image, err error) { - if err != nil { - log.Printf("error loading cover art: %v\n", err) - } else { - a.card.SetCoverImage(img) - } - }) + a.imageLoadCancel = a.im.GetFullSizeCoverArtAsync(song.CoverArtID, a.onImageLoaded) } if a.tabs != nil && a.tabs.SelectedIndex() == 1 /*lyrics*/ { @@ -264,6 +268,31 @@ func (a *NowPlayingPage) OnSongChange(song, _ *mediaprovider.Track) { } } +func (a *NowPlayingPage) onImageLoaded(img image.Image, err error) { + if err != nil { + log.Printf("error loading cover art: %v\n", err) + return + } + a.card.SetCoverImage(img) + c := dominantcolor.Find(img) + if c == a.background.StartColor { + return + } + + evenFrame := true + anim := canvas.NewColorRGBAAnimation( + a.background.StartColor, c, 50*time.Millisecond, func(c color.Color) { + // reduce fps to reduce mem allocations + if evenFrame { + a.background.StartColor = c + a.background.Refresh() + } + evenFrame = !evenFrame + }) + anim.Start() + +} + func (a *NowPlayingPage) updateLyrics() { a.lyricLock.Lock() defer a.lyricLock.Unlock() @@ -413,6 +442,17 @@ func (a *NowPlayingPage) Tapped(*fyne.PointEvent) { a.queueList.UnselectAll() } +func (a *NowPlayingPage) Refresh() { + if a.background != nil { + c := theme.Color(myTheme.ColorNamePageBackground) + if c != a.background.EndColor { + a.background.EndColor = c + a.background.Refresh() + } + } + a.BaseWidget.Refresh() +} + func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, op sharedutil.TrackReorderOp) { trackIDSet := sharedutil.ToSet(trackIDs) idxs := make([]int, 0, len(trackIDs)) diff --git a/ui/theme/theme.go b/ui/theme/theme.go index 436c56c..f2f93d9 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -25,6 +25,7 @@ const ( ColorNameInactiveLyric fyne.ThemeColorName = "InactiveLyric" ColorNameIconButton fyne.ThemeColorName = "IconButton" ColorNameHoveredIconButton fyne.ThemeColorName = "HoveredIconButton" + ColorNameNowPlayingPanel fyne.ThemeColorName = "NowPlayingPanel" ) var ( @@ -124,6 +125,10 @@ func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Col return darkenColor(foreground, 0.05) } return brightenColor(foreground, 0.2) + case ColorNameNowPlayingPanel: + pageHeader := colorOrDefault(colors.PageHeader, defColors.PageHeader, name, variant).(color.RGBA) + pageHeader.A = 180 + return pageHeader case ColorNameListHeader: return colorOrDefault(colors.ListHeader, defColors.ListHeader, name, variant) case ColorNamePageBackground: diff --git a/ui/util/util.go b/ui/util/util.go index 17f319c..75b4633 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -204,7 +204,11 @@ func NewReorderTracksSubmenu(onReorderTracks func(sharedutil.TrackReorderOp)) *f } func AddHeaderBackground(obj fyne.CanvasObject) *fyne.Container { - bgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageHeader) + return AddHeaderBackgroundWithColorName(obj, myTheme.ColorNamePageBackground) +} + +func AddHeaderBackgroundWithColorName(obj fyne.CanvasObject, colorName fyne.ThemeColorName) *fyne.Container { + bgrnd := myTheme.NewThemedRectangle(colorName) bgrnd.CornerRadiusName = theme.SizeNameInputRadius return container.NewStack(bgrnd, container.New(&layout.CustomPaddedLayout{LeftPadding: 10, RightPadding: 10, TopPadding: 10, BottomPadding: 10},