settings.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package app
  2. import (
  3. "bytes"
  4. "os"
  5. "path/filepath"
  6. "sync"
  7. "fyne.io/fyne/v2"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. var noAnimations bool // set to true at compile time if no_animations tag is passed
  11. // SettingsSchema is used for loading and storing global settings
  12. type SettingsSchema struct {
  13. // these items are used for global settings load
  14. ThemeName string `json:"theme"`
  15. Scale float32 `json:"scale"`
  16. PrimaryColor string `json:"primary_color"`
  17. CloudName string `json:"cloud_name"`
  18. CloudConfig string `json:"cloud_config"`
  19. DisableAnimations bool `json:"no_animations"`
  20. }
  21. // StoragePath returns the location of the settings storage
  22. func (sc *SettingsSchema) StoragePath() string {
  23. return filepath.Join(rootConfigDir(), "settings.json")
  24. }
  25. // Declare conformity with Settings interface
  26. var _ fyne.Settings = (*settings)(nil)
  27. type settings struct {
  28. propertyLock sync.RWMutex
  29. theme fyne.Theme
  30. themeSpecified bool
  31. variant fyne.ThemeVariant
  32. changeListeners sync.Map // map[chan fyne.Settings]bool
  33. watcher interface{} // normally *fsnotify.Watcher or nil - avoid import in this file
  34. schema SettingsSchema
  35. }
  36. func (s *settings) BuildType() fyne.BuildType {
  37. return buildMode
  38. }
  39. func (s *settings) PrimaryColor() string {
  40. s.propertyLock.RLock()
  41. defer s.propertyLock.RUnlock()
  42. return s.schema.PrimaryColor
  43. }
  44. // OverrideTheme allows the settings app to temporarily preview different theme details.
  45. // Please make sure that you remember the original settings and call this again to revert the change.
  46. func (s *settings) OverrideTheme(theme fyne.Theme, name string) {
  47. s.propertyLock.Lock()
  48. defer s.propertyLock.Unlock()
  49. s.schema.PrimaryColor = name
  50. s.theme = theme
  51. }
  52. func (s *settings) Theme() fyne.Theme {
  53. s.propertyLock.RLock()
  54. defer s.propertyLock.RUnlock()
  55. return s.theme
  56. }
  57. func (s *settings) SetTheme(theme fyne.Theme) {
  58. s.themeSpecified = true
  59. s.applyTheme(theme, s.variant)
  60. }
  61. func (s *settings) ShowAnimations() bool {
  62. return !s.schema.DisableAnimations && !noAnimations
  63. }
  64. func (s *settings) ThemeVariant() fyne.ThemeVariant {
  65. return s.variant
  66. }
  67. func (s *settings) applyTheme(theme fyne.Theme, variant fyne.ThemeVariant) {
  68. s.propertyLock.Lock()
  69. defer s.propertyLock.Unlock()
  70. s.variant = variant
  71. s.theme = theme
  72. s.apply()
  73. }
  74. func (s *settings) Scale() float32 {
  75. s.propertyLock.RLock()
  76. defer s.propertyLock.RUnlock()
  77. if s.schema.Scale < 0.0 {
  78. return 1.0 // catching any really old data still using the `-1` value for "auto" scale
  79. }
  80. return s.schema.Scale
  81. }
  82. func (s *settings) AddChangeListener(listener chan fyne.Settings) {
  83. s.changeListeners.Store(listener, true) // the boolean is just a dummy value here.
  84. }
  85. func (s *settings) apply() {
  86. s.changeListeners.Range(func(key, _ interface{}) bool {
  87. listener := key.(chan fyne.Settings)
  88. select {
  89. case listener <- s:
  90. default:
  91. l := listener
  92. go func() { l <- s }()
  93. }
  94. return true
  95. })
  96. }
  97. func (s *settings) fileChanged() {
  98. s.load()
  99. s.apply()
  100. }
  101. func (s *settings) loadSystemTheme() fyne.Theme {
  102. path := filepath.Join(rootConfigDir(), "theme.json")
  103. data, err := fyne.LoadResourceFromPath(path)
  104. if err != nil {
  105. if !os.IsNotExist(err) {
  106. fyne.LogError("Failed to load user theme file: "+path, err)
  107. }
  108. return theme.DefaultTheme()
  109. }
  110. if data != nil && data.Content() != nil {
  111. th, err := theme.FromJSONReader(bytes.NewReader(data.Content()))
  112. if err == nil {
  113. return th
  114. }
  115. fyne.LogError("Failed to parse user theme file: "+path, err)
  116. }
  117. return theme.DefaultTheme()
  118. }
  119. func (s *settings) setupTheme() {
  120. name := s.schema.ThemeName
  121. if env := os.Getenv("FYNE_THEME"); env != "" {
  122. name = env
  123. }
  124. variant := defaultVariant()
  125. effectiveTheme := s.theme
  126. if !s.themeSpecified {
  127. effectiveTheme = s.loadSystemTheme()
  128. }
  129. switch name {
  130. case "light":
  131. variant = theme.VariantLight
  132. case "dark":
  133. variant = theme.VariantDark
  134. }
  135. s.applyTheme(effectiveTheme, variant)
  136. }
  137. func loadSettings() *settings {
  138. s := &settings{}
  139. s.load()
  140. return s
  141. }