mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 20:03:01 +08:00
[Feature/1149] Dark mode (#1281)
* Add Windows version helper * Initial theme support * Support custom themes * Update docs * Honour HighContrast theme. Remove import "C". Refactor * Small refactor * Support inactive theme * Update Docs
This commit is contained in:
parent
2e21f25182
commit
48254b73e5
@ -27,7 +27,7 @@ require (
|
||||
github.com/leaanthony/idgen v1.0.0
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/leaanthony/typescriptify-golang-structs v0.1.7
|
||||
github.com/leaanthony/winc v0.0.0-20220208061147-37b059b9dc3b
|
||||
github.com/leaanthony/winc v0.0.0-20220323084916-ea5df694ec1f
|
||||
github.com/leaanthony/winicon v1.0.0
|
||||
github.com/matryer/is v1.4.0
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
|
@ -130,6 +130,8 @@ github.com/leaanthony/typescriptify-golang-structs v0.1.7 h1:yoznzWzyxkO/iWdlpq+
|
||||
github.com/leaanthony/typescriptify-golang-structs v0.1.7/go.mod h1:cWtOkiVhMF77e6phAXUcfNwYmMwCJ67Sij24lfvi9Js=
|
||||
github.com/leaanthony/winc v0.0.0-20220208061147-37b059b9dc3b h1:cJ+VfVwX3GkRGSy0SiOyZ7FjSGMPAY/rS/wJzilo23I=
|
||||
github.com/leaanthony/winc v0.0.0-20220208061147-37b059b9dc3b/go.mod h1:OPfk8SNMAKRcSv8Vw1QL0yupmwcRtJyXZUgtMoaHUGc=
|
||||
github.com/leaanthony/winc v0.0.0-20220323084916-ea5df694ec1f h1:RM0TNQXGTt06ZrSysdo+r9E9fk1ObACFBOww+W1zOiU=
|
||||
github.com/leaanthony/winc v0.0.0-20220323084916-ea5df694ec1f/go.mod h1:OPfk8SNMAKRcSv8Vw1QL0yupmwcRtJyXZUgtMoaHUGc=
|
||||
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
|
||||
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@ -45,10 +46,16 @@ type Frontend struct {
|
||||
servingFromDisk bool
|
||||
|
||||
hasStarted bool
|
||||
|
||||
// Windows build number
|
||||
versionInfo *operatingsystem.WindowsVersionInfo
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
|
||||
// Get Windows build number
|
||||
versionInfo, _ := operatingsystem.GetWindowsVersionInfo()
|
||||
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
@ -56,6 +63,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
startURL: "http://wails.localhost/",
|
||||
versionInfo: versionInfo,
|
||||
}
|
||||
|
||||
bindingsJSON, err := appBindings.ToJSON()
|
||||
@ -99,7 +107,7 @@ func (f *Frontend) Run(ctx context.Context) error {
|
||||
|
||||
f.ctx = context.WithValue(ctx, "frontend", f)
|
||||
|
||||
mainWindow := NewWindow(nil, f.frontendOptions)
|
||||
mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo)
|
||||
f.mainWindow = mainWindow
|
||||
|
||||
var _debug = ctx.Value("debug")
|
||||
|
@ -48,8 +48,10 @@ func processMenu(window *Window, menu *menu.Menu) {
|
||||
mainMenu := window.NewMenu()
|
||||
for _, menuItem := range menu.Items {
|
||||
submenu := mainMenu.AddSubMenu(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
if menuItem.SubMenu != nil {
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
mainMenu.Show()
|
||||
|
62
v2/internal/frontend/desktop/windows/theme.go
Normal file
62
v2/internal/frontend/desktop/windows/theme.go
Normal file
@ -0,0 +1,62 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
)
|
||||
|
||||
func (w *Window) updateTheme() {
|
||||
|
||||
if win32.IsCurrentlyHighContrastMode() {
|
||||
return
|
||||
}
|
||||
|
||||
if !win32.SupportsThemes() {
|
||||
return
|
||||
}
|
||||
// Only process if there's a theme change
|
||||
isDarkMode := win32.IsCurrentlyDarkMode()
|
||||
w.isDarkMode = isDarkMode
|
||||
|
||||
// Default use system theme
|
||||
winOptions := w.frontendOptions.Windows
|
||||
var customTheme *windows.ThemeSettings
|
||||
if winOptions != nil {
|
||||
customTheme = winOptions.CustomTheme
|
||||
if winOptions.Theme == windows.Dark {
|
||||
isDarkMode = true
|
||||
}
|
||||
if winOptions.Theme == windows.Light {
|
||||
isDarkMode = false
|
||||
}
|
||||
}
|
||||
|
||||
win32.SetTheme(w.Handle(), isDarkMode)
|
||||
|
||||
// Custom theme
|
||||
if win32.SupportsCustomThemes() && customTheme != nil {
|
||||
if w.isActive {
|
||||
if isDarkMode {
|
||||
println("1")
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorder)
|
||||
} else {
|
||||
println("2")
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorder)
|
||||
}
|
||||
} else {
|
||||
if isDarkMode {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorderInactive)
|
||||
} else {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorderInactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
v2/internal/frontend/desktop/windows/win32/consts.go
Normal file
27
v2/internal/frontend/desktop/windows/win32/consts.go
Normal file
@ -0,0 +1,27 @@
|
||||
package win32
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type HRESULT int32
|
||||
type HANDLE uintptr
|
||||
|
||||
var (
|
||||
moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
|
||||
procGetWindowLong = moduser32.NewProc("GetWindowLongW")
|
||||
)
|
||||
var (
|
||||
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
||||
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
|
||||
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo()
|
||||
|
||||
func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
|
||||
return windowsVersion.Major >= major &&
|
||||
windowsVersion.Minor >= minor &&
|
||||
windowsVersion.Build >= buildNumber
|
||||
}
|
92
v2/internal/frontend/desktop/windows/win32/theme.go
Normal file
92
v2/internal/frontend/desktop/windows/win32/theme.go
Normal file
@ -0,0 +1,92 @@
|
||||
package win32
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type DWMWINDOWATTRIBUTE int32
|
||||
|
||||
const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
|
||||
const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
|
||||
const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
|
||||
const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
|
||||
const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
|
||||
|
||||
const SPI_GETHIGHCONTRAST = 0x0042
|
||||
const HCF_HIGHCONTRASTON = 0x00000001
|
||||
|
||||
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
|
||||
ret, _, err := procDwmSetWindowAttribute.Call(
|
||||
hwnd,
|
||||
uintptr(dwAttribute),
|
||||
uintptr(pvAttribute),
|
||||
cbAttribute)
|
||||
if ret != 0 {
|
||||
_ = err
|
||||
// println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func SupportsThemes() bool {
|
||||
// We can't support Windows versions before 17763
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SupportsCustomThemes() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SetTheme(hwnd uintptr, useDarkMode bool) {
|
||||
if IsWindowsVersionAtLeast(10, 0, 17763) {
|
||||
attr := DwmwaUseImmersiveDarkModeBefore20h1
|
||||
if IsWindowsVersionAtLeast(10, 0, 18985) {
|
||||
attr = DwmwaUseImmersiveDarkMode
|
||||
}
|
||||
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&useDarkMode), unsafe.Sizeof(&useDarkMode))
|
||||
}
|
||||
}
|
||||
|
||||
func SetTitleBarColour(hwnd uintptr, titleBarColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
|
||||
}
|
||||
|
||||
func SetTitleTextColour(hwnd uintptr, titleTextColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
|
||||
}
|
||||
|
||||
func SetBorderColour(hwnd uintptr, titleBorderColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
|
||||
}
|
||||
|
||||
func IsCurrentlyDarkMode() bool {
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return AppsUseLightTheme == 0
|
||||
}
|
||||
|
||||
type highContrast struct {
|
||||
CbSize uint32
|
||||
DwFlags uint32
|
||||
LpszDefaultScheme *int16
|
||||
}
|
||||
|
||||
func IsCurrentlyHighContrastMode() bool {
|
||||
var result highContrast
|
||||
result.CbSize = uint32(unsafe.Sizeof(result))
|
||||
res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if res == 0 {
|
||||
_ = err
|
||||
return false
|
||||
}
|
||||
return result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
|
||||
}
|
56
v2/internal/frontend/desktop/windows/win32/window.go
Normal file
56
v2/internal/frontend/desktop/windows/win32/window.go
Normal file
@ -0,0 +1,56 @@
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
|
||||
GWL_STYLE = -16
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx
|
||||
type MARGINS struct {
|
||||
CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32
|
||||
}
|
||||
|
||||
func ExtendFrameIntoClientArea(hwnd uintptr) {
|
||||
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
|
||||
// Also shows the caption buttons if transparent ant translucent but they don't work.
|
||||
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
|
||||
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
|
||||
// are shown if transparent ant translucent.
|
||||
margins := &MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
|
||||
if err := dwmExtendFrameIntoClientArea(hwnd, margins); err != nil {
|
||||
log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func IsWindowMaximised(hwnd uintptr) bool {
|
||||
style := uint32(getWindowLong(hwnd, GWL_STYLE))
|
||||
return style&WS_MAXIMIZE != 0
|
||||
}
|
||||
|
||||
func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
|
||||
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(margins)))
|
||||
|
||||
if ret != 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWindowLong(hwnd uintptr, index int) int32 {
|
||||
ret, _, _ := procGetWindowLong.Call(
|
||||
hwnd,
|
||||
uintptr(index))
|
||||
|
||||
return int32(ret)
|
||||
}
|
@ -3,9 +3,8 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"syscall"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
"unsafe"
|
||||
|
||||
"github.com/leaanthony/winc"
|
||||
@ -20,15 +19,20 @@ type Window struct {
|
||||
applicationMenu *menu.Menu
|
||||
notifyParentWindowPositionChanged func() error
|
||||
minWidth, minHeight, maxWidth, maxHeight int
|
||||
versionInfo *operatingsystem.WindowsVersionInfo
|
||||
isDarkMode bool
|
||||
isActive bool
|
||||
}
|
||||
|
||||
func NewWindow(parent winc.Controller, appoptions *options.App) *Window {
|
||||
func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *operatingsystem.WindowsVersionInfo) *Window {
|
||||
result := &Window{
|
||||
frontendOptions: appoptions,
|
||||
minHeight: appoptions.MinHeight,
|
||||
minWidth: appoptions.MinWidth,
|
||||
maxHeight: appoptions.MaxHeight,
|
||||
maxWidth: appoptions.MaxWidth,
|
||||
versionInfo: versionInfo,
|
||||
isActive: true,
|
||||
}
|
||||
result.SetIsForm(true)
|
||||
|
||||
@ -46,7 +50,8 @@ func NewWindow(parent winc.Controller, appoptions *options.App) *Window {
|
||||
var dwStyle = w32.WS_OVERLAPPEDWINDOW
|
||||
|
||||
winc.RegClassOnlyOnce("wailsWindow")
|
||||
result.SetHandle(winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle)))
|
||||
handle := winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle))
|
||||
result.SetHandle(handle)
|
||||
winc.RegMsgHandler(result)
|
||||
result.SetParent(parent)
|
||||
|
||||
@ -69,6 +74,8 @@ func NewWindow(parent winc.Controller, appoptions *options.App) *Window {
|
||||
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
|
||||
}
|
||||
|
||||
result.updateTheme()
|
||||
|
||||
if appoptions.Windows != nil {
|
||||
if appoptions.Windows.WindowIsTranslucent {
|
||||
result.SetTranslucentBackground()
|
||||
@ -125,12 +132,26 @@ func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
|
||||
func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
|
||||
switch msg {
|
||||
case w32.WM_SETTINGCHANGE:
|
||||
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lparam)))
|
||||
if settingChanged == "ImmersiveColorSet" {
|
||||
w.updateTheme()
|
||||
}
|
||||
return 0
|
||||
case w32.WM_NCLBUTTONDOWN:
|
||||
w32.SetFocus(w.Handle())
|
||||
case w32.WM_MOVE, w32.WM_MOVING:
|
||||
if w.notifyParentWindowPositionChanged != nil {
|
||||
w.notifyParentWindowPositionChanged()
|
||||
}
|
||||
case w32.WM_ACTIVATE:
|
||||
if int(wparam) == w32.WA_INACTIVE {
|
||||
w.isActive = false
|
||||
w.updateTheme()
|
||||
} else {
|
||||
w.isActive = true
|
||||
w.updateTheme()
|
||||
}
|
||||
|
||||
// TODO move WM_DPICHANGED handling into winc
|
||||
case 0x02E0: //w32.WM_DPICHANGED
|
||||
@ -152,15 +173,7 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
// As a result we have hidden the titlebar but still have the default window frame styling.
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
|
||||
if winoptions := w.frontendOptions.Windows; winoptions == nil || !winoptions.DisableFramelessWindowDecorations {
|
||||
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
|
||||
// Also shows the caption buttons if transparent ant translucent but they don't work.
|
||||
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
|
||||
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
|
||||
// are shown if transparent ant translucent.
|
||||
margins := w32.MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
|
||||
if err := dwmExtendFrameIntoClientArea(w.Handle(), margins); err != nil {
|
||||
log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
|
||||
}
|
||||
win32.ExtendFrameIntoClientArea(w.Handle())
|
||||
}
|
||||
case w32.WM_NCCALCSIZE:
|
||||
// Disable the standard frame by allowing the client area to take the full
|
||||
@ -208,25 +221,6 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
style := uint32(w32.GetWindowLong(w.Handle(), w32.GWL_STYLE))
|
||||
return style&w32.WS_MAXIMIZE != 0
|
||||
}
|
||||
|
||||
// TODO this should be put into the winc if we are happy with this solution.
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("dwmapi.dll")
|
||||
|
||||
procDwmExtendFrameIntoClientArea = modkernel32.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
|
||||
func dwmExtendFrameIntoClientArea(hwnd w32.HWND, margins w32.MARGINS) error {
|
||||
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
|
||||
uintptr(hwnd),
|
||||
uintptr(unsafe.Pointer(&margins)))
|
||||
|
||||
if ret != 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
|
||||
return nil
|
||||
return win32.IsWindowMaximised(w.Handle())
|
||||
|
||||
}
|
||||
|
59
v2/internal/system/operatingsystem/version_windows.go
Normal file
59
v2/internal/system/operatingsystem/version_windows.go
Normal file
@ -0,0 +1,59 @@
|
||||
package operatingsystem
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type WindowsVersionInfo struct {
|
||||
Major int
|
||||
Minor int
|
||||
Build int
|
||||
DisplayVersion string
|
||||
}
|
||||
|
||||
func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
|
||||
return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber
|
||||
}
|
||||
|
||||
func GetWindowsVersionInfo() (*WindowsVersionInfo, error) {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &WindowsVersionInfo{
|
||||
Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"),
|
||||
Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"),
|
||||
Build: regStringKeyAsInt(key, "CurrentBuildNumber"),
|
||||
DisplayVersion: regKeyAsString(key, "DisplayVersion"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func regDWORDKeyAsInt(key registry.Key, name string) int {
|
||||
result, _, err := key.GetIntegerValue(name)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int(result)
|
||||
}
|
||||
|
||||
func regStringKeyAsInt(key registry.Key, name string) int {
|
||||
resultStr, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
result, err := strconv.Atoi(resultStr)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func regKeyAsString(key registry.Key, name string) string {
|
||||
resultStr, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resultStr
|
||||
}
|
@ -1,5 +1,40 @@
|
||||
package windows
|
||||
|
||||
type Theme int
|
||||
|
||||
const (
|
||||
// SystemDefault will use whatever the system theme is. The application will follow system theme changes.
|
||||
SystemDefault Theme = 0
|
||||
// Dark Mode
|
||||
Dark Theme = 1
|
||||
// Light Mode
|
||||
Light Theme = 2
|
||||
)
|
||||
|
||||
func RGB(r, g, b uint8) int32 {
|
||||
var col = int32(b)
|
||||
col = col<<8 | int32(g)
|
||||
col = col<<8 | int32(r)
|
||||
return col
|
||||
}
|
||||
|
||||
// ThemeSettings contains optional colours to use.
|
||||
// They may be set using the hex values: 0x00BBGGRR
|
||||
type ThemeSettings struct {
|
||||
DarkModeTitleBar int32
|
||||
DarkModeTitleBarInactive int32
|
||||
DarkModeTitleText int32
|
||||
DarkModeTitleTextInactive int32
|
||||
DarkModeBorder int32
|
||||
DarkModeBorderInactive int32
|
||||
LightModeTitleBar int32
|
||||
LightModeTitleBarInactive int32
|
||||
LightModeTitleText int32
|
||||
LightModeTitleTextInactive int32
|
||||
LightModeBorder int32
|
||||
LightModeBorderInactive int32
|
||||
}
|
||||
|
||||
// Options are options specific to Windows
|
||||
type Options struct {
|
||||
WebviewIsTransparent bool
|
||||
@ -13,4 +48,10 @@ type Options struct {
|
||||
// Path where the WebView2 stores the user data. If empty %APPDATA%\[BinaryName.exe] will be used.
|
||||
// If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code.
|
||||
WebviewUserDataPath string
|
||||
|
||||
// Dark/Light or System Default Theme
|
||||
Theme Theme
|
||||
|
||||
// Custom settings for dark/light mode
|
||||
CustomTheme *ThemeSettings
|
||||
}
|
||||
|
@ -47,6 +47,15 @@ func main() {
|
||||
DisableWindowIcon: false,
|
||||
DisableFramelessWindowDecorations: false,
|
||||
WebviewUserDataPath: "",
|
||||
Theme: windows.SystemDefault,
|
||||
CustomTheme: &windows.ThemeSettings{
|
||||
DarkModeTitleBar: windows.RGB(20, 20, 20),
|
||||
DarkModeTitleText: windows.RGB(200, 200, 200),
|
||||
DarkModeBorder: windows.RGB(20, 0, 20),
|
||||
LightModeTitleBar: windows.RGB(200, 200, 200),
|
||||
LightModeTitleText: windows.RGB(20, 20, 20),
|
||||
LightModeBorder: windows.RGB(200, 200, 200),
|
||||
},
|
||||
},
|
||||
Mac: &mac.Options{
|
||||
TitleBar: &mac.TitleBar{
|
||||
@ -375,6 +384,78 @@ Type: string
|
||||
|
||||
This defines the path where the WebView2 stores the user data. If empty `%APPDATA%\[BinaryName.exe]` will be used.
|
||||
|
||||
### Theme
|
||||
|
||||
Name: Theme
|
||||
|
||||
Type: `windows.Theme`
|
||||
|
||||
Minimum Windows Version: Windows 10 2004/20H1
|
||||
|
||||
This defines the theme that the application should use:
|
||||
|
||||
| Value | Description |
|
||||
| --------------- | ----------- |
|
||||
| SystemDefault | *Default*. The theme will be based on the system default. If the user changes their theme, the application will update to use the new setting |
|
||||
| Dark | The application will use a dark theme exclusively |
|
||||
| Light | The application will use a light theme exclusively |
|
||||
|
||||
|
||||
### CustomTheme
|
||||
|
||||
Name: CustomTheme
|
||||
|
||||
Type: `windows.CustomTheme`
|
||||
|
||||
Minimum Windows Version: Windows 10/11 2009/21H2 Build 22000
|
||||
|
||||
Allows you to specify custom colours for TitleBar, TitleText and Border for both light and dark mode, as well as
|
||||
when the window is active or inactive.
|
||||
|
||||
#### CustomTheme
|
||||
|
||||
The CustomTheme struct uses `int32` to specify the colour values. These are in the standard(!) Windows format of:
|
||||
`0x00BBGGAA`. A helper function is provided to do RGB conversions into this format: `windows.RGB(r,g,b uint8)`.
|
||||
|
||||
NOTE: Any value not provided will default to black.
|
||||
|
||||
```go
|
||||
type ThemeSettings struct {
|
||||
DarkModeTitleBar int32
|
||||
DarkModeTitleBarInactive int32
|
||||
DarkModeTitleText int32
|
||||
DarkModeTitleTextInactive int32
|
||||
DarkModeBorder int32
|
||||
DarkModeBorderInactive int32
|
||||
LightModeTitleBar int32
|
||||
LightModeTitleBarInactive int32
|
||||
LightModeTitleText int32
|
||||
LightModeTitleTextInactive int32
|
||||
LightModeBorder int32
|
||||
LightModeBorderInactive int32
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
```go
|
||||
CustomTheme: &windows.ThemeSettings{
|
||||
// Theme to use when window is active
|
||||
DarkModeTitleBar: windows.RGB(255, 0, 0), // Red
|
||||
DarkModeTitleText: windows.RGB(0, 255, 0), // Green
|
||||
DarkModeBorder: windows.RGB(0, 0, 255), // Blue
|
||||
LightModeTitleBar: windows.RGB(200, 200, 200),
|
||||
LightModeTitleText: windows.RGB(20, 20, 20),
|
||||
LightModeBorder: windows.RGB(200, 200, 200),
|
||||
// Theme to use when window is inactive
|
||||
DarkModeTitleBarInactive: windows.RGB(128, 0, 0),
|
||||
DarkModeTitleTextInactive: windows.RGB(0, 128, 0),
|
||||
DarkModeBorderInactive: windows.RGB(0, 0, 128),
|
||||
LightModeTitleBarInactive: windows.RGB(100, 100, 100),
|
||||
LightModeTitleTextInactive: windows.RGB(10, 10, 10),
|
||||
LightModeBorderInactive: windows.RGB(100, 100, 100),
|
||||
},
|
||||
```
|
||||
|
||||
## Mac Specific Options
|
||||
|
||||
### TitleBar
|
||||
|
Loading…
Reference in New Issue
Block a user