mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
feat: Add full dark mode support for Windows menus
This commit is contained in:
parent
b5ef00fd42
commit
a29b4f0861
10 changed files with 1574 additions and 244 deletions
|
|
@ -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 *******/
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
978
v3/pkg/w32/menubar.go
Normal file
978
v3/pkg/w32/menubar.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
17
v3/pkg/w32/timer.go
Normal file
17
v3/pkg/w32/timer.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue