Jump to content

Recommended Posts

Posted (edited)

@Kanashius I was just looking over some of the changes and noticed the comment:

; delete the last item later to avoid the menu to flicker in light mode

Is it possible that that could be the cause of the weird File menu issue that is making the other menus disappear?

That reminds me. I personally think that we should also subclass the menu in light mode as well since that is what we were doing when using ModernMenuRaw. The reason for subclassing it in light mode as well made it easier for switching back and forth between light mode and dark mode. This way, the brushes just change color and we don't have to delete brushes and stop the subclassing when going to light mode.

I haven't done any of the colors for light mode yet though. So there is still more work to do for that.

By the way, do you have any idea how to have more control over the top menu font size?

EDIT: I just pushed another commit that allows changing the font size. I have tested it up to 175% scaling and nothing was cut off on the menu text here. If you have a monitor with 200% scaling or higher, could you please test for top menu text cut off?

Edited by WildByDesign
Posted
11 hours ago, WildByDesign said:

That's a really great idea. I like it. And it makes sense to avoid any other users' usage of GUIRegisterMsg. I am not very familiar with the method that you suggest about registering callbacks. I am a bit worried that if I make a mistake with a change like that, it could be significant. Would you be willing to help convert to this callback method?

; create the callback once
Local $hProc = DllCallbackRegister('__GUIDarkMode__WinProc', 'ptr', 'hwnd;uint;wparam;lparam')
; add it to every window using the udf
Local $hPrevProc = _WinAPI_SetWindowLong($hGui, -4, DllCallbackGetPtr($hProc))

; when the gui is no longer using the udf, remove the callback
_WinAPI_SetWindowLong($hGui, -4, $hPrevProc)
; free callback at exit
DllCallbackFree($hProc)

Func __GUIDarkMode__WinProc($hWnd, $iMsg, $iwParam, $ilParam)
    Switch $iMsg
        Case $WM_NOTIFY
            ; ...
        Case $WM_MEASUREITEM
            __GUIDarkMode__WM_MEASUREITEM($hwnd, $iMsg, $iwParam, $ilParam)
        Case $WM_DRAWITEM
            __GUIDarkMode__WM_DRAWITEM($hwnd, $iMsg, $iwParam, $ilParam)
    EndSwitch
    Return _WinAPI_CallWindowProc($hPrevProc, $hWnd, $iMsg, $iwParam, $ilParam)
EndFunc

I think, the code explains everything important to do.

The WM_Message events are essentially chained function calls and we are inserting our function into that chain.
For AutoIt purposes: The Gui sends the event, it goes through the function chain and at the end it hits the function registered with GuiRegisterMsg.
In this example, the __GUIDarkMode__WinProc will be called by the previous message handler and it calls the next one before/when returning.
You can also interrupt this chain with e.g. "Return 1". Then the default AutoIt-Processing will not happen. This is normally done, if you handled an event and do not want others to handle them as well. Example: When hovering over a control you change the cursor, but AutoIt overwrites that change with their own cursor change.

This function chain starts with the gui sending the event, so that is where we are registering our callback function.
That is also, why we deregister there, when we no longer want to handle events, so we remove our callback from the chain.

Posted (edited)
28 minutes ago, Kanashius said:

I think, the code explains everything important to do.

Thank you. And I agree, the code example does explain itself. Your description also helps my understanding of how it all works.

I spent some time on it early this morning and it ended up failing. The entire GUI is blank and there is not even a frame, titlebar or control buttons. I was very careful to ensure that the parameters matched up correctly. But all of this failed in the end. I apologize, but I think that a change like this is not something that I can do personally. Maybe there is some clear mistakes that you can spot in my changes below.

I will show my relevant changes to this:

; Create Callback
Global $hMenuProc = DllCallbackRegister('__GUIDarkMenu_WinProc', 'ptr', 'hwnd;uint;wparam;lparam')
Global $hPrevMenuProc = __WinAPI_SetWindowLong($hWnd, -4, DllCallbackGetPtr($hMenuProc))

Func __GUIDarkMenu_WinProc($hWnd, $iMsg, $iwParam, $ilParam)
    Switch $iMsg
        Case $WM_WINDOWPOSCHANGED
            __GUIDarkMode__WM_WINDOWPOSCHANGED($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_ACTIVATE
            __GUIDarkMode__WM_ACTIVATE($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_MEASUREITEM
            __GUIDarkMode__WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_DRAWITEM
            __GUIDarkMode__WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    EndSwitch
EndFunc

Func __GUIDarkMode__WM_WINDOWPOSCHANGED($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $iMsg, $iwParam, $ilParam
    If $hWnd <> $hGUI Then Return $GUI_RUNDEFMSG
    _drawUAHMenuNCBottomLine($hWnd)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>__GUIDarkMode__WM_WINDOWPOSCHANGED

Func __GUIDarkMode__WM_ACTIVATE($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $iMsg, $iwParam, $ilParam
    If $hWnd <> $hGUI Then Return $GUI_RUNDEFMSG
    _drawUAHMenuNCBottomLine($hWnd)

    Return $GUI_RUNDEFMSG
EndFunc   ;==>__GUIDarkMode__WM_ACTIVATE

Func __GUIDarkMode__WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $iMsg, $iwParam
    Local Const $SM_CXDLGFRAME = 7
    Local Const $DEFAULT_GUI_FONT = 17
    Local $tagDRAWITEM = "uint CtlType;uint CtlID;uint itemID;uint itemAction;uint itemState;ptr hwndItem;handle hDC;" & _
            "long left;long top;long right;long bottom;ulong_ptr itemData"
    Local $t = DllStructCreate($tagDRAWITEM, $ilParam)
    If Not IsDllStruct($t) Then Return $GUI_RUNDEFMSG

    If $t.CtlType <> $ODT_MENU Then Return $GUI_RUNDEFMSG

    Local $hDC = $t.hDC
    Local $left = $t.left
    Local $top = $t.top
    Local $right = $t.right
    Local $bottom = $t.bottom
    Local $state = $t.itemState
    Local $itemID = $t.itemID

    ; convert itemID to position
    Local $iPos = -1
    For $i = 0 To UBound($g_aMenuText) - 1
        If $itemID = $g_aMenuText[$i][0] Then
            $iPos = $i
            ExitLoop
        EndIf
    Next

    If $iPos < 0 Then $iPos = $itemID
    If $iPos < 0 Or $iPos >= UBound($g_aMenuText) Then $iPos = 0

    Local $sText = $g_aMenuText[$iPos][1]
    $sText = StringReplace($sText, "&", "")

    ; Colors
    Local $clrBG = _ColorToCOLORREF($COLOR_MENU_BG)
    Local $clrSel = _ColorToCOLORREF($COLOR_MENU_SEL)
    Local $clrText = _ColorToCOLORREF($COLOR_MENU_TEXT)

    Static $iDrawCount = 0
    Static $bFullBarDrawn = False

    ; Count how many items were drawn in this "draw cycle"
    $iDrawCount += 1

    ; argumentum ; pre-declare all the "Local" in those IF-THEN that could be needed
    Local $tClient, $iFullWidth, $tFullMenuBar, $hFullBrush
    Local $tEmptyArea, $hEmptyBrush

    ; If we are at the first item AND the bar has not yet been drawn
    If $iPos = 0 And Not $bFullBarDrawn Then
        ; Get the full window width
        $tClient = __WinAPI_GetClientRect($hWnd)
        $iFullWidth = $tClient.right

        ; Fill the entire menu bar
        $tFullMenuBar = DllStructCreate($tagRECT)
        With $tFullMenuBar
            .left = 0
            .top = $top - 1
            .right = $iFullWidth + 3
            .bottom = $bottom
        EndWith

        $hFullBrush = __WinAPI_CreateSolidBrush($clrBG)
        __WinAPI_FillRect($hDC, $tFullMenuBar, $hFullBrush)
        __WinAPI_DeleteObject($hFullBrush)
    EndIf

    ; After drawing all items, mark as "drawn"
    If $iDrawCount >= UBound($g_aMenuText) Then
        $bFullBarDrawn = True
        $iDrawCount = 0
    EndIf

    ; Draw background for the area AFTER the last menu item
    If $iPos = (UBound($g_aMenuText) - 1) Then ; Last menu
        $tClient = __WinAPI_GetClientRect($hWnd)
        $iFullWidth = $tClient.right

        ; Fill only the area to the RIGHT of the last menu item
        If $right < $iFullWidth Then
            $tEmptyArea = DllStructCreate($tagRECT)
            With $tEmptyArea
                .left = $right
                .top = $top
                .right = $iFullWidth + __WinAPI_GetSystemMetrics($SM_CXDLGFRAME)
                .bottom = $bottom
            EndWith

            $hEmptyBrush = __WinAPI_CreateSolidBrush($clrBG)
            __WinAPI_FillRect($hDC, $tEmptyArea, $hEmptyBrush)
            __WinAPI_DeleteObject($hEmptyBrush)
        EndIf
    EndIf

    ; Draw item background (selected = lighter)
    Local $bSelected = BitAND($state, $ODS_SELECTED)
    Local $bHot = BitAND($state, $ODS_HOTLIGHT)
    Local $hBrush

    If $bSelected Then
        $hBrush = __WinAPI_CreateSolidBrush($clrSel)
    ElseIf $bHot Then
        $hBrush = __WinAPI_CreateSolidBrush($COLOR_MENU_HOT)
    Else
        $hBrush = __WinAPI_CreateSolidBrush($clrBG)
    EndIf

    Local $tItemRect = DllStructCreate($tagRECT)
    With $tItemRect
        .left = $left
        .top = $top
        .right = $right
        .bottom = $bottom
    EndWith

    __WinAPI_FillRect($hDC, $tItemRect, $hBrush)
    __WinAPI_DeleteObject($hBrush)

    ; Setup font
    Local $hFont = __SendMessage($hWnd, $WM_GETFONT, 0, 0)
    If Not $hFont Then $hFont = __WinAPI_GetStockObject($DEFAULT_GUI_FONT)
    Local $hOldFont = __WinAPI_SelectObject($hDC, $hFont)

    __WinAPI_SetBkMode($hDC, $TRANSPARENT)
    __WinAPI_SetTextColor($hDC, $clrText)

    ; Draw text
    Local $tTextRect = DllStructCreate($tagRECT)
    With $tTextRect
        .left = $left + 10
        .top = $top + 4
        .right = $right - 10
        .bottom = $bottom - 4
    EndWith

    DllCall($hUser32Dll, "int", "DrawTextW", "handle", $hDC, "wstr", $sText, "int", -1, "ptr", _
            DllStructGetPtr($tTextRect), "uint", BitOR($DT_SINGLELINE, $DT_VCENTER, $DT_LEFT))

    If $hOldFont Then __WinAPI_SelectObject($hDC, $hOldFont)

    Return 1
EndFunc   ;==>__GUIDarkMode__WM_DRAWITEM

Func __GUIDarkMode__WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $iMsg, $iwParam
    Local Const $DEFAULT_GUI_FONT = 17
    Local $tagMEASUREITEM = "uint CtlType;uint CtlID;uint itemID;uint itemWidth;uint itemHeight;ulong_ptr itemData"
    Local $t = DllStructCreate($tagMEASUREITEM, $ilParam)
    If Not IsDllStruct($t) Then Return $GUI_RUNDEFMSG

    If $t.CtlType <> $ODT_MENU Then Return $GUI_RUNDEFMSG

    Local $itemID = $t.itemID

    ; itemID is the control ID, not the position!
    ; We must derive the position from the itemID
    Local $iPos = -1
    For $i = 0 To UBound($g_aMenuText) - 1
        If $itemID = $g_aMenuText[$i][0] Then
            $iPos = $i
            ExitLoop
        EndIf
    Next

    ; Fallback: try the itemID directly
    If $iPos < 0 Then $iPos = $itemID
    If $iPos < 0 Or $iPos >= UBound($g_aMenuText) Then $iPos = 0

    Local $sText = $g_aMenuText[$iPos][1]

    ; Calculate text dimensions
    Local $hDC = __WinAPI_GetDC($hWnd)
    Local $hFont = __SendMessage($hWnd, $WM_GETFONT, 0, 0)
    If Not $hFont Then $hFont = __WinAPI_GetStockObject($DEFAULT_GUI_FONT)
    Local $hOldFont = __WinAPI_SelectObject($hDC, $hFont)

    Local $tSize = __WinAPI_GetTextExtentPoint32($hDC, $sText)
    Local $iTextWidth = $tSize.X
    Local $iTextHeight = $tSize.Y

    __WinAPI_SelectObject($hDC, $hOldFont)
    __WinAPI_ReleaseDC($hWnd, $hDC)

    ; Set dimensions with padding (with high DPI)
    $t.itemWidth = _CalcMenuItemWidth($iDPIpct, $iTextWidth)
    $t.itemHeight = $iTextHeight + 1

    Return 1
EndFunc   ;==>__GUIDarkMode__WM_MEASUREITEM

 

Edited by WildByDesign
Posted (edited)

I had a look at it and worked it out. Now only the Menu items shortly flicker, when they are removed and readded. I think this is an acceptable result.
I implemented the Themes differently, so you can now just add themes in the __GUIDarkMenu_StartUp, switching between them with __GUIDarkMenu_SetTheme($hGui, $__GUIDarkMenu_iThemeDark).

The UDF with an example at the top:

#include-once

; #INDEX# =======================================================================================================================
; Title .........: GUIDarkMenu UDF Library for AutoIt3
; AutoIt Version : 3.3.18.0
; Language ......: English
; Description ...: UDF library for applying dark theme to menubar
; Author(s) .....: WildByDesign, Kanashius (including previous code from ahmet, argumentum, UEZ)
; Version .......: 0.9.2
; ===============================================================================================================================

#include <WinAPISysWin.au3>
#include <GuiMenu.au3>
#include <GUIConstantsEx.au3>
#include <APIGdiConstants.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPIGdiDC.au3>
#include <StructureConstants.au3>
#include <AutoItConstants.au3>
#include <WinAPIGdi.au3>
#include <WinAPIHObj.au3>

; Menu Info
Const $ODT_MENU = 1
Const $ODS_SELECTED = 0x0001
Const $ODS_DISABLED = 0x0004
Const $ODS_HOTLIGHT = 0x0040

Global $__GUIDarkMenu_mData[]
Global Const $__GUIDarkMenu_iThemeLight = 1, $__GUIDarkMenu_iThemeDark = 2
Global Const $__GUIDarkMenu_vColorBG = "iColorBG", $__GUIDarkMenu_vColor = "iColor", $__GUIDarkMenu_vColorCtrlBG = "iColorCtrlBG", $__GUIDarkMenu_vColorBorder = "iColorBorder"
Global Const $__GUIDarkMenu_vColorMenuBG = "iColorMenuBG", $__GUIDarkMenu_vColorMenuHot = "iColorMenuHot", $__GUIDarkMenu_vColorMenuSel = "iColorMenuSel", $__GUIDarkMenu_vColorMenuText = "iColorMenuText"

; ================== EXAMPLE CODE START ==================
__GUIDarkMenu_StartUp()

Global $hGui = GUICreate("Example", 800, 600)
_createMenu($hGui)
Local $idButtonReload = GUICtrlCreateButton("Reload menu", 10, 10)
Local $idButtonSwitch = GUICtrlCreateButton("Switch menu", 10, 40)
GUISetState()

While True
    Switch GUIGetMsg()
        Case -3
            __GUIDarkMenu_Shutdown()
            Exit
        Case $idButtonReload
            _createMenu($hGui)
        Case $idButtonSwitch
            _createMenu($hGui, True)
    EndSwitch
WEnd

Func _createMenu($hGui, $bSwitchTheme = False)
    Local $hMenu = _GUICtrlMenu_GetMenu($hGui)
    While _GUICtrlMenu_GetItemCount($hMenu)>1
        _GUICtrlMenu_DeleteMenu($hMenu, 0)
    WEnd
    Local $idFile = GUICtrlCreateMenu("File")
    GUICtrlCreateMenuItem("Test 1", $idFile)
    GUICtrlCreateMenuItem("Test 2", $idFile)
    GUICtrlCreateMenu("Edit")
    GUICtrlCreateMenu("Options")
    GUICtrlCreateMenu("Help")

    _GUICtrlMenu_DeleteMenu($hMenu, 0)
    Local Static $iLastTheme = $__GUIDarkMenu_iThemeDark
    If $bSwitchTheme Then
        If $iLastTheme = $__GUIDarkMenu_iThemeLight Then
            $iLastTheme = $__GUIDarkMenu_iThemeDark
        Else
            $iLastTheme = $__GUIDarkMenu_iThemeLight
        EndIf
    EndIf
    __GUIDarkMenu_SetTheme($hGui, $iLastTheme)
    If @error Then ConsoleWrite("Error settings theme: "&@error&":"&@extended&@crlf)
EndFunc

; ==================  EXAMPLE CODE END  ==================

Func __GUIDarkMenu_StartUp()
    $__GUIDarkMenu_mData.hDllGDI = DllOpen("gdi32.dll")
    $__GUIDarkMenu_mData.hDllUser = DllOpen("user32.dll")
    Local $mGuis[]
    $__GUIDarkMenu_mData.mGuis = $mGuis
    $__GUIDarkMenu_mData.hProc = DllCallbackRegister('__GUIDarkMenu_WinProc', 'ptr', 'hwnd;uint;wparam;lparam')
    Local $mThemes[]
    Local $mDarkTheme[]
    $mDarkTheme[$__GUIDarkMenu_vColorBG] = 0x121212
    $mDarkTheme[$__GUIDarkMenu_vColor] = 0xE0E0E0
    $mDarkTheme[$__GUIDarkMenu_vColorCtrlBG] = 0x202020
    $mDarkTheme[$__GUIDarkMenu_vColorBorder] = 0x3F3F3F
    $mDarkTheme[$__GUIDarkMenu_vColorMenuBG] = _WinAPI_ColorAdjustLuma($mDarkTheme[$__GUIDarkMenu_vColorBG], 5)
    $mDarkTheme[$__GUIDarkMenu_vColorMenuHot] = _WinAPI_ColorAdjustLuma($mDarkTheme[$__GUIDarkMenu_vColorMenuBG], 20)
    $mDarkTheme[$__GUIDarkMenu_vColorMenuSel] = _WinAPI_ColorAdjustLuma($mDarkTheme[$__GUIDarkMenu_vColorMenuBG], 10)
    $mDarkTheme[$__GUIDarkMenu_vColorMenuText] = $mDarkTheme[$__GUIDarkMenu_vColor]
    $mThemes[$__GUIDarkMenu_iThemeDark] = $mDarkTheme
    Local $mLightTheme[]
    $mLightTheme[$__GUIDarkMenu_vColorBG] = 0xFFFFFF
    $mLightTheme[$__GUIDarkMenu_vColor] = 0x000000
    $mLightTheme[$__GUIDarkMenu_vColorCtrlBG] = 0xDDDDDD
    $mLightTheme[$__GUIDarkMenu_vColorBorder] = 0xCCCCCC
    $mLightTheme[$__GUIDarkMenu_vColorMenuBG] = _WinAPI_ColorAdjustLuma($mLightTheme[$__GUIDarkMenu_vColorBG], 95)
    $mLightTheme[$__GUIDarkMenu_vColorMenuHot] = _WinAPI_ColorAdjustLuma($mLightTheme[$__GUIDarkMenu_vColorMenuBG], 80)
    $mLightTheme[$__GUIDarkMenu_vColorMenuSel] = _WinAPI_ColorAdjustLuma($mLightTheme[$__GUIDarkMenu_vColorMenuBG], 70)
    $mLightTheme[$__GUIDarkMenu_vColorMenuText] = $mLightTheme[$__GUIDarkMenu_vColor]
    $mThemes[$__GUIDarkMenu_iThemeLight] = $mLightTheme
    $__GUIDarkMenu_mData.mThemes = $mThemes
EndFunc

Func __GUIDarkMenu_Shutdown()
    If Not __GUIDarkMenu__IsInitialized() Then Return SetError(2, 0, False)
    For $hGui In MapKeys($__GUIDarkMenu_mData.mGuis)
        __GUIDarkMenu_GuiRemove($hGui)
    Next
    DllCallbackFree($__GUIDarkMenu_mData.hProc)
    DllClose($__GUIDarkMenu_mData.hDllGDI)
    DllClose($__GUIDarkMenu_mData.hDllUser)
    Local $mNewMap[]
    $__GUIDarkMenu_mData = $mNewMap
    Return True
EndFunc

Func __GUIDarkMenu_GuiAdd($hGui)
    If Not __GUIDarkMenu__IsInitialized() Then Return SetError(2, 0, False)
    If MapExists($__GUIDarkMenu_mData.mGuis, $hGui) Then Return True
    Local $mGui[]
    Local $iDpiPct = Round(__WinAPI_GetDpiForWindow($hGUI) / 96, 2) * 100
    $mGui.iDpi = @error?100:$iDpiPct
    Local $hProc = _WinAPI_SetWindowLong($hGui, -4, DllCallbackGetPtr($__GUIDarkMenu_mData.hProc))
    If @error Then Return SetError(2, 0, False)
    $mGui.hPrevProc = $hProc
    $mGui.iTheme = $__GUIDarkMenu_iThemeLight
    $mGui.iTextSpaceHori = 20
    $mGui.iTextSpaceVert = 8
    $mGui.iFontSize = 9
    $__GUIDarkMenu_mData["mGuis"][$hGui] = $mGui
    Return True
EndFunc

Func __GUIDarkMenu_GuiRemove($hGui)
    If Not __GUIDarkMenu__IsInitialized() Then Return SetError(2, 0, False)
    If Not MapExists($__GUIDarkMenu_mData.mGuis, $hGui) Then Return SetError(1, 1, False)
    _WinAPI_SetWindowLong($hGui, -4, $__GUIDarkMenu_mData.mGuis[$hGui].hPrevProc)
    MapRemove($__GUIDarkMenu_mData.mGuis, $hGui)
    Return True
EndFunc

Func __GUIDarkMenu__IsInitialized()
    Return UBound($__GUIDarkMenu_mData)>0
EndFunc

Func __GUIDarkMenu_WinProc($hWnd, $iMsg, $iwParam, $ilParam)
    Local $sContinue = $GUI_RUNDEFMSG
    Switch $iMsg
        Case $WM_WINDOWPOSCHANGED
            $sContinue = __GUIDarkMode__WM_WINDOWPOSCHANGED($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_ACTIVATE
            $sContinue = __GUIDarkMode__WM_ACTIVATE($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_MEASUREITEM
            $sContinue = __GUIDarkMode__WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
        Case $WM_DRAWITEM
            $sContinue = __GUIDarkMode__WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    EndSwitch
    If $sContinue=$GUI_RUNDEFMSG And MapExists($__GUIDarkMenu_mData.mGuis, $hWnd) Then Return _WinAPI_CallWindowProc($__GUIDarkMenu_mData.mGuis[$hWnd].hPrevProc, $hWnd, $iMsg, $iwParam, $ilParam)
EndFunc

Func __GUIDarkMenu_SetTheme($hGui, $iTheme)
    If Not __GUIDarkMenu__IsInitialized() Then Return SetError(2, 0, False)
    __GUIDarkMenu_GuiAdd($hGui)
    If @error Then Return SetError(1, 1, False)
    If Not MapExists($__GUIDarkMenu_mData.mThemes, $iTheme) Then Return SetError(1, 2, False)
    $__GUIDarkMenu_mData["mGuis"][$hGui]["iTheme"] = $iTheme
    Local $hMenu = _GUICtrlMenu_GetMenu($hGui)
    If Not $hMenu Then Return False
    For $i = 0 To _GUICtrlMenu_GetItemCount($hMenu) - 1
        _GUICtrlMenu_SetItemType($hMenu, $i, $MFT_OWNERDRAW, True)
    Next
    __GUIDarkMenu__MenuBarSetBKColor($hMenu, __GUIDarkMenu__GetColor($hGui, $__GUIDarkMenu_vColorMenuBG))
    _GUICtrlMenu_DrawMenuBar($hGui)
    _WinAPI_RedrawWindow($hGui, 0, 0, BitOR($RDW_INVALIDATE, $RDW_UPDATENOW))
    Return True
EndFunc

Func __GUIDarkMenu__GetColor($hGui, $sColor)
    If Not __GUIDarkMenu__IsInitialized() Then Return SetError(2, 0, False)
    If Not MapExists($__GUIDarkMenu_mData.mGuis, $hGui) Then Return SetError(1, 1, 0)
    If Not MapExists($__GUIDarkMenu_mData.mThemes[$__GUIDarkMenu_mData.mGuis[$hGui].iTheme], $sColor) Then Return SetError(1, 2, 0)
    Return $__GUIDarkMenu_mData.mThemes[$__GUIDarkMenu_mData.mGuis[$hGui].iTheme][$sColor]
EndFunc

Func __GUIDarkMenu__MenuBarSetBKColor($hMenu, $iColor)
    Local $tInfo,$aResult
    Local $hBrush = DllCall($__GUIDarkMenu_mData.hDllGDI, 'hwnd', 'CreateSolidBrush', 'int', $iColor)
    If @error Then Return
    ;$tInfo = DllStructCreate("int Size;int Mask;int Style;int YMax;int hBack;int ContextHelpID;ptr MenuData")
    $tInfo = DllStructCreate("int Size;int Mask;int Style;int YMax;handle hBack;int ContextHelpID;ptr MenuData")
    DllStructSetData($tInfo, "Mask", 2)
    DllStructSetData($tInfo, "hBack", $hBrush[0])
    DllStructSetData($tInfo, "Size", DllStructGetSize($tInfo))
    $aResult = DllCall($__GUIDarkMenu_mData.hDllUser, "int", "SetMenuInfo", "hwnd", $hMenu, "ptr", DllStructGetPtr($tInfo))
    Return $aResult[0] <> 0
EndFunc   ;==>_GUICtrlMenu_SetMenuBackground

Func __WinAPI_GetDpiForWindow($hWnd)
    Local $aResult = DllCall($__GUIDarkMenu_mData.hDllUser, "uint", "GetDpiForWindow", "hwnd", $hWnd) ;requires Win10 v1607+ / no server support
    If Not IsArray($aResult) Or @error Then Return SetError(1, @extended, 0)
    If Not $aResult[0] Then Return SetError(2, @extended, 0)
    Return $aResult[0]
EndFunc   ;==>__WinAPI_GetDpiForWindow

Func __GUIDarkMenu__GetTextDimension($hWnd, $sText)
    ; Calculate text dimensions
    Local $hDC = _WinAPI_GetDC($hWnd)
    Local $hFont = _SendMessage($hWnd, $WM_GETFONT, 0, 0)
    If Not $hFont Then $hFont = _WinAPI_GetStockObject($DEFAULT_GUI_FONT)
    Local $hOldFont = _WinAPI_SelectObject($hDC, $hFont)

    Local $tSize = _WinAPI_GetTextExtentPoint32($hDC, $sText)
    Local $iTextWidth = $tSize.X + 6
    Local $iTextHeight = $tSize.Y

    _WinAPI_SelectObject($hDC, $hOldFont)
    _WinAPI_ReleaseDC($hWnd, $hDC)
    Local $arSize = [$iTextWidth, $iTextHeight]
    Return $arSize
EndFunc

Func __GUIDarkMode__WM_MEASUREITEM($hWnd, $iMsg, $wParam, $lParam)
    #forceref $iMsg, $wParam
    If Not MapExists($__GUIDarkMenu_mData.mGuis, $hWnd) Then Return $GUI_RUNDEFMSG
    Local $tagMEASUREITEM = "uint CtlType;uint CtlID;uint itemID;uint itemWidth;uint itemHeight;ulong_ptr itemData"
    Local $t = DllStructCreate($tagMEASUREITEM, $lParam)
    If Not IsDllStruct($t) Then Return $GUI_RUNDEFMSG

    If $t.CtlType <> $ODT_MENU Then Return $GUI_RUNDEFMSG

    Local $itemID = $t.itemID

    Local $sText = _GUICtrlMenu_GetItemText(_GUICtrlMenu_GetMenu($hWnd), $itemID, False)
    Local $arSize = __GUIDarkMenu__GetTextDimension($hWnd, $sText)

    ; Set dimensions with padding (with high DPI)
    $t.itemWidth = $arSize[0] + $__GUIDarkMenu_mData.mGuis[$hWnd].iTextSpaceHori/2
    $t.itemHeight = $arSize[1] + $__GUIDarkMenu_mData.mGuis[$hWnd].iTextSpaceVert
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_MEASUREITEM_Handler

Func __GUIDarkMode__WM_DRAWITEM($hWnd, $iMsg, $wParam, $lParam)
    #forceref $iMsg, $wParam
    Local $tagDRAWITEM = "uint CtlType;uint CtlID;uint itemID;uint itemAction;uint itemState;ptr hwndItem;handle hDC;" & _
            "long left;long top;long right;long bottom;ulong_ptr itemData"
    Local $tDrawItem = DllStructCreate($tagDRAWITEM, $lParam)
    If Not IsDllStruct($tDrawItem) Then Return $GUI_RUNDEFMSG
    If $tDrawItem.CtlType <> $ODT_MENU Then Return $GUI_RUNDEFMSG

    Local $hDC = $tDrawItem.hDC
    Local $iLeft = $tDrawItem.left
    Local $iTop = $tDrawItem.top
    Local $iRight = $tDrawItem.right
    Local $iBottom = $tDrawItem.bottom
    Local $iState = $tDrawItem.itemState
    Local $iItemID = $tDrawItem.itemID

    Local $hMenu = _GUICtrlMenu_GetMenu($hWnd)
    ; convert itemID to position
    Local $iPos = -1
    For $i = 0 To _GUICtrlMenu_GetItemCount($hMenu) - 1
        If $iItemID = _GUICtrlMenu_GetItemID($hMenu, $i) Then
            $iPos = $i
            ExitLoop
        EndIf
    Next
    If $iPos < 0 Then Return $GUI_RUNDEFMSG ; something must have gone seriously wrong

    Local $sText = _GUICtrlMenu_GetItemText($hMenu, $iPos)
    $sText = StringReplace($sText, "&", "")

    ; Draw item background (selected = lighter)
    Local $bSelected = BitAND($iState, $ODS_SELECTED)
    Local $bHot = BitAND($iState, $ODS_HOTLIGHT)
    Local $hBrush

    ; __GUIDarkMenu__ColorRGBToBGR()
    If $bSelected Then
        $hBrush = _WinAPI_CreateSolidBrush(__GUIDarkMenu__GetColor($hGui, $__GUIDarkMenu_vColorMenuSel))
    ElseIf $bHot Then
        $hBrush = _WinAPI_CreateSolidBrush(__GUIDarkMenu__GetColor($hGui, $__GUIDarkMenu_vColorMenuHot))
    Else
        $hBrush = _WinAPI_CreateSolidBrush(__GUIDarkMenu__GetColor($hGui, $__GUIDarkMenu_vColorMenuBG))
    EndIf

    Local $tItemRect = DllStructCreate($tagRECT)
    With $tItemRect
        .left = $iLeft
        .top = $iTop
        .right = $iRight
        .bottom = $iBottom
    EndWith

    _WinAPI_FillRect($hDC, $tItemRect, $hBrush)
    _WinAPI_DeleteObject($hBrush)

    ; Setup font
    Local $hFont = __GUIDarkMenu__CreateMenuFontByName("Segoe UI", $__GUIDarkMenu_mData.mGuis[$hWnd].iFontSize)
    If Not $hFont Then $hFont = _WinAPI_GetStockObject($DEFAULT_GUI_FONT)
    Local $hOldFont = _WinAPI_SelectObject($hDC, $hFont)

    _WinAPI_SetBkMode($hDC, $TRANSPARENT)
    _WinAPI_SetTextColor($hDC, __GUIDarkMenu__GetColor($hGui, $__GUIDarkMenu_vColorMenuText))

    ; Draw text
    Local $tTextRect = DllStructCreate($tagRECT)
    With $tTextRect
        .left = $iLeft + $__GUIDarkMenu_mData.mGuis[$hGui].iTextSpaceHori/2
        .top = $iTop + $__GUIDarkMenu_mData.mGuis[$hGui].iTextSpaceVert/2
        .right = $iRight - $__GUIDarkMenu_mData.mGuis[$hGui].iTextSpaceHori/2
        .bottom = $iBottom - $__GUIDarkMenu_mData.mGuis[$hGui].iTextSpaceVert/2
    EndWith

    DllCall($__GUIDarkMenu_mData.hDllUser, "int", "DrawTextW", "handle", $hDC, "wstr", $sText, "int", -1, "ptr", _
            DllStructGetPtr($tTextRect), "uint", BitOR($DT_SINGLELINE, $DT_VCENTER, $DT_LEFT))

    If $hOldFont Then _WinAPI_SelectObject($hDC, $hOldFont)

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_DRAWITEM

Func __GUIDarkMode__WM_WINDOWPOSCHANGED($hWnd, $iMsg, $wParam, $lParam)
    #forceref $iMsg, $wParam, $lParam
    If $hWnd <> $hGUI Then Return $GUI_RUNDEFMSG
    __GUIDarkMode__DrawUAHMenuNCBottomLine($hWnd)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_WINDOWPOSCHANGED_Handler

Func __GUIDarkMode__DrawUAHMenuNCBottomLine($hWnd)
    Local $rcClient = _WinAPI_GetClientRect($hWnd)
    DllCall($__GUIDarkMenu_mData.hDllUser, "int", "MapWindowPoints", _
        "hwnd", $hWnd, _ ; hWndFrom
        "hwnd", 0, _     ; hWndTo
        "ptr", DllStructGetPtr($rcClient), _
        "uint", 2)       ;number of points - 2 for RECT structure

    Local $rcWindow = _WinAPI_GetWindowRect($hWnd)
    _WinAPI_OffsetRect($rcClient, -$rcWindow.left, -$rcWindow.top)

    Local $rcAnnoyingLine = DllStructCreate($tagRECT)
    $rcAnnoyingLine.left = $rcClient.left
    $rcAnnoyingLine.top = $rcClient.top
    $rcAnnoyingLine.right = $rcClient.right
    $rcAnnoyingLine.bottom = $rcClient.bottom

    $rcAnnoyingLine.bottom = $rcAnnoyingLine.top
    $rcAnnoyingLine.top = $rcAnnoyingLine.top - 1

    Local $hRgn = _WinAPI_CreateRectRgn(0,0,8000,8000)

    Local $hDC = _WinAPI_GetDCEx($hWnd,$hRgn, BitOR($DCX_WINDOW,$DCX_INTERSECTRGN))
    Local $hFullBrush = _WinAPI_CreateSolidBrush(__GUIDarkMenu__GetColor($hWnd, $__GUIDarkMenu_vColorMenuBG))
    _WinAPI_FillRect($hDC, $rcAnnoyingLine, $hFullBrush)
    _WinAPI_ReleaseDC($hWnd, $hDC)
    _WinAPI_DeleteObject($hFullBrush)

EndFunc   ;==>__GUIDarkMode__DrawUAHMenuNCBottomLine

Func __GUIDarkMode__WM_ACTIVATE($hWnd, $MsgID, $wParam, $lParam)
    #forceref $MsgID, $wParam, $lParam
    If $hWnd <> $hGUI Then Return $GUI_RUNDEFMSG
    __GUIDarkMode__DrawUAHMenuNCBottomLine($hWnd)

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_ACTIVATE_Handler

Func __GUIDarkMenu__ColorRGBToBGR($iColor) ;RGB to BGR
    Local $iR = BitAND(BitShift($iColor, 16), 0xFF)
    Local $iG = BitAND(BitShift($iColor, 8), 0xFF)
    Local $iB = BitAND($iColor, 0xFF)
    Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR)
EndFunc   ;==>__GUIDarkMenu__ColorRGBToBGR

Func __GUIDarkMenu__CreateFont($nHeight, $nWidth, $nEscape, $nOrientn, $fnWeight, $bItalic, $bUnderline, $bStrikeout, $nCharset, $nOutputPrec, $nClipPrec, $nQuality, $nPitch, $ptrFontName)
    Local $hFont = DllCall($__GUIDarkMenu_mData.hDllGDI , "hwnd", "CreateFont", _
                                                "int", $nHeight, _
                                                "int", $nWidth, _
                                                "int", $nEscape, _
                                                "int", $nOrientn, _
                                                "int", $fnWeight, _
                                                "long", $bItalic, _
                                                "long", $bUnderline, _
                                                "long", $bStrikeout, _
                                                "long", $nCharset, _
                                                "long", $nOutputPrec, _
                                                "long", $nClipPrec, _
                                                "long", $nQuality, _
                                                "long", $nPitch, _
                                                "ptr", $ptrFontName)
    Return $hFont[0]
EndFunc

Func __GUIDarkMenu__CreateMenuFontByName($sFontName, $nHeight = 9, $nWidth = 400)
    Local $stFontName = DllStructCreate("char[260]")
    DllStructSetData($stFontName, 1, $sFontName)
    Local $hDC      = _WinAPI_GetDC(0)
    Local $nPixel   = _WinAPI_GetDeviceCaps($hDC, 90)
    $nHeight    = 0 - _WinAPI_MulDiv($nHeight, $nPixel, 72)
    _WinAPI_ReleaseDC(0, $hDC)
    Local $hFont = __GUIDarkMenu__CreateFont($nHeight, 0, 0, 0, $nWidth, 0, 0, 0, 0, 0, 0, 0, 0, DllStructGetPtr($stFontName))
    $stFontName = 0
    Return $hFont
EndFunc

But this is currently only for the MenuBar at the top, not the submenus :)

Edited by Kanashius
Posted
2 hours ago, Kanashius said:

I had a look at it and worked it out. Now only the Menu items shortly flicker, when they are removed and readded. I think this is an acceptable result.
I implemented the Themes differently, so you can now just add themes in the __GUIDarkMenu_StartUp, switching between them with __GUIDarkMenu_SetTheme($hGui, $__GUIDarkMenu_iThemeDark).

Patrick, you are brilliant! :thumbsup:

The more code changes that I see from you, the more I understand how intelligent your techniques and methods are to the way that you think and plan. I am incredibly impressed and I feel like I'm going to learn a lot just from reading over your code changes.

Even the way that you are organizing the DLL loading is awesome.

2 hours ago, Kanashius said:

But this is currently only for the MenuBar at the top, not the submenus :)

I personally don't think that we need to worry about subclassing and coloring the actual menus. Those are just done by the system as win32-darkmode menus. But if you want to get super colorful, that is up to you. But I really don't think we need it.

Anyway, I tested your latest GUIDarkMenu and it works wonderfully. I would suggest that you commit those changes in the repo because it is working much better now.

Thank you so much for these improvements. I find it incredible how we already do a few things with Files Au3 that even File Explorer does not do. Plus we do many of those things faster.

Posted
1 minute ago, Kanashius said:

Ah, ok, I thought that was part of the old ModernMenu UDF.

You are right, the old ModernMenuRaw UDF did have the capability to color the actual menus. But that was at a time when Windows did not have native dark mode for menus.

So you could technically add it if you wanted to. But it adds more complexity and isn’t really needed now.

Posted (edited)
16 hours ago, Kanashius said:

I pushed a commit, adapting to the new code.

I was just testing this right now. All of the language switching seems to work nicely with the updated menu code.

I only have one concern at the moment. The menu text measurement seems to be leaving quite a bit of space on the right side of each menu. For example, see screenshot:

menu.png 

I had the menu text measurement fixed previously but it seems to have regressed. But if it shows measured properly on your system, it could potentially be DPI-related and we may need to make adjustments.

 

Edited by WildByDesign

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...