Skip to content

Implement dark mode using win32-darkmodelib - #1203

Open
lostindark wants to merge 11 commits into
zufuliu:mainfrom
lostindark:main
Open

Implement dark mode using win32-darkmodelib#1203
lostindark wants to merge 11 commits into
zufuliu:mainfrom
lostindark:main

Conversation

@lostindark

Copy link
Copy Markdown
Contributor

Vendor ozone10/win32-darkmodelib source files directly in darkmodelib/ to provide native Windows dark mode support for Notepad4's UI chrome, tied to the existing Style Theme (Default/Dark) setting.

Features:

  • Dark title bar, menu bar, context menus (via system dark mode APIs)
  • Dark toolbar, rebar, status bar with darkmodelib subclassing
  • Dark tooltips and scrollbars
  • All dialogs automatically themed via WH_CALLWNDPROCRET hook on WM_INITDIALOG (no per-dialog-proc changes needed)
  • Toolbar Customize dialog fixed with temporary SetSysColors override
  • Custom color hierarchy for visual depth: Editor #1E1E1E / Dialogs #252526 / Toolbar #2D2D2D / Controls #383838
  • View colors for list boxes, list views, tree views, and headers
  • Theme switching via View > Style Theme toggles dark mode on/off
  • WM_SETTINGCHANGE handled for system dark/light mode changes

@zufuliu

zufuliu commented Apr 7, 2026

Copy link
Copy Markdown
Owner

Please wait a bit, looks win32-darkmodelib has too many codes (main branch 1M lines, old win32-darkmode only has hundred lines).

D:\Libs\win32-darkmodelib\src>perl d:\Dev\MSYS2\opt\bin\cloc.pl .
      27 text files.
      27 unique files.
       0 files ignored.

github.com/AlDanial/cloc v 2.08  T=0.08 s (322.6 files/s, 164987.9 lines/s)
---------------------------------------------------------------------------------------
Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
C++                                     10           1264           3192           6641
C/C++ Header                            15            366            589           1532
Windows Module Definition                1              0              0            183
Windows Resource File                    1              2              4             34
---------------------------------------------------------------------------------------
SUM:                                    27           1632           3785           8390
---------------------------------------------------------------------------------------

I don't understand why it's need to subcalss all common controls.

@lostindark

Copy link
Copy Markdown
Contributor Author

Please wait a bit, looks win32-darkmodelib has too many codes (main branch 1M lines, old win32-darkmode only has hundred lines).

D:\Libs\win32-darkmodelib\src>perl d:\Dev\MSYS2\opt\bin\cloc.pl .
      27 text files.
      27 unique files.
       0 files ignored.

github.com/AlDanial/cloc v 2.08  T=0.08 s (322.6 files/s, 164987.9 lines/s)
---------------------------------------------------------------------------------------
Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
C++                                     10           1264           3192           6641
C/C++ Header                            15            366            589           1532
Windows Module Definition                1              0              0            183
Windows Resource File                    1              2              4             34
---------------------------------------------------------------------------------------
SUM:                                    27           1632           3785           8390
---------------------------------------------------------------------------------------

I don't understand why it's need to subcalss all common controls.

Isn't the code 8k lines?

Those common controls won't render correctly in dark mode. Some part of the control will still rendering the way in light theme. E.g. buttons render as light, or some background render as light.

I didn't find a simple way to fix them without subclass them. In fact notepad++ also does the same way (subclass those controls).

@zufuliu

zufuliu commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Isn't the code 8k lines?

It matters at least for matepath:

D:\notepad4\notepad4\matepath\src>perl d:\Dev\MSYS2\opt\bin\cloc.pl .
      20 text files.
      18 unique files.
       2 files ignored.

github.com/AlDanial/cloc v 2.08  T=0.09 s (200.3 files/s, 132208.7 lines/s)
-----------------------------------------------------------------------------------
Language                         files          blank        comment           code
-----------------------------------------------------------------------------------
C++                                  5           1343           1129           7268
C/C++ Header                        10            174            213           1090
Windows Resource File                1             58             61            540
DOS Batch                            2              0              0              6
-----------------------------------------------------------------------------------
SUM:                                18           1575           1403           8904
-----------------------------------------------------------------------------------

@lostindark

Copy link
Copy Markdown
Contributor Author

Isn't the code 8k lines?

It matters at least for matepath:

D:\notepad4\notepad4\matepath\src>perl d:\Dev\MSYS2\opt\bin\cloc.pl .
      20 text files.
      18 unique files.
       2 files ignored.

github.com/AlDanial/cloc v 2.08  T=0.09 s (200.3 files/s, 132208.7 lines/s)
-----------------------------------------------------------------------------------
Language                         files          blank        comment           code
-----------------------------------------------------------------------------------
C++                                  5           1343           1129           7268
C/C++ Header                        10            174            213           1090
Windows Resource File                1             58             61            540
DOS Batch                            2              0              0              6
-----------------------------------------------------------------------------------
SUM:                                18           1575           1403           8904
-----------------------------------------------------------------------------------

matepath is a different project right? Haven't looked into support darkmode on it yet.

@Matteo-Nigro

Copy link
Copy Markdown
Contributor

This is a long overdue feature, it would be nice to be able to implement it. 💪🏻
If with the Dark mod of the toolbar the icons are unclear let me know and I will update them or review them 🖌️

@zufuliu zufuliu modified the milestones: v26.05, v26.07 May 5, 2026
@Casonctin

Copy link
Copy Markdown

After testing, I found that the scroll bar is white and the save changes dialog.

image

lostindark added a commit to lostindark/notepad4 that referenced this pull request May 20, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 21, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 21, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lostindark

Copy link
Copy Markdown
Contributor Author

After testing, I found that the scroll bar is white and the save changes dialog.

image

This should be fixed now.

@Casonctin

Copy link
Copy Markdown

After testing, I found that the scroll bar is white and the save changes dialog.
image

This should be fixed now.

Confirmed its fixed. Every dialog and popup is now in dark mode! Thank you!

@Matteo-Nigro

Copy link
Copy Markdown
Contributor

Great job, @lostindark it looks really nice now. Well done!
Just a quick note: the toolbars' button rollovers still use the old (light) style.
Unlike the menu bar, which uses the dark style for its rollovers.

Registrazione 2026-05-22 112219

@zufuliu zufuliu linked an issue May 23, 2026 that may be closed by this pull request
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 23, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 23, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 23, 2026
Address PR zufuliu#1203 feedback: scroll bars and the save-changes dialog were rendered in the light theme even when dark mode is active.

- Enable scroll bar fix in darkmodelib (mode 2: per-window) and rely on
  the existing enableDarkScrollBarForWindowAndChildren(hwndMain) call so
  the Scintilla edit window's scroll bars use the dark theme.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lostindark added a commit to lostindark/notepad4 that referenced this pull request May 23, 2026
Address PR zufuliu#1203 feedback so that scroll bars, message boxes, the file
open/save dialog, and toolbar button rollovers all render correctly when
dark mode is toggled on or off.

- Enable per-window scroll bar fix in darkmodelib (mode 2) so the
  Scintilla edit window's scroll bars use the dark theme via the
  existing enableDarkScrollBarForWindowAndChildren(hwndMain) call.
- Route Notepad4's MsgBox() through dmlib::darkMessageBoxW(), which
  renders message boxes as themed task dialogs when dark mode is on.
- Broadcast WM_THEMECHANGED to hwndMain and all descendants after a
  theme switch so themed controls (including scroll bars) re-open
  their theme handles and pick up the new light/dark style.
- Gate the OpenNcThemeData IAT hook on IsDarkModeActive() so scroll
  bars revert to the light theme after switching back from dark.
- Gate the WM_INITDIALOG thread hook on isExperimentalActive() and
  on the dialog's HINSTANCE matching this app's module, so system
  dialogs hosted in our process (common file open/save, etc.) are
  left untouched.
- Subclass the main window with setWindowNotifyCustomDrawSubclass so
  darkmodelib can custom-draw toolbar buttons (dark hover/checked
  states) via the toolbar's NM_CUSTOMDRAW notifications, which the
  rebar forwards up to its parent. This matches Notepad++'s approach.
- Fix two upstream darkmodelib namespacing bugs that surface when
  _DARKMODELIB_USE_SCROLLBAR_FIX is defined (DmlibHook.cpp uses
  ModuleHandle / LoadFn without the dmlib_module:: prefix).
- Add missing #include <string_view> in DmlibHook.cpp so MinGW
  GCC/Clang builds compile (upstream pulls this in via StdAfx.h
  which we do not vendor).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lostindark

Copy link
Copy Markdown
Contributor Author

Great job, @lostindark it looks really nice now. Well done! Just a quick note: the toolbars' button rollovers still use the old (light) style. Unlike the menu bar, which uses the dark style for its rollovers.

Registrazione 2026-05-22 112219

Fixed in latest commit.

@zufuliu

zufuliu commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Here needs further tuning:

  1. backgroud color for readonly edit box.
  2. size grip (scrollbar at right bottom) is missing on statusbar, and has a rect on dialog.
  3. open dark custom schemes dialog is slower than current.
  4. some dialog has custom WM_CTLCOLORSTATIC.
image image image

@zufuliu

zufuliu commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Thanks for the tweaks, I think we may need to implement our own MessageBox instead of using TaskDialog:
image
image

It has same appearance as our InfoBox, which I think it can be reused, handling extra MB_YESNOCANCEL button and hide the checkbox is easy, the hard part is dynamic resize message content (IDC_INFOBOXRECT and IDC_INFOBOXTEXT).

@zufuliu

zufuliu commented Jun 27, 2026

Copy link
Copy Markdown
Owner

I think we may need to implement our own MessageBox instead of using TaskDialog:

image image

zufuliu pushed a commit that referenced this pull request Jul 1, 2026
…ntrol

to make text rendered same as system MessageBox, PR #1203.
@zufuliu

zufuliu commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Here is possibly a simple way to fix back color for system MessageBox:
image

case WM_CTLCOLORSTATIC: {
	HDC hdc = AsPointer<HDC>(wParam);
	SetBkMode(hdc, TRANSPARENT);
	return AsInteger<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE));	
}
case WM_PAINT: {
	PAINTSTRUCT ps{};
	HDC hdc = BeginPaint(hwnd, &ps);
	RECT rc = {0, 26, 0, 25};
	MapDialogRect(hwnd, &rc);
	const int bottom = rc.top; // TODO: figure out bottom height
	printf("bottom: 26 => %d, 25 => %d\n", bottom, rc.bottom);
	rc = ps.rcPaint;
	rc.bottom -= bottom;
	::FillRect(hdc, &rc, GetSysColorBrush(COLOR_3DFACE));
	rc.top = rc.bottom;
	rc.bottom += bottom;
	::FillRect(hdc, &rc, GetSysColorBrush(COLOR_HIGHLIGHT));
	EndPaint(hwnd, &ps);
	return 0;			
}

the bottom height is 25 for our InfoBox template (74 - 49), seems is 26 for MessageBox, needs to test on Hi-DPI monitor (2K or more) and different system language/locale.

@zufuliu

zufuliu commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Using SetWindowsHookEx() will make application slow, it has impact on startup speed (enable log inside InitInstance()). an alternative approach I think is just setup dark dialog at end of WM_INITDIALOG (e.g. replace CenterDlgInParent() with DarkMode_ApplyToDialog()).

@zufuliu

zufuliu commented Jul 13, 2026

Copy link
Copy Markdown
Owner

const bool hasSizeGrip = !IsZoomed(hwndMain); can be used to check size grip on statusbar, no need to change its style.

@zufuliu

zufuliu commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Customize toolbar dialog can be changed to handle TBN_BEGINADJUST and TBN_ENDADJUST, which could also catch the dialog when double click on toolbar's whitespace area, e.g.:

case IDC_TOOLBAR:
	switch (pnmh->code) {
	case TBN_BEGINADJUST:
		DialogHook_Start(DialogRefData_CustomizeToolbar);
		break;
	case TBN_ENDADJUST:
		DialogHook_Stop();
		UpdateToolbar();
		break;

lostindark and others added 6 commits July 20, 2026 21:29
Vendor the win32-darkmodelib library
(https://github.com/ozone10/win32-darkmodelib) under darkmodelib/,
mirroring upstream's src/ + include/ layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Integrate darkmodelib into Notepad4: dark mode application logic
(DarkMode.cpp/.h and calls from Notepad4.cpp, Styles.cpp, Dialogs.cpp),
build wiring for the darkmodelib library, and the local patches to the
vendored library (disableDarkScrollBarForWindowAndChildren and the
light/dark toolbar theming color fixes).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darkmodelib's DmlibDpi.cpp dynamically loads per-monitor DPI v2 functions
(GetDpiForWindow, GetSystemMetricsForDpi, AdjustWindowRectExForDpi,
GetDpiForSystem) that Notepad4 already loads via Scintilla_LoadDpiForWindow().
This duplicated the loader and the associated dummy fallbacks.

Add a _DARKMODELIB_EXTERNAL_DPI opt-in macro: when defined, DmlibDpi.cpp's
implementation is compiled out and the host supplies the dmlib_dpi functions
the library actually uses. Notepad4 defines the macro (next to
_DARKMODELIB_NO_INI_CONFIG) and implements the seven referenced functions in
DarkMode.cpp, backed by its existing GetWindowDPI()/SystemMetricsForDpi()/
AdjustWindowRectForDpi()/g_uSystemDPI helpers. Only the two functions Notepad4
does not already load (SystemParametersInfoForDpi, OpenThemeDataForDpi) are
resolved here, with fallbacks for pre-Windows 10.

Standalone darkmodelib builds leave the macro undefined and are unchanged.
Verified building both the Win10 (x64) macro path and the Vista (x86)
function path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refine dark-mode handling for readonly edit controls, dialog size grips, InfoBox footer painting, Customize Toolbar, and large style trees. Remove the dark-mode edit focus underline for stability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the original status bar style and draw the grip only when the parent

window is not maximized, as requested in PR review feedback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 56a074ad-2620-4723-bc7a-d4c4d27d3d6b
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f46aad52-5daa-4cad-b776-cba02af08ed1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f46aad52-5daa-4cad-b776-cba02af08ed1
@zufuliu

zufuliu commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Dialog hook code may be changed to use WH_CALLWNDPROC or other types (MFC seems is using WH_CBT/CBT_CREATEWNDA) to reduce the impact on system dialog. following is my test code, not sure whether is it's correct or meaningful, or WM_INITDIALOG is whole handled when DialogHook_Hook() is called:
hook-test-0721.patch

lostindark and others added 4 commits July 24, 2026 21:27
Brings in zufuliu's dialog hook fix for PR zufuliu#1203 (7dd7e6d: WH_CALLWNDPROCRET -> WH_CBT + HCBT_CREATEWND) and aa47287.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 389d2c14-09b4-4bab-b751-ab8429f573eb
Upstream ozone10/win32-darkmodelib fa996472 (2026-07-26). Local patches
were replayed via a three-way merge and all preserved; the only conflict
was upstream changing `const LRESULT retValText` to `const auto` next to
our `_DARKMODELIB_CUSTOM_MEM` resize guard in paintStatusBar().

`dmlib_hook::hookSysColor()`/`unhookSysColor()` were reorganized upstream
into the `dmlib_hook::GetSysColor` hook group, so DarkMode.cpp now calls
`GetSysColor::hook()`/`GetSysColor::unhook()`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ffbe22e-365b-41a6-a0ec-e69b19c8823c
darkmodelib v0.75.0 added dark mode support for the common font and color
dialogs. It draws the parts those dialogs render themselves: the font
preview, the owner-drawn font combo boxes, the ChooseColor luminosity
slider, and the message boxes ChooseFont raises. All of that lives in the
library's internal ComDlgSubclass, which is installed by dmlib::HookDlgProc.

darkmodelib documents driving that hook via CF_ENABLEHOOK | CF_ENABLETEMPLATE
with a custom IDD_DARK_FONT_DIALOG template, because a hook alone makes
Windows fall back to a legacy font dialog template. That is a poor fit here:
the template would have to be duplicated into Notepad4.rc and all eleven
locale/*/Notepad4.rc files, and the font dialog would permanently lose the
automatic system translation of its captions and static texts.

Notepad4 already hooks dialog creation with WH_CBT + HCBT_CREATEWND, which
does not trigger the template downgrade. So route the common dialogs through
the existing DialogHook with a new DialogRefData_CommonDialog and call
dmlib::HookDlgProc() from DarkMode_ApplyDialogStyling(). The system's modern,
localized template is kept and no new resources are needed.

DialogRefData_CommonDialog sorts after DialogRefData_DefaultPosition, so the
dialogs keep placing themselves as before.

Also refresh the common dialog brushes on theme change; the luminosity slider
brush is derived from the darker text color, which DarkMode_SetCustomColors()
overrides.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ffbe22e-365b-41a6-a0ec-e69b19c8823c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dark Mode

4 participants