184 lines
4.0 KiB
Go
184 lines
4.0 KiB
Go
package theme
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"image/color"
|
|
"io"
|
|
"strings"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/storage"
|
|
)
|
|
|
|
// FromJSON returns a Theme created from the given JSON metadata.
|
|
// Any values not present in the data will fall back to the default theme.
|
|
// If a parse error occurs it will be returned along with a default theme.
|
|
//
|
|
// Since: 2.2
|
|
func FromJSON(data string) (fyne.Theme, error) {
|
|
return FromJSONReader(strings.NewReader(data))
|
|
}
|
|
|
|
// FromJSONReader returns a Theme created from the given JSON metadata through the reader.
|
|
// Any values not present in the data will fall back to the default theme.
|
|
// If a parse error occurs it will be returned along with a default theme.
|
|
//
|
|
// Since: 2.2
|
|
func FromJSONReader(r io.Reader) (fyne.Theme, error) {
|
|
var th *schema
|
|
if err := json.NewDecoder(r).Decode(&th); err != nil {
|
|
return DefaultTheme(), err
|
|
}
|
|
|
|
return &jsonTheme{data: th, fallback: DefaultTheme()}, nil
|
|
}
|
|
|
|
type hexColor string
|
|
|
|
func (h hexColor) color() (color.Color, error) {
|
|
data := h
|
|
switch len([]rune(h)) {
|
|
case 8, 6:
|
|
case 9, 7: // remove # prefix
|
|
data = h[1:]
|
|
case 5: // remove # prefix, then double up
|
|
data = h[1:]
|
|
fallthrough
|
|
case 4: // could be rgba or #rgb
|
|
if data[0] == '#' {
|
|
v := []rune(data[1:])
|
|
data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2]})
|
|
break
|
|
}
|
|
|
|
v := []rune(data)
|
|
data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2], v[3], v[3]})
|
|
case 3:
|
|
v := []rune(h)
|
|
data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2]})
|
|
default:
|
|
return color.Transparent, errors.New("invalid color format: " + string(h))
|
|
}
|
|
|
|
digits, err := hex.DecodeString(string(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := &color.NRGBA{R: digits[0], G: digits[1], B: digits[2]}
|
|
if len(digits) == 4 {
|
|
ret.A = digits[3]
|
|
} else {
|
|
ret.A = 0xff
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
type uriString string
|
|
|
|
func (u uriString) resource() fyne.Resource {
|
|
uri, err := storage.ParseURI(string(u))
|
|
if err != nil {
|
|
fyne.LogError("Failed to parse URI", err)
|
|
return nil
|
|
}
|
|
r, err := storage.LoadResourceFromURI(uri)
|
|
if err != nil {
|
|
fyne.LogError("Failed to load resource from URI", err)
|
|
return nil
|
|
}
|
|
return r
|
|
}
|
|
|
|
type schema struct {
|
|
Colors map[string]hexColor `json:"Colors,omitempty"`
|
|
DarkColors map[string]hexColor `json:"Colors-dark,omitempty"`
|
|
LightColors map[string]hexColor `json:"Colors-light,omitempty"`
|
|
Sizes map[string]float32 `json:"Sizes,omitempty"`
|
|
|
|
Fonts map[string]uriString `json:"Fonts,omitempty"`
|
|
Icons map[string]uriString `json:"Icons,omitempty"`
|
|
}
|
|
|
|
type jsonTheme struct {
|
|
data *schema
|
|
fallback fyne.Theme
|
|
}
|
|
|
|
func (t *jsonTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
|
switch variant {
|
|
case VariantLight:
|
|
if val, ok := t.data.LightColors[string(name)]; ok {
|
|
c, err := val.color()
|
|
if err != nil {
|
|
fyne.LogError("Failed to parse color", err)
|
|
} else {
|
|
return c
|
|
}
|
|
}
|
|
case VariantDark:
|
|
if val, ok := t.data.DarkColors[string(name)]; ok {
|
|
c, err := val.color()
|
|
if err != nil {
|
|
fyne.LogError("Failed to parse color", err)
|
|
} else {
|
|
return c
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := t.data.Colors[string(name)]; ok {
|
|
c, err := val.color()
|
|
if err != nil {
|
|
fyne.LogError("Failed to parse color", err)
|
|
} else {
|
|
return c
|
|
}
|
|
}
|
|
|
|
return t.fallback.Color(name, variant)
|
|
}
|
|
|
|
func (t *jsonTheme) Font(style fyne.TextStyle) fyne.Resource {
|
|
if val, ok := t.data.Fonts[styleString(style)]; ok {
|
|
r := val.resource()
|
|
if r != nil {
|
|
return r
|
|
}
|
|
}
|
|
return t.fallback.Font(style)
|
|
}
|
|
|
|
func (t *jsonTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
|
if val, ok := t.data.Icons[string(name)]; ok {
|
|
r := val.resource()
|
|
if r != nil {
|
|
return r
|
|
}
|
|
}
|
|
return t.fallback.Icon(name)
|
|
}
|
|
|
|
func (t *jsonTheme) Size(name fyne.ThemeSizeName) float32 {
|
|
if val, ok := t.data.Sizes[string(name)]; ok {
|
|
return val
|
|
}
|
|
|
|
return t.fallback.Size(name)
|
|
}
|
|
|
|
func styleString(s fyne.TextStyle) string {
|
|
if s.Bold {
|
|
if s.Italic {
|
|
return "boldItalic"
|
|
}
|
|
return "bold"
|
|
}
|
|
if s.Monospace {
|
|
return "monospace"
|
|
}
|
|
return "regular"
|
|
}
|