From bb9a483f80ba680df0402228903d74a322563ff6 Mon Sep 17 00:00:00 2001 From: hatelamers <97636078+hatelamers@users.noreply.github.com> Date: Sat, 23 Nov 2024 18:31:28 +0100 Subject: [PATCH] #13 added support for dark mode --- buildenv/CMakeLists.txt | 13 +- buildenv/Product.cmake | 2 +- buildenv/buildnumber.txt | 1 - buildenv/cmake/PostBuild.cmake | 14 +- src/app/AboutDlg.cpp | 45 +++- src/app/AboutDlg.h | 14 +- src/app/Draw.cpp | 251 +++++++++++++++++++++ src/app/Draw.h | 112 ++++++++++ src/app/MainFrm.h | 4 +- src/app/ShellBrowseMenu.cpp | 347 ++++++++++++++++++++++++----- src/app/ShellBrowseMenu.h | 46 +++- src/app/WinPinMenu.cpp | 8 +- src/app/WinPinMenu.rc | 26 +-- src/app/WinPinMenu.vcxproj | 15 +- src/app/WinPinMenu.vcxproj.filters | 6 + src/app/buildnumber.txt | 1 + src/app/productmeta.h | 6 +- src/app/res/app.manifest | 2 +- src/app/resource.h | 7 +- src/app/stdafx.h | 17 +- src/app/uxthemehelper.h | 329 +++++++++++++++++++++++++-- 21 files changed, 1114 insertions(+), 152 deletions(-) delete mode 100644 buildenv/buildnumber.txt create mode 100644 src/app/Draw.cpp create mode 100644 src/app/Draw.h create mode 100644 src/app/buildnumber.txt diff --git a/buildenv/CMakeLists.txt b/buildenv/CMakeLists.txt index 7b744ad..f8cf6a9 100644 --- a/buildenv/CMakeLists.txt +++ b/buildenv/CMakeLists.txt @@ -11,9 +11,13 @@ if(${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() +get_filename_component(WORKSPACE_ROOT ".." ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MAIN_TARGET_SOURCE_DIR "${WORKSPACE_ROOT}/src/app") +set(MAIN_TARGET_RESOURCE_DIR "${MAIN_TARGET_SOURCE_DIR}/res") + include("${CMAKE_CURRENT_LIST_DIR}/LocalBuild.cmake" OPTIONAL) -file(READ "${CMAKE_CURRENT_LIST_DIR}/buildnumber.txt" BUILD_NUMBER) +file(READ "${MAIN_TARGET_SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) include("${CMAKE_CURRENT_LIST_DIR}/Product.cmake") if(NOT ${BUILD_NUMBER}) set(BUILD_NUMBER 1) @@ -35,12 +39,9 @@ set(CPACK_SINGLE_TARGET_SYSTEM ON) include(${CMAKE_CURRENT_LIST_DIR}/cmake/TargetArch.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cmake/InstallSetup.cmake) -get_filename_component(WORKSPACE_ROOT ".." ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") set(PRODUCT_LICENSE_FILE "${WORKSPACE_ROOT}/LICENSE") set(MAIN_TARGET_NAME ${PROJECT_NAME}) -set(MAIN_TARGET_SOURCE_DIR "${WORKSPACE_ROOT}/src/app") -set(MAIN_TARGET_RESOURCE_DIR "${MAIN_TARGET_SOURCE_DIR}/res") configure_file("${MAIN_TARGET_SOURCE_DIR}/productmeta.h.in" "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") configure_file("${MAIN_TARGET_SOURCE_DIR}/app.manifest.in" "${MAIN_TARGET_RESOURCE_DIR}/app.manifest") @@ -65,9 +66,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_BINARY_DIR} -DSOURCE_DIR=${CMAKE_CURRENT_LIST_DIR} - -DMAIN_TARGET_SOURCE_DIR=${MAIN_TARGET_SOURCE_DIR} + -DTARGET_SOURCE_DIR=${MAIN_TARGET_SOURCE_DIR} -DBUILD_TYPE=$ - -DMAIN_TARGET_NAME=${MAIN_TARGET_NAME} + -DTARGET_NAME=${MAIN_TARGET_NAME} -P ${CMAKE_CURRENT_LIST_DIR}/cmake/PostBuild.cmake COMMENT "Performing post-build tasks ($)" ) diff --git a/buildenv/Product.cmake b/buildenv/Product.cmake index 3e19104..bc9e30a 100644 --- a/buildenv/Product.cmake +++ b/buildenv/Product.cmake @@ -5,7 +5,7 @@ set(PRODUCT_NAME WinPinMenu) set(PRODUCT_VERSION ${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}.${PRODUCT_VERSION_PATCH}.${BUILD_NUMBER}) set(PRODUCT_VENDOR "diVISION") set(PRODUCT_DESCRIPTON "diVISION Pinnable Taskbar Menu For Windows") -set(PRODUCT_MAINTANER "dimamizou@users.sf.net") +set(PRODUCT_MAINTANER "dimamizou@jasics.net") set(PRODUCT_HOMEPAGE_URL "https://github.com/hatelamers/WinPinMenu") set(PROJECT_LICENSE "GNU/GPL") set(PROJECT_LICENSE_URL "https://www.gnu.org/licenses/gpl-3.0.en.html") diff --git a/buildenv/buildnumber.txt b/buildenv/buildnumber.txt deleted file mode 100644 index 368f89c..0000000 --- a/buildenv/buildnumber.txt +++ /dev/null @@ -1 +0,0 @@ -28 \ No newline at end of file diff --git a/buildenv/cmake/PostBuild.cmake b/buildenv/cmake/PostBuild.cmake index 2df38c1..8559c7b 100644 --- a/buildenv/cmake/PostBuild.cmake +++ b/buildenv/cmake/PostBuild.cmake @@ -1,19 +1,19 @@ if(NOT ${BUILD_TYPE} MATCHES Debug) include("${SOURCE_DIR}/Product.cmake") - file(READ "${SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) + file(READ "${TARGET_SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) if(NOT ${BUILD_NUMBER}) set(BUILD_NUMBER 1) endif() math(EXPR BUILD_NUMBER "${BUILD_NUMBER} + 1") if(${CMAKE_VERSION} VERSION_LESS 3.18) - file(WRITE "${SOURCE_DIR}/buildnumber.txt" "${BUILD_NUMBER}") + file(WRITE "${TARGET_SOURCE_DIR}/buildnumber.txt" "${BUILD_NUMBER}") else() - file(CONFIGURE OUTPUT "${SOURCE_DIR}/buildnumber.txt" CONTENT "${BUILD_NUMBER}") + file(CONFIGURE OUTPUT "${TARGET_SOURCE_DIR}/buildnumber.txt" CONTENT "${BUILD_NUMBER}") endif() - file(REMOVE "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") - configure_file("${MAIN_TARGET_SOURCE_DIR}/productmeta.h.in" "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") - file(TOUCH_NOCREATE "${MAIN_TARGET_SOURCE_DIR}/${MAIN_TARGET_NAME}.rc") + file(REMOVE "${TARGET_SOURCE_DIR}/productmeta.h") + configure_file("${TARGET_SOURCE_DIR}/productmeta.h.in" "${TARGET_SOURCE_DIR}/productmeta.h") + file(TOUCH_NOCREATE "${TARGET_SOURCE_DIR}/${TARGET_NAME}.rc") - message(STATUS "Build number set to ${BUILD_NUMBER} in ${SOURCE_DIR}/buildnumber.txt") + message(STATUS "Build number set to ${BUILD_NUMBER} in ${TARGET_SOURCE_DIR}/buildnumber.txt") endif() diff --git a/src/app/AboutDlg.cpp b/src/app/AboutDlg.cpp index 64e6a99..5ded32a 100644 --- a/src/app/AboutDlg.cpp +++ b/src/app/AboutDlg.cpp @@ -5,21 +5,15 @@ #include "stdafx.h" #include "resource.h" #include "productmeta.h" +#include "Draw.h" #include "aboutdlg.h" LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { - uxTheme.AllowDarkModeForWindow(m_hWnd, true); - if (uxTheme.ShouldAppsUseDarkMode()) - { - uxTheme.SwitchWindowDarkMode(m_hWnd, true); - } - SetThemeExtendedStyle(THEME_EX_THEMECLIENTEDGE); - //EnableThemeDialogTexture(ETDT_ENABLETAB); + ATLTRACE(_T(__FUNCTION__) _T("\n")); DoDataExchange(FALSE); - //m_lnkLicense.SetHyperLinkExtendedStyle(HLINK_COMMANDBUTTON, HLINK_COMMANDBUTTON); if (m_fvi.Open()) { m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_PRODUCTNAME, SFI_PRODUCTNAME); @@ -29,10 +23,6 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lPara m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_FILEVERSION, SFI_FILEVERSION); m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_COMPANYNAME, SFI_COMPANYNAME); - //LPTSTR lpValue(NULL); - //UINT uLen(0); - //if (m_fvi.GetStringFileInfo(SFI_LEGALTRADEMARKS, lpValue, &uLen)) - // m_lnkLicense.SetToolTipText(lpValue); } m_lnkLicense.SetHyperLink(PRODUCT_LICENSE_URL); @@ -41,11 +31,42 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lPara #else m_lnkRelNotes.ShowWindow(SW_HIDE); #endif + m_clrLink = m_lnkLicense.m_clrLink; + m_clrVisited = m_lnkLicense.m_clrVisited; + + UpdateColors(); return TRUE; } +LRESULT CAboutDlg::OnThemeChange(UINT, WPARAM, LPARAM, BOOL& bHandled) +{ + bHandled = FALSE; + UpdateColors(); + return 0L; +} + + LRESULT CAboutDlg::OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { EndDialog(wID); return 0; } + +void CAboutDlg::UpdateColors() +{ + auto clrLink = m_clrLink; + auto clrVisited = m_clrVisited; + if (IsInDarkMode()) + { + clrLink = HLS_TRANSFORM(clrLink, 35, 0); + clrVisited = HLS_TRANSFORM(clrVisited, 30, 0); + } + ApplyLinkColor(m_lnkLicense, clrLink, clrVisited); + ApplyLinkColor(m_lnkRelNotes, clrLink, clrVisited); +} + +void CAboutDlg::ApplyLinkColor(CHyperLink& link, COLORREF clrLink, COLORREF clrVisited) const +{ + link.m_clrLink = clrLink; + link.m_clrVisited = clrVisited; +} diff --git a/src/app/AboutDlg.h b/src/app/AboutDlg.h index 34ce943..30dc7d7 100644 --- a/src/app/AboutDlg.h +++ b/src/app/AboutDlg.h @@ -5,20 +5,24 @@ #pragma once #include "FileVersionInfo.h" +class CAboutDlg; +using CUxAboutDialog = CUxModeWindow; + class CAboutDlg : public CDialogImpl - , public CThemeImpl + , public CUxAboutDialog , public CWinDataExchange { public: enum { IDD = IDD_ABOUTBOX }; BEGIN_MSG_MAP(CAboutDlg) + MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChange) + CHAIN_MSG_MAP(CUxAboutDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDOK, OnCloseCmd) COMMAND_ID_HANDLER(IDRETRY, OnCloseCmd) COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd) - CHAIN_MSG_MAP(CThemeImpl) END_MSG_MAP() BEGIN_DDX_MAP(CAboutDlg) @@ -33,9 +37,15 @@ class CAboutDlg // LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); + LRESULT OnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/); + void UpdateColors(); + void ApplyLinkColor(CHyperLink& link, COLORREF clrLink, COLORREF clrVisited) const; + private: + COLORREF m_clrLink{ 0 }; + COLORREF m_clrVisited{ 0 }; CHyperLink m_lnkLicense; CHyperLink m_lnkRelNotes; CFileVersionInfo m_fvi; diff --git a/src/app/Draw.cpp b/src/app/Draw.cpp new file mode 100644 index 0000000..8763581 --- /dev/null +++ b/src/app/Draw.cpp @@ -0,0 +1,251 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: Draw.cpp - implementation file + * Copyright: 2016 diVISION Soft, some rights reserved + * License: CPOL, s. https://en.wikipedia.org/wiki/Code_Project_Open_License + * @author Igor Vigdorchik (http://www.codeproject.com/Members/IgorVigdorchik) + ************************************************************************/ + +#include "stdafx.h" +//#include "Tools.h" +#include "Draw.h" + +/////////////////////////////////////////////////////////////////////////////// +HLSCOLOR RGB2HLS (COLORREF rgb) +{ + unsigned char minval = min(GetRValue(rgb), min(GetGValue(rgb), GetBValue(rgb))); + unsigned char maxval = max(GetRValue(rgb), max(GetGValue(rgb), GetBValue(rgb))); + float mdiff = float(maxval) - float(minval); + float msum = float(maxval) + float(minval); + + float luminance = msum / 510.0f; + float saturation = 0.0f; + float hue = 0.0f; + + if ( maxval != minval ) + { + float rnorm = (maxval - GetRValue(rgb) ) / mdiff; + float gnorm = (maxval - GetGValue(rgb)) / mdiff; + float bnorm = (maxval - GetBValue(rgb) ) / mdiff; + + saturation = (luminance <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum)); + + if (GetRValue(rgb) == maxval) hue = 60.0f * (6.0f + bnorm - gnorm); + if (GetGValue(rgb) == maxval) hue = 60.0f * (2.0f + rnorm - bnorm); + if (GetBValue(rgb) == maxval) hue = 60.0f * (4.0f + gnorm - rnorm); + if (hue > 360.0f) hue = hue - 360.0f; + } + return HLS ((hue*255)/360, luminance*255, saturation*255); +} + +/////////////////////////////////////////////////////////////////////////////// +static BYTE _ToRGB (float rm1, float rm2, float rh) +{ + if (rh > 360.0f) rh -= 360.0f; + else if (rh < 0.0f) rh += 360.0f; + + if (rh < 60.0f) rm1 = rm1 + (rm2 - rm1) * rh / 60.0f; + else if (rh < 180.0f) rm1 = rm2; + else if (rh < 240.0f) rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f; + + return (BYTE)(rm1 * 255); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF HLS2RGB (HLSCOLOR hls) +{ + float hue = ((int)HLS_H(hls)*360)/255.0f; + float luminance = HLS_L(hls)/255.0f; + float saturation = HLS_S(hls)/255.0f; + + if ( saturation == 0.0f ) + { + return RGB (HLS_L(hls), HLS_L(hls), HLS_L(hls)); + } + float rm1, rm2; + + if ( luminance <= 0.5f ) rm2 = luminance + luminance * saturation; + else rm2 = luminance + saturation - luminance * saturation; + rm1 = 2.0f * luminance - rm2; + BYTE red = _ToRGB (rm1, rm2, hue + 120.0f); + BYTE green = _ToRGB (rm1, rm2, hue); + BYTE blue = _ToRGB (rm1, rm2, hue - 120.0f); + + return RGB (red, green, blue); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF HLS_TRANSFORM (COLORREF rgb, int percent_L, int percent_S) +{ + HLSCOLOR hls = RGB2HLS (rgb); + BYTE h = HLS_H(hls); + BYTE l = HLS_L(hls); + BYTE s = HLS_S(hls); + + if ( percent_L > 0 ) + { + l = BYTE(l + ((255 - l) * percent_L) / 100); + } + else if ( percent_L < 0 ) + { + l = BYTE((l * (100+percent_L)) / 100); + } + if ( percent_S > 0 ) + { + s = BYTE(s + ((255 - s) * percent_S) / 100); + } + else if ( percent_S < 0 ) + { + s = BYTE((s * (100+percent_S)) / 100); + } + return HLS2RGB (HLS(h, l, s)); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CBufferDC::CBufferDC (HDC hDestDC, const CRect& rcPaint) : m_hDestDC (hDestDC) +{ + if ( rcPaint.IsRectEmpty() ) + { + ::GetClipBox (m_hDestDC, m_rect); + } + else + { + m_rect = rcPaint; + } + Attach (::CreateCompatibleDC (m_hDestDC)); + m_bitmap.Attach (::CreateCompatibleBitmap (m_hDestDC, m_rect.right, m_rect.bottom)); + m_hOldBitmap = ::SelectObject (m_hDC, m_bitmap); + + if ( m_rect.top > 0 ) + { + ExcludeClipRect (0, 0, m_rect.right, m_rect.top); + } + if ( m_rect.left > 0 ) + { + ExcludeClipRect (0, m_rect.top, m_rect.left, m_rect.bottom); + } +} + +/////////////////////////////////////////////////////////////////////////////// +CBufferDC::~CBufferDC () +{ + ::BitBlt (m_hDestDC, m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(), m_hDC, m_rect.left, m_rect.top, SRCCOPY); + ::SelectObject (m_hDC, m_hOldBitmap); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CPenDC::CPenDC (HDC hDC, COLORREF crColor) : m_hDC (hDC) +{ + m_pen.CreatePen (PS_SOLID, 1, crColor); + m_hOldPen = (HPEN)::SelectObject (m_hDC, m_pen); +} + +/////////////////////////////////////////////////////////////////////////////// +CPenDC::~CPenDC () +{ + ::SelectObject (m_hDC, m_hOldPen); +} + +/////////////////////////////////////////////////////////////////////////////// +void CPenDC::Color (COLORREF crColor) +{ + ::SelectObject (m_hDC, m_hOldPen); + m_pen.DeleteObject(); + m_pen.CreatePen (PS_SOLID, 1, crColor); + m_hOldPen = (HPEN)::SelectObject (m_hDC, m_pen); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF CPenDC::Color () const +{ + LOGPEN logPen; + + ((CPenDC*)this)->m_pen.GetLogPen (&logPen); + + return logPen.lopnColor; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CBrushDC::CBrushDC (HDC hDC, COLORREF crColor) : m_hDC (hDC) +{ + if ( crColor == CLR_NONE ) m_brush.Attach ((HBRUSH)::GetStockObject (NULL_BRUSH)); + else m_brush.CreateSolidBrush (crColor); + m_hOldBrush = (HBRUSH)::SelectObject (m_hDC, m_brush); +} + +/////////////////////////////////////////////////////////////////////////////// +CBrushDC::~CBrushDC () +{ + ::SelectObject (m_hDC, m_hOldBrush); +} + +/////////////////////////////////////////////////////////////////////////////// +void CBrushDC::Color (COLORREF crColor) +{ + ::SelectObject (m_hDC, m_hOldBrush); + m_brush.DeleteObject(); + if ( crColor == CLR_NONE ) m_brush.Attach ((HBRUSH)::GetStockObject (NULL_BRUSH)); + else m_brush.CreateSolidBrush (crColor); + m_hOldBrush = (HBRUSH)::SelectObject (m_hDC, m_brush); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF CBrushDC::Color () const +{ + LOGBRUSH logBrush; + + ((CBrushDC*)this)->m_brush.GetLogBrush (&logBrush); + + return logBrush.lbColor; +} + + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +CBoldDC::CBoldDC (HDC hDC, bool bBold) : m_hDC (hDC), m_hDefFont (NULL) +{ + LOGFONT lf; + + CFontHandle ((HFONT)::GetCurrentObject (m_hDC, OBJ_FONT)).GetLogFont (&lf); + + if ( ( bBold && lf.lfWeight != FW_BOLD) || + (!bBold && lf.lfWeight == FW_BOLD) ) + { + lf.lfWeight = bBold ? FW_BOLD : FW_NORMAL; + + m_fontBold.CreateFontIndirect (&lf); + m_hDefFont = (HFONT)::SelectObject (m_hDC, m_fontBold); + } +} + +///////////////////////////////////////////////////////////////////////////// +CBoldDC::~CBoldDC () +{ + if ( m_hDefFont != NULL ) + { + ::SelectObject (m_hDC, m_hDefFont); + } +} + +CFontDC::CFontDC(HDC hDC, HFONT hFont) + : m_font(hFont), m_hDC(hDC) + , m_hOldFont((HFONT)::SelectObject(hDC, hFont)) +{ +} + +CFontDC::~CFontDC() +{ + if (m_hOldFont) + { + ::SelectObject(m_hDC, (HFONT)m_hOldFont); + } +} diff --git a/src/app/Draw.h b/src/app/Draw.h new file mode 100644 index 0000000..8160fa4 --- /dev/null +++ b/src/app/Draw.h @@ -0,0 +1,112 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: Draw.h + * Copyright: 2006 Igor Vigdorchik, some rights reserved + * License: CPOL, s. https://en.wikipedia.org/wiki/Code_Project_Open_License + * @author Igor Vigdorchik (http://www.codeproject.com/Members/IgorVigdorchik) + ************************************************************************/ + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +typedef DWORD HLSCOLOR; +#define HLS(h,l,s) ((HLSCOLOR)(((BYTE)(h)|((WORD)((BYTE)(l))<<8))|(((DWORD)(BYTE)(s))<<16))) + +/////////////////////////////////////////////////////////////////////////////// +#define HLS_H(hls) ((BYTE)(hls)) +#define HLS_L(hls) ((BYTE)(((WORD)(hls)) >> 8)) +#define HLS_S(hls) ((BYTE)((hls)>>16)) + +/////////////////////////////////////////////////////////////////////////////// +HLSCOLOR RGB2HLS (COLORREF rgb); +COLORREF HLS2RGB (HLSCOLOR hls); + +/////////////////////////////////////////////////////////////////////////////// +// Performs some modifications on the specified color : luminance and saturation +COLORREF HLS_TRANSFORM (COLORREF rgb, int percent_L, int percent_S); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBufferDC : public CDCHandle +{ + HDC m_hDestDC; + CBitmap m_bitmap; // Bitmap in Memory DC + CRect m_rect; + HGDIOBJ m_hOldBitmap; // Previous Bitmap + +public: + CBufferDC (HDC hDestDC, const CRect& rcPaint = CRect(0,0,0,0)); + ~CBufferDC (); +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CPenDC +{ +protected: + CPen m_pen; + HDC m_hDC; + HPEN m_hOldPen; + +public: + CPenDC (HDC hDC, COLORREF crColor = CLR_NONE); + ~CPenDC (); + + void Color (COLORREF crColor); + COLORREF Color () const; +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBrushDC +{ +protected: + CBrush m_brush; + HDC m_hDC; + HBRUSH m_hOldBrush; + +public: + CBrushDC (HDC hDC, COLORREF crColor = CLR_NONE); + ~CBrushDC (); + + void Color (COLORREF crColor); + COLORREF Color () const; +}; + +class CFontDC +{ +protected: + HFONT m_font; + HDC m_hDC; + HFONT m_hOldFont; + +public: + CFontDC(HDC hDC, HFONT hFont); + virtual ~CFontDC(); + + HFONT GetFont() const noexcept + { + return m_font; + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBoldDC +{ +protected: + CFont m_fontBold; + HDC m_hDC; + HFONT m_hDefFont; + +public: + CBoldDC (HDC hDC, bool bBold); + ~CBoldDC (); +}; diff --git a/src/app/MainFrm.h b/src/app/MainFrm.h index 50f54cc..52277ad 100644 --- a/src/app/MainFrm.h +++ b/src/app/MainFrm.h @@ -12,7 +12,7 @@ class CMainFrame : public CFrameWindowImpl, - public CThemeImpl, + public CUxModeWindow, public CUpdateUI, public CMessageFilter, public CIdleHandler, public CShellBrowseMenu::ShellMenuController { @@ -44,13 +44,13 @@ class CMainFrame : END_UPDATE_UI_MAP() BEGIN_MSG_MAP(CMainFrame) + CHAIN_MSG_MAP(CUxModeWindow) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) MESSAGE_HANDLER(WM_MENUCOMMAND, OnMenuCommand) MESSAGE_HANDLER(GetDisplayPopupMessage(), OnDisplayPopup) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) - CHAIN_MSG_MAP(CThemeImpl) CHAIN_MSG_MAP_MEMBER(m_shellMenu) MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect) CHAIN_MSG_MAP(CUpdateUI) diff --git a/src/app/ShellBrowseMenu.cpp b/src/app/ShellBrowseMenu.cpp index bf5a326..608d7a3 100644 --- a/src/app/ShellBrowseMenu.cpp +++ b/src/app/ShellBrowseMenu.cpp @@ -1,9 +1,18 @@ #include "stdafx.h" #include "resource.h" + +#include "Draw.h" #include "ShellMgr.h" #include "ShellBrowseMenu.h" +CShellBrowseMenu::CShellBrowseMenu(const ShellMenuController* controller) + : m_controller(controller), m_isRendered(false), m_isCtxMenuShowing(false) +{ + LoadIconImages(); + UpdateMetrics(); +} + HRESULT CShellBrowseMenu::Rebuild() { ATLASSERT(m_controller); @@ -48,6 +57,13 @@ BOOL CShellBrowseMenu::InvokeWithSelection(LPCTSTR strVerb) const return FALSE; } +LRESULT CShellBrowseMenu::OnThemeChange(UINT, WPARAM, LPARAM, BOOL& bHandled) +{ + bHandled = FALSE; + UpdateMetrics(true); + return 0L; +} + LRESULT CShellBrowseMenu::OnInitMenuPopup(UINT, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { bHandled = FALSE; @@ -65,6 +81,9 @@ LRESULT CShellBrowseMenu::OnInitMenuPopup(UINT, WPARAM wParam, LPARAM lParam, BO ATLTRACE(_T(__FUNCTION__) _T(" hr=%p pFolder=%p\n"), hr, pFolder.p); } BuildFolderMenu(pFolder, hMenu); + + //auto hMenuWnd = ::FindWindow(_T("#32768"), NULL); + //::SetWindowTheme(hMenuWnd, L"DarkMode", L"Menu"); } else { @@ -159,23 +178,24 @@ LRESULT CShellBrowseMenu::OnMenuSelect(UINT, WPARAM wParam, LPARAM lParam, BOOL& if (::GetMenuItemInfo(hMenu, item, isPopup, &mii) && mii.dwItemData) { auto pData = (LPSHELLITEMDATA)mii.dwItemData; + if (pData->parentFolder && !pData->relativeIDL.IsNull()) + { + ShellItemSelection& selObject = isPopup ? m_folderSelection : m_selection; - ShellItemSelection& selObject = isPopup ? m_folderSelection : m_selection; - - selObject.hMenu = hMenu; - selObject.byPosition = isPopup; - selObject.menuItem = item; + selObject.hMenu = hMenu; + selObject.byPosition = isPopup; + selObject.menuItem = item; - selObject.parentFolder = pData->parentFolder.p; - selObject.parentFolder.p->AddRef(); - selObject.relativeIDL.CopyFrom(pData->relativeIDL); + selObject.parentFolder = pData->parentFolder.p; + selObject.parentFolder.p->AddRef(); + selObject.relativeIDL.CopyFrom(pData->relativeIDL); - if (isPopup) - { - ::GetMenuItemRect(m_controller->GetHWnd(), hMenu, item, &m_folderSelection.itemRect); + if (isPopup) + { + ::GetMenuItemRect(m_controller->GetHWnd(), hMenu, item, &m_folderSelection.itemRect); + } + bHandled = TRUE; } - - bHandled = TRUE; } } return 1; @@ -253,66 +273,166 @@ LRESULT CShellBrowseMenu::OnDrawItem(UINT, WPARAM /*wParam*/, LPARAM lParam, BOO { bHandled = FALSE; LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam; - if (NULL == lpDis || ODT_MENU != lpDis->CtlType || NULL == lpDis->itemData || !m_pImageList) + if (NULL == lpDis || ODT_MENU != lpDis->CtlType || NULL == lpDis->itemData) { return FALSE; } bHandled = TRUE; - - auto pData = (LPSHELLITEMDATA)lpDis->itemData; - - CShellItemIDList idlParent; - CComQIPtr persFolder(pData->parentFolder); - if (!persFolder || FAILED(persFolder->GetCurFolder(&idlParent))) - return FALSE; - - CShellItemIDList idl(CShellMgr::ConcatIDLs(idlParent, pData->relativeIDL)); - // quicker method but doesn't return all IDLs - //CShellItemIDList idl(CShellMgr::GetAbsoluteIDL(pData->parentFolder, pData->relativeIDL)); - ////ATLASSERT(!idl.IsNull()); - if (idl.IsNull()) - return FALSE; - - IMAGELISTDRAWPARAMS ildp; - ::ZeroMemory(&ildp, sizeof(IMAGELISTDRAWPARAMS)); - ildp.cbSize = sizeof(IMAGELISTDRAWPARAMS); - ildp.himl = (HIMAGELIST)m_pImageList.p; - ildp.i = CShellMgr::GetIconIndex(idl, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); - ildp.hdcDst = lpDis->hDC; - ildp.x = lpDis->rcItem.left + 1; - ildp.y = lpDis->rcItem.top + 2; - ildp.rgbBk = CLR_NONE; - ildp.rgbFg = CLR_DEFAULT; - ildp.fStyle = ILD_TRANSPARENT; - - auto hr = m_pImageList->Draw(&ildp); - //ATLTRACE(_T(__FUNCTION__) _T(" hr=%p\n"), hr); - - return SUCCEEDED(hr) ? TRUE : FALSE; + return CustomDrawMenuItem(lpDis); } LRESULT CShellBrowseMenu::OnMeasureItem(UINT, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { bHandled = FALSE; LPMEASUREITEMSTRUCT lpMis = (LPMEASUREITEMSTRUCT)lParam; - if (NULL == lpMis || ODT_MENU != lpMis->CtlType || NULL == lpMis->itemData || !m_pImageList) + if (NULL == lpMis || ODT_MENU != lpMis->CtlType || NULL == lpMis->itemData) { return FALSE; } bHandled = TRUE; + return MeasureMenuItem(lpMis); +} + +BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) +{ + + auto pData = (LPSHELLMENUITEMDATA)lpDis->itemData; + + CDCHandle dc = lpDis->hDC; + RECT& rcItem = lpDis->rcItem; + + auto isDisabled = ODS_GRAYED == (lpDis->itemState & ODS_GRAYED); + auto isSelected = ODS_SELECTED == (lpDis->itemState & ODS_SELECTED); + auto isSeparator = 0 == pData->caption.GetLength(); + + CPenDC pen(dc, isSelected && !isDisabled ? m_metrics.crHihghlight : m_metrics.crBg); + CBrushDC brush(dc, isSelected && !isDisabled ? m_metrics.crHighlightBg : m_metrics.crBg); + dc.Rectangle(&rcItem); - CSize sz; - auto hr = m_pImageList->GetIconSize((int*) &sz.cx, (int*) &sz.cy); - //ATLTRACE(_T(__FUNCTION__) _T(" hr=%p itemWidth=%d itemHeight=%d\n"), hr, sz.cx, sz.cy); - if (SUCCEEDED(hr)) + CRect rc(rcItem); + if (m_metrics.sizeIcon.cx) { - lpMis->itemHeight = sz.cx + 4; - lpMis->itemWidth = sz.cy + 4; - return TRUE; + rc.left += m_metrics.sizeIcon.cx + (m_metrics.paddingIcon.cx * 2); } - return FALSE; + + if (isSeparator) + { + rc.top += rc.Height() / 2; + rc.bottom = rc.top + 1; + CPenDC penSep(dc, m_metrics.crBorder); + //CBrushDC brushSep(dc, m_metrics.crBorder); + dc.MoveTo(rc.left, rc.top); + dc.LineTo(rc.right, rc.top); + //dc.DrawEdge(rc, EDGE_ETCHED, BF_TOP); + } + else + { + auto isSubmenu = ::IsMenu((HMENU)(UINT_PTR)lpDis->itemID); + + if (-1 < pData->iconIndex && m_pImageList) + { + IMAGELISTDRAWPARAMS ildp; + ::ZeroMemory(&ildp, sizeof(IMAGELISTDRAWPARAMS)); + ildp.cbSize = sizeof(IMAGELISTDRAWPARAMS); + ildp.himl = (HIMAGELIST)m_pImageList.p; + ildp.i = pData->iconIndex; + ildp.hdcDst = lpDis->hDC; + ildp.x = lpDis->rcItem.left + m_metrics.paddingIcon.cx; + ildp.y = lpDis->rcItem.top + m_metrics.paddingIcon.cy; + ildp.rgbBk = CLR_NONE; + ildp.rgbFg = CLR_DEFAULT; + ildp.fStyle = ILD_TRANSPARENT; + + m_pImageList->Draw(&ildp); + } + + dc.SetTextColor(isDisabled ? m_metrics.crTextDisabled : m_metrics.crText); + dc.SetBkMode(TRANSPARENT); + + rc.left += m_metrics.paddingText.cx; + CFontDC font(dc, m_metrics.fontMnu); + dc.DrawText(pData->caption, pData->caption.GetLength(), rc, DT_SINGLELINE | DT_VCENTER | DT_LEFT | DT_END_ELLIPSIS); + + if (isSubmenu) + { + CustomDrawMenuArrow(dc, &rcItem, isDisabled); + } + + dc.ExcludeClipRect(&rcItem); + } + return TRUE; +} + +BOOL CShellBrowseMenu::CustomDrawMenuArrow(HDC hdcItem, LPRECT rcItem, bool isDisabled) +{ + CRect rcArrow(rcItem); + rcArrow.left = rcItem->right - m_metrics.sizeMnuArrow.cx; + + if (rcArrow.Height() > m_metrics.sizeMnuArrow.cy) + { + rcArrow.top += (rcArrow.Height() - m_metrics.sizeMnuArrow.cy) / 2; + rcArrow.bottom = rcArrow.top + m_metrics.sizeMnuArrow.cy; + } + + CDCHandle dc(hdcItem); + + CDC arrowDC; + arrowDC.CreateCompatibleDC(hdcItem); + + CBitmap arrowBitmap; + arrowBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); + auto oldArrowBmp = arrowDC.SelectBitmap(arrowBitmap); + + CDC fillDC; + fillDC.CreateCompatibleDC(hdcItem); + + CBitmap fillBitmap; + fillBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); + auto oldFillBmp = fillDC.SelectBitmap(fillBitmap); + + CRect rcTemp(0, 0, rcArrow.Width(), rcArrow.Height()); + auto result = arrowDC.DrawFrameControl(&rcTemp, DFC_MENU, DFCS_MENUARROW); + + fillDC.FillRect(rcTemp, isDisabled ? m_metrics.brushTextDisabled : m_metrics.brushText); + + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), arrowDC, 0, 0, SRCAND); + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); + + arrowDC.SelectBitmap(oldArrowBmp); + fillDC.SelectBitmap(oldFillBmp); + return result; +} + +BOOL CShellBrowseMenu::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis) +{ + auto pData = (LPSHELLMENUITEMDATA)lpMis->itemData; + + auto isSeparator = 0 == pData->caption.GetLength(); + if (isSeparator) + { + lpMis->itemHeight = ::GetSystemMetrics(SM_CYMENU) / 2; + lpMis->itemWidth = 0; + } + else + { + CWindowDC dc(NULL); + + CFontDC font(dc, m_metrics.fontMnu); + CRect rcText; + dc.DrawText(pData->caption, -1, rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT); + + lpMis->itemHeight = m_metrics.itemHeight; + lpMis->itemWidth = rcText.Width() + m_metrics.paddingIcon.cx + m_metrics.sizeMnuArrow.cx; + if (m_metrics.sizeIcon.cx) + { + lpMis->itemWidth += (m_metrics.paddingText.cx * 2) + m_metrics.sizeIcon.cx; + } + } + + return TRUE; } HRESULT CShellBrowseMenu::LoadIconImages() @@ -320,7 +440,64 @@ HRESULT CShellBrowseMenu::LoadIconImages() return ::SHGetImageList(SHIL_SMALL, IID_IImageList, (void**) &m_pImageList); } -BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) const +void CShellBrowseMenu::UpdateMetrics(bool colorsOnly) +{ + auto isDark = IsInDarkMode(); + m_metrics.crBg = isDark ? HLS_TRANSFORM(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background), +20, -20) : ::GetSysColor(COLOR_3DFACE); + m_metrics.crText = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground) : ::GetSysColor(COLOR_MENUTEXT); + m_metrics.crTextDisabled = isDark ? HLS_TRANSFORM(m_metrics.crText, -50, 0) : ::GetSysColor(COLOR_GRAYTEXT); + m_metrics.crBorder = isDark ? HLS_TRANSFORM(m_metrics.crTextDisabled, -20, 0) : ::GetSysColor(COLOR_SCROLLBAR); + m_metrics.crHihghlight = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background) : ::GetSysColor(COLOR_MENUHILIGHT); + m_metrics.crHighlightBg = isDark ? HLS_TRANSFORM(m_metrics.crHihghlight, +40, -17) : HLS_TRANSFORM(m_metrics.crHihghlight, +70, -57); + if (isDark) + { + if (!m_metrics.brushBg.IsNull()) + m_metrics.brushBg.DeleteObject(); + m_metrics.brushBg.CreateSolidBrush(m_metrics.crBg); + } + if (!m_metrics.brushText.IsNull()) + m_metrics.brushText.DeleteObject(); + m_metrics.brushText.CreateSolidBrush(m_metrics.crText); + if (!m_metrics.brushTextDisabled.IsNull()) + m_metrics.brushTextDisabled.DeleteObject(); + m_metrics.brushTextDisabled.CreateSolidBrush(m_metrics.crTextDisabled); + + if (!colorsOnly) + { + CBitmap bmpArrow; + BITMAP bm; + // ::LoadImage(NULL, MAKEINTRESOURCE(OBM_MNARROW), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_SHARED) + if (bmpArrow.LoadOEMBitmap(OBM_MNARROW) && bmpArrow.GetBitmap(bm)) + { + m_metrics.sizeMnuArrow.cx = bm.bmWidth; + m_metrics.sizeMnuArrow.cy = bm.bmHeight; + } + + if (m_pImageList) + { + m_pImageList->GetIconSize((int*)&m_metrics.sizeIcon.cx, (int*)&m_metrics.sizeIcon.cy); + m_pImageList->GetIconSize((int*)&m_metrics.sizeIcon.cx, (int*)&m_metrics.sizeIcon.cy); + } + + m_metrics.metricsNC.cbSize = sizeof(NONCLIENTMETRICS); + if (::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, m_metrics.metricsNC.cbSize, &m_metrics.metricsNC, 0)) + { + m_metrics.fontMnu.CreateFontIndirect(&m_metrics.metricsNC.lfMenuFont); + + m_metrics.itemHeight = max(m_metrics.metricsNC.iMenuHeight, ::abs(m_metrics.metricsNC.lfMenuFont.lfHeight) + (m_metrics.paddingText.cy * 2)); + if (m_metrics.sizeIcon.cy && m_metrics.itemHeight < (m_metrics.paddingIcon.cy * 2) + m_metrics.sizeIcon.cy) + { + m_metrics.itemHeight = (m_metrics.paddingIcon.cy * 2) + m_metrics.sizeIcon.cy; + } + if (m_metrics.paddingIcon.cy > m_metrics.itemHeight - m_metrics.sizeIcon.cy - m_metrics.paddingIcon.cy) + { + m_metrics.paddingIcon.cy = (m_metrics.itemHeight - m_metrics.sizeIcon.cy) / 2; + } + } + } +} + +BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) { if (menu.IsMenu()) { @@ -329,6 +506,11 @@ BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) const mi.cbSize = sizeof(mi); mi.fMask = MIM_STYLE; mi.dwStyle = MNS_CHECKORBMP; + if (IsInDarkMode()) + { + mi.fMask |= MIM_BACKGROUND; + mi.hbrBack = m_metrics.brushBg; + } return menu.SetMenuInfo(&mi); } return FALSE; @@ -359,11 +541,19 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) ATLASSERT((int)id == ID_ACTION_FIRST + (m_openMenus.GetSize() * 0x1000)); UINT pos = isTopMenu ? m_controller->GetShellMenuAnchor() + 1 : 0; auto idMsg = MessageID::SBM_NONE; - TCHAR szBuff[MAX_PATH]; - szBuff[0] = _T('\0'); if (pFolder) { + TCHAR szBuff[MAX_PATH]; + szBuff[0] = _T('\0'); + + CShellItemIDList idlParent; + CComQIPtr persFolder(pFolder); + if (!persFolder || FAILED(persFolder->GetCurFolder(&idlParent))) + { + ATLTRACE2(_T(__FUNCTION__) _T(" failed to retrieve folder IDL\n")); + } + CShellItemIDList idlChild; ULONG ulFetched = 0; @@ -399,7 +589,14 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) : menuPopup.AppendMenu(MF_STRING, id, szBuff); if (succ) { - auto pData = new ShellItemData; + auto pData = new ShellMenuItemData; + + if (!idlParent.IsNull()) + { + CShellItemIDList absIDL(CShellMgr::ConcatIDLs(idlParent, idlChild)); + pData->iconIndex = CShellMgr::GetIconIndex(absIDL, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); + } + pData->caption = szBuff; pData->parentFolder.p = pFolder; pFolder->AddRef(); pData->relativeIDL.CopyFrom(idlChild); @@ -409,9 +606,19 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) pSelf.p->AddRef(); } + //CMenuItemInfo mii; + //mii.fMask = MIIM_DATA | MIIM_BITMAP; + //mii.hbmpItem = HBMMENU_CALLBACK; + //mii.dwItemData = (LONG_PTR)pData; + CMenuItemInfo mii; - mii.fMask = MIIM_DATA | MIIM_BITMAP; - mii.hbmpItem = HBMMENU_CALLBACK; + mii.cch = ::lstrlen(szBuff); + mii.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; + mii.dwTypeData = szBuff; + + menuPopup.GetMenuItemInfo(pos, TRUE, &mii); + + mii.fType |= MFT_OWNERDRAW; mii.dwItemData = (LONG_PTR)pData; succ = menuPopup.SetMenuItemInfo(pos, TRUE, &mii); @@ -448,7 +655,27 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) menuPopup.AppendMenu(MF_STRING, id, strMsg); menuPopup.EnableMenuItem(id, MF_BYCOMMAND | MF_DISABLED); } + else if (isTopMenu) + { + for (auto i = 0; i <= m_controller->GetShellMenuAnchor(); i++) + { + auto pData = new ShellMenuItemData; + CMenuItemInfo mii; + mii.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; + + menuPopup.GetMenuItemInfo(i, TRUE, &mii); + if (MFT_SEPARATOR != (mii.fType & MFT_SEPARATOR)) + { + ::GetMenuString(menuPopup, i, pData->caption.GetBufferSetLength(MAX_PATH), MAX_PATH, MF_BYPOSITION); + pData->caption.ReleaseBuffer(); + } + + mii.fType |= MFT_OWNERDRAW; + mii.dwItemData = (LONG_PTR)pData; + menuPopup.SetMenuItemInfo(i, TRUE, &mii); + } + } return hr; } diff --git a/src/app/ShellBrowseMenu.h b/src/app/ShellBrowseMenu.h index 2a7fb3f..84226ed 100644 --- a/src/app/ShellBrowseMenu.h +++ b/src/app/ShellBrowseMenu.h @@ -22,6 +22,14 @@ class CShellBrowseMenu virtual int GetMessageString(MessageID msgID, CString& msg) const = 0; }; + typedef struct ShellMenuItemData + : public ShellItemData + { + int iconIndex{ -1 }; + CString caption; + virtual ~ShellMenuItemData() = default; + } SHELLMENUITEMDATA, * LPSHELLMENUITEMDATA; + struct ShellItemSelection : public ShellItemData { @@ -46,11 +54,27 @@ class CShellBrowseMenu virtual ~FolderSelection() = default; }; - CShellBrowseMenu(const ShellMenuController* controller = NULL) - : m_controller(controller), m_isRendered(false), m_isCtxMenuShowing(false) + struct MenuMetrics { - LoadIconImages(); - } + COLORREF crText{RGB(0,0,0)}; + COLORREF crTextDisabled{ RGB(128,128,128) }; + COLORREF crBg{ RGB(255,255,255) }; + COLORREF crHihghlight{ RGB(200,200,200) }; + COLORREF crHighlightBg{ RGB(200,200,200) }; + COLORREF crBorder{ RGB(200,200,200) }; + CSize sizeIcon{ 0, 0 }; + CSize sizeMnuArrow{ 16, 16 }; + CSize paddingText{ 5, 5 }; + CSize paddingIcon{ 2, 2 }; + int itemHeight{-1}; + NONCLIENTMETRICS metricsNC{ 0 }; + CBrush brushText; + CBrush brushTextDisabled; + CBrush brushBg; + CFont fontMnu; + }; + + CShellBrowseMenu(const ShellMenuController* controller = NULL); virtual ~CShellBrowseMenu() = default; @@ -63,6 +87,7 @@ class CShellBrowseMenu MESSAGE_HANDLER(WM_UNINITMENUPOPUP, OnUninitMenuPopup) MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) + MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChange) END_MSG_MAP() const CShellItemIDList& GetRootIDL() const @@ -104,6 +129,7 @@ class CShellBrowseMenu private: + LRESULT OnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnInitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnUninitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMenuSelect(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); @@ -113,17 +139,27 @@ class CShellBrowseMenu LRESULT OnDrawItem(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnMeasureItem(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + BOOL CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis); + BOOL CustomDrawMenuArrow(HDC hdcItem, LPRECT rcItem, bool isDisabled); + BOOL MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis); HRESULT LoadIconImages(); - BOOL SetupMenuInfo(CMenuHandle& menu) const; + void UpdateMetrics(bool colorsOnly = false); + BOOL SetupMenuInfo(CMenuHandle& menu); HRESULT BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu); void CleanUpMenuData(HMENU hMenu); + static inline bool IsInDarkMode() + { + return uxTheme.ShouldAppsUseDarkMode() && !uxTheme.IsHighContrast(); + } + private: const ShellMenuController* m_controller; CMenuHandle m_mnuTop; bool m_isRendered; bool m_isCtxMenuShowing; + MenuMetrics m_metrics; CShellItemIDList m_rootIDL; CSimpleStack m_openMenus; CComPtr m_pImageList; diff --git a/src/app/WinPinMenu.cpp b/src/app/WinPinMenu.cpp index 274a4ba..16d4a54 100644 --- a/src/app/WinPinMenu.cpp +++ b/src/app/WinPinMenu.cpp @@ -50,11 +50,6 @@ class CWinPinMenuThreadManager return 0; } - uxTheme.AllowDarkModeForWindow(wndFrame, true); - if (uxTheme.ShouldAppsUseDarkMode()) - { - uxTheme.SwitchWindowDarkMode(wndFrame, true); - } wndFrame.ShowWindow(pData->nCmdShow); delete pData; @@ -148,7 +143,8 @@ class CWinPinMenuThreadManager int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow) { // TODO: intrusive dark mode doesn't support owner-drawn menus, we need to paint all ourselves - //uxTheme.SetPreferredAppMode(PreferredAppMode::AllowDark); + uxTheme.SetPreferredAppMode(PreferredAppMode::AllowDark); + ::SetThemeAppProperties(STAP_ALLOW_NONCLIENT | STAP_ALLOW_CONTROLS | STAP_ALLOW_WEBCONTENT); HRESULT hRes = ::CoInitialize(NULL); ATLASSERT(SUCCEEDED(hRes)); diff --git a/src/app/WinPinMenu.rc b/src/app/WinPinMenu.rc index ab10cb8..17af940 100644 --- a/src/app/WinPinMenu.rc +++ b/src/app/WinPinMenu.rc @@ -101,21 +101,21 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTERMOUSE | WS_POPUP | WS_CAPTION | WS_S CAPTION "About" FONT 9, "Segoe UI", 0, 0, 0x0 BEGIN + GROUPBOX "",IDC_STATIC,7,7,228,120 DEFPUSHBUTTON "O&K",IDOK,179,106,50,14 PUSHBUTTON "&Back To Menu",IDRETRY,122,106,50,14 - ICON IDR_MAINFRAME,IDC_STATIC,12,15,20,20 - GROUPBOX "",IDC_STATIC,7,7,228,120 - LTEXT "Product Name",IDC_TXT_PRODUCTNAME,41,15,125,18 - RTEXT "License",IDC_LNK_LICENSE,172,15,57,8 - RTEXT "Company Name",IDC_TXT_COMPANYNAME,172,26,57,8 - LTEXT "File Description",IDC_TXT_FILEDESCRIPTION,12,39,217,23 - RTEXT "Version:",IDC_STATIC,12,68,51,8 - RTEXT "Module Version:",IDC_STATIC,12,79,51,8 - RTEXT "Copyright:",IDC_STATIC,12,89,51,8 - RTEXT "Release Notes",IDC_LNK_RELNOTES,172,68,57,8 - LTEXT "1.0.0.0",IDC_TXT_PRODUCTVERSION,70,68,96,8 - LTEXT "1.0.0.1",IDC_TXT_FILEVERSION,70,79,96,8 - LTEXT "Legal Copyright",IDC_TXT_LEGALCOPYRIGHT,70,89,96,8 + ICON IDR_MAINFRAME,IDC_STATIC,12,16,18,17,0,WS_EX_TRANSPARENT + LTEXT "Product Name",IDC_TXT_PRODUCTNAME,41,16,125,18,0,WS_EX_TRANSPARENT + RTEXT "License",IDC_LNK_LICENSE,172,16,57,8,WS_TABSTOP,WS_EX_TRANSPARENT + RTEXT "Company Name",IDC_TXT_COMPANYNAME,172,27,57,8,0,WS_EX_TRANSPARENT + LTEXT "File Description",IDC_TXT_FILEDESCRIPTION,12,40,217,22 + RTEXT "Version:",IDC_STATIC,12,68,51,8,0,WS_EX_TRANSPARENT + RTEXT "Module Version:",IDC_STATIC,12,79,51,8,0,WS_EX_TRANSPARENT + RTEXT "Copyright:",IDC_STATIC,12,89,51,8,0,WS_EX_TRANSPARENT + RTEXT "Release Notes",IDC_LNK_RELNOTES,172,68,57,8,WS_TABSTOP,WS_EX_TRANSPARENT + LTEXT "1.0.0.0",IDC_TXT_PRODUCTVERSION,70,68,96,8,0,WS_EX_TRANSPARENT + LTEXT "1.0.0.1",IDC_TXT_FILEVERSION,70,79,96,8,0,WS_EX_TRANSPARENT + LTEXT "Legal Copyright",IDC_TXT_LEGALCOPYRIGHT,70,89,96,8,0,WS_EX_TRANSPARENT END diff --git a/src/app/WinPinMenu.vcxproj b/src/app/WinPinMenu.vcxproj index 6aa20ec..061b126 100644 --- a/src/app/WinPinMenu.vcxproj +++ b/src/app/WinPinMenu.vcxproj @@ -139,6 +139,7 @@ EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -169,6 +170,7 @@ EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -200,6 +202,7 @@ EnableFastChecks Disabled WIN32;_WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -227,9 +230,10 @@ Use Level4 MultiThreaded - + Sync WIN32;_WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -256,9 +260,10 @@ Use Level4 MultiThreaded - + Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -284,11 +289,11 @@ Use Level4 MultiThreaded - - + Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 Windows @@ -312,6 +317,7 @@ + @@ -328,6 +334,7 @@ + diff --git a/src/app/WinPinMenu.vcxproj.filters b/src/app/WinPinMenu.vcxproj.filters index fc7335f..2b8f47f 100644 --- a/src/app/WinPinMenu.vcxproj.filters +++ b/src/app/WinPinMenu.vcxproj.filters @@ -36,6 +36,9 @@ Source Files + + Source Files + @@ -71,6 +74,9 @@ Header Files + + Header Files + diff --git a/src/app/buildnumber.txt b/src/app/buildnumber.txt new file mode 100644 index 0000000..dc7b54a --- /dev/null +++ b/src/app/buildnumber.txt @@ -0,0 +1 @@ +33 \ No newline at end of file diff --git a/src/app/productmeta.h b/src/app/productmeta.h index 3f72ec5..99b6426 100644 --- a/src/app/productmeta.h +++ b/src/app/productmeta.h @@ -4,7 +4,7 @@ #define PRODUCT_VERSION_MAJOR 0 #define PRODUCT_VERSION_MINOR 1 #define PRODUCT_VERSION_PATCH 1 -#define PRODUCT_VERSION_TWEAK 28 +#define PRODUCT_VERSION_TWEAK 33 #define PRODUCT_NAME _T("WinPinMenu\0") #define PRODUCT_DESCRIPTION _T("diVISION Pinnable Taskbar Menu For Windows\0") #define PRODUCT_HOMEPAGE_URL _T("https://github.com/hatelamers/WinPinMenu\0") @@ -19,5 +19,5 @@ #define FILE_NAME _T("WinPinMenu\0") #define FILE_DESCRIPTION _T("diVISION Pinnable Taskbar Menu For Windows\0") -#define FILE_VERSION 0,1,1,28 -#define FILE_VERSION_S _T("0.1.1.28\0") +#define FILE_VERSION 0,1,1,33 +#define FILE_VERSION_S _T("0.1.1.33\0") diff --git a/src/app/res/app.manifest b/src/app/res/app.manifest index 7651d54..b379ef5 100644 --- a/src/app/res/app.manifest +++ b/src/app/res/app.manifest @@ -1,6 +1,6 @@ - + diVISION Pinnable Taskbar Menu For Windows diff --git a/src/app/resource.h b/src/app/resource.h index 907f884..2e5d9d4 100644 --- a/src/app/resource.h +++ b/src/app/resource.h @@ -16,6 +16,7 @@ #define IDC_TXT_PRODUCTVERSION 1005 #define IDC_TXT_FILEVERSION 1006 #define IDC_TXT_LEGALCOPYRIGHT 1007 +#define IDC_SYSLINK1 1009 #define IDS_EMPTY_FOLDER 32772 #define IDS_INVALID_SOURCE 32773 #define ID_FILE_NOACTIONSOURCE 32775 @@ -24,9 +25,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 204 -#define _APS_NEXT_COMMAND_VALUE 32776 -#define _APS_NEXT_CONTROL_VALUE 1009 +#define _APS_NEXT_RESOURCE_VALUE 205 +#define _APS_NEXT_COMMAND_VALUE 32777 +#define _APS_NEXT_CONTROL_VALUE 1010 #define _APS_NEXT_SYMED_VALUE 102 #endif #endif diff --git a/src/app/stdafx.h b/src/app/stdafx.h index 37a0e0b..665d2b8 100644 --- a/src/app/stdafx.h +++ b/src/app/stdafx.h @@ -7,6 +7,10 @@ // Change these values to use different versions #include "targetver.h" +#ifndef OEMRESOURCE +#define OEMRESOURCE 1 +#endif // !OEMRESOURCE + #include @@ -33,21 +37,8 @@ extern CAppModule _Module; #include #include #include -#include #include -//#if defined _M_IX86 -// #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") -//#elif defined _M_IA64 -// #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") -//#elif defined _M_X64 -// #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") -//#else -// #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") -//#endif - #include "taskbarautomation.h" #include "uxthemehelper.h" - -extern CUxTheme uxTheme; \ No newline at end of file diff --git a/src/app/uxthemehelper.h b/src/app/uxthemehelper.h index 12aab7d..3cd0f75 100644 --- a/src/app/uxthemehelper.h +++ b/src/app/uxthemehelper.h @@ -1,7 +1,18 @@ #pragma once +#include +#include + +#include + #pragma comment(lib, "Dwmapi.lib") +enum class IMMERSIVE_HC_CACHE_MODE +{ + IHCM_USE_CACHED_VALUE, + IHCM_REFRESH +}; + enum class PreferredAppMode { Default, @@ -51,32 +62,48 @@ struct WINDOWCOMPOSITIONATTRIBDATA }; +using FnRefreshImmersiveColorPolicyState = void (WINAPI*)(); // ordinal 104 +using FnGetIsImmersiveColorUsingHighContrast = bool (WINAPI*)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106 using FnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132 using FnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133 using FnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903 +using FnFlushMenuThemes = void (WINAPI*)(); // ordinal 136 using FnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); +#define UXTHEME_DECLARE_FUNC(name) \ +Fn##name m_pfn##name{NULL} + +#define UXTHEME_INIT_FUNC(name, hMod) \ +m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, #name)); \ +ATLASSERT(m_pfn##name) + +#define UXTHEME_INIT_ORD_FUNC(name, ord, hMod) \ +m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, MAKEINTRESOURCEA(ord))); \ +ATLASSERT(m_pfn##name) + class CUxTheme { public: + using UIColorType = winrt::Windows::UI::ViewManagement::UIColorType; + using UIElementType = winrt::Windows::UI::ViewManagement::UIElementType; + CUxTheme() - : m_hUxtheme(NULL), m_pfnAllowDarkModeForWindow(NULL), m_pfnShouldAppsUseDarkMode(NULL), m_pfnSetPreferredAppMode(NULL) { m_hUxtheme = ::LoadLibraryEx(_T("uxtheme.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); ATLASSERT(m_hUxtheme); if (m_hUxtheme) { - m_pfnShouldAppsUseDarkMode = (FnShouldAppsUseDarkMode)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(132)); - ATLASSERT(m_pfnShouldAppsUseDarkMode); - m_pfnAllowDarkModeForWindow = (FnAllowDarkModeForWindow)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(133)); - ATLASSERT(m_pfnAllowDarkModeForWindow); - m_pfnSetPreferredAppMode = (FnSetPreferredAppMode)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(135)); - ATLASSERT(m_pfnSetPreferredAppMode); + UXTHEME_INIT_ORD_FUNC(RefreshImmersiveColorPolicyState, 104, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(GetIsImmersiveColorUsingHighContrast, 106, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(ShouldAppsUseDarkMode, 132, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(AllowDarkModeForWindow, 133, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(SetPreferredAppMode, 135, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(FlushMenuThemes, 136, m_hUxtheme); } auto hModule = ::GetModuleHandle(_T("user32.dll")); if (hModule) { - m_pfnSetWindowCompositionAttribute = reinterpret_cast(::GetProcAddress(hModule, "SetWindowCompositionAttribute")); + UXTHEME_INIT_FUNC(SetWindowCompositionAttribute, hModule); } } @@ -93,6 +120,14 @@ class CUxTheme return false; } + bool IsHighContrast() const + { + HIGHCONTRASTW highContrast = { sizeof(highContrast) }; + if (::SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) + return highContrast.dwFlags & HCF_HIGHCONTRASTON; + return false; + } + PreferredAppMode SetPreferredAppMode(PreferredAppMode appMode) const { if (m_pfnSetPreferredAppMode) @@ -100,6 +135,12 @@ class CUxTheme return PreferredAppMode::Default; } + void FlushMenuThemes() const + { + if (m_pfnFlushMenuThemes) + m_pfnFlushMenuThemes(); + } + bool AllowDarkModeForWindow(HWND hWnd, bool allow) const { DWMNCRENDERINGPOLICY ncrp = allow ? DWMNCRP_ENABLED : DWMNCRP_DISABLED; @@ -137,12 +178,274 @@ class CUxTheme return result; } + bool IsColorSchemeChangeMessage(LPARAM lParam) const + { + auto result = false; + + if (lParam && 0 == ::lstrcmpi(reinterpret_cast(lParam), _T("ImmersiveColorSet"))) + { + if (m_pfnRefreshImmersiveColorPolicyState) + { + m_pfnRefreshImmersiveColorPolicyState(); + } + result = true; + } + if (m_pfnGetIsImmersiveColorUsingHighContrast) + { + m_pfnGetIsImmersiveColorUsingHighContrast(IMMERSIVE_HC_CACHE_MODE::IHCM_REFRESH); + } + return result; + } + + bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam) const + { + if (message == WM_SETTINGCHANGE) + return IsColorSchemeChangeMessage(lParam); + return false; + } + + COLORREF UIElementSysColor(UIElementType type) const + { + auto col = WinRTSettings.UIElementColor(type); + return RGB(col.R, col.G, col.B); + } + + COLORREF GetSysColorValue(UIColorType type) const + { + auto col = WinRTSettings.GetColorValue(type); + return RGB(col.R, col.G, col.B); + } + +private: + + HMODULE m_hUxtheme{NULL}; + UXTHEME_DECLARE_FUNC(RefreshImmersiveColorPolicyState); + UXTHEME_DECLARE_FUNC(GetIsImmersiveColorUsingHighContrast); + UXTHEME_DECLARE_FUNC(AllowDarkModeForWindow); + UXTHEME_DECLARE_FUNC(ShouldAppsUseDarkMode); + UXTHEME_DECLARE_FUNC(SetPreferredAppMode); + UXTHEME_DECLARE_FUNC(FlushMenuThemes); + UXTHEME_DECLARE_FUNC(SetWindowCompositionAttribute); + winrt::Windows::UI::ViewManagement::UISettings WinRTSettings; +}; + +extern CUxTheme uxTheme; + +#define UXCOLOR_DARKER(color, factor) \ +max(RGB(0x50, 0x50, 0x50), RGB(((color & 0xff0000) >> 16) * factor, ((color & 0x00ff00) >> 8) * factor, (color & 0x0000ff) * factor)) + +#define UXCOLOR_LIGHTER(color, factor) \ +min(RGB(0xfe, 0xfe, 0xfe), RGB(((color & 0xff0000) >> 16) * factor, ((color & 0x00ff00) >> 8) * factor, (color & 0x0000ff) * factor)) + +template +struct is_dialog +{ +private: + typedef std::true_type yes; + typedef std::false_type no; + + template static auto test(int) -> decltype(std::declval().GetDialogProc() != nullptr, yes()); + + template static no test(...); + +public: + + static constexpr bool value = std::is_same(0)), yes>::value; +}; + +template +class CUxModeWindow +{ +public: + BEGIN_MSG_MAP(CUxModeWindow) + if (IsDialog) + { + MESSAGE_HANDLER(WM_INITDIALOG, UxModeOnInitDialog) + } + else + { + MESSAGE_HANDLER(WM_CREATE, UxModeOnCreate) + } + MESSAGE_HANDLER(WM_CTLCOLORDLG, UxModeOnCtlColorDlg) + MESSAGE_HANDLER(WM_CTLCOLORSTATIC, UxModeOnCtlColorDlg) + MESSAGE_HANDLER(WM_SETTINGCHANGE, UxModeOnSettingChange) + MESSAGE_HANDLER(WM_THEMECHANGED, UxModeOnThemeChange) + END_MSG_MAP() + + static bool IsInDarkMode() + { + return uxTheme.ShouldAppsUseDarkMode() && !uxTheme.IsHighContrast(); + } + +public: + static bool IsDialog; + +protected: + + bool SetUxModeForButton(HWND hWnd) const + { + auto r = uxTheme.AllowDarkModeForWindow(hWnd, IsInDarkMode()); + auto hr = m_initialized ? S_OK : ::SetWindowTheme(hWnd, L"Explorer", NULL); + if (m_initialized) + { + ::SendMessage(hWnd, WM_THEMECHANGED, 0, 0); + } + return r && SUCCEEDED(hr); + } + + bool UxModeDrawGroupBox(HWND hWnd, HDC hDC) + { + CWindow wnd(hWnd); + CDCHandle dc(hDC); + + CRect rc; + wnd.GetClientRect(rc); + + CRect rcFrame(rc); + rcFrame.top += 10; + auto crBorder = uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground); + crBorder = UXCOLOR_DARKER(crBorder, 0.4); + CPen pen; + pen.CreatePen(PS_SOLID, 1, crBorder); + auto oldPen = dc.SelectPen(pen); + auto oldBrush = dc.SelectBrush(reinterpret_cast(::GetStockObject(HOLLOW_BRUSH))); + auto result = TRUE == dc.Rectangle(rcFrame); + dc.SelectPen(oldPen); + dc.SelectBrush(oldBrush); + + CString str; + wnd.GetWindowText(str); + if (str.GetLength()) + { + CRect rcText(rc); + rcText.left += 10; + rcText.bottom = 20; + auto oldFont = dc.SelectFont(reinterpret_cast(::GetStockObject(DEFAULT_GUI_FONT))); + result = 0 != dc.DrawText(str, str.GetLength(), rcText, DT_SINGLELINE | DT_VCENTER | DT_LEFT) && result; + dc.SelectFont(oldFont); + } + + if (result) + { + dc.ExcludeClipRect(rc); + } + return result; + } + private: - HMODULE m_hUxtheme; - FnAllowDarkModeForWindow m_pfnAllowDarkModeForWindow; - FnShouldAppsUseDarkMode m_pfnShouldAppsUseDarkMode; - FnSetPreferredAppMode m_pfnSetPreferredAppMode; - FnSetWindowCompositionAttribute m_pfnSetWindowCompositionAttribute; + LRESULT UxModeOnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + UxModeSetup(); + return 0L; + } + + LRESULT UxModeOnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + UxModeSetup(); + return TRUE; + } + + LRESULT UxModeOnCtlColorDlg(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + //ATLTRACE(_T(__FUNCTION__) _T("\n")); + if (IsInDarkMode() && !m_brushBg.IsNull()) + { + bHandled = TRUE; + + CWindow wnd(reinterpret_cast(lParam)); + + CDCHandle dc(reinterpret_cast(wParam)); + dc.SetBkColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + dc.SetTextColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground)); + + if (BS_GROUPBOX == (BS_GROUPBOX & wnd.GetWindowLongPtr(GWL_STYLE))) + { + UxModeDrawGroupBox(wnd, dc); + } + return reinterpret_cast(WS_EX_TRANSPARENT == (WS_EX_TRANSPARENT & wnd.GetWindowLongPtr(GWL_EXSTYLE)) + ? ::GetStockObject(HOLLOW_BRUSH) + : m_brushBg.m_hBrush); + } + return FALSE; + } + + LRESULT UxModeOnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + if (uxTheme.IsColorSchemeChangeMessage(lParam)) + { + bHandled = TRUE; + GetThis()->SendMessage(WM_THEMECHANGED); + } + return 0L; + } + + LRESULT UxModeOnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + if (m_lastModeWasDark != IsInDarkMode()) + { + bHandled = TRUE; + UxModeSetup(); + GetThis()->UpdateWindow(); + } + return 0L; + } + + T* GetThis() + { + return static_cast(this); + } + + void UxModeSetup() + { + ATLTRACE(_T(__FUNCTION__) _T(" IsDialog=%d IsAppThemed=%d IsThemeActive=%d bgColor=%x\n"), IsDialog, ::IsAppThemed(), ::IsThemeActive() + , uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + + auto isDark = IsInDarkMode(); + if (isDark && m_lastModeWasDark != isDark) + { + if (!m_brushBg.IsNull()) + m_brushBg.DeleteObject(); + m_brushBg.CreateSolidBrush(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + } + + m_lastModeWasDark = isDark; + + auto pSelf = GetThis(); + if (!m_initialized) + uxTheme.AllowDarkModeForWindow(pSelf->m_hWnd, true); + uxTheme.SwitchWindowDarkMode(pSelf->m_hWnd, isDark); + + ::EnumChildWindows(pSelf->m_hWnd, [](HWND hwnd, LPARAM lParam) -> BOOL + { + auto pParent = reinterpret_cast(lParam); + CString str; + ::GetClassName(hwnd, str.GetBufferSetLength(MAX_PATH), MAX_PATH); + str.ReleaseBuffer(); + if (_T("Button") == str) + { + pParent->SetUxModeForButton(hwnd); + } + return TRUE; + }, reinterpret_cast(this)); + + if (!m_initialized) + pSelf->SendMessage(WM_THEMECHANGED); + + m_initialized = true; + } + +protected: + bool m_initialized{ false }; + bool m_lastModeWasDark{false}; + CBrush m_brushBg; + }; +template +bool CUxModeWindow::IsDialog = is_dialog::value; \ No newline at end of file