Merge pull request #154 from dweymouth/feature/lighttheme

Add light theme
This commit is contained in:
Drew Weymouth
2023-05-03 17:49:42 -07:00
committed by GitHub
17 changed files with 236 additions and 56 deletions
+8
View File
@@ -87,6 +87,10 @@ type ReplayGainConfig struct {
PreventClipping bool PreventClipping bool
} }
type ThemeConfig struct {
Appearance string
}
type Config struct { type Config struct {
Application AppConfig Application AppConfig
Servers []*ServerConfig Servers []*ServerConfig
@@ -101,6 +105,7 @@ type Config struct {
LocalPlayback LocalPlaybackConfig LocalPlayback LocalPlaybackConfig
Scrobbling ScrobbleConfig Scrobbling ScrobbleConfig
ReplayGain ReplayGainConfig ReplayGain ReplayGainConfig
Theme ThemeConfig
} }
var SupportedStartupPages = []string{"Albums", "Favorites", "Playlists"} var SupportedStartupPages = []string{"Albums", "Favorites", "Playlists"}
@@ -158,6 +163,9 @@ func DefaultConfig(appVersionTag string) *Config {
PreampGainDB: 0.0, PreampGainDB: 0.0,
PreventClipping: true, PreventClipping: true,
}, },
Theme: ThemeConfig{
Appearance: "Dark",
},
} }
} }
+1 -5
View File
@@ -4,7 +4,6 @@ import (
"log" "log"
"supersonic/backend" "supersonic/backend"
"supersonic/ui" "supersonic/ui"
"supersonic/ui/theme"
"time" "time"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -27,10 +26,7 @@ func main() {
} }
fyneApp := app.New() fyneApp := app.New()
fyneApp.Settings().SetTheme(&theme.MyTheme{
NormalFont: myApp.Config.Application.FontNormalTTF,
BoldFont: myApp.Config.Application.FontBoldTTF,
})
w := float32(myApp.Config.Application.WindowWidth) w := float32(myApp.Config.Application.WindowWidth)
if w <= 1 { if w <= 1 {
w = 1000 w = 1000
+2 -2
View File
@@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"log" "log"
"supersonic/backend" "supersonic/backend"
"supersonic/res"
"supersonic/sharedutil" "supersonic/sharedutil"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "supersonic/ui/widgets"
@@ -170,7 +170,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() {
go page.pm.PlayAlbum(page.albumID, 0, false) 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.LoadTracks(page.tracklist.Tracks, false, true)
page.pm.PlayFromBeginning() page.pm.PlayFromBeginning()
}) })
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"supersonic/sharedutil" "supersonic/sharedutil"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "supersonic/ui/widgets"
@@ -266,7 +267,7 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
} }
a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() }) a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() })
a.playBtn = widget.NewButtonWithIcon("Play Discography", theme.MediaPlayIcon(), page.playAllTracks) 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.biographyDisp.Wrapping = fyne.TextWrapWord
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.createContainer() a.createContainer()
+4 -8
View File
@@ -7,7 +7,6 @@ import (
myTheme "supersonic/ui/theme" myTheme "supersonic/ui/theme"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
@@ -67,11 +66,8 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane {
b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload) b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload)
b.app.PlaybackManager.OnSongChange(b.onSongChange) b.app.PlaybackManager.OnSongChange(b.onSongChange)
b.pageContainer = container.NewMax( bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground)
canvas.NewRectangle(fyne.CurrentApp().Settings().Theme().Color( b.pageContainer = container.NewMax(bkgrnd, layout.NewSpacer())
myTheme.ColorNamePageBackground, theme.VariantDark,
)),
layout.NewSpacer())
b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() { b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
p := widget.NewPopUpMenu(b.settingsMenu, p := widget.NewPopUpMenu(b.settingsMenu,
fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn)) fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn))
@@ -110,8 +106,8 @@ func (b *BrowsingPane) AddSettingsMenuItem(label string, action func()) {
fyne.NewMenuItem(label, action)) fyne.NewMenuItem(label, action))
} }
func (b *BrowsingPane) AddNavigationButton(iconRes fyne.Resource, action func()) { func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, action func()) {
b.navBtnsContainer.Add(widget.NewButtonWithIcon("", iconRes, action)) b.navBtnsContainer.Add(widget.NewButtonWithIcon("", icon, action))
} }
func (b *BrowsingPane) DisableNavigationButtons() { func (b *BrowsingPane) DisableNavigationButtons() {
+4 -4
View File
@@ -3,9 +3,9 @@ package browsing
import ( import (
"log" "log"
"supersonic/backend" "supersonic/backend"
"supersonic/res"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "supersonic/ui/widgets"
"time" "time"
@@ -73,9 +73,9 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int, searchText string) {
SizeName: theme.SizeNameHeadingText, SizeName: theme.SizeNameHeadingText,
} }
a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx, a.toggleBtns = widgets.NewToggleButtonGroup(activeBtnIdx,
widget.NewButtonWithIcon("", res.ResDiscInvertPng, a.onShowFavoriteAlbums), widget.NewButtonWithIcon("", myTheme.AlbumIcon, a.onShowFavoriteAlbums),
widget.NewButtonWithIcon("", res.ResPeopleInvertPng, a.onShowFavoriteArtists), widget.NewButtonWithIcon("", myTheme.ArtistIcon, a.onShowFavoriteArtists),
widget.NewButtonWithIcon("", res.ResMusicnotesInvertPng, a.onShowFavoriteSongs)) widget.NewButtonWithIcon("", myTheme.TracksIcon, a.onShowFavoriteSongs))
a.searcher = widgets.NewSearcher() a.searcher = widgets.NewSearcher()
a.searcher.OnSearched = a.OnSearched a.searcher.OnSearched = a.OnSearched
a.searcher.Entry.Text = searchText a.searcher.Entry.Text = searchText
+3 -3
View File
@@ -2,8 +2,8 @@ package browsing
import ( import (
"supersonic/backend" "supersonic/backend"
"supersonic/res"
"supersonic/ui/controller" "supersonic/ui/controller"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "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{ g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText, 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) iter := g.lm.GenreIter(g.genre)
g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.grid) g.contr.ConnectAlbumGridActions(g.grid)
@@ -91,7 +91,7 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage {
g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText, 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.grid = widgets.NewGridViewFromState(saved.gridState)
g.searcher = widgets.NewSearcher() g.searcher = widgets.NewSearcher()
g.searcher.OnSearched = g.OnSearched g.searcher.OnSearched = g.OnSearched
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"supersonic/sharedutil" "supersonic/sharedutil"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "supersonic/ui/widgets"
@@ -205,7 +206,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
page.pm.PlayFromBeginning() page.pm.PlayFromBeginning()
}) })
// TODO: find way to pad shuffle svg rather than using a space in the label string // 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.LoadTracks(page.tracklist.Tracks, false /*append*/, true /*shuffle*/)
page.pm.PlayFromBeginning() page.pm.PlayFromBeginning()
}) })
+2 -2
View File
@@ -2,10 +2,10 @@ package browsing
import ( import (
"supersonic/backend" "supersonic/backend"
"supersonic/res"
"supersonic/sharedutil" "supersonic/sharedutil"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/layouts" "supersonic/ui/layouts"
"supersonic/ui/theme"
"supersonic/ui/widgets" "supersonic/ui/widgets"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -56,7 +56,7 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
t.title = widget.NewRichTextWithText("All Tracks") t.title = widget.NewRichTextWithText("All Tracks")
t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName 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 = widgets.NewSearcher()
t.searcher.OnSearched = t.OnSearched t.searcher.OnSearched = t.OnSearched
t.createContainer() t.createContainer()
+2 -1
View File
@@ -339,7 +339,7 @@ func (c *Controller) ShowAboutDialog() {
pop.Show() pop.Show()
} }
func (c *Controller) ShowSettingsDialog() { func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func()) {
devs, err := c.App.Player.ListAudioDevices() devs, err := c.App.Player.ListAudioDevices()
if err != nil { if err != nil {
log.Printf("error listing audio devices: %v", err) log.Printf("error listing audio devices: %v", err)
@@ -356,6 +356,7 @@ func (c *Controller) ShowSettingsDialog() {
dlg.OnAudioDeviceSettingChanged = func() { dlg.OnAudioDeviceSettingChanged = func() {
c.App.Player.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName) c.App.Player.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName)
} }
dlg.OnThemeSettingChanged = themeUpdateCallbk
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas()) pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
dlg.OnDismiss = func() { dlg.OnDismiss = func() {
pop.Hide() pop.Hide()
+25 -1
View File
@@ -9,6 +9,7 @@ import (
"supersonic/backend" "supersonic/backend"
"supersonic/player" "supersonic/player"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"supersonic/ui/widgets" "supersonic/ui/widgets"
"unicode" "unicode"
@@ -31,6 +32,7 @@ type SettingsDialog struct {
OnReplayGainSettingsChanged func() OnReplayGainSettingsChanged func()
OnAudioExclusiveSettingChanged func() OnAudioExclusiveSettingChanged func()
OnAudioDeviceSettingChanged func() OnAudioDeviceSettingChanged func()
OnThemeSettingChanged func()
OnDismiss func() OnDismiss func()
config *backend.Config config *backend.Config
@@ -50,6 +52,10 @@ func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDev
s.createPlaybackTab(), s.createPlaybackTab(),
s.createExperimentalTab(window), 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.promptText = widget.NewRichTextWithText("")
s.content = container.NewVBox(tabs, widget.NewSeparator(), s.content = container.NewVBox(tabs, widget.NewSeparator(),
container.NewHBox(s.promptText, layout.NewSpacer(), widget.NewButton("Close", func() { 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 { 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) { startupPage := widget.NewSelect(backend.SupportedStartupPages, func(choice string) {
s.config.Application.StartupPage = choice s.config.Application.StartupPage = choice
}) })
@@ -161,7 +182,10 @@ func (s *SettingsDialog) createGeneralTab() *container.TabItem {
scrobbleEnabled.Checked = s.config.Scrobbling.Enabled scrobbleEnabled.Checked = s.config.Scrobbling.Enabled
return container.NewTabItem("General", container.NewVBox( 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), container.NewHBox(systemTrayEnable, closeToTray),
s.newSectionSeparator(), s.newSectionSeparator(),
+19 -8
View File
@@ -7,6 +7,7 @@ import (
"supersonic/ui/browsing" "supersonic/ui/browsing"
"supersonic/ui/controller" "supersonic/ui/controller"
"supersonic/ui/os" "supersonic/ui/os"
"supersonic/ui/theme"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
@@ -42,6 +43,7 @@ type MainWindow struct {
BrowsingPane *browsing.BrowsingPane BrowsingPane *browsing.BrowsingPane
BottomPanel *BottomPanel BottomPanel *BottomPanel
theme *theme.MyTheme
haveSystemTray bool haveSystemTray bool
container *fyne.Container container *fyne.Container
} }
@@ -51,8 +53,13 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap
App: app, App: app,
Window: fyneApp.NewWindow(appName), Window: fyneApp.NewWindow(appName),
BrowsingPane: browsing.NewBrowsingPane(app), 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 { if app.Config.Application.EnableSystemTray {
m.SetupSystemTrayMenu(appName, fyneApp) 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.BrowsingPane.AddSettingsMenuItem("About...", m.Controller.ShowAboutDialog)
m.addNavigationButtons() m.addNavigationButtons()
m.BrowsingPane.DisableNavigationButtons() m.BrowsingPane.DisableNavigationButtons()
@@ -178,25 +189,25 @@ func (m *MainWindow) ShowNewVersionDialog(appName, versionTag string) {
} }
func (m *MainWindow) addNavigationButtons() { func (m *MainWindow) addNavigationButtons() {
m.BrowsingPane.AddNavigationButton(res.ResHeadphonesInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.NowPlayingIcon, func() {
m.Router.NavigateTo(controller.NowPlayingRoute("")) m.Router.NavigateTo(controller.NowPlayingRoute(""))
}) })
m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.FavoriteIcon, func() {
m.Router.NavigateTo(controller.FavoritesRoute()) m.Router.NavigateTo(controller.FavoritesRoute())
}) })
m.BrowsingPane.AddNavigationButton(res.ResDiscInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.AlbumIcon, func() {
m.Router.NavigateTo(controller.AlbumsRoute()) m.Router.NavigateTo(controller.AlbumsRoute())
}) })
m.BrowsingPane.AddNavigationButton(res.ResPeopleInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.ArtistIcon, func() {
m.Router.NavigateTo(controller.ArtistsRoute()) m.Router.NavigateTo(controller.ArtistsRoute())
}) })
m.BrowsingPane.AddNavigationButton(res.ResTheatermasksInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.GenreIcon, func() {
m.Router.NavigateTo(controller.GenresRoute()) m.Router.NavigateTo(controller.GenresRoute())
}) })
m.BrowsingPane.AddNavigationButton(res.ResPlaylistInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.PlaylistIcon, func() {
m.Router.NavigateTo(controller.PlaylistsRoute()) m.Router.NavigateTo(controller.PlaylistsRoute())
}) })
m.BrowsingPane.AddNavigationButton(res.ResMusicnotesInvertPng, func() { m.BrowsingPane.AddNavigationButton(theme.TracksIcon, func() {
m.Router.NavigateTo(controller.TracksRoute()) m.Router.NavigateTo(controller.TracksRoute())
}) })
} }
+113 -8
View File
@@ -6,6 +6,9 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"strings" "strings"
"supersonic/backend"
"supersonic/res"
"supersonic/sharedutil"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
@@ -13,6 +16,16 @@ import (
const ColorNamePageBackground fyne.ThemeColorName = "PageBackground" const ColorNamePageBackground fyne.ThemeColorName = "PageBackground"
type AppearanceMode string
const (
AppearanceLight AppearanceMode = "Light"
AppearanceDark AppearanceMode = "Dark"
AppearanceAuto AppearanceMode = "Auto"
DefaultAppearance AppearanceMode = AppearanceDark
)
var ( var (
normalFont fyne.Resource normalFont fyne.Resource
boldFont fyne.Resource boldFont fyne.Resource
@@ -21,30 +34,107 @@ var (
type MyTheme struct { type MyTheme struct {
NormalFont string NormalFont string
BoldFont string BoldFont string
config *backend.ThemeConfig
} }
var _ fyne.Theme = (*MyTheme)(nil) 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 { switch name {
case ColorNamePageBackground: 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: 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: 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: 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: 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 { func (m *MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
return theme.DefaultTheme().Icon(name) 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 { func (m *MyTheme) Font(style fyne.TextStyle) fyne.Resource {
switch style { switch style {
case fyne.TextStyle{}: case fyne.TextStyle{}:
@@ -74,7 +164,6 @@ func (m *MyTheme) Font(style fyne.TextStyle) fyne.Resource {
return normalFont return normalFont
} }
} }
return theme.DefaultTheme().Font(style) return theme.DefaultTheme().Font(style)
} }
@@ -82,6 +171,22 @@ func (m *MyTheme) Size(name fyne.ThemeSizeName) float32 {
return theme.DefaultTheme().Size(name) 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) { func readTTFFile(filepath string) ([]byte, error) {
if !strings.HasSuffix(filepath, ".ttf") { if !strings.HasSuffix(filepath, ".ttf") {
err := errors.New("only .ttf fonts are supported") err := errors.New("only .ttf fonts are supported")
+35
View File
@@ -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)
}
+3 -2
View File
@@ -2,6 +2,7 @@ package widgets
import ( import (
"supersonic/res" "supersonic/res"
"supersonic/ui/theme"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -32,9 +33,9 @@ func (f *FavoriteButton) Tapped(e *fyne.PointEvent) {
func (f *FavoriteButton) Refresh() { func (f *FavoriteButton) Refresh() {
if f.IsFavorited { if f.IsFavorited {
f.Icon = res.ResHeartFilledInvertPng f.Icon = theme.FavoriteIcon
} else { } else {
f.Icon = res.ResHeartOutlineInvertPng f.Icon = theme.NotFavoriteIcon
} }
f.Button.Refresh() f.Button.Refresh()
} }
+2 -2
View File
@@ -3,9 +3,9 @@ package widgets
import ( import (
"log" "log"
"supersonic/ui/layouts" "supersonic/ui/layouts"
myTheme "supersonic/ui/theme"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -42,7 +42,7 @@ func NewListHeader(cols []ListColumn, layout *layouts.ColumnsLayout) *ListHeader
for i, _ := range l.columnVisible { for i, _ := range l.columnVisible {
l.columnVisible[i] = true 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.ExtendBaseWidget(l)
l.buildColumns() l.buildColumns()
return l return l
+9 -8
View File
@@ -4,13 +4,14 @@ import (
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
"supersonic/res" "sync"
"time"
"supersonic/sharedutil" "supersonic/sharedutil"
"supersonic/ui/layouts" "supersonic/ui/layouts"
"supersonic/ui/os" "supersonic/ui/os"
myTheme "supersonic/ui/theme"
"supersonic/ui/util" "supersonic/ui/util"
"sync"
"time"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "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.album.OnTapped = func() { tracklist.onAlbumTapped(t.albumID) }
t.dur = newTrailingAlignRichText() t.dur = newTrailingAlignRichText()
t.year = newTrailingAlignRichText() t.year = newTrailingAlignRichText()
favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng) favorite := NewTappbaleIcon(myTheme.NotFavoriteIcon)
favorite.OnTapped = t.toggleFavorited favorite.OnTapped = t.toggleFavorited
t.favorite = container.NewCenter(favorite) t.favorite = container.NewCenter(favorite)
t.rating = NewStarRating() t.rating = NewStarRating()
@@ -623,10 +624,10 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) {
// Render favorite column // Render favorite column
if tr.Starred.IsZero() { if tr.Starred.IsZero() {
t.isFavorite = false t.isFavorite = false
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon
} else { } else {
t.isFavorite = true t.isFavorite = true
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon
} }
t.rating.Rating = tr.UserRating t.rating.Rating = tr.UserRating
@@ -648,12 +649,12 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) {
func (t *TrackRow) toggleFavorited() { func (t *TrackRow) toggleFavorited() {
if t.isFavorite { if t.isFavorite {
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon
t.favorite.Refresh() t.favorite.Refresh()
t.isFavorite = false t.isFavorite = false
t.tracklist.onSetFavorite(t.trackID, false) t.tracklist.onSetFavorite(t.trackID, false)
} else { } else {
t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon
t.favorite.Refresh() t.favorite.Refresh()
t.isFavorite = true t.isFavorite = true
t.tracklist.onSetFavorite(t.trackID, true) t.tracklist.onSetFavorite(t.trackID, true)