json.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package theme
  2. import (
  3. "encoding/hex"
  4. "encoding/json"
  5. "errors"
  6. "image/color"
  7. "io"
  8. "strings"
  9. "fyne.io/fyne/v2"
  10. "fyne.io/fyne/v2/storage"
  11. )
  12. // FromJSON returns a Theme created from the given JSON metadata.
  13. // Any values not present in the data will fall back to the default theme.
  14. // If a parse error occurs it will be returned along with a default theme.
  15. //
  16. // Since: 2.2
  17. func FromJSON(data string) (fyne.Theme, error) {
  18. return FromJSONReader(strings.NewReader(data))
  19. }
  20. // FromJSONReader returns a Theme created from the given JSON metadata through the reader.
  21. // Any values not present in the data will fall back to the default theme.
  22. // If a parse error occurs it will be returned along with a default theme.
  23. //
  24. // Since: 2.2
  25. func FromJSONReader(r io.Reader) (fyne.Theme, error) {
  26. var th *schema
  27. if err := json.NewDecoder(r).Decode(&th); err != nil {
  28. return DefaultTheme(), err
  29. }
  30. return &jsonTheme{data: th, fallback: DefaultTheme()}, nil
  31. }
  32. type hexColor string
  33. func (h hexColor) color() (color.Color, error) {
  34. data := h
  35. switch len([]rune(h)) {
  36. case 8, 6:
  37. case 9, 7: // remove # prefix
  38. data = h[1:]
  39. case 5: // remove # prefix, then double up
  40. data = h[1:]
  41. fallthrough
  42. case 4: // could be rgba or #rgb
  43. if data[0] == '#' {
  44. v := []rune(data[1:])
  45. data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2]})
  46. break
  47. }
  48. v := []rune(data)
  49. data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2], v[3], v[3]})
  50. case 3:
  51. v := []rune(h)
  52. data = hexColor([]rune{v[0], v[0], v[1], v[1], v[2], v[2]})
  53. default:
  54. return color.Transparent, errors.New("invalid color format: " + string(h))
  55. }
  56. digits, err := hex.DecodeString(string(data))
  57. if err != nil {
  58. return nil, err
  59. }
  60. ret := &color.NRGBA{R: digits[0], G: digits[1], B: digits[2]}
  61. if len(digits) == 4 {
  62. ret.A = digits[3]
  63. } else {
  64. ret.A = 0xff
  65. }
  66. return ret, nil
  67. }
  68. type uriString string
  69. func (u uriString) resource() fyne.Resource {
  70. uri, err := storage.ParseURI(string(u))
  71. if err != nil {
  72. fyne.LogError("Failed to parse URI", err)
  73. return nil
  74. }
  75. r, err := storage.LoadResourceFromURI(uri)
  76. if err != nil {
  77. fyne.LogError("Failed to load resource from URI", err)
  78. return nil
  79. }
  80. return r
  81. }
  82. type schema struct {
  83. Colors map[string]hexColor `json:"Colors,omitempty"`
  84. DarkColors map[string]hexColor `json:"Colors-dark,omitempty"`
  85. LightColors map[string]hexColor `json:"Colors-light,omitempty"`
  86. Sizes map[string]float32 `json:"Sizes,omitempty"`
  87. Fonts map[string]uriString `json:"Fonts,omitempty"`
  88. Icons map[string]uriString `json:"Icons,omitempty"`
  89. }
  90. type jsonTheme struct {
  91. data *schema
  92. fallback fyne.Theme
  93. }
  94. func (t *jsonTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
  95. switch variant {
  96. case VariantLight:
  97. if val, ok := t.data.LightColors[string(name)]; ok {
  98. c, err := val.color()
  99. if err != nil {
  100. fyne.LogError("Failed to parse color", err)
  101. } else {
  102. return c
  103. }
  104. }
  105. case VariantDark:
  106. if val, ok := t.data.DarkColors[string(name)]; ok {
  107. c, err := val.color()
  108. if err != nil {
  109. fyne.LogError("Failed to parse color", err)
  110. } else {
  111. return c
  112. }
  113. }
  114. }
  115. if val, ok := t.data.Colors[string(name)]; ok {
  116. c, err := val.color()
  117. if err != nil {
  118. fyne.LogError("Failed to parse color", err)
  119. } else {
  120. return c
  121. }
  122. }
  123. return t.fallback.Color(name, variant)
  124. }
  125. func (t *jsonTheme) Font(style fyne.TextStyle) fyne.Resource {
  126. if val, ok := t.data.Fonts[styleString(style)]; ok {
  127. r := val.resource()
  128. if r != nil {
  129. return r
  130. }
  131. }
  132. return t.fallback.Font(style)
  133. }
  134. func (t *jsonTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
  135. if val, ok := t.data.Icons[string(name)]; ok {
  136. r := val.resource()
  137. if r != nil {
  138. return r
  139. }
  140. }
  141. return t.fallback.Icon(name)
  142. }
  143. func (t *jsonTheme) Size(name fyne.ThemeSizeName) float32 {
  144. if val, ok := t.data.Sizes[string(name)]; ok {
  145. return val
  146. }
  147. return t.fallback.Size(name)
  148. }
  149. func styleString(s fyne.TextStyle) string {
  150. if s.Bold {
  151. if s.Italic {
  152. return "boldItalic"
  153. }
  154. return "bold"
  155. }
  156. if s.Monospace {
  157. return "monospace"
  158. }
  159. return "regular"
  160. }