diff --git a/v3/examples/theme/THEME.md b/v3/examples/theme/README.md similarity index 100% rename from v3/examples/theme/THEME.md rename to v3/examples/theme/README.md diff --git a/v3/examples/theme/WindowService.go b/v3/examples/theme/WindowService.go index 2f961e666..9f556f524 100644 --- a/v3/examples/theme/WindowService.go +++ b/v3/examples/theme/WindowService.go @@ -1,6 +1,8 @@ package main import ( + "context" + "github.com/wailsapp/wails/v3/pkg/application" ) @@ -16,13 +18,12 @@ func (s *WindowService) GetAppTheme() string { return s.app.GetTheme() } -func (s *WindowService) SetWinTheme(theme string) { +func (s *WindowService) SetWinTheme(ctx context.Context, theme string) { win := s.app.Window.Current() win.SetTheme((application.WinTheme(theme))) } -func (s *WindowService) GetWinTheme() string { - win := s.app.Window.Current() - theme := win.GetTheme() - return theme +func (s *WindowService) GetWinTheme(ctx context.Context) string { + win := ctx.Value(application.WindowKey).(application.Window) + return win.GetTheme() } diff --git a/v3/examples/theme/assets/app.js b/v3/examples/theme/assets/app.js index 396591c94..cfd23c6d5 100644 --- a/v3/examples/theme/assets/app.js +++ b/v3/examples/theme/assets/app.js @@ -1,5 +1,5 @@ import { Call, CancellablePromise, Create} from "/wails/runtime.js"; -import { Events } from "/wails/runtime.js"; +import { Events, Window} from "/wails/runtime.js"; const resultsApp = document.getElementById("app-theme"); const resultsWin = document.getElementById("win-theme"); @@ -38,9 +38,9 @@ document.getElementById("win-theme-light").addEventListener("click", () => setWi document.getElementById("win-theme-dark").addEventListener("click", () => setWinTheme("dark")); // Go Event Listeners -Events.On("applicationThemeChanged", async (ev) => { - console.log("[JS] theme changed event", ev.data.theme); - resultsApp.innerText = ev.data.theme; +Events.On("common:ApplicationThemeChanged", async (ev) => { + const appTheme = await callBinding("main.WindowService.GetAppTheme"); + resultsApp.innerText = appTheme; }); Events.On("common:ThemeChanged", async (ev) => { diff --git a/v3/examples/theme/main.go b/v3/examples/theme/main.go index 16de529dc..9abfe94d8 100644 --- a/v3/examples/theme/main.go +++ b/v3/examples/theme/main.go @@ -16,6 +16,8 @@ func main() { app := application.New(application.Options{ Name: "customEventProcessor Demo", Description: "A demo of the customEventProcessor API", + // We Start With Dark Theme + Theme: application.AppDark, Assets: application.AssetOptions{ Handler: application.BundledAssetFileServer(assets), }, @@ -28,25 +30,35 @@ func main() { }, }) - // Listen for the theme‑change event and log the payload - // app.Event.On("applicationThemeChanged", func(ev *application.CustomEvent) { - // fmt.Printf("[Go] applicationThemeChanged received, data = %v\n", ev.Data) - // }) - windowService.app = app app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Window 1", Name: "Window 1", + // Both Mac and Windows will follow light theme + Mac: application.MacWindow{ + Appearance: "NSAppearanceNameAqua", + }, + Windows: application.WindowsWindow{ + Theme: "light", + }, }) app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Window 2", Name: "Window 2", + // Both Mac and Widnows will follow Application Theme }) app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Window 3", Name: "Window 3", + // Both Mac and Widnows will follow Dark Theme + Mac: application.MacWindow{ + Appearance: "NSAppearanceNameDarkAqua", + }, + Windows: application.WindowsWindow{ + Theme: "dark", + }, }) err := app.Run() diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 1445c2d42..6eb51fba3 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -183,6 +183,12 @@ func New(appOptions Options) *App { } } + // Set the application Theme + result.theme = AppSystemDefault + if appOptions.Theme != "" { + result.theme = appOptions.Theme + } + return result } @@ -647,12 +653,6 @@ func (a *App) Run() error { a.impl.setIcon(a.options.Icon) } - // Set the application Theme - a.theme = AppSystemDefault - if a.options.Theme != "" { - a.theme = a.options.Theme - } - return a.impl.run() } diff --git a/v3/pkg/application/theme_application.go b/v3/pkg/application/theme_application.go index a16f407cb..0cad750bc 100644 --- a/v3/pkg/application/theme_application.go +++ b/v3/pkg/application/theme_application.go @@ -7,9 +7,9 @@ const ( // AppSystemDefault follows the system theme (light or dark). AppSystemDefault AppTheme = "system" // AppDark forces the application to use a dark theme. - AppDark AppTheme = "dark" + AppDark AppTheme = "dark" // AppLight forces the application to use a light theme. - AppLight AppTheme = "light" + AppLight AppTheme = "light" ) // String returns the string representation of the application theme. @@ -48,7 +48,5 @@ func (a *App) SetTheme(theme AppTheme) { } // Notify listeners of the theme change - a.Event.Emit("applicationThemeChanged", map[string]any{ - "theme": a.theme.String(), - }) + a.Event.Emit("common:ApplicationThemeChanged") } diff --git a/v3/pkg/application/theme_webview_window_darwin.go b/v3/pkg/application/theme_webview_window_darwin.go index a4a9bebbd..dd2e95492 100644 --- a/v3/pkg/application/theme_webview_window_darwin.go +++ b/v3/pkg/application/theme_webview_window_darwin.go @@ -98,7 +98,7 @@ func (w *macosWebviewWindow) getTheme() WinTheme { explicitAppearance := w.getExplicitAppearanceName() - if !explicitAppearance { + if explicitAppearance == "" { return WinThemeSystem } diff --git a/v3/pkg/application/theme_webview_window_windows.go b/v3/pkg/application/theme_webview_window_windows.go index 30a41a99a..b9b75877e 100644 --- a/v3/pkg/application/theme_webview_window_windows.go +++ b/v3/pkg/application/theme_webview_window_windows.go @@ -5,26 +5,26 @@ package application import "github.com/wailsapp/wails/v3/pkg/w32" // resolveWindowsEffectiveTheme determines the realized Theme for the window by resolving -// application-level and window-level theme settings. -func resolveWindowsEffectiveTheme(winTheme WinTheme, appTheme AppTheme) Theme { +// application-level and window-level theme settings. It also returns whether the window follows the application theme. +func resolveWindowsEffectiveTheme(winTheme WinTheme, appTheme AppTheme) (Theme, bool) { switch winTheme { case WinThemeDark: - return Dark + return Dark, false case WinThemeLight: - return Light + return Light, false case WinThemeSystem: - return SystemDefault + return SystemDefault, false default: // For WinThemeApplication and/or Unset values we default to following switch appTheme { case AppDark: - return Dark + return Dark, true case AppLight: - return Light + return Light, true case AppSystemDefault: - return SystemDefault + return SystemDefault, true default: - return SystemDefault + return SystemDefault, true } } } diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index d5dae8100..1f1ab43e8 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -1426,13 +1426,20 @@ func (w *macosWebviewWindow) run() { C.windowSetHideToolbarSeparator(w.nsWindow, C.bool(titleBarOptions.HideToolbarSeparator)) } - // if macOptions.Appearance != "" { - // C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance))) - // } + // Does the Window follow Application Theme w.parent.followApplicationTheme = true if macOptions.Appearance != "" { + // Explicit Appearance has been provided w.parent.followApplicationTheme = false w.setAppearanceByName(macOptions.Appearance) + } else { + // If we do follow Application Resolve the Window to follow Application Theme + switch globalApplication.theme { + case AppDark: + w.setAppearanceByName(NSAppearanceNameDarkAqua) + case AppLight: + w.setAppearanceByName(NSAppearanceNameAqua) + } } // Only apply invisible title bar when the native drag area is hidden diff --git a/v3/pkg/application/webview_window_options_test.go b/v3/pkg/application/webview_window_options_test.go index d3da97f04..b962dc1d8 100644 --- a/v3/pkg/application/webview_window_options_test.go +++ b/v3/pkg/application/webview_window_options_test.go @@ -297,8 +297,8 @@ func TestWindowsWindow_Defaults(t *testing.T) { if opts.DisableIcon != false { t.Error("DisableIcon should default to false") } - if opts.Theme != WinThemeApplication { - t.Error("Theme should default to SystemDefault") + if opts.Theme != "" { + t.Error("Theme should default to empty string") } } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index aafea1680..0b434a37b 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -508,21 +508,27 @@ func (w *windowsWebviewWindow) run() { } // Process the theme - // Resolve the Complicated App State to a simple theme - w.parent.followApplicationTheme = false - if options.Windows.Theme == WinThemeApplication || options.Windows.Theme == "" { - w.parent.followApplicationTheme = true - } - // System, Dark, Light - Resolved Theme to Apply - w.theme = resolveWindowsEffectiveTheme(options.Windows.Theme, globalApplication.theme) - w.syncTheme() + theme, followAppTheme := resolveWindowsEffectiveTheme(options.Windows.Theme, globalApplication.theme) + w.theme = theme + w.parent.followApplicationTheme = followAppTheme + + switch w.theme { + case SystemDefault: + w.updateTheme(w32.IsCurrentlyDarkMode()) + case Dark: + w32.AllowDarkModeForWindow(w.hwnd, true) + w.updateTheme(true) + case Light: + w.updateTheme(false) + } // Always listen to OS theme changes but only update the theme if we are following the application theme w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*ApplicationEvent) { - if w.theme != SystemDefault { + if !w.parent.followApplicationTheme || w.theme != SystemDefault { return } + InvokeAsync(func() { w.updateTheme(w32.IsCurrentlyDarkMode()) })