From 2faf8533e05276b41a0c49569a3e131656fc9fad Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 9 Feb 2025 21:59:50 +1100 Subject: [PATCH] Dark theme menus for windows --- v3/pkg/application/application_windows.go | 2 - v3/pkg/application/webview_window_options.go | 1 + v3/pkg/application/webview_window_windows.go | 13 +- v3/pkg/w32/constants.go | 1 + v3/pkg/w32/menubar.go | 437 +++++++++++++++---- v3/pkg/w32/theme.go | 7 +- v3/pkg/w32/typedef.go | 7 + v3/pkg/w32/user32.go | 18 +- v3/pkg/w32/uxtheme.go | 152 ------- 9 files changed, 395 insertions(+), 243 deletions(-) delete mode 100644 v3/pkg/w32/uxtheme.go diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go index 8ee8eda38..12fec5813 100644 --- a/v3/pkg/application/application_windows.go +++ b/v3/pkg/application/application_windows.go @@ -247,8 +247,6 @@ func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) 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" { diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 04dae199e..e99ea7ea4 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -324,6 +324,7 @@ type ThemeSettings struct { LightModeTitleTextInactive int32 LightModeBorder int32 LightModeBorderInactive int32 + DarkModeMenuBar int32 } /****** Mac Options *******/ diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 9082f8c46..cc1000013 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1056,6 +1056,7 @@ func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) { customTheme := w.parent.options.Windows.CustomTheme // Custom theme if w32.SupportsCustomThemes() && customTheme != nil { + w32.InitDarkMode(customTheme.DarkModeMenuBar) if w.isActive() { if isDarkMode { w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar) @@ -1087,6 +1088,12 @@ func (w *windowsWebviewWindow) isActive() bool { var resizePending int32 func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr { + + processed, code := w32.MenuBarWndProc(w.hwnd, msg, wparam, lparam) + if processed { + return code + } + switch msg { case w32.WM_ACTIVATE: if int(wparam&0xffff) == w32.WA_INACTIVE { @@ -1404,11 +1411,11 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp } w.setPadding(edge.Rect{}) } else { - // This is needed to workaround the resize flickering in frameless mode with WindowDecorations + // This is needed to work around the resize flickering in frameless mode with WindowDecorations // See: https://stackoverflow.com/a/6558508 // The workaround from the SO answer suggests to reduce the bottom of the window by 1px. - // However this would result in loosing 1px of the WebView content. - // Increasing the bottom also worksaround the flickering but we would loose 1px of the WebView content + // However, this would result in losing 1px of the WebView content. + // Increasing the bottom also worksaround the flickering, but we would lose 1px of the WebView content // therefore let's pad the content with 1px at the bottom. rgrc.Bottom += 1 w.setPadding(edge.Rect{Bottom: 1}) diff --git a/v3/pkg/w32/constants.go b/v3/pkg/w32/constants.go index cae7b0d0c..79192b01d 100644 --- a/v3/pkg/w32/constants.go +++ b/v3/pkg/w32/constants.go @@ -608,6 +608,7 @@ const ( WM_DPICHANGED = 0x02E0 WM_UAHDRAWMENU = 0x0091 WM_UAHDRAWMENUITEM = 0x0092 + WM_UAHMEASUREMENUITEM = 0x012E ) const ( diff --git a/v3/pkg/w32/menubar.go b/v3/pkg/w32/menubar.go index 78f59b035..7a175595b 100644 --- a/v3/pkg/w32/menubar.go +++ b/v3/pkg/w32/menubar.go @@ -1,11 +1,121 @@ package w32 -import "unsafe" +import ( + "fmt" + "unsafe" +) const ( - OBJID_MENU = 3 + OBJID_MENU = -3 ) +var ( + menuTheme HTHEME +) + +type DTTOPTS struct { + DwSize uint32 + DwFlags uint32 + CrText uint32 + CrBorder uint32 + CrShadow uint32 + ITextShadowType int32 + PtShadowOffset POINT + iBorderSize int32 + iFontPropId int32 + IColorPropId int32 + IStateId int32 + FApplyOverlay int32 + IGlowSize int32 + PfnDrawTextCallback uintptr + LParam uintptr +} + +const ( + MENU_POPUPITEM = 14 + DTT_TEXTCOLOR = 1 +) + +// Menu item states +const ( + ODS_SELECTED = 0x0001 + ODS_GRAYED = 0x0002 + ODS_DISABLED = 0x0004 + ODS_CHECKED = 0x0008 + ODS_FOCUS = 0x0010 + ODS_DEFAULT = 0x0020 + ODS_HOTLIGHT = 0x0040 + ODS_INACTIVE = 0x0080 + ODS_NOACCEL = 0x0100 + ODS_NOFOCUSRECT = 0x0200 +) + +// Menu Button Image states +const ( + MBI_NORMAL = 1 + MBI_HOT = 2 + MBI_PUSHED = 3 + MBI_DISABLED = 4 +) + +var ( + procGetMenuItemInfo = moduser32.NewProc("GetMenuItemInfoW") +) + +func GetMenuItemInfo(hmenu HMENU, item uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procGetMenuItemInfo.Call( + uintptr(hmenu), + uintptr(item), + uintptr(boolToUint(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + ) + return ret != 0 +} + +// Helper function to convert bool to uint +func boolToUint(b bool) uint { + if b { + return 1 + } + return 0 +} + +// Add these function declarations +func OpenThemeData(hwnd HWND, pszClassList *uint16) HTHEME { + ret, _, _ := procOpenThemeData.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(pszClassList)), + ) + return HTHEME(ret) +} + +func DrawThemeBackground(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pRect *RECT, pClipRect *RECT) HRESULT { + ret, _, _ := procDrawThemeBackground.Call( + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(pRect)), + uintptr(unsafe.Pointer(pClipRect)), + ) + return HRESULT(ret) +} + +func DrawThemeTextEx(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, text *uint16, textLen int32, dwFlags uint32, pRect *RECT, pOptions *DTTOPTS) HRESULT { + ret, _, _ := procDrawThemeTextEx.Call( + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(text)), + uintptr(textLen), + uintptr(dwFlags), + uintptr(unsafe.Pointer(pRect)), + uintptr(unsafe.Pointer(pOptions)), + ) + return HRESULT(ret) +} + type UAHMENU struct { Hmenu HMENU Hdc HDC @@ -56,6 +166,21 @@ func (u *UAHMENUITEMMETRICS) RgsizePopup() *[4]struct{ cx, cy uint32 } { return (*[4]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data)) } +type UAHMEASUREMENUITEM struct { + UM UAHMENU + UAMI UAHMENUITEM + Mis MEASUREITEMSTRUCT +} + +type MEASUREITEMSTRUCT struct { + CtlType uint32 + CtlID uint32 + ItemID uint32 + ItemWidth uint32 + ItemHeight uint32 + ItemData uintptr +} + type UAHMENUPOPUPMETRICS struct { Rgcx [4]uint32 // Array of 4 DWORDs FUpdateMaxWidths uint32 // Bit-field represented as a uint32 @@ -75,7 +200,13 @@ var darkModeTitleBarBrush HBRUSH func init() { darkModeTitleBarBrush, _, _ = procCreateSolidBrush.Call( - uintptr(0x8080FF), + uintptr(0x00262525), + ) +} + +func InitDarkMode(darkModeMenuBarColour int32) { + darkModeTitleBarBrush, _, _ = procCreateSolidBrush.Call( + uintptr(darkModeMenuBarColour), ) } @@ -86,18 +217,36 @@ func CreateSolidBrush(color COLORREF) HBRUSH { return HBRUSH(ret) } -func UAHDrawMenu(hwnd HWND, wParam uintptr, lParam uintptr) uintptr { - if !IsCurrentlyDarkMode() { - return 0 - } - udm := (*UAHMENU)(unsafe.Pointer(lParam)) - var rc RECT +func RGB(r, g, b byte) uint32 { + return uint32(r) | uint32(g)<<8 | uint32(b)<<16 +} - // get the menubar rect - { +func TrackPopupMenu(hmenu HMENU, flags uint32, x, y int32, reserved int32, hwnd HWND, prcRect *RECT) bool { + ret, _, _ := procTrackPopupMenu.Call( + uintptr(hmenu), + uintptr(flags), + uintptr(x), + uintptr(y), + uintptr(reserved), + uintptr(hwnd), + uintptr(unsafe.Pointer(prcRect)), + ) + return ret != 0 +} + +func MenuBarWndProc(hwnd HWND, msg uint32, wParam WPARAM, lParam LPARAM) (bool, LRESULT) { + if !IsCurrentlyDarkMode() { + return false, 0 + } + switch msg { + case WM_UAHDRAWMENU: + udm := (*UAHMENU)(unsafe.Pointer(lParam)) + var rc RECT + + // get the menubar rect menuBarInfo, err := GetMenuBarInfo(hwnd, OBJID_MENU, 0) if err != nil { - return 0 + return false, 0 } winRect := GetWindowRect(hwnd) @@ -105,88 +254,210 @@ func UAHDrawMenu(hwnd HWND, wParam uintptr, lParam uintptr) uintptr { // 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 true, 0 + case WM_UAHDRAWMENUITEM: + udmi := (*UAHDRAWMENUITEM)(unsafe.Pointer(lParam)) + + // Create buffer for menu text + menuString := make([]uint16, 256) + + // Setup menu item info structure + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + GetMenuItemInfo(udmi.UM.Hmenu, uint32(udmi.UAMI.Position), true, &mii) + if udmi.DIS.ItemState&ODS_HOTLIGHT != 0 && mii.HSubMenu != 0 { + // If this is a menu item with a submenu and we're hovering, + // tell the menu to track + TrackPopupMenu(mii.HSubMenu, + TPM_LEFTALIGN|TPM_TOPALIGN, + int32(udmi.DIS.RcItem.Left), + int32(udmi.DIS.RcItem.Bottom), + 0, hwnd, nil) + } + dwFlags := uint32(DT_CENTER | DT_SINGLELINE | DT_VCENTER) + + // Use different colors for menubar vs popup items + var bgBrush HBRUSH + var textColor uint32 + + if udmi.DIS.ItemState&ODS_HOTLIGHT != 0 { + // Hot state - use a specific color for hover + bgBrush = CreateSolidBrush(RGB(45, 45, 45)) // Dark gray for hover + textColor = RGB(255, 255, 255) // White text + } else if udmi.DIS.ItemState&ODS_SELECTED != 0 { + // Selected state + bgBrush = CreateSolidBrush(RGB(60, 60, 60)) // Slightly lighter for selected + textColor = RGB(255, 255, 255) + } else { + // Normal state + bgBrush = darkModeTitleBarBrush + textColor = RGB(255, 255, 255) + } + + // Fill background + FillRect(udmi.UM.Hdc, &udmi.DIS.RcItem, bgBrush) + + // Delete the temporary brush if we created one + if bgBrush != darkModeTitleBarBrush { + DeleteObject(bgBrush) + } + + // Draw text + SetTextColor(udmi.UM.Hdc, textColor) + SetBkMode(udmi.UM.Hdc, TRANSPARENT) + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + + return true, 1 + case WM_UAHMEASUREMENUITEM: + // Cast lParam to UAHMEASUREMENUITEM pointer + mmi := (*UAHMEASUREMENUITEM)(unsafe.Pointer(lParam)) + + // Let the default window procedure handle the basic measurement + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Modify the width to be 1/3rd wider + mmi.Mis.ItemWidth = (mmi.Mis.ItemWidth * 4) / 3 + + return true, result + case WM_NCPAINT, WM_NCACTIVATE: + result := DefWindowProc(hwnd, msg, wParam, lParam) + _, err := GetMenuBarInfo(hwnd, OBJID_MENU, 0) + if err != nil { + return false, 0 + } + + clientRect := GetClientRect(hwnd) + points := []POINT{ + { + X: clientRect.Left, + Y: clientRect.Top, + }, + { + X: clientRect.Right, + Y: clientRect.Bottom, + }, + } + MapWindowPoints(hwnd, 0, uintptr(unsafe.Pointer(&points[0])), 2) + clientRect.Left = points[0].X + clientRect.Top = points[0].Y + clientRect.Right = points[1].X + clientRect.Bottom = points[1].Y + winRect := GetWindowRect(hwnd) + + OffsetRect(clientRect, int(-winRect.Left), int(-winRect.Top)) + + line := *clientRect + line.Bottom = line.Top + line.Top = line.Top - 1 + + hdc := GetWindowDC(hwnd) + FillRect(hdc, &line, darkModeTitleBarBrush) + ReleaseDC(hwnd, hdc) + return true, result } + return false, 0 +} - FillRect(udm.Hdc, &rc, darkModeTitleBarBrush) +func MapWindowPoints(hWndFrom HWND, hWndTo HWND, points uintptr, numPoints uint32) { - return 1 + // Call the MapWindowPoints function + ret, _, _ := procMapWindowPoints.Call( + uintptr(hWndFrom), + uintptr(hWndTo), + points, + uintptr(numPoints), + ) + + // Check for errors + if ret == 0 { + fmt.Println("MapWindowPoints failed") + } } 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 + //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) { +func GetMenuBarInfo(hwnd HWND, idObject int32, idItem uint32) (*MENUBARINFO, error) { var mi MENUBARINFO - mi.CbSize = uint32(unsafe.Sizeof(&mi)) + mi.CbSize = uint32(unsafe.Sizeof(mi)) ret, _, err := procGetMenuBarInfo.Call( hwnd, uintptr(idObject), diff --git a/v3/pkg/w32/theme.go b/v3/pkg/w32/theme.go index 050d5fd48..0e8c038b0 100644 --- a/v3/pkg/w32/theme.go +++ b/v3/pkg/w32/theme.go @@ -24,6 +24,8 @@ const HCF_HIGHCONTRASTON = 0x00000001 type WINDOWCOMPOSITIONATTRIB DWORD +type HTHEME HANDLE + const ( WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0 WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1 @@ -67,7 +69,10 @@ type WINDOWCOMPOSITIONATTRIBDATA struct { var ( uxtheme = syscall.NewLazyDLL("uxtheme.dll") procSetWindowTheme = uxtheme.NewProc("SetWindowTheme") + procOpenThemeData = uxtheme.NewProc("OpenThemeData") + procDrawThemeBackground = uxtheme.NewProc("DrawThemeBackground") procAllowDarkModeForApplication = uxtheme.NewProc("AllowDarkModeForApp") + procDrawThemeTextEx = uxtheme.NewProc("DrawThemeTextEx") ) type PreferredAppMode = int32 @@ -206,7 +211,7 @@ func SetMenuTheme(hwnd uintptr, useDarkMode bool) { // Set the window theme themeName := "Explorer" if useDarkMode { - themeName = "DarkMode" + themeName = "DarkMode_Explorer" } procSetWindowTheme.Call(HWND(hwnd), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(themeName))), 0) diff --git a/v3/pkg/w32/typedef.go b/v3/pkg/w32/typedef.go index eac8ffad3..cf1c13c09 100644 --- a/v3/pkg/w32/typedef.go +++ b/v3/pkg/w32/typedef.go @@ -241,6 +241,13 @@ func (r *RECT) String() string { return fmt.Sprintf("RECT (%p): Left: %d, Top: %d, Right: %d, Bottom: %d", r, r.Left, r.Top, r.Right, r.Bottom) } +func RectToPoints(rect *RECT) []POINT { + return []POINT{ + {X: rect.Left, Y: rect.Top}, // Top-left + {X: rect.Right, Y: rect.Bottom}, // Bottom-right + } +} + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx type WNDCLASSEX struct { Size uint32 diff --git a/v3/pkg/w32/user32.go b/v3/pkg/w32/user32.go index 84e7670fe..f244507b4 100644 --- a/v3/pkg/w32/user32.go +++ b/v3/pkg/w32/user32.go @@ -162,12 +162,17 @@ var ( procAppendMenu = moduser32.NewProc("AppendMenuW") procSetMenuItemInfo = moduser32.NewProc("SetMenuItemInfoW") procDrawMenuBar = moduser32.NewProc("DrawMenuBar") + procTrackPopupMenu = moduser32.NewProc("TrackPopupMenu") procTrackPopupMenuEx = moduser32.NewProc("TrackPopupMenuEx") procGetKeyState = moduser32.NewProc("GetKeyState") procGetSysColorBrush = moduser32.NewProc("GetSysColorBrush") + procGetSysColor = moduser32.NewProc("GetSysColor") + + procMapWindowPoints = moduser32.NewProc("MapWindowPoints") procGetWindowPlacement = moduser32.NewProc("GetWindowPlacement") procSetWindowPlacement = moduser32.NewProc("SetWindowPlacement") + procGetWindowDC = moduser32.NewProc("GetWindowDC") procGetScrollInfo = moduser32.NewProc("GetScrollInfo") procSetScrollInfo = moduser32.NewProc("SetScrollInfo") @@ -187,6 +192,11 @@ func init() { mainThread = GetCurrentThreadId() } +func GetWindowDC(hwnd HWND) HDC { + ret, _, _ := procGetWindowDC.Call(hwnd) + return ret +} + func GET_X_LPARAM(lp uintptr) int32 { return int32(int16(LOWORD(uint32(lp)))) } @@ -1058,10 +1068,14 @@ func FillRect(hDC HDC, lprc *RECT, hbr HBRUSH) bool { return ret != 0 } -func DrawText(hDC HDC, text string, uCount int, lpRect *RECT, uFormat uint) int { +func DrawText(hDC HDC, text []uint16, uCount int, lpRect *RECT, uFormat uint32) int { + + // Convert the string to a UTF16 pointer + // This is necessary because the DrawText function expects a UTF16 pointer + ret, _, _ := procDrawText.Call( uintptr(hDC), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), + uintptr(unsafe.Pointer(&text[0])), uintptr(uCount), uintptr(unsafe.Pointer(lpRect)), uintptr(uFormat)) diff --git a/v3/pkg/w32/uxtheme.go b/v3/pkg/w32/uxtheme.go deleted file mode 100644 index ed80d487f..000000000 --- a/v3/pkg/w32/uxtheme.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build windows - -/* - * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. - * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. - */ - -package w32 - -import ( - "syscall" - "unsafe" -) - -// LISTVIEW parts -const ( - LVP_LISTITEM = 1 - LVP_LISTGROUP = 2 - LVP_LISTDETAIL = 3 - LVP_LISTSORTEDDETAIL = 4 - LVP_EMPTYTEXT = 5 - LVP_GROUPHEADER = 6 - LVP_GROUPHEADERLINE = 7 - LVP_EXPANDBUTTON = 8 - LVP_COLLAPSEBUTTON = 9 - LVP_COLUMNDETAIL = 10 -) - -// LVP_LISTITEM states -const ( - LISS_NORMAL = 1 - LISS_HOT = 2 - LISS_SELECTED = 3 - LISS_DISABLED = 4 - LISS_SELECTEDNOTFOCUS = 5 - LISS_HOTSELECTED = 6 -) - -// TREEVIEW parts -const ( - TVP_TREEITEM = 1 - TVP_GLYPH = 2 - TVP_BRANCH = 3 - TVP_HOTGLYPH = 4 -) - -// TVP_TREEITEM states -const ( - TREIS_NORMAL = 1 - TREIS_HOT = 2 - TREIS_SELECTED = 3 - TREIS_DISABLED = 4 - TREIS_SELECTEDNOTFOCUS = 5 - TREIS_HOTSELECTED = 6 -) - -type HTHEME HANDLE - -var ( - // Library - libuxtheme uintptr - - // Functions - closeThemeData uintptr - drawThemeBackground uintptr - drawThemeText uintptr - getThemeTextExtent uintptr - openThemeData uintptr - setWindowTheme uintptr -) - -func init() { - // Library - libuxtheme = MustLoadLibrary("uxtheme.dll") - - // Functions - closeThemeData = MustGetProcAddress(libuxtheme, "CloseThemeData") - drawThemeBackground = MustGetProcAddress(libuxtheme, "DrawThemeBackground") - drawThemeText = MustGetProcAddress(libuxtheme, "DrawThemeText") - getThemeTextExtent = MustGetProcAddress(libuxtheme, "GetThemeTextExtent") - openThemeData = MustGetProcAddress(libuxtheme, "OpenThemeData") - setWindowTheme = MustGetProcAddress(libuxtheme, "SetWindowTheme") -} - -func CloseThemeData(hTheme HTHEME) HRESULT { - ret, _, _ := syscall.SyscallN(closeThemeData, - uintptr(hTheme), - 0, - 0) - - return HRESULT(ret) -} - -func DrawThemeBackground(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pRect, pClipRect *RECT) HRESULT { - ret, _, _ := syscall.Syscall6(drawThemeBackground, 6, - uintptr(hTheme), - uintptr(hdc), - uintptr(iPartId), - uintptr(iStateId), - uintptr(unsafe.Pointer(pRect)), - uintptr(unsafe.Pointer(pClipRect))) - - return HRESULT(ret) -} - -func DrawThemeText(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pszText *uint16, iCharCount int32, dwTextFlags, dwTextFlags2 uint32, pRect *RECT) HRESULT { - ret, _, _ := syscall.Syscall9(drawThemeText, 9, - uintptr(hTheme), - uintptr(hdc), - uintptr(iPartId), - uintptr(iStateId), - uintptr(unsafe.Pointer(pszText)), - uintptr(iCharCount), - uintptr(dwTextFlags), - uintptr(dwTextFlags2), - uintptr(unsafe.Pointer(pRect))) - - return HRESULT(ret) -} - -func GetThemeTextExtent(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pszText *uint16, iCharCount int32, dwTextFlags uint32, pBoundingRect, pExtentRect *RECT) HRESULT { - ret, _, _ := syscall.Syscall9(getThemeTextExtent, 9, - uintptr(hTheme), - uintptr(hdc), - uintptr(iPartId), - uintptr(iStateId), - uintptr(unsafe.Pointer(pszText)), - uintptr(iCharCount), - uintptr(dwTextFlags), - uintptr(unsafe.Pointer(pBoundingRect)), - uintptr(unsafe.Pointer(pExtentRect))) - - return HRESULT(ret) -} - -func OpenThemeData(hwnd HWND, pszClassList *uint16) HTHEME { - ret, _, _ := syscall.SyscallN(openThemeData, - uintptr(hwnd), - uintptr(unsafe.Pointer(pszClassList)), - 0) - - return HTHEME(ret) -} - -func SetWindowTheme(hwnd HWND, pszSubAppName, pszSubIdList *uint16) HRESULT { - ret, _, _ := syscall.SyscallN(setWindowTheme, - uintptr(hwnd), - uintptr(unsafe.Pointer(pszSubAppName)), - uintptr(unsafe.Pointer(pszSubIdList))) - - return HRESULT(ret) -}