Mercurial > foo_out_sdl
comparison foosdk/sdk/libPPUI/DarkMode.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "stdafx.h" | |
| 2 #include "DarkMode.h" | |
| 3 #include "DarkModeEx.h" | |
| 4 #include "win32_utility.h" | |
| 5 #include "win32_op.h" | |
| 6 #include "PaintUtils.h" | |
| 7 #include "ImplementOnFinalMessage.h" | |
| 8 #include <vsstyle.h> | |
| 9 #include "GDIUtils.h" | |
| 10 #include "CListControl.h" | |
| 11 #include "CListControl-Subst.h" | |
| 12 #include "ReStyleWnd.h" | |
| 13 #include <map> | |
| 14 | |
| 15 // Allow scary undocumented ordinal-dll-export functions? | |
| 16 #define DARKMODE_ALLOW_HAX 1 | |
| 17 | |
| 18 #define DARKMODE_DEBUG 0 | |
| 19 | |
| 20 #if DARKMODE_DEBUG | |
| 21 #define DARKMODE_DEBUG_PRINT(...) PFC_DEBUG_PRINT("DarkMode: ", __VA_ARGS__) | |
| 22 #else | |
| 23 #define DARKMODE_DEBUG_PRINT(...) | |
| 24 #endif | |
| 25 | |
| 26 | |
| 27 #include <dwmapi.h> | |
| 28 #pragma comment(lib, "dwmapi.lib") | |
| 29 | |
| 30 /* | |
| 31 | |
| 32 ==== DARK MODE KNOWLEDGE BASE ==== | |
| 33 | |
| 34 == Generic window == | |
| 35 UpdateTitleBar() to set title bar to black | |
| 36 | |
| 37 == Dialog box == | |
| 38 Use WM_CTLCOLORDLG, background of 0x383838 | |
| 39 UpdateTitleBar() to set title bar to black | |
| 40 | |
| 41 == Edit box == | |
| 42 Use WM_CTLCOLOREDIT, background of 0x383838 | |
| 43 ApplyDarkThemeCtrl() with "Explorer" | |
| 44 | |
| 45 == Drop list combo == | |
| 46 Method #1: ::SetWindowTheme(wnd, L"DarkMode_CFD", nullptr); | |
| 47 Method #2: ::SetWindowTheme(wnd, L"", L""); to obey WM_CTLCOLOR* but leaves oldstyle classic-colors button and breaks comboboxex | |
| 48 | |
| 49 == Button == | |
| 50 Use WM_CTLCOLORBTN, background of 0x383838 | |
| 51 ApplyDarkThemeCtrl() with "Explorer" | |
| 52 | |
| 53 == Scroll bar == | |
| 54 ApplyDarkThemeCtrl() with "Explorer" | |
| 55 ^^ on either scrollbar or the window that implicitly creates scrollbars | |
| 56 | |
| 57 == Header == | |
| 58 ApplyDarkThemeCtrl() with "ItemsView" | |
| 59 Handle custom draw, override text color | |
| 60 | |
| 61 == Tree view == | |
| 62 ApplyDarkThemeCtrl() with "Explorer" | |
| 63 Set text/bk colors explicitly | |
| 64 | |
| 65 Text color: 0xdedede | |
| 66 Background: 0x191919 | |
| 67 | |
| 68 Label-editing: | |
| 69 Pass WM_CTLCOLOR* to parent, shim TVM_EDITLABEL to pass theme to the editbox (not really necessary tho) | |
| 70 | |
| 71 | |
| 72 == Rebar == | |
| 73 Can be beaten into working to some extent with a combination of: | |
| 74 * ::SetWindowTheme(toolbar, L"", L""); or else RB_SETTEXTCOLOR & REBARBANDINFO colors are disregarded | |
| 75 * Use RB_SETTEXTCOLOR / SetTextColor() + RB_SETBKCOLOR / SetBkColor() to override text/background colors | |
| 76 * Override WM_ERASEBKGND to draw band frames, RB_SETBKCOLOR doesn't seem to be thorough | |
| 77 * NM_CUSTOMDRAW on parent window to paint band labels & grippers without annoying glitches | |
| 78 NM_CUSTOMDRAW is buggy, doesn't hand you band indexes to query text, have to use hit tests to know what text to render | |
| 79 | |
| 80 Solution: full custom draw | |
| 81 | |
| 82 | |
| 83 == Toolbar == | |
| 84 Source: https://stackoverflow.com/questions/61271578/winapi-toolbar-text-color | |
| 85 Respects background color of its parent | |
| 86 Override text: | |
| 87 ::SetWindowTheme(toolbar, L"", L""); or else NM_CUSTOMDRAW color is disregarded | |
| 88 NM_CUSTOMDRAW handler: | |
| 89 switch (cd->nmcd.dwDrawStage) { | |
| 90 case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW; | |
| 91 case CDDS_ITEMPREPAINT: cd->clrText = DarkMode::GetSysColor(COLOR_WINDOWTEXT); return CDRF_DODEFAULT; | |
| 92 } | |
| 93 | |
| 94 == Tab control == | |
| 95 Full custom draw, see CTabsHook | |
| 96 | |
| 97 == List View == | |
| 98 Dark scrollbars are shown only if using "Explorer" theme, but other stuff needs "ItemsView" theme??? | |
| 99 Other projects shim Windows functions to bypass the above. | |
| 100 Avoid using List View, use libPPUI CListControl instead. | |
| 101 | |
| 102 == List Box == | |
| 103 Use WM_CTLCOLOR* | |
| 104 | |
| 105 == Status Bar == | |
| 106 Full custom draw | |
| 107 | |
| 108 == Check box, radio button == | |
| 109 SetWindowTheme(wnd, L"", L""); works but not 100% pretty, disabled text ugly in particular | |
| 110 Full custom draw preferred | |
| 111 | |
| 112 == Group box === | |
| 113 SetWindowTheme(wnd, L"", L""); works but not 100% pretty, disabled text ugly in particular | |
| 114 Full custom draw preferred (we don't do this). | |
| 115 Avoid disabling groupboxes / use something else. | |
| 116 | |
| 117 ==== NOTES ==== | |
| 118 AllowDarkModeForWindow() needs SetPreferredAppMode() to take effect, hence we implicitly call it | |
| 119 AllowDarkModeForWindow() must be called BEFORE SetWindowTheme() to take effect | |
| 120 | |
| 121 It seems it is interchangeable to do: | |
| 122 AllowDarkModeForWindow(wnd, true); SetWindowTheme(wnd, L"foo", nullptr); | |
| 123 vs | |
| 124 SetWindowTheme(wnd, L"DarkMode_foo", nullptr) | |
| 125 But the latter doesn't require undocumented function calls and doesn't infect all menus with dark mode | |
| 126 */ | |
| 127 | |
| 128 namespace { | |
| 129 enum class PreferredAppMode | |
| 130 { | |
| 131 Default, | |
| 132 AllowDark, | |
| 133 ForceDark, | |
| 134 ForceLight, | |
| 135 Max | |
| 136 }; | |
| 137 | |
| 138 enum WINDOWCOMPOSITIONATTRIB | |
| 139 { | |
| 140 WCA_UNDEFINED = 0, | |
| 141 WCA_NCRENDERING_ENABLED = 1, | |
| 142 WCA_NCRENDERING_POLICY = 2, | |
| 143 WCA_TRANSITIONS_FORCEDISABLED = 3, | |
| 144 WCA_ALLOW_NCPAINT = 4, | |
| 145 WCA_CAPTION_BUTTON_BOUNDS = 5, | |
| 146 WCA_NONCLIENT_RTL_LAYOUT = 6, | |
| 147 WCA_FORCE_ICONIC_REPRESENTATION = 7, | |
| 148 WCA_EXTENDED_FRAME_BOUNDS = 8, | |
| 149 WCA_HAS_ICONIC_BITMAP = 9, | |
| 150 WCA_THEME_ATTRIBUTES = 10, | |
| 151 WCA_NCRENDERING_EXILED = 11, | |
| 152 WCA_NCADORNMENTINFO = 12, | |
| 153 WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, | |
| 154 WCA_VIDEO_OVERLAY_ACTIVE = 14, | |
| 155 WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, | |
| 156 WCA_DISALLOW_PEEK = 16, | |
| 157 WCA_CLOAK = 17, | |
| 158 WCA_CLOAKED = 18, | |
| 159 WCA_ACCENT_POLICY = 19, | |
| 160 WCA_FREEZE_REPRESENTATION = 20, | |
| 161 WCA_EVER_UNCLOAKED = 21, | |
| 162 WCA_VISUAL_OWNER = 22, | |
| 163 WCA_HOLOGRAPHIC = 23, | |
| 164 WCA_EXCLUDED_FROM_DDA = 24, | |
| 165 WCA_PASSIVEUPDATEMODE = 25, | |
| 166 WCA_USEDARKMODECOLORS = 26, | |
| 167 WCA_LAST = 27 | |
| 168 }; | |
| 169 | |
| 170 struct WINDOWCOMPOSITIONATTRIBDATA | |
| 171 { | |
| 172 WINDOWCOMPOSITIONATTRIB Attrib; | |
| 173 PVOID pvData; | |
| 174 SIZE_T cbData; | |
| 175 }; | |
| 176 #if DARKMODE_ALLOW_HAX | |
| 177 using fnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133 | |
| 178 using fnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, since 1809 | |
| 179 using fnFlushMenuThemes = void (WINAPI*)(); // ordinal 136 | |
| 180 fnAllowDarkModeForWindow _AllowDarkModeForWindow = nullptr; | |
| 181 fnSetPreferredAppMode _SetPreferredAppMode = nullptr; | |
| 182 fnFlushMenuThemes _FlushMenuThemes = nullptr; | |
| 183 | |
| 184 bool ImportsInited = false; | |
| 185 | |
| 186 void InitImports() { | |
| 187 if (ImportsInited) return; | |
| 188 if (DarkMode::IsSupportedSystem()) { | |
| 189 HMODULE hUxtheme = LoadLibraryEx(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); | |
| 190 if (hUxtheme) { | |
| 191 _AllowDarkModeForWindow = reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133))); | |
| 192 _SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135))); | |
| 193 _FlushMenuThemes = reinterpret_cast<fnFlushMenuThemes>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136))); | |
| 194 } | |
| 195 } | |
| 196 ImportsInited = true; | |
| 197 } | |
| 198 #endif | |
| 199 } | |
| 200 | |
| 201 | |
| 202 | |
| 203 namespace DarkMode { | |
| 204 UINT msgSetDarkMode() { | |
| 205 // No need to threadguard this, should be main thread only, not much harm even if it's not | |
| 206 static UINT val = 0; | |
| 207 if (val == 0) val = RegisterWindowMessage(L"libPPUI:msgSetDarkMode"); | |
| 208 return val; | |
| 209 } | |
| 210 bool IsSupportedSystem() { | |
| 211 return Win10BuildNumber() >= 17763 && !IsWine(); // require at least Win10 1809 / Server 2019 | |
| 212 } | |
| 213 bool IsWindows11() { | |
| 214 return Win10BuildNumber() >= 22000; | |
| 215 } | |
| 216 bool QueryUserOption() { | |
| 217 DWORD v = 0; | |
| 218 DWORD cb = sizeof(v); | |
| 219 DWORD type = 0; | |
| 220 if (RegGetValue(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", L"AppsUseLightTheme", RRF_RT_REG_DWORD, &type, &v, &cb) == 0) { | |
| 221 if (type == REG_DWORD) { | |
| 222 return v == 0; | |
| 223 } | |
| 224 } | |
| 225 return false; | |
| 226 } | |
| 227 void UpdateTitleBar(HWND hWnd, bool bDark) { | |
| 228 if (!IsSupportedSystem()) return; | |
| 229 | |
| 230 CWindow wnd(hWnd); | |
| 231 DWORD style = wnd.GetStyle(); | |
| 232 if ((style & WS_CAPTION) != WS_CAPTION) return; | |
| 233 | |
| 234 #if 0 | |
| 235 // Some apps do this - no idea why, doesn't work | |
| 236 // Kept for future reference | |
| 237 AllowDarkModeForWindow(hWnd, bDark); | |
| 238 SetProp(hWnd, L"UseImmersiveDarkModeColors", (HANDLE)(INT_PTR)(bDark ? TRUE : FALSE)); | |
| 239 #endif | |
| 240 | |
| 241 if (IsWindows11()) { | |
| 242 // DwmSetWindowAttribute() | |
| 243 // Windows 11 : works | |
| 244 // Windows 10 @ late 2021 : doesn't work | |
| 245 // Server 2019 : as good as SetWindowCompositionAttribute(), needs ModifyStyle() hack for full effect | |
| 246 BOOL arg = !!bDark; | |
| 247 DwmSetWindowAttribute(hWnd, 19 /*DWMWA_USE_IMMERSIVE_DARK_MODE*/, &arg, sizeof(arg)); | |
| 248 } else { | |
| 249 // Windows 10 mode | |
| 250 using fnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); | |
| 251 static fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = reinterpret_cast<fnSetWindowCompositionAttribute>(GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute")); | |
| 252 if (_SetWindowCompositionAttribute != nullptr) { | |
| 253 BOOL dark = !!bDark; | |
| 254 WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) }; | |
| 255 _SetWindowCompositionAttribute(hWnd, &data); | |
| 256 | |
| 257 // Neither of these fixes stuck titlebar (kept in here for future reference) | |
| 258 // ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW); | |
| 259 // ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_DRAWFRAME); | |
| 260 | |
| 261 // Apparently the least painful way to reliably fix stuck titlebar | |
| 262 // 2x SWP_FRAMECHANGED needed with actual style changes | |
| 263 | |
| 264 if (style & WS_VISIBLE) { // Only do this if visible | |
| 265 wnd.ModifyStyle(WS_BORDER, 0, SWP_FRAMECHANGED); | |
| 266 wnd.ModifyStyle(0, WS_BORDER, SWP_FRAMECHANGED); | |
| 267 } | |
| 268 | |
| 269 } | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 void ApplyDarkThemeCtrl2(HWND ctrl, bool bDark, const wchar_t* ThemeID_light, const wchar_t * ThemeID_dark) { | |
| 274 if (ctrl == NULL) return; | |
| 275 AllowDarkModeForWindow(ctrl, bDark); | |
| 276 if (bDark && IsSupportedSystem()) { | |
| 277 ::SetWindowTheme(ctrl, ThemeID_dark, NULL); | |
| 278 } else { | |
| 279 ::SetWindowTheme(ctrl, ThemeID_light, NULL); | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 void ApplyDarkThemeCtrl(HWND ctrl, bool bDark, const wchar_t* ThemeID) { | |
| 284 if ( ctrl == NULL ) return; | |
| 285 AllowDarkModeForWindow(ctrl, bDark); | |
| 286 if (bDark && IsSupportedSystem()) { | |
| 287 std::wstring temp = L"DarkMode_"; temp += ThemeID; | |
| 288 ::SetWindowTheme(ctrl, temp.c_str(), NULL); | |
| 289 } else { | |
| 290 ::SetWindowTheme(ctrl, ThemeID, NULL); | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 void DarkenEditLite(HWND ctrl) { | |
| 295 if (IsSupportedSystem()) { | |
| 296 ::SetWindowTheme(ctrl, L"DarkMode_Explorer", NULL); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 void DarkenComboLite(HWND ctrl) { | |
| 301 if (IsSupportedSystem()) { | |
| 302 ::SetWindowTheme(ctrl, L"DarkMode_CFD", NULL); | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 bool IsDCDark(HDC dc_) { | |
| 307 CDCHandle dc(dc_); | |
| 308 return IsThemeDark(dc.GetTextColor(), dc.GetBkColor()); | |
| 309 } | |
| 310 bool IsDialogDark(HWND dlg, UINT msgSend) { | |
| 311 CWindowDC dc(dlg); | |
| 312 dc.SetTextColor(0x000000); | |
| 313 dc.SetBkColor(0xFFFFFF); | |
| 314 ::SendMessage(dlg, msgSend, (WPARAM)dc.m_hDC, (LPARAM)dlg); | |
| 315 return IsDCDark(dc); | |
| 316 } | |
| 317 | |
| 318 COLORREF GetSysColor(int idx, bool bDark) { | |
| 319 if (!bDark) return ::GetSysColor(idx); | |
| 320 switch (idx) { | |
| 321 case COLOR_MENU: | |
| 322 case COLOR_BTNFACE: | |
| 323 case COLOR_WINDOW: | |
| 324 case COLOR_MENUBAR: | |
| 325 // Explorer: | |
| 326 // return 0x383838; | |
| 327 return 0x202020; | |
| 328 case COLOR_BTNSHADOW: | |
| 329 return 0; | |
| 330 case COLOR_WINDOWTEXT: | |
| 331 case COLOR_MENUTEXT: | |
| 332 case COLOR_BTNTEXT: | |
| 333 case COLOR_CAPTIONTEXT: | |
| 334 // Explorer: | |
| 335 // return 0xdedede; | |
| 336 return 0xC0C0C0; | |
| 337 case COLOR_BTNHIGHLIGHT: | |
| 338 case COLOR_MENUHILIGHT: | |
| 339 return 0x383838; | |
| 340 case COLOR_HIGHLIGHT: | |
| 341 return 0x777777; | |
| 342 case COLOR_HIGHLIGHTTEXT: | |
| 343 return 0x101010; | |
| 344 case COLOR_GRAYTEXT: | |
| 345 return 0x777777; | |
| 346 case COLOR_HOTLIGHT: | |
| 347 return 0xd69c56; | |
| 348 default: | |
| 349 return ::GetSysColor(idx); | |
| 350 } | |
| 351 } | |
| 352 #if DARKMODE_ALLOW_HAX | |
| 353 void SetAppDarkMode(bool bDark) { | |
| 354 InitImports(); | |
| 355 | |
| 356 if (_SetPreferredAppMode != nullptr) { | |
| 357 static PreferredAppMode lastMode = PreferredAppMode::Default; | |
| 358 PreferredAppMode wantMode = bDark ? PreferredAppMode::ForceDark : PreferredAppMode::ForceLight; | |
| 359 if (lastMode != wantMode) { | |
| 360 _SetPreferredAppMode(wantMode); | |
| 361 lastMode = wantMode; | |
| 362 if (_FlushMenuThemes) _FlushMenuThemes(); | |
| 363 } | |
| 364 } | |
| 365 } | |
| 366 #else | |
| 367 void SetAppDarkMode(bool) {} | |
| 368 #endif | |
| 369 #if DARKMODE_ALLOW_HAX | |
| 370 void AllowDarkModeForWindow(HWND wnd, bool bDark) { | |
| 371 InitImports(); | |
| 372 | |
| 373 if (_AllowDarkModeForWindow) _AllowDarkModeForWindow(wnd, bDark); | |
| 374 } | |
| 375 #else | |
| 376 void AllowDarkModeForWindow(HWND, bool) {} | |
| 377 #endif | |
| 378 | |
| 379 bool IsThemeDark(COLORREF text, COLORREF background) { | |
| 380 if (!IsSupportedSystem() || IsHighContrast()) return false; | |
| 381 auto l_text = PaintUtils::Luminance(text); | |
| 382 auto l_bk = PaintUtils::Luminance(background); | |
| 383 if (l_text > l_bk) { | |
| 384 if (l_bk <= PaintUtils::Luminance(GetSysColor(COLOR_BTNFACE))) { | |
| 385 return true; | |
| 386 } | |
| 387 } | |
| 388 return false; | |
| 389 } | |
| 390 | |
| 391 bool IsHighContrast() { | |
| 392 HIGHCONTRASTW highContrast = { sizeof(highContrast) }; | |
| 393 if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) | |
| 394 return (highContrast.dwFlags & HCF_HIGHCONTRASTON) != 0; | |
| 395 return false; | |
| 396 } | |
| 397 | |
| 398 static void DrawTab(CTabCtrl& tabs, CDCHandle dc, int iTab, bool selected, bool focused, const RECT * rcPaint) { | |
| 399 (void)focused; | |
| 400 PFC_ASSERT((tabs.GetStyle() & TCS_VERTICAL) == 0); | |
| 401 | |
| 402 CRect rc; | |
| 403 if (!tabs.GetItemRect(iTab, rc)) return; | |
| 404 | |
| 405 if ( rcPaint != nullptr ) { | |
| 406 CRect foo; | |
| 407 if (!foo.IntersectRect(rc, rcPaint)) return; | |
| 408 } | |
| 409 const int edgeCX = MulDiv(1, QueryScreenDPI_X(tabs), 120); // note: MulDiv() rounds up from +0.5, this will | |
| 410 const auto colorBackground = GetSysColor(selected ? COLOR_HIGHLIGHT : COLOR_BTNFACE); | |
| 411 const auto colorFrame = GetSysColor(COLOR_WINDOWFRAME); | |
| 412 dc.SetDCBrushColor(colorBackground); | |
| 413 dc.FillSolidRect(rc, colorBackground); | |
| 414 | |
| 415 { | |
| 416 CPen pen; | |
| 417 WIN32_OP_D(pen.CreatePen(PS_SOLID, edgeCX, colorFrame)); | |
| 418 SelectObjectScope scope(dc, pen); | |
| 419 dc.MoveTo(rc.left, rc.bottom); | |
| 420 dc.LineTo(rc.left, rc.top); | |
| 421 dc.LineTo(rc.right, rc.top); | |
| 422 dc.LineTo(rc.right, rc.bottom); | |
| 423 } | |
| 424 | |
| 425 wchar_t text[512] = {}; | |
| 426 TCITEM item = {}; | |
| 427 item.mask = TCIF_TEXT; | |
| 428 item.pszText = text; | |
| 429 item.cchTextMax = (int)(std::size(text) - 1); | |
| 430 if (tabs.GetItem(iTab, &item)) { | |
| 431 SelectObjectScope fontScope(dc, tabs.GetFont()); | |
| 432 dc.SetBkMode(TRANSPARENT); | |
| 433 dc.SetTextColor(GetSysColor(selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); | |
| 434 dc.DrawText(text, (int)wcslen(text), rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); | |
| 435 } | |
| 436 } | |
| 437 | |
| 438 void PaintTabs(CTabCtrl tabs, CDCHandle dc, const RECT * rcPaint) { | |
| 439 CRect rcClient; tabs.GetClientRect(rcClient); | |
| 440 CRect rcArea = rcClient; tabs.AdjustRect(FALSE, rcArea); | |
| 441 int dx = rcClient.bottom - rcArea.bottom; | |
| 442 int dy = rcClient.right - rcArea.right; | |
| 443 CRect rcFrame = rcArea; rcFrame.InflateRect(dx/2, dy/2); | |
| 444 dc.SetDCBrushColor(GetSysColor(COLOR_WINDOWFRAME)); | |
| 445 dc.FrameRect(rcFrame, (HBRUSH)GetStockObject(DC_BRUSH)); | |
| 446 const int tabCount = tabs.GetItemCount(); | |
| 447 const int tabSelected = tabs.GetCurSel(); | |
| 448 const int tabFocused = tabs.GetCurFocus(); | |
| 449 for (int iTab = 0; iTab < tabCount; ++iTab) { | |
| 450 if (iTab != tabSelected) DrawTab(tabs, dc, iTab, false, iTab == tabFocused, rcPaint); | |
| 451 } | |
| 452 if (tabSelected >= 0) DrawTab(tabs, dc, tabSelected, true, tabSelected == tabFocused, rcPaint); | |
| 453 } | |
| 454 | |
| 455 void PaintTabsErase(CTabCtrl tabs, CDCHandle dc) { | |
| 456 CRect rcClient; WIN32_OP_D(tabs.GetClientRect(rcClient)); | |
| 457 dc.FillSolidRect(&rcClient, GetSysColor(COLOR_BTNFACE)); | |
| 458 } | |
| 459 | |
| 460 | |
| 461 // ================================================= | |
| 462 // NM_CUSTOMDRAW handlers | |
| 463 // ================================================= | |
| 464 | |
| 465 // We keep a global list of HWNDs that require dark rendering, so dialogs can call DarkMode::OnCustomDraw() which deals with this nonsense behind the scenes | |
| 466 // This way there's no need to subclass parent windows at random | |
| 467 | |
| 468 enum class whichDark_t { | |
| 469 none, toolbar | |
| 470 }; | |
| 471 | |
| 472 // readWriteLock used in case someone uses off-main-thread UI, though it should not really happen in real life | |
| 473 static pfc::readWriteLock lstDarkGuard; | |
| 474 static std::map<HWND, whichDark_t> lstDark; | |
| 475 static whichDark_t lstDark_query(HWND w) { | |
| 476 PFC_INSYNC_READ(lstDarkGuard); | |
| 477 auto iter = lstDark.find(w); | |
| 478 if (iter == lstDark.end()) return whichDark_t::none; | |
| 479 return iter->second; | |
| 480 } | |
| 481 static void lstDark_set(HWND w, whichDark_t which) { | |
| 482 PFC_INSYNC_WRITE(lstDarkGuard); | |
| 483 lstDark[w] = which; | |
| 484 } | |
| 485 static void lstDark_clear(HWND w) { | |
| 486 PFC_INSYNC_WRITE(lstDarkGuard); | |
| 487 lstDark.erase(w); | |
| 488 } | |
| 489 | |
| 490 LRESULT CustomDrawToolbar(NMHDR* hdr) { | |
| 491 LPNMTBCUSTOMDRAW cd = reinterpret_cast<LPNMTBCUSTOMDRAW>(hdr); | |
| 492 switch (cd->nmcd.dwDrawStage) { | |
| 493 case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW; | |
| 494 case CDDS_ITEMPREPAINT: | |
| 495 cd->clrText = DarkMode::GetSysColor(COLOR_WINDOWTEXT); | |
| 496 cd->clrBtnFace = DarkMode::GetSysColor(COLOR_BTNFACE); | |
| 497 cd->clrBtnHighlight = DarkMode::GetSysColor(COLOR_BTNHIGHLIGHT); | |
| 498 return CDRF_DODEFAULT; | |
| 499 default: | |
| 500 return CDRF_DODEFAULT; | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 LRESULT OnCustomDraw(int,NMHDR* hdr, BOOL& bHandled) { | |
| 505 switch (lstDark_query(hdr->hwndFrom)) { | |
| 506 case whichDark_t::toolbar: | |
| 507 bHandled = TRUE; | |
| 508 return CustomDrawToolbar(hdr); | |
| 509 default: | |
| 510 bHandled = FALSE; return 0; | |
| 511 } | |
| 512 } | |
| 513 | |
| 514 namespace { | |
| 515 | |
| 516 class CToolbarHook { | |
| 517 bool m_dark = false; | |
| 518 const bool m_explorerTheme; | |
| 519 CToolBarCtrl m_wnd; | |
| 520 public: | |
| 521 CToolbarHook(HWND wnd, bool initial, bool bExplorerTheme) : m_wnd(wnd), m_explorerTheme(bExplorerTheme) { | |
| 522 SetDark(initial); | |
| 523 } | |
| 524 | |
| 525 void SetDark(bool v) { | |
| 526 if (m_dark == v) return; | |
| 527 m_dark = v; | |
| 528 if (v) { | |
| 529 lstDark_set(m_wnd, whichDark_t::toolbar); | |
| 530 if (m_explorerTheme) ::SetWindowTheme(m_wnd, L"", L""); // if we don't do this, NM_CUSTOMDRAW color overrides get disregarded | |
| 531 } else { | |
| 532 lstDark_clear(m_wnd); | |
| 533 if (m_explorerTheme) ::SetWindowTheme(m_wnd, L"Explorer", NULL); | |
| 534 } | |
| 535 m_wnd.Invalidate(); | |
| 536 | |
| 537 ApplyDarkThemeCtrl(m_wnd.GetToolTips(), v); | |
| 538 } | |
| 539 ~CToolbarHook() { | |
| 540 if (m_dark) lstDark_clear(m_wnd); | |
| 541 } | |
| 542 | |
| 543 }; | |
| 544 | |
| 545 class CTabsHook : public CWindowImpl<CTabsHook, CTabCtrl> { | |
| 546 public: | |
| 547 CTabsHook(bool bDark = false) : m_dark(bDark) {} | |
| 548 BEGIN_MSG_MAP_EX(CTabsHook) | |
| 549 MSG_WM_PAINT(OnPaint) | |
| 550 MSG_WM_ERASEBKGND(OnEraseBkgnd) | |
| 551 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 552 END_MSG_MAP() | |
| 553 | |
| 554 void SetDark(bool v = false); | |
| 555 private: | |
| 556 void OnPaint(CDCHandle); | |
| 557 BOOL OnEraseBkgnd(CDCHandle); | |
| 558 | |
| 559 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 560 switch (wp) { | |
| 561 case 0: SetDark(false); break; | |
| 562 case 1: SetDark(true); break; | |
| 563 } | |
| 564 return 1; | |
| 565 } | |
| 566 | |
| 567 bool m_dark = false; | |
| 568 }; | |
| 569 | |
| 570 void CTabsHook::OnPaint(CDCHandle target) { | |
| 571 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 572 if (target) { | |
| 573 PaintTabs(m_hWnd, target); | |
| 574 } else { | |
| 575 CPaintDC dc(*this); | |
| 576 PaintTabs(m_hWnd, dc.m_hDC, &dc.m_ps.rcPaint); | |
| 577 } | |
| 578 } | |
| 579 BOOL CTabsHook::OnEraseBkgnd(CDCHandle dc) { | |
| 580 if (m_dark) { | |
| 581 PaintTabsErase(*this, dc); | |
| 582 return TRUE; | |
| 583 } | |
| 584 SetMsgHandled(FALSE); | |
| 585 return FALSE; | |
| 586 } | |
| 587 | |
| 588 void CTabsHook::SetDark(bool v) { | |
| 589 m_dark = v; | |
| 590 if (m_hWnd != NULL) Invalidate(); | |
| 591 | |
| 592 ApplyDarkThemeCtrl(GetToolTips(), v); | |
| 593 } | |
| 594 | |
| 595 class CTreeViewHook : public CWindowImpl<CTreeViewHook, CTreeViewCtrl> { | |
| 596 bool m_dark; | |
| 597 public: | |
| 598 CTreeViewHook(bool v) : m_dark(v) {} | |
| 599 | |
| 600 BEGIN_MSG_MAP_EX(CTreeViewHook) | |
| 601 MESSAGE_RANGE_HANDLER_EX(WM_CTLCOLORMSGBOX, WM_CTLCOLORSTATIC, OnCtlColor) | |
| 602 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 603 MESSAGE_HANDLER_EX(TVM_EDITLABEL, OnEditLabel) | |
| 604 END_MSG_MAP() | |
| 605 | |
| 606 LRESULT OnCtlColor(UINT uMsg, WPARAM wParam, LPARAM lParam) { | |
| 607 return GetParent().SendMessage(uMsg, wParam, lParam); | |
| 608 } | |
| 609 LRESULT OnEditLabel(UINT, WPARAM, LPARAM) { | |
| 610 LRESULT ret = DefWindowProc(); | |
| 611 if (ret != 0) { | |
| 612 HWND edit = (HWND) ret; | |
| 613 PFC_ASSERT( ::IsWindow(edit) ); | |
| 614 ApplyDarkThemeCtrl( edit, m_dark ); | |
| 615 } | |
| 616 return ret; | |
| 617 } | |
| 618 void SetDark(bool v) { | |
| 619 if (m_dark == v) return; | |
| 620 m_dark = v; | |
| 621 ApplyDark(); | |
| 622 } | |
| 623 void ApplyDark() { | |
| 624 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 625 COLORREF bk = m_dark ? GetSysColor(COLOR_WINDOW) : (COLORREF)(-1); | |
| 626 COLORREF tx = m_dark ? GetSysColor(COLOR_WINDOWTEXT) : (COLORREF)(-1); | |
| 627 this->SetTextColor(tx); this->SetLineColor(tx); | |
| 628 this->SetBkColor(bk); | |
| 629 | |
| 630 ApplyDarkThemeCtrl(GetToolTips(), m_dark); | |
| 631 } | |
| 632 | |
| 633 void SubclassWindow(HWND wnd) { | |
| 634 WIN32_OP_D( __super::SubclassWindow(wnd) ); | |
| 635 this->ApplyDark(); | |
| 636 } | |
| 637 | |
| 638 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 639 switch (wp) { | |
| 640 case 0: SetDark(false); break; | |
| 641 case 1: SetDark(true); break; | |
| 642 } | |
| 643 return 1; | |
| 644 } | |
| 645 }; | |
| 646 | |
| 647 class CDialogHook : public CWindowImpl<CDialogHook> { | |
| 648 bool m_enabled; | |
| 649 public: | |
| 650 CDialogHook(bool v) : m_enabled(v) {} | |
| 651 | |
| 652 void SetDark(bool v) { | |
| 653 if (m_enabled == v) return; | |
| 654 | |
| 655 // Important: PostMessage()'ing this caused bugs | |
| 656 SendMessage(WM_THEMECHANGED); | |
| 657 | |
| 658 m_enabled = v; | |
| 659 | |
| 660 // Ensure menu bar redraw with RDW_FRAME | |
| 661 RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME); | |
| 662 } | |
| 663 | |
| 664 // Undocumented Windows menu drawing API | |
| 665 // Source: https://github.com/adzm/win32-custom-menubar-aero-theme | |
| 666 | |
| 667 static constexpr unsigned WM_UAHDRAWMENU = 0x0091; | |
| 668 static constexpr unsigned WM_UAHDRAWMENUITEM = 0x0092; | |
| 669 | |
| 670 typedef union tagUAHMENUITEMMETRICS | |
| 671 { | |
| 672 struct { | |
| 673 DWORD cx; | |
| 674 DWORD cy; | |
| 675 } rgsizeBar[2]; | |
| 676 struct { | |
| 677 DWORD cx; | |
| 678 DWORD cy; | |
| 679 } rgsizePopup[4]; | |
| 680 } UAHMENUITEMMETRICS; | |
| 681 | |
| 682 typedef struct tagUAHMENUPOPUPMETRICS | |
| 683 { | |
| 684 DWORD rgcx[4]; | |
| 685 DWORD fUpdateMaxWidths : 2; | |
| 686 } UAHMENUPOPUPMETRICS; | |
| 687 | |
| 688 typedef struct tagUAHMENU | |
| 689 { | |
| 690 HMENU hmenu; | |
| 691 HDC hdc; | |
| 692 DWORD dwFlags; | |
| 693 } UAHMENU; | |
| 694 | |
| 695 typedef struct tagUAHMENUITEM | |
| 696 { | |
| 697 int iPosition; | |
| 698 UAHMENUITEMMETRICS umim; | |
| 699 UAHMENUPOPUPMETRICS umpm; | |
| 700 } UAHMENUITEM; | |
| 701 | |
| 702 typedef struct UAHDRAWMENUITEM | |
| 703 { | |
| 704 DRAWITEMSTRUCT dis; | |
| 705 UAHMENU um; | |
| 706 UAHMENUITEM umi; | |
| 707 } UAHDRAWMENUITEM; | |
| 708 | |
| 709 | |
| 710 BEGIN_MSG_MAP_EX(CDialogHook) | |
| 711 MSG_WM_CTLCOLORDLG(ctlColorCommon) | |
| 712 MSG_WM_CTLCOLORSTATIC(ctlColorCommon) | |
| 713 MSG_WM_CTLCOLOREDIT(ctlColorCommon) | |
| 714 MSG_WM_CTLCOLORBTN(ctlColorCommon) | |
| 715 MSG_WM_CTLCOLORLISTBOX(ctlColorCommon) | |
| 716 MSG_WM_CTLCOLORSCROLLBAR(ctlColorCommon) | |
| 717 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, DarkMode::OnCustomDraw) | |
| 718 MESSAGE_HANDLER_EX(WM_UAHDRAWMENU, Handle_WM_UAHDRAWMENU) | |
| 719 MESSAGE_HANDLER_EX(WM_UAHDRAWMENUITEM, Handle_WM_UAHDRAWMENUITEM) | |
| 720 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 721 END_MSG_MAP() | |
| 722 | |
| 723 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 724 switch (wp) { | |
| 725 case 0: SetDark(false); break; | |
| 726 case 1: SetDark(true); break; | |
| 727 } | |
| 728 return 1; | |
| 729 } | |
| 730 | |
| 731 static COLORREF GetBkColor() { return DarkMode::GetSysColor(COLOR_WINDOW); } | |
| 732 static COLORREF GetTextColor() { return DarkMode::GetSysColor(COLOR_WINDOWTEXT); } | |
| 733 | |
| 734 HBRUSH ctlColorDlg(CDCHandle dc, CWindow wnd) { | |
| 735 if (m_enabled && ::IsThemeDialogTextureEnabled(*this)) { | |
| 736 auto bkColor = DarkMode::GetSysColor(COLOR_HIGHLIGHT); | |
| 737 auto txColor = GetTextColor(); | |
| 738 | |
| 739 dc.SetTextColor(txColor); | |
| 740 dc.SetBkColor(bkColor); | |
| 741 dc.SetDCBrushColor(bkColor); | |
| 742 return (HBRUSH)GetStockObject(DC_BRUSH); | |
| 743 } | |
| 744 return ctlColorCommon(dc, wnd); | |
| 745 } | |
| 746 | |
| 747 HBRUSH ctlColorCommon(CDCHandle dc, CWindow wnd) { | |
| 748 (void)wnd; | |
| 749 if (m_enabled) { | |
| 750 auto bkColor = GetBkColor(); | |
| 751 auto txColor = GetTextColor(); | |
| 752 | |
| 753 dc.SetTextColor(txColor); | |
| 754 dc.SetBkColor(bkColor); | |
| 755 dc.SetDCBrushColor(bkColor); | |
| 756 return (HBRUSH)GetStockObject(DC_BRUSH); | |
| 757 } | |
| 758 SetMsgHandled(FALSE); | |
| 759 return NULL; | |
| 760 } | |
| 761 LRESULT Handle_WM_UAHDRAWMENU(UINT, WPARAM wParam, LPARAM lParam) { | |
| 762 if (!m_enabled) { | |
| 763 SetMsgHandled(FALSE); | |
| 764 return 0; | |
| 765 } | |
| 766 UAHMENU* pUDM = (UAHMENU*)lParam; | |
| 767 CRect rc; | |
| 768 | |
| 769 MENUBARINFO mbi = { sizeof(mbi) }; | |
| 770 WIN32_OP_D(GetMenuBarInfo(m_hWnd, OBJID_MENU, 0, &mbi)); | |
| 771 | |
| 772 CRect rcWindow; | |
| 773 WIN32_OP_D(GetWindowRect(rcWindow)); | |
| 774 | |
| 775 rc = mbi.rcBar; | |
| 776 OffsetRect(&rc, -rcWindow.left, -rcWindow.top); | |
| 777 | |
| 778 rc.top -= 1; | |
| 779 | |
| 780 CDCHandle dc(pUDM->hdc); | |
| 781 dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_MENUBAR)); | |
| 782 return 0; | |
| 783 } | |
| 784 LRESULT Handle_WM_UAHDRAWMENUITEM(UINT, WPARAM wParam, LPARAM lParam) { | |
| 785 if (!m_enabled) { | |
| 786 SetMsgHandled(FALSE); | |
| 787 return 0; | |
| 788 } | |
| 789 | |
| 790 UAHDRAWMENUITEM* pUDMI = (UAHDRAWMENUITEM*)lParam; | |
| 791 CMenuHandle hMenu = pUDMI->um.hmenu; | |
| 792 | |
| 793 CString menuString; | |
| 794 WIN32_OP_D(hMenu.GetMenuString(pUDMI->umi.iPosition, menuString, MF_BYPOSITION) > 0); | |
| 795 | |
| 796 DWORD drawTextFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; | |
| 797 | |
| 798 int iTextStateID = MPI_NORMAL; | |
| 799 int iBackgroundStateID = MPI_NORMAL; | |
| 800 if ((pUDMI->dis.itemState & ODS_INACTIVE) | (pUDMI->dis.itemState & ODS_DEFAULT)) { | |
| 801 iTextStateID = MPI_NORMAL; | |
| 802 iBackgroundStateID = MPI_NORMAL; | |
| 803 } | |
| 804 if (pUDMI->dis.itemState & (ODS_HOTLIGHT|ODS_SELECTED)) { | |
| 805 iTextStateID = MPI_HOT; | |
| 806 iBackgroundStateID = MPI_HOT; | |
| 807 } | |
| 808 if (pUDMI->dis.itemState & (ODS_GRAYED|ODS_DISABLED)) { | |
| 809 iTextStateID = MPI_DISABLED; | |
| 810 iBackgroundStateID = MPI_DISABLED; | |
| 811 } | |
| 812 if (pUDMI->dis.itemState & ODS_NOACCEL) { | |
| 813 drawTextFlags |= DT_HIDEPREFIX; | |
| 814 } | |
| 815 | |
| 816 if (m_menuTheme == NULL) { | |
| 817 m_menuTheme.OpenThemeData(m_hWnd, L"Menu"); | |
| 818 } | |
| 819 | |
| 820 CDCHandle dc(pUDMI->um.hdc); | |
| 821 switch (iBackgroundStateID) { | |
| 822 case MPI_NORMAL: | |
| 823 case MPI_DISABLED: | |
| 824 dc.FillSolidRect(&pUDMI->dis.rcItem, DarkMode::GetSysColor(COLOR_MENUBAR)); | |
| 825 break; | |
| 826 case MPI_HOT: | |
| 827 case MPI_DISABLEDHOT: | |
| 828 dc.FillSolidRect(&pUDMI->dis.rcItem, DarkMode::GetSysColor(COLOR_MENUHILIGHT)); | |
| 829 break; | |
| 830 default: | |
| 831 DrawThemeBackground(m_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iBackgroundStateID, &pUDMI->dis.rcItem, nullptr); | |
| 832 break; | |
| 833 } | |
| 834 DTTOPTS dttopts = { sizeof(dttopts) }; | |
| 835 if (iTextStateID == MPI_NORMAL || iTextStateID == MPI_HOT) | |
| 836 { | |
| 837 dttopts.dwFlags |= DTT_TEXTCOLOR; | |
| 838 dttopts.crText = DarkMode::GetSysColor(COLOR_WINDOWTEXT); | |
| 839 } | |
| 840 DrawThemeTextEx(m_menuTheme, dc, MENU_POPUPITEM, iTextStateID, menuString, menuString.GetLength(), drawTextFlags, &pUDMI->dis.rcItem, &dttopts); | |
| 841 | |
| 842 return 0; | |
| 843 } | |
| 844 CTheme m_menuTheme; | |
| 845 }; | |
| 846 | |
| 847 | |
| 848 class CStatusBarHook : public CWindowImpl<CStatusBarHook, CStatusBarCtrl> { | |
| 849 public: | |
| 850 CStatusBarHook(bool v = false) : m_dark(v) {} | |
| 851 | |
| 852 BEGIN_MSG_MAP_EX(CStatusBarHook) | |
| 853 MSG_WM_ERASEBKGND(OnEraseBkgnd) | |
| 854 MSG_WM_PAINT(OnPaint) | |
| 855 MESSAGE_HANDLER_EX(SB_SETTEXT, OnSetText) | |
| 856 MESSAGE_HANDLER_EX(SB_SETICON, OnSetIcon) | |
| 857 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 858 END_MSG_MAP() | |
| 859 | |
| 860 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 861 switch (wp) { | |
| 862 case 0: SetDark(false); break; | |
| 863 case 1: SetDark(true); break; | |
| 864 } | |
| 865 return 1; | |
| 866 } | |
| 867 | |
| 868 void SetDark(bool v = true) { | |
| 869 if (m_dark != v) { | |
| 870 m_dark = v; | |
| 871 Invalidate(); | |
| 872 ApplyDarkThemeCtrl(m_hWnd, v); | |
| 873 } | |
| 874 } | |
| 875 | |
| 876 void SubclassWindow(HWND wnd) { | |
| 877 WIN32_OP_D(__super::SubclassWindow(wnd)); | |
| 878 Invalidate(); | |
| 879 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 880 } | |
| 881 LRESULT OnSetIcon(UINT, WPARAM wp, LPARAM lp) { | |
| 882 unsigned idx = (unsigned)wp; | |
| 883 if (idx < 32) { | |
| 884 CSize sz; | |
| 885 if (lp != 0) sz = GetIconSize((HICON)lp); | |
| 886 m_iconSizeCache[idx] = sz; | |
| 887 } | |
| 888 SetMsgHandled(FALSE); | |
| 889 return 0; | |
| 890 } | |
| 891 LRESULT OnSetText(UINT, WPARAM wp, LPARAM) { | |
| 892 // Status bar won't tell us about ownerdraw from GetText() | |
| 893 // Have to listen to relevant messages to know | |
| 894 unsigned idx = (unsigned)(wp & 0xFF); | |
| 895 if (idx < 32) { | |
| 896 uint32_t flag = 1 << idx; | |
| 897 if (wp & SBT_OWNERDRAW) { | |
| 898 m_ownerDrawMask |= flag; | |
| 899 } else { | |
| 900 m_ownerDrawMask &= ~flag; | |
| 901 } | |
| 902 } | |
| 903 | |
| 904 SetMsgHandled(FALSE); | |
| 905 return 0; | |
| 906 } | |
| 907 | |
| 908 void Paint(CDCHandle dc) { | |
| 909 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); | |
| 910 dc.FillSolidRect(rcClient, GetSysColor(COLOR_BTNFACE)); // Wine seems to not call our WM_ERASEBKGND handler, fill the background here too | |
| 911 | |
| 912 dc.SelectFont(GetFont()); | |
| 913 dc.SetBkMode(TRANSPARENT); | |
| 914 dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); | |
| 915 CPen pen; pen.CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT)); | |
| 916 dc.SelectPen(pen); | |
| 917 int count = this->GetParts(0, nullptr); | |
| 918 for (int iPart = 0; iPart < count; ++iPart) { | |
| 919 CRect rcPart; | |
| 920 this->GetRect(iPart, rcPart); | |
| 921 if (rcPart.left > 0) { | |
| 922 dc.MoveTo(rcPart.left, rcPart.top); | |
| 923 dc.LineTo(rcPart.left, rcPart.bottom); | |
| 924 } | |
| 925 int type = 0; | |
| 926 CString text; | |
| 927 this->GetText(iPart, text, &type); | |
| 928 | |
| 929 HICON icon = this->GetIcon(iPart); | |
| 930 int iconMargin = 0; | |
| 931 if (icon != NULL && (unsigned)iPart < std::size(m_iconSizeCache)) { | |
| 932 | |
| 933 auto size = m_iconSizeCache[iPart]; | |
| 934 | |
| 935 dc.DrawIconEx(rcPart.left + size.cx / 4, (rcPart.top + rcPart.bottom) / 2 - size.cy / 2, icon, size.cx, size.cy); | |
| 936 iconMargin = MulDiv(size.cx, 3, 2); | |
| 937 } | |
| 938 | |
| 939 if (m_ownerDrawMask & (1 << iPart)) { // statusbar won't tell us about ownerdraw from GetText() | |
| 940 DRAWITEMSTRUCT ds = {}; | |
| 941 ds.CtlType = ODT_STATIC; | |
| 942 ds.CtlID = this->GetDlgCtrlID(); | |
| 943 ds.itemID = iPart; | |
| 944 ds.hwndItem = m_hWnd; | |
| 945 ds.hDC = dc; | |
| 946 ds.rcItem = rcPart; | |
| 947 | |
| 948 DCStateScope scope(dc); | |
| 949 GetParent().SendMessage(WM_DRAWITEM, GetDlgCtrlID(), (LPARAM)&ds); | |
| 950 } else { | |
| 951 CRect rcText = rcPart; | |
| 952 int defMargin = rcText.Height() / 4; | |
| 953 int l = iconMargin > 0 ? iconMargin : defMargin; | |
| 954 int r = defMargin; | |
| 955 rcText.DeflateRect(l, 0, r, 0); | |
| 956 dc.DrawText(text, text.GetLength(), rcText, DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX); | |
| 957 } | |
| 958 | |
| 959 if (GetStyle() & SBARS_SIZEGRIP) { | |
| 960 CSize size; | |
| 961 auto theme = OpenThemeData(*this, L"status"); | |
| 962 PFC_ASSERT(theme != NULL); | |
| 963 GetThemePartSize(theme, dc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &size); | |
| 964 auto rc = rcClient; | |
| 965 rc.left = rc.right - size.cx; | |
| 966 rc.top = rc.bottom - size.cy; | |
| 967 DrawThemeBackground(theme, dc, SP_GRIPPER, 0, &rc, nullptr); | |
| 968 CloseThemeData(theme); | |
| 969 } | |
| 970 } | |
| 971 } | |
| 972 | |
| 973 void OnPaint(CDCHandle target) { | |
| 974 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 975 if (target) { | |
| 976 Paint(target); | |
| 977 } else { | |
| 978 CPaintDC dc(*this); | |
| 979 Paint(dc.m_hDC); | |
| 980 } | |
| 981 } | |
| 982 | |
| 983 BOOL OnEraseBkgnd(CDCHandle dc) { | |
| 984 if (m_dark) { | |
| 985 CRect rc; WIN32_OP_D(GetClientRect(rc)); dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE)); return TRUE; | |
| 986 } | |
| 987 SetMsgHandled(FALSE); return FALSE; | |
| 988 } | |
| 989 | |
| 990 bool m_dark = false; | |
| 991 | |
| 992 uint32_t m_ownerDrawMask = 0; | |
| 993 CSize m_iconSizeCache[32]; | |
| 994 }; | |
| 995 | |
| 996 class CCheckBoxHook : public CWindowImpl<CCheckBoxHook, CButton> { | |
| 997 public: | |
| 998 CCheckBoxHook(bool v = false) : m_dark(v) {} | |
| 999 | |
| 1000 BEGIN_MSG_MAP_EX(CCheckBoxHook) | |
| 1001 MSG_WM_PAINT(OnPaint) | |
| 1002 MSG_WM_PRINTCLIENT(OnPaint) | |
| 1003 MSG_WM_ERASEBKGND(OnEraseBkgnd) | |
| 1004 MSG_WM_UPDATEUISTATE(OnUpdateUIState) | |
| 1005 | |
| 1006 // Note that checkbox implementation likes to paint on its own in response to events | |
| 1007 // instead of invalidating and handling WM_PAINT | |
| 1008 // We have to specifically trigger WM_PAINT to override their rendering with ours | |
| 1009 MESSAGE_HANDLER_EX(WM_SETFOCUS, OnMsgRedraw) | |
| 1010 MESSAGE_HANDLER_EX(WM_KILLFOCUS, OnMsgRedraw) | |
| 1011 MESSAGE_HANDLER_EX(WM_ENABLE, OnMsgRedraw) | |
| 1012 MESSAGE_HANDLER_EX(WM_SETTEXT, OnMsgRedraw) | |
| 1013 | |
| 1014 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1015 END_MSG_MAP() | |
| 1016 | |
| 1017 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1018 switch (wp) { | |
| 1019 case 0: SetDark(false); break; | |
| 1020 case 1: SetDark(true); break; | |
| 1021 } | |
| 1022 return 1; | |
| 1023 } | |
| 1024 | |
| 1025 LRESULT OnMsgRedraw(UINT, WPARAM, LPARAM) { | |
| 1026 if ( m_dark ) { | |
| 1027 // PROBLEM: | |
| 1028 // Can't invalidate prior to their handling of the message | |
| 1029 // Causes bugs with specific chains of events - EnableWindow() followed immediately SetWindowText() | |
| 1030 LRESULT ret = DefWindowProc(); | |
| 1031 Invalidate(); | |
| 1032 return ret; | |
| 1033 } | |
| 1034 SetMsgHandled(FALSE); return 0; | |
| 1035 } | |
| 1036 | |
| 1037 void OnUpdateUIState(WORD nAction, WORD nState) { | |
| 1038 (void)nAction; | |
| 1039 if ( m_dark && (nState & (UISF_HIDEACCEL | UISF_HIDEFOCUS)) != 0) { | |
| 1040 // PROBLEM: | |
| 1041 // Can't invalidate prior to their handling of the message | |
| 1042 // Causes bugs with specific chains of events - EnableWindow() followed immediately SetWindowText() | |
| 1043 DefWindowProc(); | |
| 1044 Invalidate(); | |
| 1045 return; | |
| 1046 } | |
| 1047 SetMsgHandled(FALSE); | |
| 1048 } | |
| 1049 void PaintHandler(CDCHandle dc) { | |
| 1050 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); | |
| 1051 | |
| 1052 const bool bDisabled = !this->IsWindowEnabled(); | |
| 1053 | |
| 1054 dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT)); | |
| 1055 dc.SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1056 dc.SetBkMode(OPAQUE); | |
| 1057 dc.SelectFont(GetFont()); | |
| 1058 GetParent().SendMessage(WM_CTLCOLORBTN, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd); | |
| 1059 if (bDisabled) dc.SetTextColor(DarkMode::GetSysColor(COLOR_GRAYTEXT)); // override WM_CTLCOLORBTN | |
| 1060 | |
| 1061 const DWORD btnStyle = GetStyle(); | |
| 1062 const DWORD btnType = btnStyle & BS_TYPEMASK; | |
| 1063 const bool bRadio = (btnType == BS_RADIOBUTTON || btnType == BS_AUTORADIOBUTTON); | |
| 1064 const int part = bRadio ? BP_RADIOBUTTON : BP_CHECKBOX; | |
| 1065 | |
| 1066 const auto ctrlState = GetState(); | |
| 1067 const DWORD uiState = (DWORD)SendMessage(WM_QUERYUISTATE); | |
| 1068 | |
| 1069 | |
| 1070 const bool bChecked = (ctrlState & BST_CHECKED) != 0; | |
| 1071 const bool bMixed = (ctrlState & BST_INDETERMINATE) != 0; | |
| 1072 const bool bHot = (ctrlState & BST_HOT) != 0; | |
| 1073 const bool bFocus = (ctrlState & BST_FOCUS) != 0 && (uiState & UISF_HIDEFOCUS) == 0; | |
| 1074 | |
| 1075 HTHEME theme = OpenThemeData(m_hWnd, L"button"); | |
| 1076 | |
| 1077 CRect rcCheckBox = rcClient; | |
| 1078 bool bDrawn = false; | |
| 1079 int margin = 0; | |
| 1080 if (theme != NULL && IsThemePartDefined(theme, part, 0)) { | |
| 1081 int state = 0; | |
| 1082 if (bDisabled) { | |
| 1083 if ( bChecked ) state = CBS_CHECKEDDISABLED; | |
| 1084 else if ( bMixed ) state = CBS_MIXEDDISABLED; | |
| 1085 else state = CBS_UNCHECKEDDISABLED; | |
| 1086 } else if (bHot) { | |
| 1087 if ( bChecked ) state = CBS_CHECKEDHOT; | |
| 1088 else if ( bMixed ) state = CBS_MIXEDHOT; | |
| 1089 else state = CBS_UNCHECKEDNORMAL; | |
| 1090 } else { | |
| 1091 if ( bChecked ) state = CBS_CHECKEDNORMAL; | |
| 1092 else if ( bMixed ) state = CBS_MIXEDNORMAL; | |
| 1093 else state = CBS_UNCHECKEDNORMAL; | |
| 1094 } | |
| 1095 | |
| 1096 CSize size; | |
| 1097 if (SUCCEEDED(GetThemePartSize(theme, dc, part, state, rcCheckBox, TS_TRUE, &size))) { | |
| 1098 if (size.cx <= rcCheckBox.Width() && size.cy <= rcCheckBox.Height()) { | |
| 1099 CRect rc = rcCheckBox; | |
| 1100 margin = MulDiv(size.cx, 5, 4); | |
| 1101 // rc.left += (rc.Width() - size.cx) / 2; | |
| 1102 rc.top += (rc.Height() - size.cy) / 2; | |
| 1103 rc.right = rc.left + size.cx; | |
| 1104 rc.bottom = rc.top + size.cy; | |
| 1105 DrawThemeBackground(theme, dc, part, state, rc, &rc); | |
| 1106 bDrawn = true; | |
| 1107 } | |
| 1108 } | |
| 1109 } | |
| 1110 if (theme != NULL) CloseThemeData(theme); | |
| 1111 if (!bDrawn) { | |
| 1112 int stateEx = bRadio ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK; | |
| 1113 if (bChecked) stateEx |= DFCS_CHECKED; | |
| 1114 // FIX ME bMixed ? | |
| 1115 if (bDisabled) stateEx |= DFCS_INACTIVE; | |
| 1116 else if (bHot) stateEx |= DFCS_HOT; | |
| 1117 | |
| 1118 const int dpi = GetDeviceCaps(dc, LOGPIXELSX); | |
| 1119 int w = MulDiv(16, dpi, 96); | |
| 1120 | |
| 1121 CRect rc = rcCheckBox; | |
| 1122 if (rc.Width() > w) rc.right = rc.left + w;; | |
| 1123 | |
| 1124 DrawFrameControl(dc, rc, DFC_BUTTON, stateEx); | |
| 1125 margin = MulDiv(20, dpi, 96); | |
| 1126 } | |
| 1127 | |
| 1128 CString text; | |
| 1129 if (margin < rcClient.Width()) GetWindowText(text); | |
| 1130 if (!text.IsEmpty()) { | |
| 1131 CRect rcText = rcClient; | |
| 1132 rcText.left += margin; | |
| 1133 UINT dtFlags = DT_VCENTER; | |
| 1134 if (btnStyle & BS_MULTILINE) { | |
| 1135 dtFlags |= DT_WORDBREAK; | |
| 1136 } else { | |
| 1137 dtFlags |= DT_END_ELLIPSIS | DT_SINGLELINE; | |
| 1138 } | |
| 1139 dc.DrawText(text, text.GetLength(), rcText, dtFlags); | |
| 1140 if (bFocus) { | |
| 1141 dc.DrawText(text, text.GetLength(), rcText, DT_CALCRECT | dtFlags); | |
| 1142 dc.DrawFocusRect(rcText); | |
| 1143 } | |
| 1144 } else if (bFocus) { | |
| 1145 dc.DrawFocusRect(rcClient); | |
| 1146 } | |
| 1147 } | |
| 1148 void OnPaint(CDCHandle userDC, UINT flags = 0) { | |
| 1149 (void)flags; | |
| 1150 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 1151 if (userDC) { | |
| 1152 PaintHandler(userDC); | |
| 1153 } else { | |
| 1154 CPaintDC dc(*this); | |
| 1155 PaintHandler(dc.m_hDC); | |
| 1156 } | |
| 1157 } | |
| 1158 BOOL OnEraseBkgnd(CDCHandle dc) { | |
| 1159 if (m_dark) { | |
| 1160 CRect rc; WIN32_OP_D(GetClientRect(rc)); | |
| 1161 | |
| 1162 dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT)); | |
| 1163 dc.SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1164 auto br = (HBRUSH)GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd); | |
| 1165 if (br != NULL) { | |
| 1166 dc.FillRect(rc, br); | |
| 1167 } else { | |
| 1168 dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1169 } | |
| 1170 return TRUE; | |
| 1171 } | |
| 1172 SetMsgHandled(FALSE); return FALSE; | |
| 1173 } | |
| 1174 | |
| 1175 void SetDark(bool v = true) { | |
| 1176 if (v != m_dark) { | |
| 1177 m_dark = v; | |
| 1178 Invalidate(); | |
| 1179 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 1180 } | |
| 1181 } | |
| 1182 | |
| 1183 void SubclassWindow(HWND wnd) { | |
| 1184 WIN32_OP_D(__super::SubclassWindow(wnd)); | |
| 1185 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 1186 } | |
| 1187 | |
| 1188 bool m_dark = false; | |
| 1189 }; | |
| 1190 | |
| 1191 class CGripperHook : public CWindowImpl<CGripperHook> { | |
| 1192 public: | |
| 1193 CGripperHook(bool v) : m_dark(v) {} | |
| 1194 | |
| 1195 BEGIN_MSG_MAP_EX(CGRipperHook) | |
| 1196 MSG_WM_ERASEBKGND(OnEraseBkgnd) | |
| 1197 MSG_WM_PAINT(OnPaint) | |
| 1198 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1199 END_MSG_MAP() | |
| 1200 | |
| 1201 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1202 switch (wp) { | |
| 1203 case 0: SetDark(false); break; | |
| 1204 case 1: SetDark(true); break; | |
| 1205 } | |
| 1206 return 1; | |
| 1207 } | |
| 1208 | |
| 1209 void SetDark(bool v) { | |
| 1210 if (v != m_dark) { | |
| 1211 m_dark = v; | |
| 1212 ApplyDarkThemeCtrl(*this, m_dark); | |
| 1213 } | |
| 1214 } | |
| 1215 | |
| 1216 void SubclassWindow(HWND wnd) { | |
| 1217 WIN32_OP_D(__super::SubclassWindow(wnd)); | |
| 1218 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 1219 } | |
| 1220 | |
| 1221 void PaintGripper(CDCHandle dc) { | |
| 1222 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); | |
| 1223 CSize size; | |
| 1224 auto theme = OpenThemeData(*this, L"status"); | |
| 1225 PFC_ASSERT(theme != NULL); | |
| 1226 GetThemePartSize(theme, dc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &size); | |
| 1227 auto rc = rcClient; | |
| 1228 rc.left = rc.right - size.cx; | |
| 1229 rc.top = rc.bottom - size.cy; | |
| 1230 DrawThemeBackground(theme, dc, SP_GRIPPER, 0, &rc, nullptr); | |
| 1231 CloseThemeData(theme); | |
| 1232 } | |
| 1233 | |
| 1234 void OnPaint(CDCHandle dc) { | |
| 1235 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 1236 if (dc) PaintGripper(dc); | |
| 1237 else {CPaintDC pdc(*this); PaintGripper(pdc.m_hDC);} | |
| 1238 } | |
| 1239 | |
| 1240 BOOL OnEraseBkgnd(CDCHandle dc) { | |
| 1241 if (m_dark) { | |
| 1242 CRect rc; GetClientRect(rc); | |
| 1243 dc.FillSolidRect(rc, GetSysColor(COLOR_WINDOW)); | |
| 1244 return TRUE; | |
| 1245 } | |
| 1246 SetMsgHandled(FALSE); return FALSE; | |
| 1247 } | |
| 1248 bool m_dark = false; | |
| 1249 }; | |
| 1250 | |
| 1251 class CReBarHook : public CWindowImpl<CReBarHook, CReBarCtrl> { | |
| 1252 bool m_dark; | |
| 1253 public: | |
| 1254 CReBarHook(bool v) : m_dark(v) {} | |
| 1255 BEGIN_MSG_MAP_EX(CReBarHook) | |
| 1256 MSG_WM_ERASEBKGND(OnEraseBkgnd) | |
| 1257 MSG_WM_DESTROY(OnDestroy) | |
| 1258 MSG_WM_PAINT(OnPaint) | |
| 1259 MSG_WM_PRINTCLIENT(OnPaint) | |
| 1260 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, DarkMode::OnCustomDraw) | |
| 1261 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1262 END_MSG_MAP() | |
| 1263 | |
| 1264 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1265 switch (wp) { | |
| 1266 case 0: SetDark(false); break; | |
| 1267 case 1: SetDark(true); break; | |
| 1268 } | |
| 1269 return 1; | |
| 1270 } | |
| 1271 | |
| 1272 void OnPaint(CDCHandle target, unsigned flags = 0) { | |
| 1273 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 1274 (void)flags; | |
| 1275 if (target) { | |
| 1276 HandlePaint(target); | |
| 1277 } else { | |
| 1278 CPaintDC pdc(*this); | |
| 1279 HandlePaint(pdc.m_hDC); | |
| 1280 } | |
| 1281 } | |
| 1282 | |
| 1283 void HandlePaint(CDCHandle dc) { | |
| 1284 const int total = this->GetBandCount(); | |
| 1285 for (int iBand = 0; iBand < total; ++iBand) { | |
| 1286 CRect rc; | |
| 1287 WIN32_OP_D(this->GetRect(iBand, rc)); | |
| 1288 | |
| 1289 wchar_t buffer[256] = {}; | |
| 1290 REBARBANDINFO info = { sizeof(info) }; | |
| 1291 info.fMask = RBBIM_TEXT | RBBIM_CHILD | RBBIM_STYLE; | |
| 1292 info.lpText = buffer; info.cch = (UINT)std::size(buffer); | |
| 1293 WIN32_OP_D(this->GetBandInfo(iBand, &info)); | |
| 1294 | |
| 1295 HFONT useFont; | |
| 1296 // Sadly overriding the font breaks the layout | |
| 1297 // MS implementation disregards fonts too | |
| 1298 // useFont = this->GetFont(); | |
| 1299 useFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); | |
| 1300 SelectObjectScope fontScope(dc, useFont); | |
| 1301 dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT)); | |
| 1302 dc.SetBkMode(TRANSPARENT); | |
| 1303 | |
| 1304 CRect rcText = rc; | |
| 1305 if ((info.fStyle & RBBS_NOGRIPPER) == 0) { | |
| 1306 auto color = PaintUtils::BlendColor(DarkMode::GetSysColor(COLOR_WINDOWFRAME), DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1307 dc.SetDCPenColor(color); | |
| 1308 SelectObjectScope penScope(dc, GetStockObject(DC_PEN)); | |
| 1309 dc.MoveTo(rcText.TopLeft()); | |
| 1310 dc.LineTo(CPoint(rcText.left, rcText.bottom)); | |
| 1311 rcText.left += 6; // this should be DPI-scaled, only it's not because rebar layout isn't either | |
| 1312 } else { | |
| 1313 rcText.left += 2; | |
| 1314 } | |
| 1315 dc.DrawText(buffer, (int)wcslen(buffer), &rcText, DT_VCENTER | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX); | |
| 1316 } | |
| 1317 } | |
| 1318 | |
| 1319 | |
| 1320 void OnDestroy() { | |
| 1321 SetMsgHandled(FALSE); | |
| 1322 } | |
| 1323 BOOL OnEraseBkgnd(CDCHandle dc) { | |
| 1324 if (m_dark) { | |
| 1325 CRect rc; | |
| 1326 WIN32_OP_D(GetClientRect(rc)); | |
| 1327 dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1328 return TRUE; | |
| 1329 } | |
| 1330 SetMsgHandled(FALSE); return FALSE; | |
| 1331 } | |
| 1332 void SetDark(bool v) { | |
| 1333 if (v != m_dark) { | |
| 1334 m_dark = v; Apply(); | |
| 1335 } | |
| 1336 } | |
| 1337 void Apply() { | |
| 1338 if (m_dark) { | |
| 1339 this->SetTextColor(DarkMode::GetSysColor(COLOR_WINDOWTEXT)); | |
| 1340 this->SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE)); | |
| 1341 } else { | |
| 1342 this->SetTextColor((COLORREF)-1); | |
| 1343 this->SetBkColor((COLORREF)-1); | |
| 1344 } | |
| 1345 Invalidate(); | |
| 1346 } | |
| 1347 void SubclassWindow(HWND wnd) { | |
| 1348 WIN32_OP_D(__super::SubclassWindow(wnd)); | |
| 1349 Apply(); | |
| 1350 } | |
| 1351 }; | |
| 1352 | |
| 1353 class CStaticHook : public CWindowImpl<CStaticHook, CStatic> { | |
| 1354 bool m_dark; | |
| 1355 public: | |
| 1356 CStaticHook(bool v) : m_dark(v) {} | |
| 1357 | |
| 1358 BEGIN_MSG_MAP_EX(CStaticHook) | |
| 1359 MSG_WM_PAINT(OnPaint) | |
| 1360 MESSAGE_HANDLER_EX(WM_ENABLE, OnMsgRedraw) | |
| 1361 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1362 END_MSG_MAP() | |
| 1363 | |
| 1364 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1365 switch (wp) { | |
| 1366 case 0: SetDark(false); break; | |
| 1367 case 1: SetDark(true); break; | |
| 1368 } | |
| 1369 return 1; | |
| 1370 } | |
| 1371 | |
| 1372 LRESULT OnMsgRedraw(UINT, WPARAM, LPARAM) { | |
| 1373 Invalidate(); | |
| 1374 SetMsgHandled(FALSE); | |
| 1375 return 0; | |
| 1376 } | |
| 1377 | |
| 1378 void SetDark(bool v) { | |
| 1379 if (m_dark != v) { | |
| 1380 m_dark = v; | |
| 1381 Invalidate(); | |
| 1382 } | |
| 1383 } | |
| 1384 | |
| 1385 void OnPaint(CDCHandle dc) { | |
| 1386 // ONLY override the dark+disabled or dark+icon behavior | |
| 1387 if (!m_dark || (this->IsWindowEnabled() && this->GetIcon() == NULL) ) { | |
| 1388 SetMsgHandled(FALSE); return; | |
| 1389 } | |
| 1390 | |
| 1391 if (dc) HandlePaint(dc); | |
| 1392 else { | |
| 1393 CPaintDC pdc(*this); HandlePaint(pdc.m_hDC); | |
| 1394 } | |
| 1395 } | |
| 1396 void HandlePaint(CDCHandle dc) { | |
| 1397 CString str; | |
| 1398 CIconHandle icon = this->GetIcon(); | |
| 1399 this->GetWindowTextW(str); | |
| 1400 | |
| 1401 CRect rcClient; | |
| 1402 WIN32_OP_D(GetClientRect(rcClient)); | |
| 1403 | |
| 1404 const DWORD style = this->GetStyle(); | |
| 1405 | |
| 1406 dc.SelectFont(GetFont()); | |
| 1407 | |
| 1408 HBRUSH br = (HBRUSH) GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd);; | |
| 1409 if (br == NULL) { | |
| 1410 dc.FillSolidRect(rcClient, DarkMode::GetSysColor(COLOR_WINDOW)); | |
| 1411 } else { | |
| 1412 WIN32_OP_D(dc.FillRect(rcClient, br)); | |
| 1413 } | |
| 1414 | |
| 1415 if (icon != NULL) { | |
| 1416 dc.DrawIcon(0, 0, icon); | |
| 1417 } else { | |
| 1418 DWORD flags = 0; | |
| 1419 if (style & SS_SIMPLE) flags |= DT_SINGLELINE | DT_WORD_ELLIPSIS; | |
| 1420 else flags |= DT_WORDBREAK; | |
| 1421 if (style & SS_RIGHT) flags |= DT_RIGHT; | |
| 1422 else if (style & SS_CENTER) flags |= DT_CENTER; | |
| 1423 | |
| 1424 dc.SetTextColor(DarkMode::GetSysColor(COLOR_GRAYTEXT)); | |
| 1425 dc.SetBkMode(TRANSPARENT); | |
| 1426 dc.DrawText(str, str.GetLength(), rcClient, flags); | |
| 1427 } | |
| 1428 } | |
| 1429 }; | |
| 1430 | |
| 1431 class CUpDownHook : public CWindowImpl<CUpDownHook, CUpDownCtrl> { | |
| 1432 bool m_dark; | |
| 1433 public: | |
| 1434 CUpDownHook(bool v) : m_dark(v) {} | |
| 1435 | |
| 1436 void SetDark(bool v) { | |
| 1437 if (v != m_dark) { | |
| 1438 m_dark = v; Invalidate(); | |
| 1439 } | |
| 1440 } | |
| 1441 | |
| 1442 BEGIN_MSG_MAP_EX(CUpDownHook) | |
| 1443 MSG_WM_PAINT(OnPaint) | |
| 1444 MSG_WM_PRINTCLIENT(OnPaint) | |
| 1445 MSG_WM_MOUSEMOVE(OnMouseMove) | |
| 1446 MSG_WM_LBUTTONDOWN(OnMouseBtn) | |
| 1447 MSG_WM_LBUTTONUP(OnMouseBtn) | |
| 1448 MSG_WM_MOUSELEAVE(OnMouseLeave) | |
| 1449 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1450 END_MSG_MAP() | |
| 1451 | |
| 1452 private: | |
| 1453 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1454 switch (wp) { | |
| 1455 case 0: SetDark(false); break; | |
| 1456 case 1: SetDark(true); break; | |
| 1457 } | |
| 1458 return 1; | |
| 1459 } | |
| 1460 | |
| 1461 struct layout_t { | |
| 1462 CRect whole, upper, lower; | |
| 1463 int yCenter; | |
| 1464 }; | |
| 1465 layout_t Layout(CRect const & rcClient) { | |
| 1466 CRect rc = rcClient; | |
| 1467 rc.DeflateRect(1, 1); | |
| 1468 int yCenter = (rc.top + rc.bottom) / 2; | |
| 1469 layout_t ret; | |
| 1470 ret.yCenter = yCenter; | |
| 1471 ret.whole = rc; | |
| 1472 ret.upper = rc; | |
| 1473 ret.upper.bottom = yCenter; | |
| 1474 ret.lower = rc; | |
| 1475 ret.lower.top = yCenter; | |
| 1476 return ret; | |
| 1477 } | |
| 1478 layout_t Layout() { | |
| 1479 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); return Layout(rcClient); | |
| 1480 } | |
| 1481 int m_hot = 0; | |
| 1482 bool m_btnDown = false; | |
| 1483 void SetHot(int iHot) { | |
| 1484 if (iHot != m_hot) { | |
| 1485 m_hot = iHot; Invalidate(); | |
| 1486 } | |
| 1487 } | |
| 1488 void OnMouseLeave() { | |
| 1489 SetHot(0); | |
| 1490 SetMsgHandled(FALSE); | |
| 1491 } | |
| 1492 int HitTest(CPoint pt) { | |
| 1493 auto layout = Layout(); | |
| 1494 if (layout.upper.PtInRect(pt)) return 1; | |
| 1495 else if (layout.lower.PtInRect(pt)) return 2; | |
| 1496 else return 0; | |
| 1497 } | |
| 1498 void OnMouseBtn(UINT flags, CPoint) { | |
| 1499 bool bDown = (flags & MK_LBUTTON) != 0; | |
| 1500 if (bDown != m_btnDown) { | |
| 1501 m_btnDown = bDown; | |
| 1502 Invalidate(); | |
| 1503 } | |
| 1504 SetMsgHandled(FALSE); | |
| 1505 } | |
| 1506 void OnMouseMove(UINT, CPoint pt) { | |
| 1507 SetHot(HitTest(pt)); | |
| 1508 SetMsgHandled(FALSE); | |
| 1509 } | |
| 1510 void OnPaint(CDCHandle target, unsigned flags = 0) { | |
| 1511 if (!m_dark) { SetMsgHandled(FALSE); return; } | |
| 1512 (void)flags; | |
| 1513 if (target) { | |
| 1514 HandlePaint(target); | |
| 1515 } else { | |
| 1516 CPaintDC pdc(*this); | |
| 1517 HandlePaint(pdc.m_hDC); | |
| 1518 } | |
| 1519 } | |
| 1520 void HandlePaint(CDCHandle dc) { | |
| 1521 // no corresponding getsyscolor values for this, hardcoded values taken from actual button control | |
| 1522 // + frame trying to fit edit control frame | |
| 1523 const COLORREF colorText = 0xFFFFFF; | |
| 1524 const COLORREF colorFrame = 0xFFFFFF; | |
| 1525 const COLORREF colorBk = 0x333333; | |
| 1526 const COLORREF colorHot = 0x454545; | |
| 1527 const COLORREF colorPressed = 0x666666; | |
| 1528 | |
| 1529 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); | |
| 1530 auto layout = Layout(rcClient); | |
| 1531 dc.FillRect(rcClient, MakeTempBrush(dc, colorBk)); | |
| 1532 | |
| 1533 if (m_hot != 0) { | |
| 1534 auto color = m_btnDown ? colorPressed : colorHot; | |
| 1535 switch (m_hot) { | |
| 1536 case 1: | |
| 1537 dc.FillSolidRect(layout.upper, color); | |
| 1538 break; | |
| 1539 case 2: | |
| 1540 dc.FillSolidRect(layout.lower, color); | |
| 1541 break; | |
| 1542 } | |
| 1543 } | |
| 1544 | |
| 1545 dc.FrameRect(layout.whole, MakeTempBrush(dc, colorFrame)); | |
| 1546 dc.SetDCPenColor(colorFrame); | |
| 1547 dc.SelectPen((HPEN)GetStockObject(DC_PEN)); | |
| 1548 dc.MoveTo(layout.whole.left, layout.yCenter); | |
| 1549 dc.LineTo(layout.whole.right, layout.yCenter); | |
| 1550 | |
| 1551 CFontHandle f = GetFont(); | |
| 1552 if (f == NULL) { | |
| 1553 auto buddy = this->GetBuddy(); | |
| 1554 if ( buddy ) f = buddy.GetFont(); | |
| 1555 } | |
| 1556 if (f == NULL) f = (HFONT) GetStockObject(DEFAULT_GUI_FONT); | |
| 1557 PFC_ASSERT(f != NULL); | |
| 1558 CFont font2; | |
| 1559 CreateScaledFont(font2, f, 0.5); | |
| 1560 SelectObjectScope selectFont(dc, font2); | |
| 1561 dc.SetBkMode(TRANSPARENT); | |
| 1562 dc.SetTextColor(colorText); | |
| 1563 dc.DrawText(L"˄", 1, layout.upper, DT_SINGLELINE | DT_VCENTER | DT_CENTER | DT_NOPREFIX); | |
| 1564 dc.DrawText(L"˅", 1, layout.lower, DT_SINGLELINE | DT_VCENTER | DT_CENTER | DT_NOPREFIX); | |
| 1565 } | |
| 1566 }; | |
| 1567 | |
| 1568 class CNCFrameHook : public CWindowImpl<CNCFrameHook, CWindow> { | |
| 1569 public: | |
| 1570 CNCFrameHook(bool dark) : m_dark(dark) {} | |
| 1571 | |
| 1572 BEGIN_MSG_MAP_EX(CNCFrameHook) | |
| 1573 MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode) | |
| 1574 MSG_WM_NCPAINT(OnNCPaint) | |
| 1575 END_MSG_MAP() | |
| 1576 | |
| 1577 void SetDark(bool v) { | |
| 1578 if (v != m_dark) { | |
| 1579 m_dark = v; ApplyDark(); | |
| 1580 } | |
| 1581 } | |
| 1582 BOOL SubclassWindow(HWND wnd) { | |
| 1583 auto rv = __super::SubclassWindow(wnd); | |
| 1584 if (rv) { | |
| 1585 ApplyDark(); | |
| 1586 } | |
| 1587 return rv; | |
| 1588 } | |
| 1589 private: | |
| 1590 void OnNCPaint(HRGN rgn) { | |
| 1591 if (m_dark) { | |
| 1592 NCPaintDarkFrame(m_hWnd, rgn); | |
| 1593 return; | |
| 1594 } | |
| 1595 SetMsgHandled(FALSE); | |
| 1596 } | |
| 1597 void ApplyDark() { | |
| 1598 ApplyDarkThemeCtrl(m_hWnd, m_dark); | |
| 1599 Invalidate(); | |
| 1600 } | |
| 1601 LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) { | |
| 1602 switch (wp) { | |
| 1603 case 0: SetDark(false); break; | |
| 1604 case 1: SetDark(true); break; | |
| 1605 } | |
| 1606 return 1; | |
| 1607 } | |
| 1608 | |
| 1609 bool m_dark; | |
| 1610 }; | |
| 1611 } | |
| 1612 | |
| 1613 void CHooks::AddPopup(HWND wnd) { | |
| 1614 addOp( [wnd, this] { | |
| 1615 UpdateTitleBar(wnd, m_dark); | |
| 1616 } ); | |
| 1617 } | |
| 1618 | |
| 1619 void CHooks::AddDialogWithControls(HWND wnd) { | |
| 1620 AddDialog(wnd); AddControls(wnd); | |
| 1621 } | |
| 1622 | |
| 1623 void CHooks::AddDialog(HWND wnd) { | |
| 1624 | |
| 1625 { | |
| 1626 CWindow w(wnd); | |
| 1627 if ((w.GetStyle() & WS_CHILD) == 0) { | |
| 1628 AddPopup(wnd); | |
| 1629 } | |
| 1630 } | |
| 1631 | |
| 1632 auto hook = new ImplementOnFinalMessage< CDialogHook > (m_dark); | |
| 1633 hook->SubclassWindow(wnd); | |
| 1634 AddCtrlMsg(wnd); | |
| 1635 } | |
| 1636 void CHooks::AddTabCtrl(HWND wnd) { | |
| 1637 auto hook = new ImplementOnFinalMessage<CTabsHook>(m_dark); | |
| 1638 hook->SubclassWindow(wnd); | |
| 1639 AddCtrlMsg(wnd); | |
| 1640 } | |
| 1641 void CHooks::AddComboBox(HWND wnd) { | |
| 1642 { | |
| 1643 CComboBox combo = wnd; | |
| 1644 COMBOBOXINFO info = {sizeof(info)}; | |
| 1645 WIN32_OP_D( combo.GetComboBoxInfo(&info) ); | |
| 1646 if (info.hwndList != NULL) { | |
| 1647 AddListBox( info.hwndList ); | |
| 1648 } | |
| 1649 } | |
| 1650 | |
| 1651 addOp([wnd, this] { | |
| 1652 SetWindowTheme(wnd, m_dark ? L"DarkMode_CFD" : L"Explorer", NULL); | |
| 1653 }); | |
| 1654 } | |
| 1655 void CHooks::AddComboBoxEx(HWND wnd) { | |
| 1656 this->AddControls(wnd); // recurse to add the combo box | |
| 1657 } | |
| 1658 void CHooks::AddEditBox(HWND wnd) { | |
| 1659 #if 0 // Experimental | |
| 1660 auto hook = new ImplementOnFinalMessage<CNCFrameHook>(m_dark); | |
| 1661 hook->SubclassWindow( wnd ); | |
| 1662 AddCtrlMsg( wnd ); | |
| 1663 #else | |
| 1664 AddGeneric(wnd); | |
| 1665 #endif | |
| 1666 } | |
| 1667 void CHooks::AddButton(HWND wnd) { | |
| 1668 CButton btn(wnd); | |
| 1669 auto style = btn.GetButtonStyle(); | |
| 1670 auto type = style & BS_TYPEMASK; | |
| 1671 if ((type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON || type == BS_3STATE || type == BS_AUTO3STATE) && (style & BS_PUSHLIKE) == 0) { | |
| 1672 // MS checkbox implementation is terminally retarded and won't draw text in correct color | |
| 1673 // Subclass it and draw our own content | |
| 1674 // Other button types seem OK | |
| 1675 auto hook = new ImplementOnFinalMessage<CCheckBoxHook>(m_dark); | |
| 1676 hook->SubclassWindow(wnd); | |
| 1677 AddCtrlMsg(wnd); | |
| 1678 } else if (type == BS_GROUPBOX) { | |
| 1679 SetWindowTheme(wnd, L"", L""); | |
| 1680 // SNAFU: re-creation of other controls such as list boxes causes repaint bugs due to overlapping | |
| 1681 // Even though this is not a fault of the groupbox, fix it here - by defer-pushing all group boxes to the back | |
| 1682 // Can't move to back right away, breaks window enumeration | |
| 1683 m_lstMoveToBack.push_back(wnd); | |
| 1684 } else { | |
| 1685 AddGeneric(wnd); | |
| 1686 } | |
| 1687 | |
| 1688 } | |
| 1689 void CHooks::AddGeneric(HWND wnd, const wchar_t * name) { | |
| 1690 this->addOp([wnd, this, name] {ApplyDarkThemeCtrl(wnd, m_dark, name); }); | |
| 1691 } | |
| 1692 void CHooks::AddClassic(HWND wnd, const wchar_t* normalTheme) { | |
| 1693 this->addOp([wnd, this, normalTheme] { | |
| 1694 if (m_dark) ::SetWindowTheme(wnd, L"", L""); | |
| 1695 else ::SetWindowTheme(wnd, normalTheme, nullptr); | |
| 1696 }); | |
| 1697 } | |
| 1698 void CHooks::AddStatusBar(HWND wnd) { | |
| 1699 auto hook = new ImplementOnFinalMessage<CStatusBarHook>(m_dark); | |
| 1700 hook->SubclassWindow(wnd); | |
| 1701 this->AddCtrlMsg(wnd); | |
| 1702 } | |
| 1703 void CHooks::AddScrollBar(HWND wnd) { | |
| 1704 CWindow w(wnd); | |
| 1705 if (w.GetStyle() & SBS_SIZEGRIP) { | |
| 1706 auto hook = new ImplementOnFinalMessage<CGripperHook>(m_dark); | |
| 1707 hook->SubclassWindow(wnd); | |
| 1708 this->AddCtrlMsg(wnd); | |
| 1709 } else { | |
| 1710 AddGeneric(wnd); | |
| 1711 } | |
| 1712 } | |
| 1713 | |
| 1714 void CHooks::AddReBar(HWND wnd) { | |
| 1715 auto hook = new ImplementOnFinalMessage<CReBarHook>(m_dark); | |
| 1716 hook->SubclassWindow(wnd); | |
| 1717 this->AddCtrlMsg(wnd); | |
| 1718 } | |
| 1719 | |
| 1720 void CHooks::AddToolBar(HWND wnd, bool bExplorerTheme) { | |
| 1721 // Not a subclass | |
| 1722 addObj(new CToolbarHook(wnd, m_dark, bExplorerTheme)); | |
| 1723 } | |
| 1724 | |
| 1725 void CHooks::AddStatic(HWND wnd) { | |
| 1726 auto hook = new ImplementOnFinalMessage<CStaticHook>(m_dark); | |
| 1727 hook->SubclassWindow(wnd); | |
| 1728 this->AddCtrlMsg(wnd); | |
| 1729 } | |
| 1730 | |
| 1731 void CHooks::AddUpDown(HWND wnd) { | |
| 1732 auto hook = new ImplementOnFinalMessage<CUpDownHook>(m_dark); | |
| 1733 hook->SubclassWindow(wnd); | |
| 1734 this->AddCtrlMsg(wnd); | |
| 1735 } | |
| 1736 | |
| 1737 void CHooks::AddTreeView(HWND wnd) { | |
| 1738 auto hook = new ImplementOnFinalMessage<CTreeViewHook>(m_dark); | |
| 1739 hook->SubclassWindow(wnd); | |
| 1740 this->AddCtrlMsg(wnd); | |
| 1741 } | |
| 1742 void CHooks::AddListBox(HWND wnd) { | |
| 1743 this->AddGeneric( wnd ); | |
| 1744 #if 0 | |
| 1745 auto subst = CListControl_ReplaceListBox(wnd); | |
| 1746 if (subst) AddPPListControl(subst); | |
| 1747 #endif | |
| 1748 } | |
| 1749 | |
| 1750 void CHooks::AddListView(HWND wnd) { | |
| 1751 auto subst = CListControl_ReplaceListView(wnd); | |
| 1752 if (subst) AddPPListControl(subst); | |
| 1753 } | |
| 1754 | |
| 1755 void CHooks::AddPPListControl(HWND wnd) { | |
| 1756 this->AddCtrlMsg(wnd); | |
| 1757 // this->addOp([this, wnd] { CListControl::wndSetDarkMode(wnd, m_dark); }); | |
| 1758 } | |
| 1759 | |
| 1760 void CHooks::SetDark(bool v) { | |
| 1761 // Important: some handlers to ugly things if told to apply when no state change occurred - UpdateTitleBar() stuff in particular | |
| 1762 if (m_dark != v) { | |
| 1763 m_dark = v; | |
| 1764 for (auto& f : m_apply) f(); | |
| 1765 } | |
| 1766 } | |
| 1767 void CHooks::flushMoveToBack() { | |
| 1768 for (auto w : m_lstMoveToBack) { | |
| 1769 ::SetWindowPos(w, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); | |
| 1770 } | |
| 1771 m_lstMoveToBack.clear(); | |
| 1772 } | |
| 1773 void CHooks::AddControls(HWND wndParent) { | |
| 1774 for (HWND walk = GetWindow(wndParent, GW_CHILD); walk != NULL; ) { | |
| 1775 HWND next = GetWindow(walk, GW_HWNDNEXT); | |
| 1776 AddCtrlAuto(walk); | |
| 1777 walk = next; | |
| 1778 } | |
| 1779 this->flushMoveToBack(); | |
| 1780 // EnumChildWindows(wndParent, [this](HWND ctrl) {this->AddCtrlAuto(ctrl);}); | |
| 1781 } | |
| 1782 | |
| 1783 void CHooks::AddCtrlMsg(HWND w) { | |
| 1784 this->addOp([this, w] { | |
| 1785 ::SendMessage(w, msgSetDarkMode(), this->m_dark ? 1 : 0, 0); | |
| 1786 }); | |
| 1787 } | |
| 1788 | |
| 1789 void CHooks::AddCtrlAuto(HWND wnd) { | |
| 1790 | |
| 1791 if (::SendMessage(wnd, msgSetDarkMode(), -1, -1)) { | |
| 1792 AddCtrlMsg(wnd); return; | |
| 1793 } | |
| 1794 | |
| 1795 wchar_t buffer[128] = {}; | |
| 1796 | |
| 1797 ::GetClassName(wnd, buffer, (int)(std::size(buffer) - 1)); | |
| 1798 | |
| 1799 const wchar_t* cls = buffer; | |
| 1800 if (_wcsnicmp(cls, L"ATL:", 4) == 0) cls += 4; | |
| 1801 | |
| 1802 | |
| 1803 if (_wcsicmp(cls, CButton::GetWndClassName()) == 0) { | |
| 1804 AddButton(wnd); | |
| 1805 } else if (_wcsicmp(cls, CComboBox::GetWndClassName()) == 0) { | |
| 1806 AddComboBox(wnd); | |
| 1807 } else if (_wcsicmp(cls, CComboBoxEx::GetWndClassName()) == 0 ) { | |
| 1808 AddComboBoxEx(wnd); | |
| 1809 } else if (_wcsicmp(cls, CTabCtrl::GetWndClassName()) == 0) { | |
| 1810 AddTabCtrl(wnd); | |
| 1811 } else if (_wcsicmp(cls, CStatusBarCtrl::GetWndClassName()) == 0) { | |
| 1812 AddStatusBar(wnd); | |
| 1813 } else if (_wcsicmp(cls, CEdit::GetWndClassName()) == 0) { | |
| 1814 AddEditBox(wnd); | |
| 1815 } else if (_wcsicmp(cls, WC_SCROLLBAR) == 0) { | |
| 1816 AddScrollBar(wnd); | |
| 1817 } else if (_wcsicmp(cls, CToolBarCtrl::GetWndClassName()) == 0) { | |
| 1818 AddToolBar(wnd); | |
| 1819 } else if (_wcsicmp(cls, CTrackBarCtrl::GetWndClassName()) == 0) { | |
| 1820 AddGeneric(wnd); | |
| 1821 } else if (_wcsicmp(cls, CTreeViewCtrl::GetWndClassName()) == 0) { | |
| 1822 AddTreeView(wnd); | |
| 1823 } else if (_wcsicmp(cls, CStatic::GetWndClassName()) == 0) { | |
| 1824 AddStatic(wnd); | |
| 1825 } else if (_wcsicmp(cls, CUpDownCtrl::GetWndClassName()) == 0) { | |
| 1826 AddUpDown(wnd); | |
| 1827 } else if (_wcsicmp(cls, CListViewCtrl::GetWndClassName()) == 0) { | |
| 1828 AddListView(wnd); | |
| 1829 } else if (_wcsicmp(cls, CListBox::GetWndClassName()) == 0) { | |
| 1830 AddListBox(wnd); | |
| 1831 } else if (_wcsicmp(cls, CReBarCtrl::GetWndClassName()) == 0) { | |
| 1832 AddReBar(wnd); | |
| 1833 } else { | |
| 1834 #if PFC_DEBUG | |
| 1835 pfc::outputDebugLine(pfc::format("DarkMode: unknown class - ", buffer)); | |
| 1836 #endif | |
| 1837 } | |
| 1838 } | |
| 1839 | |
| 1840 void CHooks::clear() { | |
| 1841 m_apply.clear(); | |
| 1842 for (auto v : m_cleanup) v(); | |
| 1843 m_cleanup.clear(); | |
| 1844 } | |
| 1845 | |
| 1846 void CHooks::AddApp() { | |
| 1847 addOp([this] { | |
| 1848 SetAppDarkMode(this->m_dark); | |
| 1849 }); | |
| 1850 } | |
| 1851 | |
| 1852 void NCPaintDarkFrame(HWND wnd_, HRGN rgn_) { | |
| 1853 // rgn is in SCREEN COORDINATES, possibly (HRGN)1 to indicate no clipping / whole nonclient area redraw | |
| 1854 // we're working with SCREEN COORDINATES until actual DC painting | |
| 1855 CWindow wnd = wnd_; | |
| 1856 | |
| 1857 CRect rcWindow, rcClient; | |
| 1858 WIN32_OP_D( wnd.GetWindowRect(rcWindow) ); | |
| 1859 WIN32_OP_D( wnd.GetClientRect(rcClient) ); | |
| 1860 WIN32_OP_D( wnd.ClientToScreen( rcClient ) ); // transform all to same coordinate system | |
| 1861 | |
| 1862 CRgn rgnClip; | |
| 1863 WIN32_OP_D( rgnClip.CreateRectRgnIndirect(rcWindow) != NULL ); | |
| 1864 if (rgn_ != NULL && rgn_ != (HRGN)1) { | |
| 1865 // we have a valid HRGN from caller? | |
| 1866 if (rgnClip.CombineRgn(rgn_, RGN_AND) == NULLREGION) return; // nothing to draw, exit early | |
| 1867 } | |
| 1868 | |
| 1869 { | |
| 1870 // Have scroll bars? Have DefWindowProc() them then exclude from our rgnClip. | |
| 1871 SCROLLBARINFO si = { sizeof(si) }; | |
| 1872 if (::GetScrollBarInfo(wnd, OBJID_VSCROLL, &si) && (si.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0 && si.rcScrollBar.left < si.rcScrollBar.right) { | |
| 1873 CRect rc = si.rcScrollBar; | |
| 1874 // rcClient.right = rc.right; | |
| 1875 CRgn rgn; WIN32_OP_D( rgn.CreateRectRgnIndirect(rc) ); | |
| 1876 int status = SIMPLEREGION; | |
| 1877 if (rgnClip) { | |
| 1878 status = rgn.CombineRgn(rgn, rgnClip, RGN_AND); | |
| 1879 } | |
| 1880 if (status != NULLREGION) { | |
| 1881 DefWindowProc(wnd, WM_NCPAINT, (WPARAM)rgn.m_hRgn, 0); | |
| 1882 rgnClip.CombineRgn(rgn, RGN_DIFF); // exclude from further drawing | |
| 1883 } | |
| 1884 } | |
| 1885 if (::GetScrollBarInfo(wnd, OBJID_HSCROLL, &si) && (si.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0 && si.rcScrollBar.top < si.rcScrollBar.bottom) { | |
| 1886 CRect rc = si.rcScrollBar; | |
| 1887 // rcClient.bottom = rc.bottom; | |
| 1888 CRgn rgn; WIN32_OP_D(rgn.CreateRectRgnIndirect(rc)); | |
| 1889 int status = SIMPLEREGION; | |
| 1890 if (rgnClip) { | |
| 1891 status = rgn.CombineRgn(rgn, rgnClip, RGN_AND); | |
| 1892 } | |
| 1893 if (status != NULLREGION) { | |
| 1894 DefWindowProc(wnd, WM_NCPAINT, (WPARAM)rgn.m_hRgn, 0); | |
| 1895 rgnClip.CombineRgn(rgn, RGN_DIFF); // exclude from further drawing | |
| 1896 } | |
| 1897 } | |
| 1898 } | |
| 1899 | |
| 1900 const auto colorLight = DarkMode::GetSysColor(COLOR_BTNHIGHLIGHT); | |
| 1901 const auto colorDark = DarkMode::GetSysColor(COLOR_BTNSHADOW); | |
| 1902 | |
| 1903 CWindowDC dc( wnd ); | |
| 1904 if (dc.IsNull()) { | |
| 1905 PFC_ASSERT(!"???"); | |
| 1906 return; | |
| 1907 } | |
| 1908 | |
| 1909 | |
| 1910 // Window DC has (0,0) in upper-left corner of our window (not screen, not client) | |
| 1911 // Turn rcWindow to (0,0), (winWidth, winHeight) | |
| 1912 CPoint origin = rcWindow.TopLeft(); | |
| 1913 rcWindow.OffsetRect(-origin); | |
| 1914 rcClient.OffsetRect(-origin); | |
| 1915 | |
| 1916 if (!rgnClip.IsNull()) { | |
| 1917 // rgnClip is still in screen coordinates, fix this here | |
| 1918 rgnClip.OffsetRgn(-origin); | |
| 1919 dc.SelectClipRgn(rgnClip); | |
| 1920 } | |
| 1921 | |
| 1922 // bottom | |
| 1923 dc.FillSolidRect(CRect(rcClient.left, rcClient.bottom, rcWindow.right, rcWindow.bottom), colorLight); | |
| 1924 // right | |
| 1925 dc.FillSolidRect(CRect(rcClient.right, rcWindow.top, rcWindow.right, rcClient.bottom), colorLight); | |
| 1926 // top | |
| 1927 dc.FillSolidRect(CRect(rcWindow.left, rcWindow.top, rcWindow.right, rcClient.top), colorDark); | |
| 1928 // left | |
| 1929 dc.FillSolidRect(CRect(rcWindow.left, rcClient.top, rcClient.left, rcWindow.bottom), colorDark); | |
| 1930 } | |
| 1931 } |
