diff --git a/backend/config.go b/backend/config.go index ab866ad..91851db 100644 --- a/backend/config.go +++ b/backend/config.go @@ -87,6 +87,10 @@ type ReplayGainConfig struct { PreventClipping bool } +type ThemeConfig struct { + Appearance string +} + type Config struct { Application AppConfig Servers []*ServerConfig @@ -101,6 +105,7 @@ type Config struct { LocalPlayback LocalPlaybackConfig Scrobbling ScrobbleConfig ReplayGain ReplayGainConfig + Theme ThemeConfig } var SupportedStartupPages = []string{"Albums", "Favorites", "Playlists"} @@ -158,6 +163,9 @@ func DefaultConfig(appVersionTag string) *Config { PreampGainDB: 0.0, PreventClipping: true, }, + Theme: ThemeConfig{ + Appearance: "Dark", + }, } } diff --git a/main.go b/main.go index 52a919a..9b66bf1 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "log" "supersonic/backend" "supersonic/ui" - "supersonic/ui/theme" "time" "fyne.io/fyne/v2" @@ -27,10 +26,7 @@ func main() { } fyneApp := app.New() - fyneApp.Settings().SetTheme(&theme.MyTheme{ - NormalFont: myApp.Config.Application.FontNormalTTF, - BoldFont: myApp.Config.Application.FontBoldTTF, - }) + w := float32(myApp.Config.Application.WindowWidth) if w <= 1 { w = 1000 diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 5dfade6..cbbee98 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -4,10 +4,10 @@ import ( "fmt" "log" "supersonic/backend" - "supersonic/res" "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" @@ -170,7 +170,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { go page.pm.PlayAlbum(page.albumID, 0, false) }) - shuffleBtn := widget.NewButtonWithIcon(" Shuffle", res.ResShuffleInvertSvg, func() { + shuffleBtn := widget.NewButtonWithIcon(" Shuffle", myTheme.ShuffleIcon, func() { page.pm.LoadTracks(page.tracklist.Tracks, false, true) page.pm.PlayFromBeginning() }) diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index fa53b9d..fc90500 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -9,6 +9,7 @@ import ( "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" @@ -266,7 +267,7 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader { } a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() }) a.playBtn = widget.NewButtonWithIcon("Play Discography", theme.MediaPlayIcon(), page.playAllTracks) - a.playRadioBtn = widget.NewButtonWithIcon("Play Artist Radio", res.ResShuffleInvertSvg, page.playArtistRadio) + a.playRadioBtn = widget.NewButtonWithIcon(" Play Artist Radio", myTheme.ShuffleIcon, page.playArtistRadio) a.biographyDisp.Wrapping = fyne.TextWrapWord a.ExtendBaseWidget(a) a.createContainer() diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 91e76d3..77c33f9 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -7,7 +7,6 @@ import ( myTheme "supersonic/ui/theme" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" @@ -67,11 +66,8 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane { b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload) b.app.PlaybackManager.OnSongChange(b.onSongChange) - b.pageContainer = container.NewMax( - canvas.NewRectangle(fyne.CurrentApp().Settings().Theme().Color( - myTheme.ColorNamePageBackground, theme.VariantDark, - )), - layout.NewSpacer()) + bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground) + b.pageContainer = container.NewMax(bkgrnd, layout.NewSpacer()) b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() { p := widget.NewPopUpMenu(b.settingsMenu, fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn)) @@ -110,8 +106,8 @@ func (b *BrowsingPane) AddSettingsMenuItem(label string, action func()) { fyne.NewMenuItem(label, action)) } -func (b *BrowsingPane) AddNavigationButton(iconRes fyne.Resource, action func()) { - b.navBtnsContainer.Add(widget.NewButtonWithIcon("", iconRes, action)) +func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, action func()) { + b.navBtnsContainer.Add(widget.NewButtonWithIcon("", icon, action)) } func (b *BrowsingPane) DisableNavigationButtons() { diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 5ebbaa7..02b22a4 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -3,9 +3,9 @@ package browsing import ( "log" "supersonic/backend" - "supersonic/res" "supersonic/ui/controller" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" "time" @@ -73,9 +73,9 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) { SizeName: theme.SizeNameHeadingText, } a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx, - widget.NewButtonWithIcon("", res.ResDiscInvertPng, a.onShowFavoriteAlbums), - widget.NewButtonWithIcon("", res.ResPeopleInvertPng, a.onShowFavoriteArtists), - widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, a.onShowFavoriteSongs)) + widget.NewButtonWithIcon("", myTheme.AlbumIcon, a.onShowFavoriteAlbums), + widget.NewButtonWithIcon("", myTheme.ArtistIcon, a.onShowFavoriteArtists), + widget.NewButtonWithIcon("", myTheme.TracksIcon, a.onShowFavoriteSongs)) a.searcher = widgets.NewSearcher() a.searcher.OnSearched = a.OnSearched a.searcher.Entry.Text = searchText diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 9f36836..9deb1be 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -2,8 +2,8 @@ package browsing import ( "supersonic/backend" - "supersonic/res" "supersonic/ui/controller" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" @@ -50,7 +50,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, } - g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs) + g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) iter := g.lm.GenreIter(g.genre) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.grid) @@ -91,7 +91,7 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage { g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, } - g.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, g.playRandomSongs) + g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) g.grid = widgets.NewGridViewFromState(saved.gridState) g.searcher = widgets.NewSearcher() g.searcher.OnSearched = g.OnSearched diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 45dde3d..5317e33 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -8,6 +8,7 @@ import ( "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" @@ -205,7 +206,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { page.pm.PlayFromBeginning() }) // TODO: find way to pad shuffle svg rather than using a space in the label string - shuffleBtn := widget.NewButtonWithIcon(" Shuffle", res.ResShuffleInvertSvg, func() { + shuffleBtn := widget.NewButtonWithIcon(" Shuffle", myTheme.ShuffleIcon, func() { page.pm.LoadTracks(page.tracklist.Tracks, false /*append*/, true /*shuffle*/) page.pm.PlayFromBeginning() }) diff --git a/ui/browsing/trackspage.go b/ui/browsing/trackspage.go index 99f8b75..b3e06c9 100644 --- a/ui/browsing/trackspage.go +++ b/ui/browsing/trackspage.go @@ -2,10 +2,10 @@ package browsing import ( "supersonic/backend" - "supersonic/res" "supersonic/sharedutil" "supersonic/ui/controller" "supersonic/ui/layouts" + "supersonic/ui/theme" "supersonic/ui/widgets" "fyne.io/fyne/v2" @@ -56,7 +56,7 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, t.title = widget.NewRichTextWithText("All Tracks") t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName - t.playRandom = widget.NewButtonWithIcon("Play random", res.ResShuffleInvertSvg, t.playRandomSongs) + t.playRandom = widget.NewButtonWithIcon("Play random", theme.ShuffleIcon, t.playRandomSongs) t.searcher = widgets.NewSearcher() t.searcher.OnSearched = t.OnSearched t.createContainer() diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 95c1ea9..a805134 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -339,7 +339,7 @@ func (c *Controller) ShowAboutDialog() { pop.Show() } -func (c *Controller) ShowSettingsDialog() { +func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func()) { devs, err := c.App.Player.ListAudioDevices() if err != nil { log.Printf("error listing audio devices: %v", err) @@ -356,6 +356,7 @@ func (c *Controller) ShowSettingsDialog() { dlg.OnAudioDeviceSettingChanged = func() { c.App.Player.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName) } + dlg.OnThemeSettingChanged = themeUpdateCallbk pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas()) dlg.OnDismiss = func() { pop.Hide() diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index a870391..2609ef0 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -9,6 +9,7 @@ import ( "supersonic/backend" "supersonic/player" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "supersonic/ui/util" "supersonic/ui/widgets" "unicode" @@ -31,6 +32,7 @@ type SettingsDialog struct { OnReplayGainSettingsChanged func() OnAudioExclusiveSettingChanged func() OnAudioDeviceSettingChanged func() + OnThemeSettingChanged func() OnDismiss func() config *backend.Config @@ -50,6 +52,10 @@ func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDev s.createPlaybackTab(), s.createExperimentalTab(window), ) + // workaround issue where inactivated tabs don't fully update when theme setting is changed + tabs.OnSelected = func(ti *container.TabItem) { + ti.Content.Refresh() + } s.promptText = widget.NewRichTextWithText("") s.content = container.NewVBox(tabs, widget.NewSeparator(), container.NewHBox(s.promptText, layout.NewSpacer(), widget.NewButton("Close", func() { @@ -62,6 +68,21 @@ func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDev } func (s *SettingsDialog) createGeneralTab() *container.TabItem { + themeSelect := widget.NewSelect([]string{ + string(myTheme.AppearanceDark), + string(myTheme.AppearanceLight), + string(myTheme.AppearanceAuto)}, nil) + themeSelect.OnChanged = func(_ string) { + s.config.Theme.Appearance = themeSelect.Options[themeSelect.SelectedIndex()] + if s.OnThemeSettingChanged != nil { + s.OnThemeSettingChanged() + } + } + themeSelect.SetSelected(s.config.Theme.Appearance) + if themeSelect.Selected == "" { + themeSelect.SetSelectedIndex(0) + } + startupPage := widget.NewSelect(backend.SupportedStartupPages, func(choice string) { s.config.Application.StartupPage = choice }) @@ -161,7 +182,10 @@ func (s *SettingsDialog) createGeneralTab() *container.TabItem { scrobbleEnabled.Checked = s.config.Scrobbling.Enabled return container.NewTabItem("General", container.NewVBox( - container.New(layout.NewFormLayout(), widget.NewLabel("Startup page"), startupPage), + container.New(layout.NewFormLayout(), + widget.NewLabel("Appearance"), container.NewGridWithColumns(2, themeSelect), + widget.NewLabel("Startup page"), container.NewGridWithColumns(2, startupPage), + ), container.NewHBox(systemTrayEnable, closeToTray), s.newSectionSeparator(), diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 17bbf5c..b45f650 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -7,6 +7,7 @@ import ( "supersonic/ui/browsing" "supersonic/ui/controller" "supersonic/ui/os" + "supersonic/ui/theme" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -42,6 +43,7 @@ type MainWindow struct { BrowsingPane *browsing.BrowsingPane BottomPanel *BottomPanel + theme *theme.MyTheme haveSystemTray bool container *fyne.Container } @@ -51,8 +53,13 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap App: app, Window: fyneApp.NewWindow(appName), BrowsingPane: browsing.NewBrowsingPane(app), + theme: theme.NewMyTheme(&app.Config.Theme), } + m.theme.NormalFont = app.Config.Application.FontNormalTTF + m.theme.BoldFont = app.Config.Application.FontBoldTTF + fyneApp.Settings().SetTheme(m.theme) + if app.Config.Application.EnableSystemTray { m.SetupSystemTrayMenu(appName, fyneApp) } @@ -117,7 +124,11 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap } }() }) - m.BrowsingPane.AddSettingsMenuItem("Settings...", m.Controller.ShowSettingsDialog) + m.BrowsingPane.AddSettingsMenuItem("Settings...", func() { + m.Controller.ShowSettingsDialog(func() { + fyneApp.Settings().SetTheme(m.theme) + }) + }) m.BrowsingPane.AddSettingsMenuItem("About...", m.Controller.ShowAboutDialog) m.addNavigationButtons() m.BrowsingPane.DisableNavigationButtons() @@ -178,25 +189,25 @@ func (m *MainWindow) ShowNewVersionDialog(appName, versionTag string) { } func (m *MainWindow) addNavigationButtons() { - m.BrowsingPane.AddNavigationButton(res.ResHeadphonesInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.NowPlayingIcon, func() { m.Router.NavigateTo(controller.NowPlayingRoute("")) }) - m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.FavoriteIcon, func() { m.Router.NavigateTo(controller.FavoritesRoute()) }) - m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.AlbumIcon, func() { m.Router.NavigateTo(controller.AlbumsRoute()) }) - m.BrowsingPane.AddNavigationButton(res.ResPeopleInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.ArtistIcon, func() { m.Router.NavigateTo(controller.ArtistsRoute()) }) - m.BrowsingPane.AddNavigationButton(res.ResTheatermasksInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.GenreIcon, func() { m.Router.NavigateTo(controller.GenresRoute()) }) - m.BrowsingPane.AddNavigationButton(res.ResPlaylistInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.PlaylistIcon, func() { m.Router.NavigateTo(controller.PlaylistsRoute()) }) - m.BrowsingPane.AddNavigationButton(res.ResMusicnotesInvertPng, func() { + m.BrowsingPane.AddNavigationButton(theme.TracksIcon, func() { m.Router.NavigateTo(controller.TracksRoute()) }) } diff --git a/ui/theme/theme.go b/ui/theme/theme.go index e420137..11d5238 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -6,6 +6,9 @@ import ( "io/ioutil" "log" "strings" + "supersonic/backend" + "supersonic/res" + "supersonic/sharedutil" "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" @@ -13,6 +16,16 @@ import ( const ColorNamePageBackground fyne.ThemeColorName = "PageBackground" +type AppearanceMode string + +const ( + AppearanceLight AppearanceMode = "Light" + AppearanceDark AppearanceMode = "Dark" + AppearanceAuto AppearanceMode = "Auto" + + DefaultAppearance AppearanceMode = AppearanceDark +) + var ( normalFont fyne.Resource boldFont fyne.Resource @@ -21,30 +34,107 @@ var ( type MyTheme struct { NormalFont string BoldFont string + config *backend.ThemeConfig } var _ fyne.Theme = (*MyTheme)(nil) -func (m *MyTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { +func NewMyTheme(config *backend.ThemeConfig) *MyTheme { + m := &MyTheme{config: config} + m.createThemeIcons() + return m +} + +func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { + variant := m.getVariant() switch name { case ColorNamePageBackground: - return color.RGBA{R: 15, G: 15, B: 15, A: 255} + if variant == theme.VariantDark { + return color.RGBA{R: 15, G: 15, B: 15, A: 255} + } + return color.RGBA{R: 250, G: 250, B: 250, A: 255} case theme.ColorNameBackground: - return color.RGBA{R: 30, G: 30, B: 30, A: 255} + if variant == theme.VariantDark { + return color.RGBA{R: 35, G: 35, B: 35, A: 255} + } + return color.RGBA{R: 225, G: 223, B: 225, A: 255} case theme.ColorNameScrollBar: - return theme.DarkTheme().Color(theme.ColorNameForeground, variant) + if variant == theme.VariantDark { + return theme.DarkTheme().Color(theme.ColorNameForeground, variant) + } + return theme.LightTheme().Color(theme.ColorNameForeground, variant) case theme.ColorNameButton: - return color.RGBA{R: 20, G: 20, B: 20, A: 50} + if variant == theme.VariantDark { + return color.RGBA{R: 20, G: 20, B: 20, A: 50} + } + return color.RGBA{R: 200, G: 200, B: 200, A: 240} case theme.ColorNameInputBackground: - return color.RGBA{R: 20, G: 20, B: 20, A: 50} + if variant == theme.VariantDark { + return color.RGBA{R: 20, G: 20, B: 20, A: 50} + } + case theme.ColorNameForeground: + if variant == theme.VariantLight { + return color.RGBA{R: 10, G: 10, B: 10, A: 255} + } + case theme.ColorNamePrimary: + if variant == theme.VariantLight { + return color.RGBA{R: 25, G: 25, B: 250, A: 255} + } } - return theme.DarkTheme().Color(name, variant) + return theme.DefaultTheme().Color(name, variant) } func (m *MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(name) } +type myThemedResource struct { + myTheme MyTheme + darkVariant *fyne.StaticResource + lightVariant *fyne.StaticResource +} + +var _ fyne.Resource = myThemedResource{} + +func (p myThemedResource) Content() []byte { + if p.myTheme.getVariant() == theme.VariantDark { + return p.darkVariant.StaticContent + } + return p.lightVariant.StaticContent +} + +func (p myThemedResource) Name() string { + if p.myTheme.getVariant() == theme.VariantDark { + return p.darkVariant.StaticName + } + return p.lightVariant.StaticName +} + +var ( + AlbumIcon fyne.Resource + ArtistIcon fyne.Resource + FavoriteIcon fyne.Resource + NotFavoriteIcon fyne.Resource + GenreIcon fyne.Resource + NowPlayingIcon fyne.Resource + PlaylistIcon fyne.Resource + ShuffleIcon fyne.Resource + TracksIcon fyne.Resource +) + +// MUST be called at startup! +func (m MyTheme) createThemeIcons() { + AlbumIcon = myThemedResource{myTheme: m, darkVariant: res.ResDiscInvertPng, lightVariant: res.ResDiscPng} + ArtistIcon = myThemedResource{myTheme: m, darkVariant: res.ResPeopleInvertPng, lightVariant: res.ResPeoplePng} + FavoriteIcon = myThemedResource{myTheme: m, darkVariant: res.ResHeartFilledInvertPng, lightVariant: res.ResHeartFilledPng} + NotFavoriteIcon = myThemedResource{myTheme: m, darkVariant: res.ResHeartOutlineInvertPng, lightVariant: res.ResHeartOutlinePng} + GenreIcon = myThemedResource{myTheme: m, darkVariant: res.ResTheatermasksInvertPng, lightVariant: res.ResTheatermasksPng} + NowPlayingIcon = myThemedResource{myTheme: m, darkVariant: res.ResHeadphonesInvertPng, lightVariant: res.ResHeadphonesPng} + PlaylistIcon = myThemedResource{myTheme: m, darkVariant: res.ResPlaylistInvertPng, lightVariant: res.ResPlaylistPng} + ShuffleIcon = myThemedResource{myTheme: m, darkVariant: res.ResShuffleInvertSvg, lightVariant: res.ResShuffleSvg} + TracksIcon = myThemedResource{myTheme: m, darkVariant: res.ResMusicnotesInvertPng, lightVariant: res.ResMusicnotesPng} +} + func (m *MyTheme) Font(style fyne.TextStyle) fyne.Resource { switch style { case fyne.TextStyle{}: @@ -74,7 +164,6 @@ func (m *MyTheme) Font(style fyne.TextStyle) fyne.Resource { return normalFont } } - return theme.DefaultTheme().Font(style) } @@ -82,6 +171,22 @@ func (m *MyTheme) Size(name fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(name) } +func (m *MyTheme) getVariant() fyne.ThemeVariant { + v := DefaultAppearance // default if config has invalid or missing setting + if sharedutil.SliceContains( + []string{string(AppearanceLight), string(AppearanceDark), string(AppearanceAuto)}, + m.config.Appearance) { + v = AppearanceMode(m.config.Appearance) + } + + if AppearanceMode(v) == AppearanceDark { + return theme.VariantDark + } else if AppearanceMode(v) == AppearanceLight { + return theme.VariantLight + } + return fyne.CurrentApp().Settings().ThemeVariant() +} + func readTTFFile(filepath string) ([]byte, error) { if !strings.HasSuffix(filepath, ".ttf") { err := errors.New("only .ttf fonts are supported") diff --git a/ui/theme/themedrectangle.go b/ui/theme/themedrectangle.go new file mode 100644 index 0000000..663274f --- /dev/null +++ b/ui/theme/themedrectangle.go @@ -0,0 +1,35 @@ +package theme + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" +) + +type ThemedRectangle struct { + widget.BaseWidget + + rect *canvas.Rectangle + + ColorName fyne.ThemeColorName +} + +func NewThemedRectangle(colorName fyne.ThemeColorName) *ThemedRectangle { + t := &ThemedRectangle{ + ColorName: colorName, + rect: canvas.NewRectangle(fyne.CurrentApp().Settings().Theme().Color(colorName, + fyne.CurrentApp().Settings().ThemeVariant())), + } + t.ExtendBaseWidget(t) + return t +} + +func (t *ThemedRectangle) Refresh() { + t.rect.FillColor = fyne.CurrentApp().Settings().Theme().Color(t.ColorName, + fyne.CurrentApp().Settings().ThemeVariant()) + t.BaseWidget.Refresh() +} + +func (t *ThemedRectangle) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(t.rect) +} diff --git a/ui/widgets/favoritebutton.go b/ui/widgets/favoritebutton.go index 942ffac..4c49733 100644 --- a/ui/widgets/favoritebutton.go +++ b/ui/widgets/favoritebutton.go @@ -2,6 +2,7 @@ package widgets import ( "supersonic/res" + "supersonic/ui/theme" "fyne.io/fyne/v2" "fyne.io/fyne/v2/widget" @@ -32,9 +33,9 @@ func (f *FavoriteButton) Tapped(e *fyne.PointEvent) { func (f *FavoriteButton) Refresh() { if f.IsFavorited { - f.Icon = res.ResHeartFilledInvertPng + f.Icon = theme.FavoriteIcon } else { - f.Icon = res.ResHeartOutlineInvertPng + f.Icon = theme.NotFavoriteIcon } f.Button.Refresh() } diff --git a/ui/widgets/listheader.go b/ui/widgets/listheader.go index e90d829..8935309 100644 --- a/ui/widgets/listheader.go +++ b/ui/widgets/listheader.go @@ -3,9 +3,9 @@ package widgets import ( "log" "supersonic/ui/layouts" + myTheme "supersonic/ui/theme" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -42,7 +42,7 @@ func NewListHeader(cols []ListColumn, layout *layouts.ColumnsLayout) *ListHeader for i, _ := range l.columnVisible { l.columnVisible[i] = true } - l.container = container.NewMax(canvas.NewRectangle(theme.BackgroundColor()), l.columnsContainer) + l.container = container.NewMax(myTheme.NewThemedRectangle(theme.ColorNameBackground), l.columnsContainer) l.ExtendBaseWidget(l) l.buildColumns() return l diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index af7b367..1bea566 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -4,13 +4,14 @@ import ( "fmt" "log" "strconv" - "supersonic/res" + "sync" + "time" + "supersonic/sharedutil" "supersonic/ui/layouts" "supersonic/ui/os" + myTheme "supersonic/ui/theme" "supersonic/ui/util" - "sync" - "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -524,7 +525,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.album.OnTapped = func() { tracklist.onAlbumTapped(t.albumID) } t.dur = newTrailingAlignRichText() t.year = newTrailingAlignRichText() - favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng) + favorite := NewTappbaleIcon(myTheme.NotFavoriteIcon) favorite.OnTapped = t.toggleFavorited t.favorite = container.NewCenter(favorite) t.rating = NewStarRating() @@ -623,10 +624,10 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) { // Render favorite column if tr.Starred.IsZero() { t.isFavorite = false - t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon } else { t.isFavorite = true - t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon } t.rating.Rating = tr.UserRating @@ -648,12 +649,12 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) { func (t *TrackRow) toggleFavorited() { if t.isFavorite { - t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon t.favorite.Refresh() t.isFavorite = false t.tracklist.onSetFavorite(t.trackID, false) } else { - t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon t.favorite.Refresh() t.isFavorite = true t.tracklist.onSetFavorite(t.trackID, true)