move default theme to ThemeFile system
This commit is contained in:
@@ -89,6 +89,7 @@ type ReplayGainConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ThemeConfig struct {
|
type ThemeConfig struct {
|
||||||
|
ThemeFile string
|
||||||
Appearance string
|
Appearance string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ var ResFilterSvg = &fyne.StaticResource{
|
|||||||
StaticContent: []byte(
|
StaticContent: []byte(
|
||||||
"<svg fill=\"#000000\" version=\"1.1\" id=\"Capa_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \r\n\t width=\"800px\" height=\"800px\" viewBox=\"0 0 971.986 971.986\"\r\n\t xml:space=\"preserve\">\r\n<g>\r\n\t<path d=\"M370.216,459.3c10.2,11.1,15.8,25.6,15.8,40.6v442c0,26.601,32.1,40.101,51.1,21.4l123.3-141.3\r\n\t\tc16.5-19.8,25.6-29.601,25.6-49.2V500c0-15,5.7-29.5,15.8-40.601L955.615,75.5c26.5-28.8,6.101-75.5-33.1-75.5h-873\r\n\t\tc-39.2,0-59.7,46.6-33.1,75.5L370.216,459.3z\"/>\r\n</g>\r\n</svg>\r\n"),
|
"<svg fill=\"#000000\" version=\"1.1\" id=\"Capa_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \r\n\t width=\"800px\" height=\"800px\" viewBox=\"0 0 971.986 971.986\"\r\n\t xml:space=\"preserve\">\r\n<g>\r\n\t<path d=\"M370.216,459.3c10.2,11.1,15.8,25.6,15.8,40.6v442c0,26.601,32.1,40.101,51.1,21.4l123.3-141.3\r\n\t\tc16.5-19.8,25.6-29.601,25.6-49.2V500c0-15,5.7-29.5,15.8-40.601L955.615,75.5c26.5-28.8,6.101-75.5-33.1-75.5h-873\r\n\t\tc-39.2,0-59.7,46.6-33.1,75.5L370.216,459.3z\"/>\r\n</g>\r\n</svg>\r\n"),
|
||||||
}
|
}
|
||||||
|
var ResDefaultToml = &fyne.StaticResource{
|
||||||
|
StaticName: "default.toml",
|
||||||
|
StaticContent: []byte(
|
||||||
|
"[SupersonicTheme]\nName = \"Default\"\nVersion = \"0.1\"\nSupportsDark = true\nSupportsLight = true\n\n[DarkColors]\nPageBackground = \"#0F0F0F\"\nBackground = \"#232323\"\nScrollBar = \"#F3F3F3\"\nButton = \"#14141432\"\nInputBackground = \"#14141432\"\n\n[LightColors]\nPageBackground = \"#FAFAFA\"\nBackground = \"#E1DFE1\"\nScrollBar = \"#565656\"\nButton = \"#C8C8C8F0\"\nDisabledButton = \"#CDCDCDF0\"\nForeground = \"#0A0A0A\"\nPrimary = \"#1919FA\""),
|
||||||
|
}
|
||||||
var ResLICENSE = &fyne.StaticResource{
|
var ResLICENSE = &fyne.StaticResource{
|
||||||
StaticName: "LICENSE",
|
StaticName: "LICENSE",
|
||||||
StaticContent: []byte(
|
StaticContent: []byte(
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ fyne bundle -append -prefix Res icons/publicdomain/grid.svg >> bundled.go
|
|||||||
fyne bundle -append -prefix Res icons/publicdomain/list.svg >> bundled.go
|
fyne bundle -append -prefix Res icons/publicdomain/list.svg >> bundled.go
|
||||||
fyne bundle -append -prefix Res icons/publicdomain/filter.svg >> bundled.go
|
fyne bundle -append -prefix Res icons/publicdomain/filter.svg >> bundled.go
|
||||||
|
|
||||||
|
fyne bundle -append -prefix Res themes/default.toml >> bundled.go
|
||||||
|
|
||||||
fyne bundle -append -prefix Res ../LICENSE >> bundled.go
|
fyne bundle -append -prefix Res ../LICENSE >> bundled.go
|
||||||
fyne bundle -append -prefix Res licenses/BSDLICENSE >> bundled.go
|
fyne bundle -append -prefix Res licenses/BSDLICENSE >> bundled.go
|
||||||
fyne bundle -append -prefix Res licenses/MITLICENSE >> bundled.go
|
fyne bundle -append -prefix Res licenses/MITLICENSE >> bundled.go
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[SupersonicTheme]
|
||||||
|
Name = "Default"
|
||||||
|
Version = "0.1"
|
||||||
|
SupportsDark = true
|
||||||
|
SupportsLight = true
|
||||||
|
|
||||||
|
[DarkColors]
|
||||||
|
PageBackground = "#0F0F0F"
|
||||||
|
Background = "#232323"
|
||||||
|
ScrollBar = "#F3F3F3"
|
||||||
|
Button = "#14141432"
|
||||||
|
InputBackground = "#14141432"
|
||||||
|
|
||||||
|
[LightColors]
|
||||||
|
PageBackground = "#FAFAFA"
|
||||||
|
Background = "#E1DFE1"
|
||||||
|
ScrollBar = "#565656"
|
||||||
|
Button = "#C8C8C8F0"
|
||||||
|
DisabledButton = "#CDCDCDF0"
|
||||||
|
Foreground = "#0A0A0A"
|
||||||
|
Primary = "#1919FA"
|
||||||
+86
-30
@@ -1,10 +1,13 @@
|
|||||||
package theme
|
package theme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
@@ -36,57 +39,98 @@ type MyTheme struct {
|
|||||||
NormalFont string
|
NormalFont string
|
||||||
BoldFont string
|
BoldFont string
|
||||||
config *backend.ThemeConfig
|
config *backend.ThemeConfig
|
||||||
|
themeFileDir string
|
||||||
|
|
||||||
|
loadedThemeFilename string
|
||||||
|
loadedThemeFile *ThemeFile
|
||||||
|
defaultThemeFile *ThemeFile
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ fyne.Theme = (*MyTheme)(nil)
|
var _ fyne.Theme = (*MyTheme)(nil)
|
||||||
|
|
||||||
func NewMyTheme(config *backend.ThemeConfig) *MyTheme {
|
func NewMyTheme(config *backend.ThemeConfig) *MyTheme {
|
||||||
m := &MyTheme{config: config}
|
m := &MyTheme{config: config}
|
||||||
|
m.defaultThemeFile, _ = DecodeThemeFile(bytes.NewReader(res.ResDefaultToml.StaticContent))
|
||||||
m.createThemeIcons()
|
m.createThemeIcons()
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
|
func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
|
||||||
|
// load theme file if necessary
|
||||||
|
if m.loadedThemeFile == nil || m.config.ThemeFile != m.loadedThemeFilename {
|
||||||
|
t, err := ReadThemeFile(path.Join(m.themeFileDir, m.config.ThemeFile))
|
||||||
|
if err == nil {
|
||||||
|
m.loadedThemeFile = t
|
||||||
|
} else {
|
||||||
|
log.Printf("failed to load theme file %q: %s", m.config.ThemeFile, err.Error())
|
||||||
|
m.loadedThemeFile = m.defaultThemeFile
|
||||||
|
}
|
||||||
|
m.loadedThemeFilename = m.config.ThemeFile
|
||||||
|
}
|
||||||
|
|
||||||
variant := m.getVariant()
|
variant := m.getVariant()
|
||||||
|
thFile := m.loadedThemeFile
|
||||||
|
if !thFile.SupportsVariant(variant) {
|
||||||
|
thFile = m.defaultThemeFile
|
||||||
|
}
|
||||||
|
colors := thFile.DarkColors
|
||||||
|
if variant == theme.VariantLight {
|
||||||
|
colors = thFile.LightColors
|
||||||
|
}
|
||||||
switch name {
|
switch name {
|
||||||
case ColorNamePageBackground:
|
case ColorNamePageBackground:
|
||||||
if variant == theme.VariantDark {
|
return colorOrDefault(colors.PageBackground, name, variant)
|
||||||
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:
|
||||||
if variant == theme.VariantDark {
|
return colorOrDefault(colors.Background, name, variant)
|
||||||
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:
|
|
||||||
if variant == theme.VariantDark {
|
|
||||||
return theme.DarkTheme().Color(theme.ColorNameForeground, variant)
|
|
||||||
}
|
|
||||||
return theme.LightTheme().Color(theme.ColorNameForeground, variant)
|
|
||||||
case theme.ColorNameButton:
|
case theme.ColorNameButton:
|
||||||
if variant == theme.VariantDark {
|
return colorOrDefault(colors.Button, name, variant)
|
||||||
return color.RGBA{R: 20, G: 20, B: 20, A: 50}
|
case theme.ColorNameDisabled:
|
||||||
}
|
return colorOrDefault(colors.Disabled, name, variant)
|
||||||
return color.RGBA{R: 200, G: 200, B: 200, A: 240}
|
|
||||||
case theme.ColorNameDisabledButton:
|
case theme.ColorNameDisabledButton:
|
||||||
if variant == theme.VariantLight {
|
return colorOrDefault(colors.DisabledButton, name, variant)
|
||||||
return color.RGBA{R: 205, G: 205, B: 205, A: 240}
|
case theme.ColorNameError:
|
||||||
}
|
return colorOrDefault(colors.Error, name, variant)
|
||||||
return theme.DefaultTheme().Color(theme.ColorNameDisabledButton, variant)
|
case theme.ColorNameFocus:
|
||||||
case theme.ColorNameInputBackground:
|
return colorOrDefault(colors.Focus, name, variant)
|
||||||
if variant == theme.VariantDark {
|
|
||||||
return color.RGBA{R: 20, G: 20, B: 20, A: 50}
|
|
||||||
}
|
|
||||||
case theme.ColorNameForeground:
|
case theme.ColorNameForeground:
|
||||||
if variant == theme.VariantLight {
|
return colorOrDefault(colors.Foreground, name, variant)
|
||||||
return color.RGBA{R: 10, G: 10, B: 10, A: 255}
|
case theme.ColorNameHover:
|
||||||
}
|
return colorOrDefault(colors.Hover, name, variant)
|
||||||
|
case theme.ColorNameInputBackground:
|
||||||
|
return colorOrDefault(colors.InputBackground, name, variant)
|
||||||
|
case theme.ColorNameInputBorder:
|
||||||
|
return colorOrDefault(colors.InputBorder, name, variant)
|
||||||
|
case theme.ColorNameMenuBackground:
|
||||||
|
return colorOrDefault(colors.MenuBackground, name, variant)
|
||||||
|
case theme.ColorNameOverlayBackground:
|
||||||
|
return colorOrDefault(colors.OverlayBackground, name, variant)
|
||||||
|
case theme.ColorNamePlaceHolder:
|
||||||
|
return colorOrDefault(colors.Placeholder, name, variant)
|
||||||
|
case theme.ColorNamePressed:
|
||||||
|
return colorOrDefault(colors.Pressed, name, variant)
|
||||||
case theme.ColorNamePrimary:
|
case theme.ColorNamePrimary:
|
||||||
if variant == theme.VariantLight {
|
return colorOrDefault(colors.Primary, name, variant)
|
||||||
return color.RGBA{R: 25, G: 25, B: 250, A: 255}
|
case theme.ColorNameScrollBar:
|
||||||
|
return colorOrDefault(colors.ScrollBar, name, variant)
|
||||||
|
case theme.ColorNameSelection:
|
||||||
|
return colorOrDefault(colors.Selection, name, variant)
|
||||||
|
case theme.ColorNameSeparator:
|
||||||
|
return colorOrDefault(colors.Separator, name, variant)
|
||||||
|
case theme.ColorNameShadow:
|
||||||
|
return colorOrDefault(colors.Shadow, name, variant)
|
||||||
|
case theme.ColorNameSuccess:
|
||||||
|
return colorOrDefault(colors.Success, name, variant)
|
||||||
|
case theme.ColorNameWarning:
|
||||||
|
return colorOrDefault(colors.Warning, name, variant)
|
||||||
|
default:
|
||||||
|
return colorOrDefault("", name, variant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func colorOrDefault(colorStr string, name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||||
|
if c, err := ColorStringToColor(colorStr); err == nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
return theme.DefaultTheme().Color(name, variant)
|
return theme.DefaultTheme().Color(name, variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +138,18 @@ func (m *MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
|||||||
return theme.DefaultTheme().Icon(name)
|
return theme.DefaultTheme().Icon(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a map [themeFileName] -> displayName
|
||||||
|
func (m *MyTheme) ListThemeFiles() map[string]string {
|
||||||
|
files, _ := filepath.Glob(m.themeFileDir + "/*.toml")
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, filename := range files {
|
||||||
|
if themeFile, err := ReadThemeFile(path.Join(m.themeFileDir, filename)); err == nil {
|
||||||
|
result[filename] = themeFile.SupersonicTheme.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
type myThemedResource struct {
|
type myThemedResource struct {
|
||||||
myTheme MyTheme
|
myTheme MyTheme
|
||||||
darkVariant *fyne.StaticResource
|
darkVariant *fyne.StaticResource
|
||||||
|
|||||||
+15
-1
@@ -5,9 +5,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
)
|
)
|
||||||
@@ -83,8 +86,12 @@ func ReadThemeFile(filePath string) (*ThemeFile, error) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
return DecodeThemeFile(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeThemeFile(reader io.Reader) (*ThemeFile, error) {
|
||||||
theme := &ThemeFile{}
|
theme := &ThemeFile{}
|
||||||
if err := toml.NewDecoder(f).Decode(theme); err != nil {
|
if err := toml.NewDecoder(reader).Decode(theme); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +105,13 @@ func ReadThemeFile(filePath string) (*ThemeFile, error) {
|
|||||||
return theme, nil
|
return theme, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ThemeFile) SupportsVariant(v fyne.ThemeVariant) bool {
|
||||||
|
if v == theme.VariantDark {
|
||||||
|
return t.SupersonicTheme.SupportsDark
|
||||||
|
}
|
||||||
|
return t.SupersonicTheme.SupportsLight
|
||||||
|
}
|
||||||
|
|
||||||
// Parses a CSS-style #RRGGBB or #RRGGBBAA string
|
// Parses a CSS-style #RRGGBB or #RRGGBBAA string
|
||||||
func ColorStringToColor(colorStr string) (color.Color, error) {
|
func ColorStringToColor(colorStr string) (color.Color, error) {
|
||||||
if !strings.HasPrefix(colorStr, "#") || !sharedutil.SliceContains([]int{7, 9}, len(colorStr)) {
|
if !strings.HasPrefix(colorStr, "#") || !sharedutil.SliceContains([]int{7, 9}, len(colorStr)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user