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
  • 2 weeks later...
Posted

Hi folks,

maybe it's a bit over the top, but we could use Discussions directly on GitHub or even a roadmap feature (on GitHub) to express the thoughts and upcoming features (plans). Even the way with "GitHub issues" would be very common, but I also see the downside of missing clever people who usually help here and not on GitHub because they are not active their or in the GitHub Organization "AutoIt Community".

Pro: it's all in one place, well organized (via Kanban or Roadmap board) and the overview about "whats next", who is working on what is good.
Cons: this would decrease the activity here and we would lose people to support the project.

---------

.. few thoughts about this. I guess I mix up the idea of an team (which is working on a concrete project) versus a community (that can have several contributors, but who are not always active or have only been active once).

Best regards
Sven

==> AutoIt related: 🔗 Organization AutoIt Community🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet🔗 autoit-webdriver-boilerplate

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Posted
12 hours ago, WildByDesign said:

If I put together a couple of smaller PRs and assuming they get accepted/merged, would that cause problems for your current work on it?

I go through everything, so there will not really be something I do not touch, but do not let that stop you from further progress. I will look over all changes and transfer them, that is no problem. I'm happy for any new features.
I think I'm progressing pretty well, so there should be something soonish as well :)

Posted
6 hours ago, SOLVE-SMART said:

but we could use Discussions directly on GitHub or even a roadmap feature (on GitHub) to express the thoughts and upcoming features (plans). Even the way with "GitHub issues" would be very common

That is a very good point. You will have to enable Discussions though because it is disabled by default on any repos. But yes, from either Issues or Discussions we can discuss planning and all things like that. I agree. :)

7 minutes ago, Kanashius said:

I go through everything, so there will not really be something I do not touch, but do not let that stop you from further progress. I will look over all changes and transfer them, that is no problem. I'm happy for any new features.

That sounds great. I will try to keep my PRs relatively small so that it is easier to port over to your upcoming refactoring changes. And I'll do a separate PR for each feature-related change so that it should be easier for you as well.

9 minutes ago, Kanashius said:

I think I'm progressing pretty well, so there should be something soonish as well :)

I'm excited to when you are done. Whenever you go silent for days/weeks, you always show up with Christmas-like surprises. So I always know to be patient and have no worries.

I have a few code changes ready to implement and a bunch more ideas after that. So this project is going to be incredible.

  • 2 weeks later...
Posted

I just added a pull request for MsgBox Improvements to allow for dark mode MsgBox. It switches properly between light mode and dark mode.

@Kanashius I noticed a problem with our About dialog (not related to the new MsgBox improvements PR).

The problem is that obtaining the version number of the TreeListExplorer UDF fails when the binaries are compiled. It makes sense that it would fail because we don't have the script .au3 files to parse once compiled as binary.

Do you think that we should just manually add the TreeListExplorer UDF version to the About dialog?

Posted
6 hours ago, WildByDesign said:

Do you think that we should just manually add the TreeListExplorer UDF version to the About dialog?

The code was just to extract from sources. There are ways to get the source code from a compiled file:

But those require including the sourcecode in the binary/... so I'm not sure its worth it.

I thought of it more as a way to write a script, which is executed before the compilation of the release to write all versions of the used udfs into a Versions.au3, which is then included in the compiled script. This way it would not have to be done manually and would automatically be done => not missed when bumping the version or something... But it was planned as more of a convenience thing in an extra tool :)

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...