diff --git a/Makefile.in b/Makefile.in index 2359e53008..d9a5ffa452 100644 --- a/Makefile.in +++ b/Makefile.in @@ -3150,6 +3150,7 @@ COND_TOOLKIT_MSW_GUI_HDR = \ wx/msw/datectrl.h \ wx/msw/calctrl.h \ wx/generic/activityindicator.h \ + wx/msw/darkmode.h \ wx/msw/checklst.h \ wx/msw/fdrepdlg.h \ wx/msw/fontdlg.h \ diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl index 18a596c268..aad00cee62 100644 --- a/build/bakefiles/files.bkl +++ b/build/bakefiles/files.bkl @@ -1953,6 +1953,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file! wx/msw/datectrl.h wx/msw/calctrl.h wx/generic/activityindicator.h + wx/msw/darkmode.h diff --git a/build/cmake/files.cmake b/build/cmake/files.cmake index 514016d530..cb52dd35c7 100644 --- a/build/cmake/files.cmake +++ b/build/cmake/files.cmake @@ -1843,6 +1843,7 @@ set(MSW_HDR wx/msw/datetimectrl.h wx/msw/timectrl.h wx/generic/activityindicator.h + wx/msw/darkmode.h ) set(MSW_RSC diff --git a/build/files b/build/files index 6ad7a8fc82..34eb2756f0 100644 --- a/build/files +++ b/build/files @@ -1760,6 +1760,7 @@ MSW_HDR = wx/msw/ctrlsub.h wx/msw/cursor.h wx/msw/custombgwin.h + wx/msw/darkmode.h wx/msw/datectrl.h wx/msw/datetimectrl.h wx/msw/dc.h diff --git a/build/msw/wx_core.vcxproj b/build/msw/wx_core.vcxproj index 98ded50540..6629a45445 100644 --- a/build/msw/wx_core.vcxproj +++ b/build/msw/wx_core.vcxproj @@ -1517,6 +1517,7 @@ + diff --git a/build/msw/wx_core.vcxproj.filters b/build/msw/wx_core.vcxproj.filters index dfe24b3836..1fc9c41d52 100644 --- a/build/msw/wx_core.vcxproj.filters +++ b/build/msw/wx_core.vcxproj.filters @@ -1738,6 +1738,9 @@ MSW Headers + + MSW Headers + MSW Headers diff --git a/include/wx/msw/app.h b/include/wx/msw/app.h index 6b50e59386..56a77065f9 100644 --- a/include/wx/msw/app.h +++ b/include/wx/msw/app.h @@ -17,6 +17,7 @@ class WXDLLIMPEXP_FWD_CORE wxFrame; class WXDLLIMPEXP_FWD_CORE wxWindow; class WXDLLIMPEXP_FWD_CORE wxApp; +class WXDLLIMPEXP_FWD_CORE wxDarkModeSettings; class WXDLLIMPEXP_FWD_CORE wxKeyEvent; class WXDLLIMPEXP_FWD_BASE wxLog; @@ -38,12 +39,16 @@ public: virtual int GetPrintMode() const { return m_printMode; } // MSW-specific function to enable experimental dark mode support. + // + // If settings are specified, the function takes ownership of the pointer, + // otherwise the defaults are used. enum { DarkMode_Auto = 0, // Use dark mode if the system is using it. DarkMode_Always = 1 // Force using dark mode. }; - bool MSWEnableDarkMode(int flags = 0); + bool + MSWEnableDarkMode(int flags = 0, wxDarkModeSettings* settings = nullptr); // implementation only void OnIdle(wxIdleEvent& event); diff --git a/include/wx/msw/darkmode.h b/include/wx/msw/darkmode.h new file mode 100644 index 0000000000..4cce68d7c9 --- /dev/null +++ b/include/wx/msw/darkmode.h @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/msw/darkmode.h +// Purpose: MSW-specific header with dark mode related declarations. +// Author: Vadim Zeitlin +// Created: 2023-02-19 +// Copyright: (c) 2023 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_MSW_DARKMODE_H_ +#define _WX_MSW_DARKMODE_H_ + +#include "wx/settings.h" + +// ---------------------------------------------------------------------------- +// wxDarkModeSettings: allows to customize some of dark mode settings +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_CORE wxDarkModeSettings +{ +public: + wxDarkModeSettings() = default; + virtual ~wxDarkModeSettings(); + + // Get the colour to use for the given system colour when dark mode is on. + virtual wxColour GetColour(wxSystemColour index); + +private: + wxDECLARE_NO_COPY_CLASS(wxDarkModeSettings); +}; + +#endif // _WX_MSW_DARKMODE_H_ diff --git a/interface/wx/app.h b/interface/wx/app.h index c4aea965db..7d1bb20651 100644 --- a/interface/wx/app.h +++ b/interface/wx/app.h @@ -1212,6 +1212,8 @@ public: dark mode for the application, even if the system doesn't use the dark mode by default. Otherwise dark mode is only used if it is the default mode for the applications on the current system. + @param settings If specified, allows to customize dark mode appearance. + Please see wxDarkModeSettings documentation for more information. @return @true if dark mode support was enabled, @false if it couldn't be done, most likely because the system doesn't support dark mode. @@ -1220,7 +1222,8 @@ public: @since 3.3.0 */ - bool MSWEnableDarkMode(int flags = 0); + bool + MSWEnableDarkMode(int flags = 0, wxDarkModeSettings* settings = nullptr); //@} }; diff --git a/interface/wx/msw/darkmode.h b/interface/wx/msw/darkmode.h new file mode 100644 index 0000000000..1a9fd41c4c --- /dev/null +++ b/interface/wx/msw/darkmode.h @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wx/msw/darkmode.h +// Purpose: Documentation for MSW-specific dark mode functionality. +// Author: Vadim Zeitlin +// Created: 2023-02-19 +// Copyright: (c) 2023 Vadim Zeitlin +// Licence: wxWindows Licence +///////////////////////////////////////////////////////////////////////////// + +/** + Allows to customize some of the settings used in MSW dark mode. + + An object of this class may be passed to wxApp::MSWEnableDarkMode() to + customize some aspects of the dark mode when it is used under MSW systems. + + For example, to customize the background colour to use a reddish black + instead of normal black used by default, you could do the following: + @code + class MySettings : public wxDarkModeSettings + { + public: + wxColour GetColour(wxSystemColour index) override + { + switch ( index ) + { + case wxSYS_COLOUR_ACTIVECAPTION: + case wxSYS_COLOUR_APPWORKSPACE: + case wxSYS_COLOUR_INFOBK: + case wxSYS_COLOUR_LISTBOX: + case wxSYS_COLOUR_WINDOW: + case wxSYS_COLOUR_BTNFACE: + // Default colour used here is 0x202020. + return wxColour(0x402020); + } + + return wxDarkModeSettings::GetColour(index); + } + }; + + wxTheApp->MSWEnableDarkMode(wxApp::DarkMode_Always, new MySettings()); + @endcode + + @since 3.3.0 + */ +class wxDarkModeSettings +{ +public: + /** + Default constructor does nothing. + */ + wxDarkModeSettings() = default; + + /** + Get the colour to use for the given system colour when dark mode is on. + + The base class version of this function returns the colours commonly + used in dark mode. As the rest of dark mode support, their exact values + are not documented and are subject to change in the future Windows or + wxWidgets versions. + */ + virtual wxColour GetColour(wxSystemColour index); +}; diff --git a/src/msw/darkmode.cpp b/src/msw/darkmode.cpp index e7a0de778a..71b2aeb151 100644 --- a/src/msw/darkmode.cpp +++ b/src/msw/darkmode.cpp @@ -44,11 +44,14 @@ #include "wx/dynlib.h" #include "wx/module.h" +#include "wx/msw/darkmode.h" #include "wx/msw/dc.h" #include "wx/msw/uxtheme.h" #include "wx/msw/private/darkmode.h" +#include + static const char* TRACE_DARKMODE = "msw-darkmode"; #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -168,10 +171,25 @@ public: virtual bool OnInit() override { return true; } virtual void OnExit() override { + ms_settings.reset(); + ms_pfnDwmSetWindowAttribute = (DwmSetWindowAttribute_t)-1; ms_dllDWM.Unload(); } + // Takes ownership of the provided pointer. + static void SetSettings(wxDarkModeSettings* settings) + { + ms_settings.reset(settings); + } + + // Returns the currently used settings: may only be called when the dark + // mode is on. + static wxDarkModeSettings& GetSettings() + { + return *ms_settings; + } + static DwmSetWindowAttribute_t GetDwmSetWindowAttribute() { if ( ms_pfnDwmSetWindowAttribute == (DwmSetWindowAttribute_t)-1 ) @@ -187,12 +205,15 @@ private: static wxDynamicLibrary ms_dllDWM; static DwmSetWindowAttribute_t ms_pfnDwmSetWindowAttribute; + static std::unique_ptr ms_settings; + wxDECLARE_DYNAMIC_CLASS(wxDarkModeModule); }; wxIMPLEMENT_DYNAMIC_CLASS(wxDarkModeModule, wxModule); wxDynamicLibrary wxDarkModeModule::ms_dllDWM; +std::unique_ptr wxDarkModeModule::ms_settings; DwmSetWindowAttribute_t wxDarkModeModule::ms_pfnDwmSetWindowAttribute = (DwmSetWindowAttribute_t)-1; @@ -201,7 +222,7 @@ wxDarkModeModule::ms_pfnDwmSetWindowAttribute = (DwmSetWindowAttribute_t)-1; // Public API // ---------------------------------------------------------------------------- -bool wxApp::MSWEnableDarkMode(int flags) +bool wxApp::MSWEnableDarkMode(int flags, wxDarkModeSettings* settings) { if ( !wxMSWImpl::InitDarkMode() ) return false; @@ -220,61 +241,23 @@ bool wxApp::MSWEnableDarkMode(int flags) gs_appMode = mode; + // Set up the settings to use, allocating a default one if none specified. + if ( !settings ) + settings = new wxDarkModeSettings(); + + wxDarkModeModule::SetSettings(settings); + return true; } // ---------------------------------------------------------------------------- -// Supporting functions for the rest of wxMSW code +// Default wxDarkModeSettings implementation // ---------------------------------------------------------------------------- -namespace wxMSWDarkMode -{ +// Implemented here to ensure that it's generated inside the DLL. +wxDarkModeSettings::~wxDarkModeSettings() = default; -bool IsActive() -{ - return wxMSWImpl::ShouldUseDarkMode(); -} - -void EnableForTLW(HWND hwnd) -{ - // Nothing to do, dark mode support not enabled or dark mode is not used. - if ( !wxMSWImpl::ShouldUseDarkMode() ) - return; - - BOOL useDarkMode = TRUE; - HRESULT hr = wxDarkModeModule::GetDwmSetWindowAttribute() - ( - hwnd, - DWMWA_USE_IMMERSIVE_DARK_MODE, - &useDarkMode, - sizeof(useDarkMode) - ); - if ( FAILED(hr) ) - wxLogApiError("DwmSetWindowAttribute(USE_IMMERSIVE_DARK_MODE)", hr); - - wxMSWImpl::AllowDarkModeForWindow(hwnd, true); -} - -void AllowForWindow(HWND hwnd, const wchar_t* themeName, const wchar_t* themeId) -{ - if ( !wxMSWImpl::ShouldUseDarkMode() ) - return; - - if ( wxMSWImpl::AllowDarkModeForWindow(hwnd, true) ) - wxLogTrace(TRACE_DARKMODE, "Allow dark mode for %p failed", hwnd); - - if ( themeName || themeId ) - { - HRESULT hr = ::SetWindowTheme(hwnd, themeName, themeId); - if ( FAILED(hr) ) - { - wxLogApiError(wxString::Format("SetWindowTheme(%p, %s, %s)", - hwnd, themeName, themeId), hr); - } - } -} - -wxColour GetColour(wxSystemColour index) +wxColour wxDarkModeSettings::GetColour(wxSystemColour index) { // This is not great at all, but better than using light mode colours that // are not appropriate for the dark mode. @@ -347,6 +330,62 @@ wxColour GetColour(wxSystemColour index) return wxColour(); } +// ---------------------------------------------------------------------------- +// Supporting functions for the rest of wxMSW code +// ---------------------------------------------------------------------------- + +namespace wxMSWDarkMode +{ + +bool IsActive() +{ + return wxMSWImpl::ShouldUseDarkMode(); +} + +void EnableForTLW(HWND hwnd) +{ + // Nothing to do, dark mode support not enabled or dark mode is not used. + if ( !wxMSWImpl::ShouldUseDarkMode() ) + return; + + BOOL useDarkMode = TRUE; + HRESULT hr = wxDarkModeModule::GetDwmSetWindowAttribute() + ( + hwnd, + DWMWA_USE_IMMERSIVE_DARK_MODE, + &useDarkMode, + sizeof(useDarkMode) + ); + if ( FAILED(hr) ) + wxLogApiError("DwmSetWindowAttribute(USE_IMMERSIVE_DARK_MODE)", hr); + + wxMSWImpl::AllowDarkModeForWindow(hwnd, true); +} + +void AllowForWindow(HWND hwnd, const wchar_t* themeName, const wchar_t* themeId) +{ + if ( !wxMSWImpl::ShouldUseDarkMode() ) + return; + + if ( wxMSWImpl::AllowDarkModeForWindow(hwnd, true) ) + wxLogTrace(TRACE_DARKMODE, "Allow dark mode for %p failed", hwnd); + + if ( themeName || themeId ) + { + HRESULT hr = ::SetWindowTheme(hwnd, themeName, themeId); + if ( FAILED(hr) ) + { + wxLogApiError(wxString::Format("SetWindowTheme(%p, %s, %s)", + hwnd, themeName, themeId), hr); + } + } +} + +wxColour GetColour(wxSystemColour index) +{ + return wxDarkModeModule::GetSettings().GetColour(index); +} + HBRUSH GetBackgroundBrush() { wxBrush* const brush = @@ -630,7 +669,9 @@ HandleMenuMessage(WXLRESULT* result, #else // !wxUSE_DARK_MODE -bool wxApp::MSWEnableDarkMode(int WXUNUSED(flags)) +bool +wxApp::MSWEnableDarkMode(int WXUNUSED(flags), + wxDarkModeSettings* WXUNUSED(settings)) { return false; }