// Package theme defines how a Fyne app should look when rendered. package theme // import "fyne.io/fyne/v2/theme" import ( "bytes" "image/color" "os" "path/filepath" "strings" "fyne.io/fyne/v2" internalApp "fyne.io/fyne/v2/internal/app" "fyne.io/fyne/v2/internal/cache" internaltheme "fyne.io/fyne/v2/internal/theme" ) // Keep in mind to add new constants to the tests at test/theme.go. const ( // VariantDark is the version of a theme that satisfies a user preference for a dark look. // // Since: 2.0 VariantDark = internaltheme.VariantDark // VariantLight is the version of a theme that satisfies a user preference for a light look. // // Since: 2.0 VariantLight = internaltheme.VariantLight ) var defaultTheme, systemTheme fyne.Theme // DarkTheme defines the built-in dark theme colors and sizes. // // Deprecated: This method ignores user preference and should not be used, it will be removed in v3.0. // If developers want to ignore user preference for theme variant they can set a custom theme. func DarkTheme() fyne.Theme { theme := &builtinTheme{variant: VariantDark} theme.initFonts() return theme } // DefaultTheme returns a built-in theme that can adapt to the user preference of light or dark colors. // // Since: 2.0 func DefaultTheme() fyne.Theme { if defaultTheme == nil { defaultTheme = setupDefaultTheme() } // check system too if systemTheme != nil { return systemTheme } return defaultTheme } // LightTheme defines the built-in light theme colors and sizes. // // Deprecated: This method ignores user preference and should not be used, it will be removed in v3.0. // If developers want to ignore user preference for theme variant they can set a custom theme. func LightTheme() fyne.Theme { theme := &builtinTheme{variant: VariantLight} theme.initFonts() return theme } type builtinTheme struct { variant fyne.ThemeVariant regular, bold, italic, boldItalic, monospace, symbol fyne.Resource } func (t *builtinTheme) initFonts() { t.regular = regular t.bold = bold t.italic = italic t.boldItalic = bolditalic t.monospace = monospace t.symbol = symbol font := os.Getenv("FYNE_FONT") if font != "" { t.regular = loadCustomFont(font, "Regular", regular) if t.regular == regular { // failed to load t.bold = loadCustomFont(font, "Bold", bold) t.italic = loadCustomFont(font, "Italic", italic) t.boldItalic = loadCustomFont(font, "BoldItalic", bolditalic) } else { // first custom font loaded, fall back to that t.bold = loadCustomFont(font, "Bold", t.regular) t.italic = loadCustomFont(font, "Italic", t.regular) t.boldItalic = loadCustomFont(font, "BoldItalic", t.regular) } } font = os.Getenv("FYNE_FONT_MONOSPACE") if font != "" { t.monospace = loadCustomFont(font, "Regular", monospace) } font = os.Getenv("FYNE_FONT_SYMBOL") if font != "" { t.symbol = loadCustomFont(font, "Regular", symbol) } } func (t *builtinTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { if t.variant != internaltheme.VariantNameUserPreference { v = t.variant } primary := fyne.CurrentApp().Settings().PrimaryColor() if n == ColorNamePrimary || n == ColorNameHyperlink { return internaltheme.PrimaryColorNamed(primary) } else if n == ColorNameForegroundOnPrimary { return internaltheme.ForegroundOnPrimaryColorNamed(primary) } else if n == ColorNameFocus { return focusColorNamed(primary) } else if n == ColorNameSelection { return selectionColorNamed(primary) } if v == VariantLight { return lightPaletteColorNamed(n) } return darkPaletteColorNamed(n) } func (t *builtinTheme) Font(style fyne.TextStyle) fyne.Resource { if style.Monospace { return t.monospace } if style.Bold { if style.Italic { return t.boldItalic } return t.bold } if style.Italic { return t.italic } if style.Symbol { return t.symbol } return t.regular } // Current returns the theme that is currently used for the running application. // It looks up based on user preferences and application configuration. // // Since: 2.5 func Current() fyne.Theme { app := fyne.CurrentApp() if app == nil { return DarkTheme() } currentTheme := app.Settings().Theme() if currentTheme == nil { return DarkTheme() } return internaltheme.CurrentlyRenderingWithFallback(currentTheme) } // CurrentForWidget returns the theme that is currently used for the specified widget. // It looks for widget overrides and falls back to the application's current theme. // // Since: 2.5 func CurrentForWidget(w fyne.CanvasObject) fyne.Theme { if custom := cache.WidgetTheme(w); custom != nil { return custom } return Current() } func currentVariant() fyne.ThemeVariant { if std, ok := Current().(*builtinTheme); ok { if std.variant != internaltheme.VariantNameUserPreference { return std.variant // override if using the old LightTheme() or DarkTheme() constructor } } return fyne.CurrentApp().Settings().ThemeVariant() } func darkPaletteColorNamed(name fyne.ThemeColorName) color.Color { switch name { case ColorNameBackground: return colorDarkBackground case ColorNameButton: return colorDarkButton case ColorNameDisabled: return colorDarkDisabled case ColorNameDisabledButton: return colorDarkDisabledButton case ColorNameError: return colorDarkError case ColorNameForeground: return colorDarkForeground case ColorNameForegroundOnError: return colorDarkForegroundOnError case ColorNameForegroundOnSuccess: return colorDarkForegroundOnSuccess case ColorNameForegroundOnWarning: return colorDarkForegroundOnWarning case ColorNameHover: return colorDarkHover case ColorNameHeaderBackground: return colorDarkHeaderBackground case ColorNameInputBackground: return colorDarkInputBackground case ColorNameInputBorder: return colorDarkInputBorder case ColorNameMenuBackground: return colorDarkMenuBackground case ColorNameOverlayBackground: return colorDarkOverlayBackground case ColorNamePlaceHolder: return colorDarkPlaceholder case ColorNamePressed: return colorDarkPressed case ColorNameScrollBar: return colorDarkScrollBar case ColorNameScrollBarBackground: return colorDarkScrollBarBackground case ColorNameSeparator: return colorDarkSeparator case ColorNameShadow: return colorDarkShadow case ColorNameSuccess: return colorDarkSuccess case ColorNameWarning: return colorDarkWarning } return color.Transparent } func focusColorNamed(name string) color.NRGBA { switch name { case ColorRed: return colorLightFocusRed case ColorOrange: return colorLightFocusOrange case ColorYellow: return colorLightFocusYellow case ColorGreen: return colorLightFocusGreen case ColorPurple: return colorLightFocusPurple case ColorBrown: return colorLightFocusBrown case ColorGray: return colorLightFocusGray } // We return the value for ColorBlue for every other value. // There is no need to have it in the switch above. return colorLightFocusBlue } func lightPaletteColorNamed(name fyne.ThemeColorName) color.Color { switch name { case ColorNameBackground: return colorLightBackground case ColorNameButton: return colorLightButton case ColorNameDisabled: return colorLightDisabled case ColorNameDisabledButton: return colorLightDisabledButton case ColorNameError: return colorLightError case ColorNameForeground: return colorLightForeground case ColorNameForegroundOnError: return colorLightForegroundOnError case ColorNameForegroundOnSuccess: return colorLightForegroundOnSuccess case ColorNameForegroundOnWarning: return colorLightForegroundOnWarning case ColorNameHover: return colorLightHover case ColorNameHeaderBackground: return colorLightHeaderBackground case ColorNameInputBackground: return colorLightInputBackground case ColorNameInputBorder: return colorLightInputBorder case ColorNameMenuBackground: return colorLightMenuBackground case ColorNameOverlayBackground: return colorLightOverlayBackground case ColorNamePlaceHolder: return colorLightPlaceholder case ColorNamePressed: return colorLightPressed case ColorNameScrollBar: return colorLightScrollBar case ColorNameScrollBarBackground: return colorLightScrollBarBackground case ColorNameSeparator: return colorLightSeparator case ColorNameShadow: return colorLightShadow case ColorNameSuccess: return colorLightSuccess case ColorNameWarning: return colorLightWarning } return color.Transparent } func loadCustomFont(env, variant string, fallback fyne.Resource) fyne.Resource { variantPath := strings.Replace(env, "Regular", variant, -1) res, err := fyne.LoadResourceFromPath(variantPath) if err != nil { fyne.LogError("Error loading specified font", err) return fallback } return res } func selectionColorNamed(name string) color.NRGBA { switch name { case ColorRed: return colorLightSelectionRed case ColorOrange: return colorLightSelectionOrange case ColorYellow: return colorLightSelectionYellow case ColorGreen: return colorLightSelectionGreen case ColorPurple: return colorLightSelectionPurple case ColorBrown: return colorLightSelectionBrown case ColorGray: return colorLightSelectionGray } // We return the value for ColorBlue for every other value. // There is no need to have it in the switch above. return colorLightSelectionBlue } func setupDefaultTheme() fyne.Theme { theme := &builtinTheme{variant: internaltheme.VariantNameUserPreference} theme.initFonts() systemTheme = setupSystemTheme(theme) return theme } func setupSystemTheme(fallback fyne.Theme) fyne.Theme { root := internalApp.RootConfigDir() path := filepath.Join(root, "theme.json") data, err := fyne.LoadResourceFromPath(path) if err != nil { if !os.IsNotExist(err) { fyne.LogError("Failed to load user theme file: "+path, err) } return nil } if data != nil && data.Content() != nil { th, err := fromJSONWithFallback(bytes.NewReader(data.Content()), fallback) if err == nil { return th } fyne.LogError("Failed to parse user theme file: "+path, err) } return nil }