refactor(theme): fix window run() for both macos and windows. refactor events for app.SetTheme() and win.SetTheme()

This commit is contained in:
devmadhava 2026-03-10 14:48:40 +05:30
commit 2c123b8530
11 changed files with 73 additions and 49 deletions

View file

@ -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()
}

View file

@ -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) => {

View file

@ -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 themechange 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()

View file

@ -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()
}

View file

@ -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")
}

View file

@ -98,7 +98,7 @@ func (w *macosWebviewWindow) getTheme() WinTheme {
explicitAppearance := w.getExplicitAppearanceName()
if !explicitAppearance {
if explicitAppearance == "" {
return WinThemeSystem
}

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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")
}
}

View file

@ -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())
})