5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 23:20:51 +08:00
This commit is contained in:
Lea Anthony 2025-02-09 11:43:26 +11:00
parent c94803fedb
commit 8a3d7eb12a
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
8 changed files with 415 additions and 44 deletions

View File

@ -352,7 +352,7 @@ func main() {
app.SetMenu(menu)
app.NewWebviewWindow()
app.NewWebviewWindow().SetMenu(menu)
err := app.Run()

View File

@ -125,6 +125,7 @@ func (m *windowsApp) setApplicationMenu(menu *Menu) {
}
func (m *windowsApp) run() error {
m.setupCommonEvents()
for eventID := range m.parent.applicationEventListeners {
m.on(eventID)
@ -221,6 +222,33 @@ func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr)
}
switch msg {
/*
case WM_UAHDRAWMENU:
{
UAHMENU* pUDM = (UAHMENU*)lParam;
RECT rc = { 0 };
// get the menubar rect
{
MENUBARINFO mbi = { sizeof(mbi) };
GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi);
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
// the rcBar is offset by the window rect
rc = mbi.rcBar;
OffsetRect(&rc, -rcWindow.left, -rcWindow.top);
}
FillRect(pUDM->hdc, &rc, g_brBarBackground);
return true;
}
*/
case w32.WM_UAHDRAWMENU:
return w32.UAHDrawMenu(hwnd, wParam, lParam)
case w32.WM_SETTINGCHANGE:
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
if settingChanged == "ImmersiveColorSet" {

View File

@ -408,6 +408,8 @@ func (w *windowsWebviewWindow) run() {
}
// Process the theme
w32.AllowDarkModeForWindow(w.hwnd, true)
switch options.Windows.Theme {
case SystemDefault:
w.updateTheme(w32.IsCurrentlyDarkMode())

View File

@ -606,6 +606,8 @@ const (
WM_MOUSELEAVE = 0x2A3
WM_CLIPBOARDUPDATE = 0x031D
WM_DPICHANGED = 0x02E0
WM_UAHDRAWMENU = 0x0091
WM_UAHDRAWMENUITEM = 0x0092
)
const (

199
v3/pkg/w32/menubar.go Normal file
View File

@ -0,0 +1,199 @@
package w32
import "unsafe"
const (
OBJID_MENU = 3
)
type UAHMENU struct {
Hmenu HMENU
Hdc HDC
DwFlags uint32
}
type MENUBARINFO struct {
CbSize uint32
Bar RECT
Menu HMENU
Window HWND
BarFocused int32
Focused int32
}
type DRAWITEMSTRUCT struct {
ControlType uint32
ControlID uint32
ItemID uint32
ItemAction uint32
ItemState uint32
HWNDItem HWND
HDC HDC
RcItem RECT
ItemData uintptr
}
type UAHDRAWMENUITEM struct {
DIS DRAWITEMSTRUCT
UM UAHMENU
UAMI UAHMENUITEM
}
type UAHMENUITEM struct {
Position int
Umim UAHMENUITEMMETRICS
Umpm UAHMENUPOPUPMETRICS
}
type UAHMENUITEMMETRICS struct {
data [32]byte // Total size of the union in bytes (4 DWORDs * 4 bytes each * 2 arrays)
}
func (u *UAHMENUITEMMETRICS) RgsizeBar() *[2]struct{ cx, cy uint32 } {
return (*[2]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data))
}
func (u *UAHMENUITEMMETRICS) RgsizePopup() *[4]struct{ cx, cy uint32 } {
return (*[4]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data))
}
type UAHMENUPOPUPMETRICS struct {
Rgcx [4]uint32 // Array of 4 DWORDs
FUpdateMaxWidths uint32 // Bit-field represented as a uint32
}
// Helper function to get the value of the fUpdateMaxWidths bit-field
func (u *UAHMENUPOPUPMETRICS) GetFUpdateMaxWidths() uint32 {
return u.FUpdateMaxWidths & 0x3 // Mask to get the first 2 bits
}
// Helper function to set the value of the fUpdateMaxWidths bit-field
func (u *UAHMENUPOPUPMETRICS) SetFUpdateMaxWidths(value uint32) {
u.FUpdateMaxWidths = (u.FUpdateMaxWidths &^ 0x3) | (value & 0x3) // Clear and set the first 2 bits
}
var darkModeTitleBarBrush HBRUSH
func init() {
darkModeTitleBarBrush, _, _ = procCreateSolidBrush.Call(
uintptr(0x8080FF),
)
}
func CreateSolidBrush(color COLORREF) HBRUSH {
ret, _, _ := procCreateSolidBrush.Call(
uintptr(color),
)
return HBRUSH(ret)
}
func UAHDrawMenu(hwnd HWND, wParam uintptr, lParam uintptr) uintptr {
if !IsCurrentlyDarkMode() {
return 0
}
udm := (*UAHMENU)(unsafe.Pointer(lParam))
var rc RECT
// get the menubar rect
{
menuBarInfo, err := GetMenuBarInfo(hwnd, OBJID_MENU, 0)
if err != nil {
return 0
}
winRect := GetWindowRect(hwnd)
// the rcBar is offset by the window rect
rc = menuBarInfo.Bar
OffsetRect(&rc, int(-winRect.Left), int(-winRect.Top))
}
FillRect(udm.Hdc, &rc, darkModeTitleBarBrush)
return 1
}
func UAHDrawMenuItem(hwnd HWND, wParam uintptr, lParam uintptr) uintptr {
pUDMI := (*UAHDRAWMENUITEM)(unsafe.Pointer(lParam))
var pbrBackground, pbrBorder *HBRUSH
pbrBackground = &brItemBackground
pbrBorder = &brItemBackground
// get the menu item string
menuString := make([]uint16, 256)
mii := MENUITEMINFO{
CbSize: uint32(unsafe.Sizeof(mii)),
FMask: MIIM_STRING,
DwTypeData: uintptr(unsafe.Pointer(&menuString[0])),
Cch: uint32(len(menuString) - 1),
}
GetMenuItemInfo(pUDMI.um.hmenu, pUDMI.umi.iPosition, true, &mii)
// get the item state for drawing
dwFlags := DT_CENTER | DT_SINGLELINE | DT_VCENTER
iTextStateID := 0
iBackgroundStateID := 0
switch {
case pUDMI.dis.itemState&ODS_INACTIVE != 0 || pUDMI.dis.itemState&ODS_DEFAULT != 0:
// normal display
iTextStateID = MBI_NORMAL
iBackgroundStateID = MBI_NORMAL
case pUDMI.dis.itemState&ODS_HOTLIGHT != 0:
// hot tracking
iTextStateID = MBI_HOT
iBackgroundStateID = MBI_HOT
pbrBackground = &brItemBackgroundHot
pbrBorder = &brItemBorder
case pUDMI.dis.itemState&ODS_SELECTED != 0:
// clicked
iTextStateID = MBI_PUSHED
iBackgroundStateID = MBI_PUSHED
pbrBackground = &brItemBackgroundSelected
pbrBorder = &brItemBorder
case pUDMI.dis.itemState&ODS_GRAYED != 0 || pUDMI.dis.itemState&ODS_DISABLED != 0:
// disabled / grey text
iTextStateID = MBI_DISABLED
iBackgroundStateID = MBI_DISABLED
case pUDMI.dis.itemState&ODS_NOACCEL != 0:
// hide prefix
dwFlags |= DT_HIDEPREFIX
}
if g_menuTheme == 0 {
g_menuTheme = OpenThemeData(hwnd, "Menu")
}
opts := DTTOPTS{
DtSize: uint32(unsafe.Sizeof(opts)),
DwFlags: DTT_TEXTCOLOR,
CrText: RGB(0x00, 0x00, 0x20),
ITextState: iTextStateID,
}
if iTextStateID == MBI_DISABLED {
opts.CrText = RGB(0x40, 0x40, 0x40)
}
FillRect(pUDMI.um.hdc, &pUDMI.dis.rcItem, *pbrBackground)
FrameRect(pUDMI.um.hdc, &pUDMI.dis.rcItem, *pbrBorder)
DrawThemeTextEx(g_menuTheme, pUDMI.um.hdc, MENU_BARITEM, MBI_NORMAL, uintptr(unsafe.Pointer(&menuString[0])), mii.cch, dwFlags, &pUDMI.dis.rcItem, &opts)
return 1
}
func GetMenuBarInfo(hwnd HWND, idObject uint32, idItem uint32) (*MENUBARINFO, error) {
var mi MENUBARINFO
mi.CbSize = uint32(unsafe.Sizeof(&mi))
ret, _, err := procGetMenuBarInfo.Call(
hwnd,
uintptr(idObject),
uintptr(idItem),
uintptr(unsafe.Pointer(&mi)))
if ret == 0 {
return nil, err
}
return &mi, nil
}

View File

@ -3,8 +3,10 @@
package w32
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
@ -20,6 +22,148 @@ const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
const SPI_GETHIGHCONTRAST = 0x0042
const HCF_HIGHCONTRASTON = 0x00000001
type WINDOWCOMPOSITIONATTRIB DWORD
const (
WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0
WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1
WCA_NCRENDERING_POLICY WINDOWCOMPOSITIONATTRIB = 2
WCA_TRANSITIONS_FORCEDISABLED WINDOWCOMPOSITIONATTRIB = 3
WCA_ALLOW_NCPAINT WINDOWCOMPOSITIONATTRIB = 4
WCA_CAPTION_BUTTON_BOUNDS WINDOWCOMPOSITIONATTRIB = 5
WCA_NONCLIENT_RTL_LAYOUT WINDOWCOMPOSITIONATTRIB = 6
WCA_FORCE_ICONIC_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 7
WCA_EXTENDED_FRAME_BOUNDS WINDOWCOMPOSITIONATTRIB = 8
WCA_HAS_ICONIC_BITMAP WINDOWCOMPOSITIONATTRIB = 9
WCA_THEME_ATTRIBUTES WINDOWCOMPOSITIONATTRIB = 10
WCA_NCRENDERING_EXILED WINDOWCOMPOSITIONATTRIB = 11
WCA_NCADORNMENTINFO WINDOWCOMPOSITIONATTRIB = 12
WCA_EXCLUDED_FROM_LIVEPREVIEW WINDOWCOMPOSITIONATTRIB = 13
WCA_VIDEO_OVERLAY_ACTIVE WINDOWCOMPOSITIONATTRIB = 14
WCA_FORCE_ACTIVEWINDOW_APPEARANCE WINDOWCOMPOSITIONATTRIB = 15
WCA_DISALLOW_PEEK WINDOWCOMPOSITIONATTRIB = 16
WCA_CLOAK WINDOWCOMPOSITIONATTRIB = 17
WCA_CLOAKED WINDOWCOMPOSITIONATTRIB = 18
WCA_ACCENT_POLICY WINDOWCOMPOSITIONATTRIB = 19
WCA_FREEZE_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 20
WCA_EVER_UNCLOAKED WINDOWCOMPOSITIONATTRIB = 21
WCA_VISUAL_OWNER WINDOWCOMPOSITIONATTRIB = 22
WCA_HOLOGRAPHIC WINDOWCOMPOSITIONATTRIB = 23
WCA_EXCLUDED_FROM_DDA WINDOWCOMPOSITIONATTRIB = 24
WCA_PASSIVEUPDATEMODE WINDOWCOMPOSITIONATTRIB = 25
WCA_USEDARKMODECOLORS WINDOWCOMPOSITIONATTRIB = 26
WCA_CORNER_STYLE WINDOWCOMPOSITIONATTRIB = 27
WCA_PART_COLOR WINDOWCOMPOSITIONATTRIB = 28
WCA_DISABLE_MOVESIZE_FEEDBACK WINDOWCOMPOSITIONATTRIB = 29
WCA_LAST WINDOWCOMPOSITIONATTRIB = 30
)
type WINDOWCOMPOSITIONATTRIBDATA struct {
Attrib WINDOWCOMPOSITIONATTRIB
PvData unsafe.Pointer
CbData uintptr
}
var (
uxtheme = syscall.NewLazyDLL("uxtheme.dll")
procSetWindowTheme = uxtheme.NewProc("SetWindowTheme")
procAllowDarkModeForApplication = uxtheme.NewProc("AllowDarkModeForApp")
)
type PreferredAppMode = int32
const (
PreferredAppModeDefault PreferredAppMode = iota
PreferredAppModeAllowDark
PreferredAppModeForceDark
PreferredAppModeForceLight
PreferredAppModeMax
)
var (
AllowDarkModeForWindow func(hwnd HWND, allow bool) uintptr
SetPreferredAppMode func(mode int32) uintptr
FlushMenuThemes func()
RefreshImmersiveColorPolicyState func()
ShouldAppsUseDarkMode func() bool
)
func init() {
if IsWindowsVersionAtLeast(10, 0, 18334) {
// AllowDarkModeForWindow is only available on Windows 10+
localUXTheme, err := windows.LoadLibrary("uxtheme.dll")
if err == nil {
procAllowDarkModeForWindow, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(133))
if err == nil {
AllowDarkModeForWindow = func(hwnd HWND, allow bool) uintptr {
var allowInt int32
if allow {
allowInt = 1
}
ret, _, _ := syscall.SyscallN(procAllowDarkModeForWindow, uintptr(allowInt))
return ret
}
}
// Add ShouldAppsUseDarkMode
procShouldAppsUseDarkMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(132))
if err == nil {
ShouldAppsUseDarkMode = func() bool {
ret, _, _ := syscall.SyscallN(procShouldAppsUseDarkMode)
return ret != 0
}
}
// SetPreferredAppMode is only available on Windows 10+
procSetPreferredAppMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(135))
if err == nil {
SetPreferredAppMode = func(mode int32) uintptr {
ret, _, _ := syscall.SyscallN(procSetPreferredAppMode, uintptr(mode))
return ret
}
}
// Add FlushMenuThemes
procFlushMenuThemesAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(136))
if err == nil {
FlushMenuThemes = func() {
syscall.SyscallN(procFlushMenuThemesAddr)
}
}
// Add RefreshImmersiveColorPolicyState
procRefreshImmersiveColorPolicyStateAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(104))
if err == nil {
RefreshImmersiveColorPolicyState = func() {
syscall.SyscallN(procRefreshImmersiveColorPolicyStateAddr)
}
}
//procAllowDarkModeForApp, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(137))
//if err == nil {
// procAllowDarkModeForApplication = func(hwnd HWND, allow bool) uintptr {
// var allowInt int32
// if allow {
// allowInt = 1
// }
// ret, _, _ := syscall.SyscallN(procAllowDarkModeForApp, uintptr(allowInt))
// return ret
// }
//}
// Initialize dark mode
if SetPreferredAppMode != nil {
SetPreferredAppMode(PreferredAppModeAllowDark)
if RefreshImmersiveColorPolicyState != nil {
RefreshImmersiveColorPolicyState()
}
}
windows.FreeLibrary(localUXTheme)
}
}
}
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
ret, _, err := procDwmSetWindowAttribute.Call(
hwnd,
@ -49,6 +193,42 @@ func SupportsImmersiveDarkMode() bool {
return IsWindowsVersionAtLeast(10, 0, 18985)
}
func SetMenuTheme(hwnd uintptr, useDarkMode bool) {
if !SupportsThemes() {
return
}
// Check if dark mode is supported and enabled
if useDarkMode && ShouldAppsUseDarkMode != nil && !ShouldAppsUseDarkMode() {
useDarkMode = false
}
// Set the window theme
themeName := "Explorer"
if useDarkMode {
themeName = "DarkMode"
}
procSetWindowTheme.Call(HWND(hwnd), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(themeName))), 0)
// Update the theme state
if RefreshImmersiveColorPolicyState != nil {
RefreshImmersiveColorPolicyState()
}
// Flush menu themes to force a refresh
if FlushMenuThemes != nil {
FlushMenuThemes()
}
// Set dark mode for the window
if AllowDarkModeForWindow != nil {
AllowDarkModeForWindow(HWND(hwnd), useDarkMode)
}
// Force a redraw
InvalidateRect(HWND(hwnd), nil, true)
}
func SetTheme(hwnd uintptr, useDarkMode bool) {
if SupportsThemes() {
attr := DwmwaUseImmersiveDarkModeBefore20h1
@ -60,6 +240,7 @@ func SetTheme(hwnd uintptr, useDarkMode bool) {
winDark = 1
}
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
SetMenuTheme(hwnd, useDarkMode)
}
}

View File

@ -780,48 +780,6 @@ type ACCENT_POLICY struct {
AnimationId DWORD
}
type WINDOWCOMPOSITIONATTRIBDATA struct {
Attrib WINDOWCOMPOSITIONATTRIB
PvData PVOID
CbData SIZE_T
}
type WINDOWCOMPOSITIONATTRIB DWORD
const (
WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0
WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1
WCA_NCRENDERING_POLICY WINDOWCOMPOSITIONATTRIB = 2
WCA_TRANSITIONS_FORCEDISABLED WINDOWCOMPOSITIONATTRIB = 3
WCA_ALLOW_NCPAINT WINDOWCOMPOSITIONATTRIB = 4
WCA_CAPTION_BUTTON_BOUNDS WINDOWCOMPOSITIONATTRIB = 5
WCA_NONCLIENT_RTL_LAYOUT WINDOWCOMPOSITIONATTRIB = 6
WCA_FORCE_ICONIC_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 7
WCA_EXTENDED_FRAME_BOUNDS WINDOWCOMPOSITIONATTRIB = 8
WCA_HAS_ICONIC_BITMAP WINDOWCOMPOSITIONATTRIB = 9
WCA_THEME_ATTRIBUTES WINDOWCOMPOSITIONATTRIB = 10
WCA_NCRENDERING_EXILED WINDOWCOMPOSITIONATTRIB = 11
WCA_NCADORNMENTINFO WINDOWCOMPOSITIONATTRIB = 12
WCA_EXCLUDED_FROM_LIVEPREVIEW WINDOWCOMPOSITIONATTRIB = 13
WCA_VIDEO_OVERLAY_ACTIVE WINDOWCOMPOSITIONATTRIB = 14
WCA_FORCE_ACTIVEWINDOW_APPEARANCE WINDOWCOMPOSITIONATTRIB = 15
WCA_DISALLOW_PEEK WINDOWCOMPOSITIONATTRIB = 16
WCA_CLOAK WINDOWCOMPOSITIONATTRIB = 17
WCA_CLOAKED WINDOWCOMPOSITIONATTRIB = 18
WCA_ACCENT_POLICY WINDOWCOMPOSITIONATTRIB = 19
WCA_FREEZE_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 20
WCA_EVER_UNCLOAKED WINDOWCOMPOSITIONATTRIB = 21
WCA_VISUAL_OWNER WINDOWCOMPOSITIONATTRIB = 22
WCA_HOLOGRAPHIC WINDOWCOMPOSITIONATTRIB = 23
WCA_EXCLUDED_FROM_DDA WINDOWCOMPOSITIONATTRIB = 24
WCA_PASSIVEUPDATEMODE WINDOWCOMPOSITIONATTRIB = 25
WCA_USEDARKMODECOLORS WINDOWCOMPOSITIONATTRIB = 26
WCA_CORNER_STYLE WINDOWCOMPOSITIONATTRIB = 27
WCA_PART_COLOR WINDOWCOMPOSITIONATTRIB = 28
WCA_DISABLE_MOVESIZE_FEEDBACK WINDOWCOMPOSITIONATTRIB = 29
WCA_LAST WINDOWCOMPOSITIONATTRIB = 30
)
// -------------------------
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684225.aspx

View File

@ -176,7 +176,8 @@ var (
procSetMenuItemBitmaps = moduser32.NewProc("SetMenuItemBitmaps")
procRedrawWindow = moduser32.NewProc("RedrawWindow")
procRedrawWindow = moduser32.NewProc("RedrawWindow")
procGetMenuBarInfo = moduser32.NewProc("GetMenuBarInfo")
mainThread HANDLE
)