diff --git a/ui/theme/themefile.go b/ui/theme/themefile.go new file mode 100644 index 0000000..950df01 --- /dev/null +++ b/ui/theme/themefile.go @@ -0,0 +1,115 @@ +package theme + +import ( + "encoding/hex" + "errors" + "fmt" + "image/color" + "os" + "strings" + + "github.com/dweymouth/supersonic/sharedutil" + "github.com/pelletier/go-toml" +) + +type ThemeFileHeader struct { + Name string + Version string + SupportsDark bool + SupportsLight bool +} + +type ThemeFile struct { + SupersonicTheme ThemeFileHeader + + DarkColors ThemeColors + LightColors ThemeColors +} + +type ThemeColors struct { + // Supersonic-specific colors + + PageBackground string + + // Fyne colors + + Background string + + Button string + + DisabledButton string + + Disabled string + + Error string + + Focus string + + Foreground string + + Hover string + + InputBackground string + + InputBorder string + + MenuBackground string + + OverlayBackground string + + Placeholder string + + Pressed string + + Primary string + + ScrollBar string + + Selection string + + Separator string + + Shadow string + + Success string + + Warning string +} + +func ReadThemeFile(filePath string) (*ThemeFile, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer f.Close() + + theme := &ThemeFile{} + if err := toml.NewDecoder(f).Decode(theme); err != nil { + return nil, err + } + + if theme.SupersonicTheme.Name == "" || theme.SupersonicTheme.Version != "0.1" { + return nil, errors.New("invalid theme file name or version") + } + if !(theme.SupersonicTheme.SupportsDark || theme.SupersonicTheme.SupportsLight) { + return nil, errors.New("invalid theme file: must support one or both of light/dark") + } + + return theme, nil +} + +// Parses a CSS-style #RRGGBB or #RRGGBBAA string +func ColorStringToColor(colorStr string) (color.Color, error) { + if !strings.HasPrefix(colorStr, "#") || !sharedutil.SliceContains([]int{7, 9}, len(colorStr)) { + return color.Black, errors.New("invalid color string") + } + colorBytes := make([]byte, 4) + n, err := hex.Decode(colorBytes, []byte(colorStr[1:])) + if err != nil { + return color.Black, fmt.Errorf("invalid color string: %s", err.Error()) + } + if n == 3 { + colorBytes[3] = 255 // opaque alpha + } + return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2], A: colorBytes[3]}, nil +}