Match window border color to app theme rather than OS theme (#958)
* MacOS: match window chrome to app light/dark mode when app setting is different from OS * match window border to app theme on Windows
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
fynetooltip "github.com/dweymouth/fyne-tooltip"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
@@ -655,3 +657,22 @@ func (c *Controller) GetSongRadioTracks(sourceTrack *mediaprovider.Track) ([]*me
|
||||
tracks = append(tracks, filteredTracks...)
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func SetWindowThemeMode(win fyne.Window, mode myTheme.AppearanceMode) {
|
||||
arg := 0
|
||||
switch fyne.CurrentApp().Settings().Theme().(*myTheme.MyTheme).AppearanceMode() {
|
||||
case myTheme.AppearanceDark:
|
||||
arg = 1
|
||||
case myTheme.AppearanceLight:
|
||||
arg = 2
|
||||
}
|
||||
win.(driver.NativeWindow).RunNative(func(ctx any) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// ensure dark mode setting is applied to window controls in title bar on Mac
|
||||
setWindowDarkTheme(ctx.(driver.MacWindowContext).NSWindow, arg)
|
||||
case "windows":
|
||||
setWindowDarkTheme(ctx.(driver.WindowsWindowContext).HWND, arg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/dweymouth/supersonic/backend/player"
|
||||
"github.com/dweymouth/supersonic/backend/player/mpv"
|
||||
"github.com/dweymouth/supersonic/ui/shortcuts"
|
||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
"github.com/dweymouth/supersonic/ui/visualizations"
|
||||
)
|
||||
@@ -68,6 +69,7 @@ func (c *Controller) ShowPeakMeter() {
|
||||
c.peakMeter.Refresh()
|
||||
}
|
||||
c.peakMeterWin.Show()
|
||||
SetWindowThemeMode(c.peakMeterWin, fyne.CurrentApp().Settings().Theme().(*myTheme.MyTheme).AppearanceMode())
|
||||
}
|
||||
|
||||
func (c *Controller) stopVisualizationAnim() {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build darwin
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
void setWindowDarkMode(void* windowPtr, int mode) {
|
||||
if (windowPtr == NULL) return;
|
||||
|
||||
NSWindow* window = (__bridge NSWindow*)windowPtr;
|
||||
|
||||
if (mode == 1) {
|
||||
// Forces the window chrome and borders into Dark Mode
|
||||
window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
|
||||
} else if (mode == 2) {
|
||||
// Forces the window chrome and borders into Light Mode
|
||||
window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight];
|
||||
} else {
|
||||
// Reverts the window back to standard/system default behavior
|
||||
window.appearance = nil; // Fallback to inherited system style
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build darwin
|
||||
|
||||
package controller
|
||||
|
||||
/*
|
||||
void setWindowDarkMode(void* windowPtr, int useDarkMode);
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// setWindowDarkTheme takes an unsafe.Pointer referencing the macOS NSWindow
|
||||
// and explicitly applies or clears the dark mode appearance.
|
||||
// Mode arg: 0 = auto, 1 = dark, 2 = light
|
||||
func setWindowDarkTheme(nsWindowPtr uintptr, mode int) {
|
||||
C.setWindowDarkMode(unsafe.Pointer(nsWindowPtr), C.int(mode))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !darwin && !windows
|
||||
|
||||
package controller
|
||||
|
||||
func setWindowDarkTheme(ptr uintptr, mode int) {}
|
||||
@@ -0,0 +1,57 @@
|
||||
//go:build windows
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
dwm = syscall.NewLazyDLL("dwmapi.dll")
|
||||
setAtt = dwm.NewProc("DwmSetWindowAttribute")
|
||||
)
|
||||
|
||||
func setWindowDarkTheme(hwnd uintptr, mode int) {
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
arg := 0
|
||||
switch mode {
|
||||
case 0: /*auto*/
|
||||
if isDark() {
|
||||
arg = 1
|
||||
}
|
||||
case 1: /*dark*/
|
||||
arg = 1
|
||||
}
|
||||
|
||||
// copied from Fyne internals
|
||||
ret, _, err := setAtt.Call(uintptr(unsafe.Pointer(hwnd)), // window handle
|
||||
20, // DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
uintptr(unsafe.Pointer(&arg)), // on or off
|
||||
4) // sizeof(bool for windows))
|
||||
if ret != 0 && ret != 0x80070057 { // err is always non-nil, we check return value (except erroneous code)
|
||||
fyne.LogError("Failed to set dark mode", err)
|
||||
}
|
||||
}
|
||||
|
||||
// copied from Fyne internals
|
||||
func isDark() bool {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
if err != nil { // older version of Windows will not have this key
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
useLight, _, err := k.GetIntegerValue("AppsUseLightTheme")
|
||||
if err != nil { // older version of Windows will not have this value
|
||||
return false
|
||||
}
|
||||
|
||||
return useLight == 0
|
||||
}
|
||||
@@ -557,6 +557,9 @@ func (m *MainWindow) addShortcuts() {
|
||||
func (m *MainWindow) showSettingsDialog() {
|
||||
m.Controller.ShowSettingsDialog(func() {
|
||||
fyne.CurrentApp().Settings().SetTheme(m.theme)
|
||||
for _, w := range fyne.CurrentApp().Driver().AllWindows() {
|
||||
controller.SetWindowThemeMode(w, m.theme.AppearanceMode())
|
||||
}
|
||||
}, m.theme.ListThemeFiles())
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,16 @@ func (m *MyTheme) ReloadThemeFile() {
|
||||
m.loadedThemeFile = nil
|
||||
}
|
||||
|
||||
func (m *MyTheme) AppearanceMode() AppearanceMode {
|
||||
v := DefaultAppearance // default if config has invalid or missing setting
|
||||
if slices.Contains(
|
||||
[]string{string(AppearanceLight), string(AppearanceDark), string(AppearanceAuto)},
|
||||
m.config.Appearance) {
|
||||
v = AppearanceMode(m.config.Appearance)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *MyTheme) Color(name fyne.ThemeColorName, defVariant fyne.ThemeVariant) color.Color {
|
||||
// load theme file if necessary
|
||||
if m.loadedThemeFile == nil || m.config.ThemeFile != m.loadedThemeFilename {
|
||||
|
||||
Reference in New Issue
Block a user