diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 04dae199e..712adf4ad 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -95,10 +95,6 @@ type WebviewWindowOptions struct { // Y is the starting Y position of the window. Y int - // TransparentTitlebar will make the titlebar transparent. - // TODO: Move to mac window options - FullscreenButtonEnabled bool - // Hidden will hide the window when it is first created. Hidden bool @@ -163,6 +159,13 @@ func NewRGB(red, green, blue uint8) RGBA { } } +func NewRGBPtr(red, green, blue uint8) *uint32 { + result := uint32(red) + result |= uint32(green) << 8 + result |= uint32(blue) << 16 + return &result +} + type BackgroundType int const ( @@ -233,7 +236,7 @@ type WindowsWindow struct { // Specify custom colours to use for dark/light mode // Default: nil - CustomTheme *ThemeSettings + CustomTheme ThemeSettings // Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown. // "Rounded Corners" are only available on Windows 11. @@ -309,21 +312,56 @@ const ( Light Theme = 2 ) +type WindowTheme struct { + // BorderColour is the colour of the window border + BorderColour *uint32 + + // TitleBarColour is the colour of the window title bar + TitleBarColour *uint32 + + // TitleTextColour is the colour of the window title text + TitleTextColour *uint32 +} + +type TextTheme struct { + // Text is the colour of the text + Text *uint32 + + // Background is the background colour of the text + Background *uint32 +} + +type MenuBarTheme struct { + // Default is the default theme + Default *TextTheme + + // Hover defines the theme to use when the menu item is hovered + Hover *TextTheme + + // Selected defines the theme to use when the menu item is selected + Selected *TextTheme +} + // ThemeSettings defines custom colours to use in dark or light mode. // 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 + // Dark mode active window + DarkModeActive *WindowTheme + + // Dark mode inactive window + DarkModeInactive *WindowTheme + + // Light mode active window + LightModeActive *WindowTheme + + // Light mode inactive window + LightModeInactive *WindowTheme + + // Dark mode MenuBar + DarkModeMenuBar *MenuBarTheme + + // Light mode MenuBar + LightModeMenuBar *MenuBarTheme } /****** Mac Options *******/ diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 71401920d..a39696943 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -76,6 +76,9 @@ type windowsWebviewWindow struct { // isMinimizing indicates whether the window is currently being minimized // Used to prevent unnecessary redraws during minimize/restore operations isMinimizing bool + + // menubarTheme is the theme for the menubar + menubarTheme *w32.MenuBarTheme } func (w *windowsWebviewWindow) setMenu(menu *Menu) { @@ -83,6 +86,26 @@ func (w *windowsWebviewWindow) setMenu(menu *Menu) { w.menu = NewApplicationMenu(w, menu) w.menu.parentWindow = w w32.SetMenu(w.hwnd, w.menu.menu) + + // Set menu background if theme is active + if w.menubarTheme != nil { + globalApplication.debug("Applying menubar theme in setMenu", "window", w.parent.id) + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + // Force a repaint of the menu area + w32.InvalidateRect(w.hwnd, nil, true) + } else { + globalApplication.debug("No menubar theme to apply in setMenu", "window", w.parent.id) + } + + // Check if using translucent background with Mica - this makes menubars invisible + if w.parent.options.BackgroundType == BackgroundTypeTranslucent && + (w.parent.options.Windows.BackdropType == Mica || + w.parent.options.Windows.BackdropType == Acrylic || + w.parent.options.Windows.BackdropType == Tabbed) { + // Log warning about menubar visibility issue + globalApplication.debug("Warning: Menubars may be invisible when using translucent backgrounds with Mica/Acrylic/Tabbed effects", "window", w.parent.id) + } } func (w *windowsWebviewWindow) cut() { @@ -422,7 +445,13 @@ func (w *windowsWebviewWindow) run() { // Process the theme switch options.Windows.Theme { case SystemDefault: - w.updateTheme(w32.IsCurrentlyDarkMode()) + isDark := w32.IsCurrentlyDarkMode() + if isDark { + w32.AllowDarkModeForWindow(w.hwnd, true) + } + w.updateTheme(isDark) + // Don't initialize default dark theme here if custom theme might be set + // The updateTheme call above will handle both default and custom themes w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*ApplicationEvent) { InvokeAsync(func() { w.updateTheme(w32.IsCurrentlyDarkMode()) @@ -431,7 +460,10 @@ func (w *windowsWebviewWindow) run() { case Light: w.updateTheme(false) case Dark: + w32.AllowDarkModeForWindow(w.hwnd, true) w.updateTheme(true) + // Don't initialize default dark theme here if custom theme might be set + // The updateTheme call above will handle custom themes } switch options.BackgroundType { @@ -738,6 +770,10 @@ func (w *windowsWebviewWindow) fullscreen() { int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left), int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top), w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) + + // Hide the menubar in fullscreen mode + w32.SetMenu(w.hwnd, 0) + w.chromium.Focus() w.parent.emit(events.Windows.WindowFullscreen) } @@ -756,6 +792,12 @@ func (w *windowsWebviewWindow) unfullscreen() { w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle) w32.SetWindowPlacement(w.hwnd, &w.previousWindowPlacement) w.isCurrentlyFullscreen = false + + // Restore the menubar when exiting fullscreen + if w.menu != nil { + w32.SetMenu(w.hwnd, w.menu.menu) + } + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) w.enableSizeConstraints() @@ -794,10 +836,6 @@ func (w *windowsWebviewWindow) isVisible() bool { return style&w32.WS_VISIBLE != 0 } -func (w *windowsWebviewWindow) setFullscreenButtonEnabled(_ bool) { - // Unused in Windows -} - func (w *windowsWebviewWindow) focus() { w32.SetForegroundWindow(w.hwnd) @@ -1074,7 +1112,7 @@ func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) { w32.SetWindowCompositionAttribute(w.hwnd, &data) } else { - w32.EnableTranslucency(w.hwnd, int32(backdropType)) + w32.EnableTranslucency(w.hwnd, uint32(backdropType)) } } @@ -1096,6 +1134,13 @@ func (w *windowsWebviewWindow) disableIcon() { ) } +func (w *windowsWebviewWindow) processThemeColour(fn func(w32.HWND, uint32), value *uint32) { + if value == nil { + return + } + fn(w.hwnd, *value) +} + func (w *windowsWebviewWindow) isDisabled() bool { style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) return style&w32.WS_DISABLED != 0 @@ -1113,30 +1158,105 @@ func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) { w32.SetTheme(w.hwnd, isDarkMode) + // Clear any existing theme first + if w.menubarTheme != nil && !isDarkMode { + // Reset menu to default Windows theme when switching to light mode + w.menubarTheme = nil + if w.menu != nil { + // Clear the menu background by setting it to default + var mi w32.MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = w32.MIIM_BACKGROUND | w32.MIIM_APPLYTOSUBMENUS + mi.HbrBack = 0 // NULL brush resets to default + w32.SetMenuInfo(w.menu.menu, &mi) + } + } + // Custom theme processing customTheme := w.parent.options.Windows.CustomTheme // Custom theme - if w32.SupportsCustomThemes() && customTheme != nil { - if w.isActive() { - if isDarkMode { - w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar) - w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText) - w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder) - } else { - w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar) - w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText) - w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder) - } + if w32.SupportsCustomThemes() { + var userTheme *MenuBarTheme + if isDarkMode { + userTheme = customTheme.DarkModeMenuBar } else { + userTheme = customTheme.LightModeMenuBar + } + + if userTheme != nil { + modeStr := "light" if isDarkMode { - w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive) - w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive) - w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive) - } else { - w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive) - w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive) - w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive) + modeStr = "dark" } + globalApplication.debug("Setting custom "+modeStr+" menubar theme", "window", w.parent.id) + w.menubarTheme = &w32.MenuBarTheme{ + TitleBarBackground: userTheme.Default.Background, + TitleBarText: userTheme.Default.Text, + MenuBarBackground: userTheme.Default.Background, // Use default background for menubar + MenuHoverBackground: userTheme.Hover.Background, + MenuHoverText: userTheme.Hover.Text, + MenuSelectedBackground: userTheme.Selected.Background, + MenuSelectedText: userTheme.Selected.Text, + } + w.menubarTheme.Init() + + // If menu is already set, update it + if w.menu != nil { + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + } else if userTheme == nil && isDarkMode { + // Use default dark theme if no custom theme provided + globalApplication.debug("Setting default dark menubar theme", "window", w.parent.id) + w.menubarTheme = &w32.MenuBarTheme{ + TitleBarBackground: w32.RGBptr(45, 45, 45), // Dark titlebar + TitleBarText: w32.RGBptr(222, 222, 222), // Slightly muted white + MenuBarBackground: w32.RGBptr(33, 33, 33), // Standard dark mode (#212121) + MenuHoverBackground: w32.RGBptr(48, 48, 48), // Slightly lighter for hover (#303030) + MenuHoverText: w32.RGBptr(222, 222, 222), // Slightly muted white + MenuSelectedBackground: w32.RGBptr(48, 48, 48), // Same as hover + MenuSelectedText: w32.RGBptr(222, 222, 222), // Slightly muted white + } + w.menubarTheme.Init() + + // If menu is already set, update it + if w.menu != nil { + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + } else if userTheme == nil && !isDarkMode && w.menu != nil { + // No custom theme for light mode - ensure menu is reset to default + globalApplication.debug("Resetting menu to default light theme", "window", w.parent.id) + var mi w32.MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = w32.MIIM_BACKGROUND | w32.MIIM_APPLYTOSUBMENUS + mi.HbrBack = 0 // NULL brush resets to default + w32.SetMenuInfo(w.menu.menu, &mi) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + // Define a map for theme selection + themeMap := map[bool]map[bool]*WindowTheme{ + true: { // Window is active + true: customTheme.DarkModeActive, // Dark mode + false: customTheme.LightModeActive, // Light mode + }, + false: { // Window is inactive + true: customTheme.DarkModeInactive, // Dark mode + false: customTheme.LightModeInactive, // Light mode + }, + } + + // Select the appropriate theme + theme := themeMap[w.isActive()][isDarkMode] + + // Apply theme colors + if theme != nil { + w.processThemeColour(w32.SetTitleBarColour, theme.TitleBarColour) + w.processThemeColour(w32.SetTitleTextColour, theme.TitleTextColour) + w.processThemeColour(w32.SetBorderColour, theme.BorderColour) } } } @@ -1148,6 +1268,13 @@ func (w *windowsWebviewWindow) isActive() bool { var resizePending int32 func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr { + + // Use the original implementation that works perfectly for maximized + processed, code := w32.MenuBarWndProc(w.hwnd, msg, wparam, lparam, w.menubarTheme) + if processed { + return code + } + switch msg { case w32.WM_ACTIVATE: if int(wparam&0xffff) == w32.WA_INACTIVE { @@ -1234,6 +1361,7 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp case w32.WM_ERASEBKGND: w.parent.emit(events.Windows.WindowBackgroundErase) return 1 // Let WebView2 handle background erasing + // WM_UAHDRAWMENUITEM is handled by MenuBarWndProc at the top of this function // Check for keypress case w32.WM_SYSCOMMAND: switch wparam { @@ -1271,6 +1399,11 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp switch wparam { case w32.SIZE_MAXIMIZED: w.parent.emit(events.Windows.WindowMaximise) + // Force complete redraw when maximized + if w.menu != nil && w.menubarTheme != nil { + // Invalidate the entire window to force complete redraw + w32.RedrawWindow(w.hwnd, nil, 0, w32.RDW_FRAME|w32.RDW_INVALIDATE|w32.RDW_UPDATENOW) + } case w32.SIZE_RESTORED: if w.isMinimizing { w.parent.emit(events.Windows.WindowUnMinimise) @@ -1468,11 +1601,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 234933ba6..83ed4b9d1 100644 --- a/v3/pkg/w32/constants.go +++ b/v3/pkg/w32/constants.go @@ -528,6 +528,9 @@ const ( WM_PAINT = 15 WM_PAINTCLIPBOARD = 777 WM_PAINTICON = 38 + WM_UAHDRAWMENU = 0x0091 + WM_UAHDRAWMENUITEM = 0x0092 + WM_UAHMEASUREMENUITEM = 0x0094 WM_PALETTECHANGED = 785 WM_PALETTEISCHANGING = 784 WM_PARENTNOTIFY = 528 @@ -1371,6 +1374,7 @@ const ( SM_STARTER = 88 SM_SERVERR2 = 89 SM_CMETRICS = 91 + SM_CXPADDEDBORDER = 92 SM_REMOTESESSION = 0x1000 SM_SHUTTINGDOWN = 0x2000 SM_REMOTECONTROL = 0x2001 diff --git a/v3/pkg/w32/gdi32.go b/v3/pkg/w32/gdi32.go index b4b9053e6..04d11ca14 100644 --- a/v3/pkg/w32/gdi32.go +++ b/v3/pkg/w32/gdi32.go @@ -62,6 +62,11 @@ var ( procGetPixelFormat = modgdi32.NewProc("GetPixelFormat") procSetPixelFormat = modgdi32.NewProc("SetPixelFormat") procSwapBuffers = modgdi32.NewProc("SwapBuffers") + procSaveDC = modgdi32.NewProc("SaveDC") + procRestoreDC = modgdi32.NewProc("RestoreDC") + procSelectClipRgn = modgdi32.NewProc("SelectClipRgn") + procExcludeClipRect = modgdi32.NewProc("ExcludeClipRect") + procExtTextOut = modgdi32.NewProc("ExtTextOutW") ) func GetDeviceCaps(hdc HDC, index int) int { @@ -524,3 +529,53 @@ func SwapBuffers(hdc HDC) bool { ret, _, _ := procSwapBuffers.Call(uintptr(hdc)) return ret == TRUE } + +func SaveDC(hdc HDC) int { + ret, _, _ := procSaveDC.Call(uintptr(hdc)) + return int(ret) +} + +func RestoreDC(hdc HDC, nSavedDC int) bool { + ret, _, _ := procRestoreDC.Call( + uintptr(hdc), + uintptr(nSavedDC)) + return ret != 0 +} + +func SelectClipRgn(hdc HDC, hrgn HRGN) int { + ret, _, _ := procSelectClipRgn.Call( + uintptr(hdc), + uintptr(hrgn)) + return int(ret) +} + +func ExcludeClipRect(hdc HDC, left, top, right, bottom int32) int { + ret, _, _ := procExcludeClipRect.Call( + uintptr(hdc), + uintptr(left), + uintptr(top), + uintptr(right), + uintptr(bottom)) + return int(ret) +} + +func ExtTextOut(hdc HDC, x, y int32, fuOptions uint32, lprc *RECT, lpString *uint16, cbCount uint32, lpDx *int) bool { + var rectPtr uintptr + if lprc != nil { + rectPtr = uintptr(unsafe.Pointer(lprc)) + } + var dxPtr uintptr + if lpDx != nil { + dxPtr = uintptr(unsafe.Pointer(lpDx)) + } + ret, _, _ := procExtTextOut.Call( + uintptr(hdc), + uintptr(x), + uintptr(y), + uintptr(fuOptions), + rectPtr, + uintptr(unsafe.Pointer(lpString)), + uintptr(cbCount), + dxPtr) + return ret != 0 +} diff --git a/v3/pkg/w32/menubar.go b/v3/pkg/w32/menubar.go new file mode 100644 index 000000000..155eb0d1c --- /dev/null +++ b/v3/pkg/w32/menubar.go @@ -0,0 +1,978 @@ +package w32 + +import ( + "os" + "unsafe" +) + +const ( + OBJID_MENU = -3 + ODT_MENU = 1 + // Menu info flags + MIIM_BACKGROUND = 0x00000002 + MIIM_APPLYTOSUBMENUS = 0x80000000 +) + +var ( + menuTheme HTHEME + procSetMenuInfo = moduser32.NewProc("SetMenuInfo") +) + +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 + MENU_BARITEM = 8 // Menu bar item part ID for theme drawing + 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") + procGetMenuItemCount = moduser32.NewProc("GetMenuItemCount") + procGetMenuItemRect = moduser32.NewProc("GetMenuItemRect") +) + +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 +} + +func GetMenuItemCount(hmenu HMENU) int { + ret, _, _ := procGetMenuItemCount.Call(uintptr(hmenu)) + return int(ret) +} + +func GetMenuItemRect(hwnd HWND, hmenu HMENU, item uint32, rect *RECT) bool { + ret, _, _ := procGetMenuItemRect.Call( + uintptr(hwnd), + uintptr(hmenu), + uintptr(item), + uintptr(unsafe.Pointer(rect)), + ) + return ret != 0 +} + +// Helper function to convert bool to uint +func boolToUint(b bool) uint { + if b { + return 1 + } + return 0 +} + +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 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 +} + +// 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 +} + +type MenuBarTheme struct { + TitleBarBackground *uint32 + TitleBarText *uint32 + MenuBarBackground *uint32 // Separate color for menubar + MenuHoverBackground *uint32 + MenuHoverText *uint32 + MenuSelectedBackground *uint32 + MenuSelectedText *uint32 + + // private brushes + titleBarBackgroundBrush HBRUSH + menuBarBackgroundBrush HBRUSH // Separate brush for menubar + menuHoverBackgroundBrush HBRUSH + menuSelectedBackgroundBrush HBRUSH +} + +func createColourWithDefaultColor(color *uint32, def uint32) *uint32 { + if color == nil { + return &def + } + return color +} + +func (d *MenuBarTheme) Init() { + d.TitleBarBackground = createColourWithDefaultColor(d.TitleBarBackground, RGB(25, 25, 26)) + d.TitleBarText = createColourWithDefaultColor(d.TitleBarText, RGB(222, 222, 222)) + d.MenuBarBackground = createColourWithDefaultColor(d.MenuBarBackground, RGB(33, 33, 33)) + d.MenuSelectedText = createColourWithDefaultColor(d.MenuSelectedText, RGB(222, 222, 222)) + d.MenuSelectedBackground = createColourWithDefaultColor(d.MenuSelectedBackground, RGB(48, 48, 48)) + d.MenuHoverText = createColourWithDefaultColor(d.MenuHoverText, RGB(222, 222, 222)) + d.MenuHoverBackground = createColourWithDefaultColor(d.MenuHoverBackground, RGB(48, 48, 48)) + // Create brushes + d.titleBarBackgroundBrush = CreateSolidBrush(*d.TitleBarBackground) + d.menuBarBackgroundBrush = CreateSolidBrush(*d.MenuBarBackground) + d.menuHoverBackgroundBrush = CreateSolidBrush(*d.MenuHoverBackground) + d.menuSelectedBackgroundBrush = CreateSolidBrush(*d.MenuSelectedBackground) +} + +// SetMenuBackground sets the menu background brush directly +func (d *MenuBarTheme) SetMenuBackground(hmenu HMENU) { + var mi MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = MIIM_BACKGROUND | MIIM_APPLYTOSUBMENUS + mi.HbrBack = d.menuBarBackgroundBrush // Use separate menubar brush + SetMenuInfo(hmenu, &mi) +} + +// SetMenuInfo wrapper function +func SetMenuInfo(hmenu HMENU, lpcmi *MENUINFO) bool { + ret, _, _ := procSetMenuInfo.Call( + uintptr(hmenu), + uintptr(unsafe.Pointer(lpcmi))) + return ret != 0 +} + +func CreateSolidBrush(color COLORREF) HBRUSH { + ret, _, _ := procCreateSolidBrush.Call( + uintptr(color), + ) + return HBRUSH(ret) +} + +func RGB(r, g, b byte) uint32 { + return uint32(r) | uint32(g)<<8 | uint32(b)<<16 +} + +func RGBptr(r, g, b byte) *uint32 { + result := uint32(r) | uint32(g)<<8 | uint32(b)<<16 + return &result +} + +// Track hover state for menubar items when maximized +var ( + currentHoverItem int = -1 + menuIsOpen bool = false // Track if a dropdown menu is open +) + +func MenuBarWndProc(hwnd HWND, msg uint32, wParam WPARAM, lParam LPARAM, theme *MenuBarTheme) (bool, LRESULT) { + // Only proceed if we have a theme (either for dark or light mode) + if theme == nil { + return false, 0 + } + switch msg { + case WM_UAHDRAWMENU: + udm := (*UAHMENU)(unsafe.Pointer(lParam)) + + // Check if maximized first + isMaximized := IsZoomed(hwnd) + + // get the menubar rect + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + return false, 0 + } + + winRect := GetWindowRect(hwnd) + + // the rcBar is offset by the window rect + rc := menuBarInfo.Bar + OffsetRect(&rc, int(-winRect.Left), int(-winRect.Top)) + + // DEBUG: Log the coordinates + // println("WM_UAHDRAWMENU: maximized=", isMaximized) + // println(" menubar screen rect: L=", menuBarInfo.Bar.Left, "T=", menuBarInfo.Bar.Top, + // "R=", menuBarInfo.Bar.Right, "B=", menuBarInfo.Bar.Bottom) + // println(" window rect: L=", winRect.Left, "T=", winRect.Top, + // "R=", winRect.Right, "B=", winRect.Bottom) + // println(" converted rect: L=", rc.Left, "T=", rc.Top, + // "R=", rc.Right, "B=", rc.Bottom) + + // When maximized, Windows extends the window beyond the visible area + // We need to adjust the menubar rect to ensure it's fully visible + if isMaximized { + // Get the frame size - this is how much the window extends beyond visible area when maximized + frameY := GetSystemMetrics(SM_CYSIZEFRAME) + paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER) + + // In Windows 10/11, the actual border is frame + padding + borderSize := frameY + paddedBorder + + // println(" Frame metrics: frameY=", frameY, "paddedBorder=", paddedBorder, "borderSize=", borderSize) + + // First, fill the area from the top of the visible area to the menubar + topFillRect := RECT{ + Left: rc.Left, + Top: int32(borderSize), // Start of visible area in window coordinates + Right: rc.Right, + Bottom: rc.Top, // Up to where the menubar starts + } + FillRect(udm.Hdc, &topFillRect, theme.menuBarBackgroundBrush) + } + + // Fill the entire menubar background with dark color + FillRect(udm.Hdc, &rc, theme.menuBarBackgroundBrush) + + // Paint over the menubar border explicitly + // The border is typically 1-2 pixels at the bottom + borderRect := rc + borderRect.Top = borderRect.Bottom - 1 + borderRect.Bottom = borderRect.Bottom + 2 + FillRect(udm.Hdc, &borderRect, theme.menuBarBackgroundBrush) + + // When maximized, we still need to handle the drawing ourselves + // Some projects found that returning false here causes issues + + // When maximized, manually draw all menu items here + if isMaximized { + // Draw each menu item manually + itemCount := GetMenuItemCount(menuBarInfo.Menu) + for i := 0; i < itemCount; i++ { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) { + // Convert to window coordinates + OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top)) + + // Check if this item is hovered + if i == currentHoverItem { + // Fill with hover background + FillRect(udm.Hdc, &itemRect, theme.menuHoverBackgroundBrush) + } + + // Get menu text + menuString := make([]uint16, 256) + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if GetMenuItemInfo(menuBarInfo.Menu, uint32(i), true, &mii) { + // Draw the text + if i == currentHoverItem { + SetTextColor(udm.Hdc, COLORREF(*theme.MenuHoverText)) + } else { + SetTextColor(udm.Hdc, COLORREF(*theme.TitleBarText)) + } + SetBkMode(udm.Hdc, TRANSPARENT) + DrawText(udm.Hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } + } + } + + // Return the original HDC so Windows can draw the menu text + return true, LRESULT(udm.Hdc) + case WM_DRAWITEM: + // Handle owner-drawn menu items + dis := (*DRAWITEMSTRUCT)(unsafe.Pointer(lParam)) + + // Check if this is a menu item + if dis.ControlType == ODT_MENU { + // Draw the menu item background + var bgBrush HBRUSH + var textColor uint32 + + if dis.ItemState&ODS_SELECTED != 0 { + // Selected state + bgBrush = theme.menuSelectedBackgroundBrush + textColor = *theme.MenuSelectedText + } else { + // Normal state + bgBrush = theme.titleBarBackgroundBrush + textColor = *theme.TitleBarText + } + + // Fill background + FillRect(dis.HDC, &dis.RcItem, bgBrush) + + // Draw text if we have item data + if dis.ItemData != 0 { + text := (*uint16)(unsafe.Pointer(dis.ItemData)) + if text != nil { + // Set text color and draw + SetTextColor(dis.HDC, COLORREF(textColor)) + SetBkMode(dis.HDC, TRANSPARENT) + DrawText(dis.HDC, (*[256]uint16)(unsafe.Pointer(text))[:], -1, &dis.RcItem, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } + + return true, 1 + } + case WM_UAHDRAWMENUITEM: + udmi := (*UAHDRAWMENUITEM)(unsafe.Pointer(lParam)) + + // Check if we're getting menu item draw messages when maximized or fullscreen + isMaximized := IsZoomed(hwnd) + + // Create buffer for menu text + menuString := make([]uint16, 256) + + // Setup menu item info structure + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING | MIIM_SUBMENU, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if !GetMenuItemInfo(udmi.UM.Hmenu, uint32(udmi.UAMI.Position), true, &mii) { + // Failed to get menu item info, let default handler process + return false, 0 + } + + // Remove automatic popup on hover - menus should only open on click + // This was causing the menu to appear at wrong coordinates + dwFlags := uint32(DT_CENTER | DT_SINGLELINE | DT_VCENTER) + + // When maximized/fullscreen, try without VCENTER to see if text appears + if isMaximized && os.Getenv("WAILS_TEST_NO_VCENTER") == "1" { + dwFlags = uint32(DT_CENTER | DT_SINGLELINE) + println(" Using dwFlags without VCENTER") + } + + // Check if this is a menubar item + // When dwFlags has 0x0A00 (2560) it's a menubar item + isMenuBarItem := (udmi.UM.DwFlags&0x0A00) == 0x0A00 || udmi.UM.DwFlags == 0 + + // 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 = theme.menuHoverBackgroundBrush + textColor = *theme.MenuHoverText + } else if udmi.DIS.ItemState&ODS_SELECTED != 0 { + // Selected state + bgBrush = theme.menuSelectedBackgroundBrush + textColor = *theme.MenuSelectedText + } else { + // Normal state + if isMenuBarItem { + // Menubar items in normal state + bgBrush = theme.menuBarBackgroundBrush + textColor = *theme.TitleBarText + } else { + // Popup menu items in normal state - use same color as menubar + bgBrush = theme.menuBarBackgroundBrush + textColor = *theme.TitleBarText + } + } + + // Fill background + if bgBrush != 0 { + FillRect(udmi.UM.Hdc, &udmi.DIS.RcItem, bgBrush) + } + + // Draw text + SetTextColor(udmi.UM.Hdc, COLORREF(textColor)) + SetBkMode(udmi.UM.Hdc, TRANSPARENT) + + // When maximized/fullscreen and menubar item, use the same font settings as drawMenuBarText + if isMaximized && isMenuBarItem { + // Create a non-bold font explicitly + menuFont := LOGFONT{ + Height: -12, // Standard Windows menu font height (9pt) + Weight: 400, // FW_NORMAL (not bold) + CharSet: 1, // DEFAULT_CHARSET + Quality: 5, // CLEARTYPE_QUALITY + PitchAndFamily: 0, // DEFAULT_PITCH + } + // Set font face name to "Segoe UI" (Windows default) + fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0} + copy(menuFont.FaceName[:], fontName) + + hFont := CreateFontIndirect(&menuFont) + if hFont != 0 { + oldFont := SelectObject(udmi.UM.Hdc, HGDIOBJ(hFont)) + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + SelectObject(udmi.UM.Hdc, oldFont) + DeleteObject(HGDIOBJ(hFont)) + } else { + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + } + return true, 4 // CDRF_SKIPDEFAULT + } else { + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + } + + // Return appropriate value based on whether we're in maximized/fullscreen + // For maximized, we need to ensure Windows doesn't override our drawing + if isMaximized { + // Skip default processing to prevent Windows from overriding our colors + return true, 4 // CDRF_SKIPDEFAULT + } + // Return 1 to indicate we've handled the drawing + return true, 1 + case WM_UAHMEASUREMENUITEM: + // Let the default window procedure handle the menu item measurement + // We're not modifying the default sizing anymore + result := DefWindowProc(hwnd, msg, wParam, lParam) + + return true, result + case WM_NCPAINT: + // Paint our custom menubar first + paintDarkMenuBar(hwnd, theme) + + // Then let Windows do its default painting + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Paint again to ensure our painting is on top + paintDarkMenuBar(hwnd, theme) + + return true, result + case WM_NCACTIVATE: + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Force paint the menubar with dark background + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_PAINT: + // Let Windows paint first + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Then paint our menubar + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_ACTIVATEAPP, WM_ACTIVATE: + // Handle app activation/deactivation + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_SIZE, WM_WINDOWPOSCHANGED: + // Handle window size changes + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar after size change + paintDarkMenuBar(hwnd, theme) + + // CRITICAL: Force complete menubar redraw when maximized + if msg == WM_SIZE && wParam == SIZE_MAXIMIZED { + // Invalidate the entire menubar area to force redraw + var mbi MENUBARINFO + mbi.CbSize = uint32(unsafe.Sizeof(mbi)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi) { + InvalidateRect(hwnd, &mbi.Bar, true) + DrawMenuBar(hwnd) + } + } + + return false, result + case WM_SETFOCUS, WM_KILLFOCUS: + // Handle focus changes (e.g., when inspector opens) + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar after focus change + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_ERASEBKGND: + // When maximized, draw menubar text here + if IsZoomed(hwnd) { + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + hdc := HDC(wParam) + drawMenuBarText(hwnd, hdc, &menuBarInfo, theme) + } + } + return false, 0 + case WM_NCMOUSEMOVE, WM_MOUSEMOVE: + // Track mouse movement for hover effects when maximized + if IsZoomed(hwnd) { + // Don't process hover changes while menu is open + if menuIsOpen { + return false, 0 + } + + var screenX, screenY int32 + if msg == WM_NCMOUSEMOVE { + // For NC messages, lParam contains screen coordinates + screenX = int32(LOWORD(uint32(lParam))) + screenY = int32(HIWORD(uint32(lParam))) + } else { + // For regular MOUSEMOVE, convert client to screen coordinates + clientX := int32(LOWORD(uint32(lParam))) + clientY := int32(HIWORD(uint32(lParam))) + sx, sy := ClientToScreen(hwnd, int(clientX), int(clientY)) + screenX = int32(sx) + screenY = int32(sy) + } + + // Check if we're over the menubar + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + // menuBarInfo.Bar already contains screen coordinates + // Check if mouse is over menubar using screen coordinates + if screenX >= menuBarInfo.Bar.Left && screenX <= menuBarInfo.Bar.Right && + screenY >= menuBarInfo.Bar.Top && screenY <= menuBarInfo.Bar.Bottom { + + // Always re-request mouse tracking to ensure we get leave messages + TrackMouseEvent(&TRACKMOUSEEVENT{ + CbSize: uint32(unsafe.Sizeof(TRACKMOUSEEVENT{})), + DwFlags: TME_LEAVE | TME_NONCLIENT, + HwndTrack: hwnd, + DwHoverTime: 0, + }) + // Find which menu item we're over + itemCount := GetMenuItemCount(menuBarInfo.Menu) + newHoverItem := -1 + + for i := 0; i < itemCount; i++ { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) { + // itemRect is already in screen coordinates from GetMenuItemRect + // Check using screen coordinates + if screenX >= itemRect.Left && screenX <= itemRect.Right && + screenY >= itemRect.Top && screenY <= itemRect.Bottom { + newHoverItem = i + break + } + } + } + + // If hover item changed, update and redraw just the menubar + if newHoverItem != currentHoverItem { + currentHoverItem = newHoverItem + // Get the actual menubar rect for precise invalidation + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + // Convert to window coordinates + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + // Invalidate only the menubar + InvalidateRect(hwnd, &menubarRect, false) + } + } else { + // Mouse left menubar + if currentHoverItem != -1 { + currentHoverItem = -1 + // Get the actual menubar rect + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + // Convert to window coordinates + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + } + } + } + return false, 0 + case WM_NCLBUTTONDOWN: + // When clicking on menubar, clear hover state immediately + if IsZoomed(hwnd) && currentHoverItem != -1 { + // Check if click is on menubar + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + // Get click position (screen coordinates) + clickX := int32(LOWORD(uint32(lParam))) + clickY := int32(HIWORD(uint32(lParam))) + + if clickX >= menuBarInfo.Bar.Left && clickX <= menuBarInfo.Bar.Right && + clickY >= menuBarInfo.Bar.Top && clickY <= menuBarInfo.Bar.Bottom { + // Click is on menubar - clear hover + currentHoverItem = -1 + } + } + } + return false, 0 + case WM_NCMOUSELEAVE, WM_MOUSELEAVE: + // Clear hover state when mouse leaves (but not if menu is open) + if IsZoomed(hwnd) && currentHoverItem != -1 && !menuIsOpen { + currentHoverItem = -1 + // Get menubar info for precise invalidation + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + } + return false, 0 + case WM_ENTERMENULOOP: + // Menu is being opened - clear hover state + menuIsOpen = true + if IsZoomed(hwnd) && currentHoverItem != -1 { + oldHoverItem := currentHoverItem + currentHoverItem = -1 + // Redraw the previously hovered item to remove hover effect + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(oldHoverItem), &itemRect) { + winRect := GetWindowRect(hwnd) + // Convert to window coordinates + itemRect.Left -= winRect.Left + itemRect.Top -= winRect.Top + itemRect.Right -= winRect.Left + itemRect.Bottom -= winRect.Top + // Add some padding + itemRect.Left -= 5 + itemRect.Right += 5 + itemRect.Top -= 5 + itemRect.Bottom += 5 + InvalidateRect(hwnd, &itemRect, false) + } + } + } + return false, 0 + case WM_EXITMENULOOP: + // Menu has been closed + menuIsOpen = false + // Clear any existing hover state first + currentHoverItem = -1 + // Force a complete menubar redraw + if IsZoomed(hwnd) { + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + // Force a timer to restart mouse tracking + SetTimer(hwnd, 1001, 50, 0) + } + return false, 0 + case WM_TIMER: + // Handle our mouse tracking restart timer + if wParam == 1001 { + KillTimer(hwnd, 1001) + if IsZoomed(hwnd) { + // Get current mouse position and simulate a mouse move + x, y, _ := GetCursorPos() + // Check if mouse is over the window + winRect := GetWindowRect(hwnd) + if x >= int(winRect.Left) && x <= int(winRect.Right) && + y >= int(winRect.Top) && y <= int(winRect.Bottom) { + // Check if we're over the menubar specifically + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + if int32(x) >= menuBarInfo.Bar.Left && int32(x) <= menuBarInfo.Bar.Right && + int32(y) >= menuBarInfo.Bar.Top && int32(y) <= menuBarInfo.Bar.Bottom { + // Post a non-client mouse move to restart tracking + PostMessage(hwnd, WM_NCMOUSEMOVE, 0, uintptr(y)<<16|uintptr(x)&0xFFFF) + } else { + // Convert to client coordinates for regular mouse move + clientX, clientY, _ := ScreenToClient(hwnd, x, y) + // Post a mouse move message to restart tracking + PostMessage(hwnd, WM_MOUSEMOVE, 0, uintptr(clientY)<<16|uintptr(clientX)&0xFFFF) + } + } + } + } + return true, 0 + } + return false, 0 + } + return false, 0 +} + +// paintDarkMenuBar paints the menubar with dark background +func paintDarkMenuBar(hwnd HWND, theme *MenuBarTheme) { + // Get menubar info + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + return + } + + // Get window DC + hdc := GetWindowDC(hwnd) + if hdc == 0 { + return + } + defer ReleaseDC(hwnd, hdc) + + // Check if window is maximized or fullscreen + isMaximized := IsZoomed(hwnd) + isFullscreen := false + + // Check if window is in fullscreen by checking if it covers the monitor + windowRect := GetWindowRect(hwnd) + monitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY) + var monitorInfo MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if GetMonitorInfo(monitor, &monitorInfo) { + // If window matches monitor bounds, it's fullscreen + if windowRect.Left == monitorInfo.RcMonitor.Left && + windowRect.Top == monitorInfo.RcMonitor.Top && + windowRect.Right == monitorInfo.RcMonitor.Right && + windowRect.Bottom == monitorInfo.RcMonitor.Bottom { + isFullscreen = true + } + } + + // When maximized or fullscreen, we need to handle the special case + if isMaximized || isFullscreen { + // Convert menubar rect from screen to window coordinates + menubarRect := menuBarInfo.Bar + menubarRect.Left -= windowRect.Left + menubarRect.Top -= windowRect.Top + menubarRect.Right -= windowRect.Left + menubarRect.Bottom -= windowRect.Top + + if isMaximized && !isFullscreen { + // Get the frame size (only for maximized, not fullscreen) + frameY := GetSystemMetrics(SM_CYSIZEFRAME) + paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER) + borderSize := frameY + paddedBorder + + // Fill from visible area top to menubar + topFillRect := RECT{ + Left: menubarRect.Left, + Top: int32(borderSize), // Start of visible area + Right: menubarRect.Right, + Bottom: menubarRect.Top, + } + FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush) + } else if isFullscreen { + // In fullscreen, fill from the very top + topFillRect := RECT{ + Left: menubarRect.Left, + Top: 0, // Start from top in fullscreen + Right: menubarRect.Right, + Bottom: menubarRect.Top, + } + FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush) + } + + // Fill the menubar itself + FillRect(hdc, &menubarRect, theme.menuBarBackgroundBrush) + } else { + // Paint the menubar background with dark color + FillRect(hdc, &menuBarInfo.Bar, theme.menuBarBackgroundBrush) + } + + // Get window and client rects to find the non-client area + clientRect := GetClientRect(hwnd) + + // Convert client rect top-left to screen coordinates + _, screenY := ClientToScreen(hwnd, int(clientRect.Left), int(clientRect.Top)) + + // Paint the entire area between menubar and client area + // This should cover any borders + borderRect := RECT{ + Left: 0, + Top: menuBarInfo.Bar.Bottom - windowRect.Top, + Right: windowRect.Right - windowRect.Left, + Bottom: int32(screenY) - windowRect.Top, + } + FillRect(hdc, &borderRect, theme.menuBarBackgroundBrush) + + // When maximized or fullscreen, also draw menubar text + if isMaximized || isFullscreen { + drawMenuBarText(hwnd, hdc, &menuBarInfo, theme) + } +} + +func drawMenuBarText(hwnd HWND, hdc HDC, menuBarInfo *MENUBARINFO, theme *MenuBarTheme) { + // Get the menu handle + hmenu := menuBarInfo.Menu + if hmenu == 0 { + return + } + + // Get the number of menu items + itemCount := GetMenuItemCount(hmenu) + if itemCount <= 0 { + return + } + + // Create a non-bold font explicitly + menuFont := LOGFONT{ + Height: -12, // Standard Windows menu font height (9pt) + Weight: 400, // FW_NORMAL (not bold) + CharSet: 1, // DEFAULT_CHARSET + Quality: 5, // CLEARTYPE_QUALITY + PitchAndFamily: 0, // DEFAULT_PITCH + } + // Set font face name to "Segoe UI" (Windows default) + fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0} + copy(menuFont.FaceName[:], fontName) + + hFont := CreateFontIndirect(&menuFont) + if hFont != 0 { + oldFont := SelectObject(hdc, HGDIOBJ(hFont)) + defer func() { + SelectObject(hdc, oldFont) + DeleteObject(HGDIOBJ(hFont)) + }() + } + + // Set text color and background mode + SetTextColor(hdc, COLORREF(*theme.TitleBarText)) + SetBkMode(hdc, TRANSPARENT) + + // Get the window rect for coordinate conversion + winRect := GetWindowRect(hwnd) + + // Iterate through each menu item + for i := 0; i < itemCount; i++ { + // Get the menu item rect + var itemRect RECT + if !GetMenuItemRect(hwnd, hmenu, uint32(i), &itemRect) { + continue + } + + // Convert to window coordinates + OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top)) + + // Check if this item is hovered + if i == currentHoverItem { + // Fill with hover background + FillRect(hdc, &itemRect, theme.menuHoverBackgroundBrush) + } + + // Get the menu item text + menuString := make([]uint16, 256) + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if GetMenuItemInfo(hmenu, uint32(i), true, &mii) { + // Set text color based on hover state + if i == currentHoverItem { + SetTextColor(hdc, COLORREF(*theme.MenuHoverText)) + } else { + SetTextColor(hdc, COLORREF(*theme.TitleBarText)) + } + // Draw the text + DrawText(hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } +} diff --git a/v3/pkg/w32/theme.go b/v3/pkg/w32/theme.go index 40ebb69bd..bbb510ef6 100644 --- a/v3/pkg/w32/theme.go +++ b/v3/pkg/w32/theme.go @@ -3,8 +3,10 @@ package w32 import ( + "syscall" "unsafe" + "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -20,6 +22,154 @@ const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38 const SPI_GETHIGHCONTRAST = 0x0042 const HCF_HIGHCONTRASTON = 0x00000001 +type WINDOWCOMPOSITIONATTRIB DWORD + +type HTHEME HANDLE + +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") + procOpenThemeData = uxtheme.NewProc("OpenThemeData") + procCloseThemeData = uxtheme.NewProc("CloseThemeData") + procDrawThemeBackground = uxtheme.NewProc("DrawThemeBackground") + procAllowDarkModeForApplication = uxtheme.NewProc("AllowDarkModeForApp") + procDrawThemeTextEx = uxtheme.NewProc("DrawThemeTextEx") +) + +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 +199,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_Explorer" + } + 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,22 +246,25 @@ func SetTheme(hwnd uintptr, useDarkMode bool) { winDark = 1 } dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark)) + SetMenuTheme(hwnd, useDarkMode) } } -func EnableTranslucency(hwnd uintptr, backdrop int32) { +func EnableTranslucency(hwnd uintptr, backdrop uint32) { dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop)) } -func SetTitleBarColour(hwnd uintptr, titleBarColour int32) { +func SetTitleBarColour(hwnd uintptr, titleBarColour uint32) { + // Debug: Print the color value being set + // fmt.Printf("Setting titlebar color to: 0x%08X (RGB: %d, %d, %d)\n", titleBarColour, titleBarColour&0xFF, (titleBarColour>>8)&0xFF, (titleBarColour>>16)&0xFF) dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour)) } -func SetTitleTextColour(hwnd uintptr, titleTextColour int32) { +func SetTitleTextColour(hwnd uintptr, titleTextColour uint32) { dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour)) } -func SetBorderColour(hwnd uintptr, titleBorderColour int32) { +func SetBorderColour(hwnd uintptr, titleBorderColour uint32) { dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour)) } @@ -110,3 +299,38 @@ func IsCurrentlyHighContrastMode() bool { r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON return r } + +// OpenThemeData opens theme data for a window and its class +func OpenThemeData(hwnd HWND, pszClassList string) HTHEME { + ret, _, _ := procOpenThemeData.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(pszClassList)))) + return HTHEME(ret) +} + +// CloseThemeData closes theme data handle +func CloseThemeData(hTheme HTHEME) error { + ret, _, err := procCloseThemeData.Call(uintptr(hTheme)) + if ret != 0 { + return err + } + return nil +} + +// DrawThemeTextEx draws theme text with extended options +func DrawThemeTextEx(hTheme HTHEME, hdc HDC, iPartId int32, iStateId int32, pszText []uint16, cchText int32, dwTextFlags uint32, pRect *RECT, pOptions *DTTOPTS) error { + ret, _, err := procDrawThemeTextEx.Call( + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(&pszText[0])), + uintptr(cchText), + uintptr(dwTextFlags), + uintptr(unsafe.Pointer(pRect)), + uintptr(unsafe.Pointer(pOptions))) + if ret != 0 { + return err + } + return nil +} diff --git a/v3/pkg/w32/timer.go b/v3/pkg/w32/timer.go new file mode 100644 index 000000000..7f49f07fa --- /dev/null +++ b/v3/pkg/w32/timer.go @@ -0,0 +1,17 @@ +package w32 + +func SetTimer(hwnd HWND, nIDEvent uintptr, uElapse uint32, lpTimerFunc uintptr) uintptr { + ret, _, _ := procSetTimer.Call( + uintptr(hwnd), + nIDEvent, + uintptr(uElapse), + lpTimerFunc) + return ret +} + +func KillTimer(hwnd HWND, nIDEvent uintptr) bool { + ret, _, _ := procKillTimer.Call( + uintptr(hwnd), + nIDEvent) + return ret != 0 +} \ No newline at end of file diff --git a/v3/pkg/w32/typedef.go b/v3/pkg/w32/typedef.go index bbd6aa9d2..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 @@ -780,48 +787,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 diff --git a/v3/pkg/w32/user32.go b/v3/pkg/w32/user32.go index 4117afd2e..8988b786e 100644 --- a/v3/pkg/w32/user32.go +++ b/v3/pkg/w32/user32.go @@ -26,6 +26,7 @@ var ( procShowWindow = moduser32.NewProc("ShowWindow") procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") procShowWindowAsync = moduser32.NewProc("ShowWindowAsync") + procIsZoomed = moduser32.NewProc("IsZoomed") procUpdateWindow = moduser32.NewProc("UpdateWindow") procCreateWindowEx = moduser32.NewProc("CreateWindowExW") procAdjustWindowRect = moduser32.NewProc("AdjustWindowRect") @@ -106,6 +107,7 @@ var ( procTranslateAccelerator = moduser32.NewProc("TranslateAcceleratorW") procSetWindowPos = moduser32.NewProc("SetWindowPos") procFillRect = moduser32.NewProc("FillRect") + procFrameRect = moduser32.NewProc("FrameRect") procDrawText = moduser32.NewProc("DrawTextW") procAddClipboardFormatListener = moduser32.NewProc("AddClipboardFormatListener") procRemoveClipboardFormatListener = moduser32.NewProc("RemoveClipboardFormatListener") @@ -145,6 +147,8 @@ var ( procEnumWindows = moduser32.NewProc("EnumWindows") procEnumChildWindows = moduser32.NewProc("EnumChildWindows") procChangeDisplaySettingsEx = moduser32.NewProc("ChangeDisplaySettingsExW") + procSetTimer = moduser32.NewProc("SetTimer") + procKillTimer = moduser32.NewProc("KillTimer") procSendInput = moduser32.NewProc("SendInput") procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW") procUnhookWindowsHookEx = moduser32.NewProc("UnhookWindowsHookEx") @@ -162,12 +166,17 @@ var ( procAppendMenu = moduser32.NewProc("AppendMenuW") procSetMenuItemInfo = moduser32.NewProc("SetMenuItemInfoW") procDrawMenuBar = moduser32.NewProc("DrawMenuBar") + procTrackPopupMenu = moduser32.NewProc("TrackPopupMenu") procTrackPopupMenuEx = moduser32.NewProc("TrackPopupMenuEx") + procGetMenuBarInfo = moduser32.NewProc("GetMenuBarInfo") + procMapWindowPoints = moduser32.NewProc("MapWindowPoints") procGetKeyState = moduser32.NewProc("GetKeyState") procGetSysColorBrush = moduser32.NewProc("GetSysColorBrush") + procGetSysColor = moduser32.NewProc("GetSysColor") procGetWindowPlacement = moduser32.NewProc("GetWindowPlacement") procSetWindowPlacement = moduser32.NewProc("SetWindowPlacement") + procGetWindowDC = moduser32.NewProc("GetWindowDC") procGetScrollInfo = moduser32.NewProc("GetScrollInfo") procSetScrollInfo = moduser32.NewProc("SetScrollInfo") @@ -188,6 +197,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)))) } @@ -267,6 +281,11 @@ func ShowWindow(hwnd HWND, cmdshow int) bool { return ret != 0 } +func IsZoomed(hwnd HWND) bool { + ret, _, _ := procIsZoomed.Call(uintptr(hwnd)) + return ret != 0 +} + func ShowWindowAsync(hwnd HWND, cmdshow int) bool { ret, _, _ := procShowWindowAsync.Call( uintptr(hwnd), @@ -754,6 +773,13 @@ func GetSysColorBrush(nIndex int) HBRUSH { return HBRUSH(ret) } +func GetSysColor(nIndex int) COLORREF { + ret, _, _ := procGetSysColor.Call( + uintptr(nIndex)) + + return COLORREF(ret) +} + func CopyRect(dst, src *RECT) bool { ret, _, _ := procCopyRect.Call( uintptr(unsafe.Pointer(dst)), @@ -1057,10 +1083,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)) @@ -1424,3 +1454,41 @@ func RedrawWindow(hwnd HWND, lprcUpdate *RECT, hrgnUpdate HRGN, flags uint32) bo uintptr(flags)) return ret != 0 } + +func FrameRect(hDC HDC, lprc *RECT, hbr HBRUSH) int { + ret, _, _ := procFrameRect.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(lprc)), + uintptr(hbr)) + return int(ret) +} + +func GetMenuBarInfo(hwnd HWND, idObject int32, idItem uint32, pmbi *MENUBARINFO) bool { + ret, _, _ := procGetMenuBarInfo.Call( + uintptr(hwnd), + uintptr(idObject), + uintptr(idItem), + uintptr(unsafe.Pointer(pmbi))) + return ret != 0 +} + +func MapWindowPoints(hwndFrom, hwndTo HWND, lpPoints *POINT, cPoints uint) int { + ret, _, _ := procMapWindowPoints.Call( + uintptr(hwndFrom), + uintptr(hwndTo), + uintptr(unsafe.Pointer(lpPoints)), + uintptr(cPoints)) + return int(ret) +} + +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 +} 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) -}