Merge branch 'msw-dark-mode'
Add experimental support for dark mode for wxMSW. See #23028.
This commit is contained in:
commit
208142c14a
62 changed files with 1946 additions and 306 deletions
16
Makefile.in
16
Makefile.in
|
|
@ -5291,6 +5291,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS = \
|
|||
monodll_timectrl.o \
|
||||
monodll_datecontrols.o \
|
||||
monodll_generic_activityindicator.o \
|
||||
monodll_darkmode.o \
|
||||
monodll_msw_checklst.o \
|
||||
monodll_msw_fdrepdlg.o \
|
||||
monodll_msw_fontdlg.o
|
||||
|
|
@ -7046,6 +7047,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_1 = \
|
|||
monolib_timectrl.o \
|
||||
monolib_datecontrols.o \
|
||||
monolib_generic_activityindicator.o \
|
||||
monolib_darkmode.o \
|
||||
monolib_msw_checklst.o \
|
||||
monolib_msw_fdrepdlg.o \
|
||||
monolib_msw_fontdlg.o
|
||||
|
|
@ -8955,6 +8957,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_2 = \
|
|||
coredll_timectrl.o \
|
||||
coredll_datecontrols.o \
|
||||
coredll_generic_activityindicator.o \
|
||||
coredll_darkmode.o \
|
||||
coredll_msw_checklst.o \
|
||||
coredll_msw_fdrepdlg.o \
|
||||
coredll_msw_fontdlg.o
|
||||
|
|
@ -10440,6 +10443,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_3 = \
|
|||
corelib_timectrl.o \
|
||||
corelib_datecontrols.o \
|
||||
corelib_generic_activityindicator.o \
|
||||
corelib_darkmode.o \
|
||||
corelib_msw_checklst.o \
|
||||
corelib_msw_fdrepdlg.o \
|
||||
corelib_msw_fontdlg.o
|
||||
|
|
@ -15253,6 +15257,9 @@ monodll_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(MONODLL_ODEP)
|
|||
monodll_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(MONODLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
|
||||
|
||||
monodll_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(MONODLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
|
||||
|
||||
monodll_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(MONODLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
|
||||
|
||||
|
|
@ -20017,6 +20024,9 @@ monolib_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(MONOLIB_ODEP)
|
|||
monolib_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(MONOLIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
|
||||
|
||||
monolib_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(MONOLIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
|
||||
|
||||
monolib_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(MONOLIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
|
||||
|
||||
|
|
@ -25465,6 +25475,9 @@ coredll_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(COREDLL_ODEP)
|
|||
coredll_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(COREDLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
|
||||
|
||||
coredll_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(COREDLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
|
||||
|
||||
coredll_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(COREDLL_ODEP)
|
||||
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
|
||||
|
||||
|
|
@ -29197,6 +29210,9 @@ corelib_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(CORELIB_ODEP)
|
|||
corelib_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(CORELIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
|
||||
|
||||
corelib_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(CORELIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
|
||||
|
||||
corelib_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(CORELIB_ODEP)
|
||||
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -1847,6 +1847,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
|
|||
src/msw/timectrl.cpp
|
||||
src/msw/datecontrols.cpp
|
||||
src/generic/activityindicator.cpp
|
||||
src/msw/darkmode.cpp
|
||||
</set>
|
||||
<set var="MSW_HDR" hints="files">
|
||||
wx/generic/clrpickerg.h
|
||||
|
|
|
|||
|
|
@ -1736,6 +1736,7 @@ set(MSW_SRC
|
|||
src/msw/datetimectrl.cpp
|
||||
src/msw/hyperlink.cpp
|
||||
src/generic/activityindicator.cpp
|
||||
src/msw/darkmode.cpp
|
||||
)
|
||||
|
||||
set(MSW_HDR
|
||||
|
|
|
|||
|
|
@ -1685,6 +1685,7 @@ MSW_SRC =
|
|||
src/msw/commandlinkbutton.cpp
|
||||
src/msw/control.cpp
|
||||
src/msw/customdraw.cpp
|
||||
src/msw/darkmode.cpp
|
||||
src/msw/datecontrols.cpp
|
||||
src/msw/datectrl.cpp
|
||||
src/msw/datetimectrl.cpp
|
||||
|
|
|
|||
|
|
@ -1997,6 +1997,7 @@ ____CORE_SRC_FILENAMES_OBJECTS = \
|
|||
$(OBJS)\monodll_timectrl.o \
|
||||
$(OBJS)\monodll_datecontrols.o \
|
||||
$(OBJS)\monodll_activityindicator.o \
|
||||
$(OBJS)\monodll_darkmode.o \
|
||||
$(OBJS)\monodll_msw_checklst.o \
|
||||
$(OBJS)\monodll_msw_fdrepdlg.o \
|
||||
$(OBJS)\monodll_fontdlg.o \
|
||||
|
|
@ -2845,6 +2846,7 @@ ____CORE_SRC_FILENAMES_1_OBJECTS = \
|
|||
$(OBJS)\monolib_timectrl.o \
|
||||
$(OBJS)\monolib_datecontrols.o \
|
||||
$(OBJS)\monolib_activityindicator.o \
|
||||
$(OBJS)\monolib_darkmode.o \
|
||||
$(OBJS)\monolib_msw_checklst.o \
|
||||
$(OBJS)\monolib_msw_fdrepdlg.o \
|
||||
$(OBJS)\monolib_fontdlg.o \
|
||||
|
|
@ -3575,6 +3577,7 @@ ____CORE_SRC_FILENAMES_2_OBJECTS = \
|
|||
$(OBJS)\coredll_timectrl.o \
|
||||
$(OBJS)\coredll_datecontrols.o \
|
||||
$(OBJS)\coredll_activityindicator.o \
|
||||
$(OBJS)\coredll_darkmode.o \
|
||||
$(OBJS)\coredll_msw_checklst.o \
|
||||
$(OBJS)\coredll_msw_fdrepdlg.o \
|
||||
$(OBJS)\coredll_fontdlg.o \
|
||||
|
|
@ -4262,6 +4265,7 @@ ____CORE_SRC_FILENAMES_3_OBJECTS = \
|
|||
$(OBJS)\corelib_timectrl.o \
|
||||
$(OBJS)\corelib_datecontrols.o \
|
||||
$(OBJS)\corelib_activityindicator.o \
|
||||
$(OBJS)\corelib_darkmode.o \
|
||||
$(OBJS)\corelib_msw_checklst.o \
|
||||
$(OBJS)\corelib_msw_fdrepdlg.o \
|
||||
$(OBJS)\corelib_fontdlg.o \
|
||||
|
|
@ -7507,6 +7511,9 @@ $(OBJS)\monodll_timectrl.o: ../../src/msw/timectrl.cpp
|
|||
$(OBJS)\monodll_datecontrols.o: ../../src/msw/datecontrols.cpp
|
||||
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\monodll_darkmode.o: ../../src/msw/darkmode.cpp
|
||||
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\monodll_msw_checklst.o: ../../src/msw/checklst.cpp
|
||||
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
|
|
@ -10094,6 +10101,9 @@ $(OBJS)\monolib_timectrl.o: ../../src/msw/timectrl.cpp
|
|||
$(OBJS)\monolib_datecontrols.o: ../../src/msw/datecontrols.cpp
|
||||
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\monolib_darkmode.o: ../../src/msw/darkmode.cpp
|
||||
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\monolib_msw_checklst.o: ../../src/msw/checklst.cpp
|
||||
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
|
|
@ -13107,6 +13117,9 @@ $(OBJS)\coredll_timectrl.o: ../../src/msw/timectrl.cpp
|
|||
$(OBJS)\coredll_datecontrols.o: ../../src/msw/datecontrols.cpp
|
||||
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\coredll_darkmode.o: ../../src/msw/darkmode.cpp
|
||||
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\coredll_msw_checklst.o: ../../src/msw/checklst.cpp
|
||||
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
|
|
@ -14857,6 +14870,9 @@ $(OBJS)\corelib_timectrl.o: ../../src/msw/timectrl.cpp
|
|||
$(OBJS)\corelib_datecontrols.o: ../../src/msw/datecontrols.cpp
|
||||
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\corelib_darkmode.o: ../../src/msw/darkmode.cpp
|
||||
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
$(OBJS)\corelib_msw_checklst.o: ../../src/msw/checklst.cpp
|
||||
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<
|
||||
|
||||
|
|
|
|||
|
|
@ -2314,6 +2314,7 @@ ____CORE_SRC_FILENAMES_OBJECTS = \
|
|||
$(OBJS)\monodll_timectrl.obj \
|
||||
$(OBJS)\monodll_datecontrols.obj \
|
||||
$(OBJS)\monodll_activityindicator.obj \
|
||||
$(OBJS)\monodll_darkmode.obj \
|
||||
$(OBJS)\monodll_msw_checklst.obj \
|
||||
$(OBJS)\monodll_msw_fdrepdlg.obj \
|
||||
$(OBJS)\monodll_fontdlg.obj \
|
||||
|
|
@ -3162,6 +3163,7 @@ ____CORE_SRC_FILENAMES_1_OBJECTS = \
|
|||
$(OBJS)\monolib_timectrl.obj \
|
||||
$(OBJS)\monolib_datecontrols.obj \
|
||||
$(OBJS)\monolib_activityindicator.obj \
|
||||
$(OBJS)\monolib_darkmode.obj \
|
||||
$(OBJS)\monolib_msw_checklst.obj \
|
||||
$(OBJS)\monolib_msw_fdrepdlg.obj \
|
||||
$(OBJS)\monolib_fontdlg.obj \
|
||||
|
|
@ -3942,6 +3944,7 @@ ____CORE_SRC_FILENAMES_2_OBJECTS = \
|
|||
$(OBJS)\coredll_timectrl.obj \
|
||||
$(OBJS)\coredll_datecontrols.obj \
|
||||
$(OBJS)\coredll_activityindicator.obj \
|
||||
$(OBJS)\coredll_darkmode.obj \
|
||||
$(OBJS)\coredll_msw_checklst.obj \
|
||||
$(OBJS)\coredll_msw_fdrepdlg.obj \
|
||||
$(OBJS)\coredll_fontdlg.obj \
|
||||
|
|
@ -4627,6 +4630,7 @@ ____CORE_SRC_FILENAMES_3_OBJECTS = \
|
|||
$(OBJS)\corelib_timectrl.obj \
|
||||
$(OBJS)\corelib_datecontrols.obj \
|
||||
$(OBJS)\corelib_activityindicator.obj \
|
||||
$(OBJS)\corelib_darkmode.obj \
|
||||
$(OBJS)\corelib_msw_checklst.obj \
|
||||
$(OBJS)\corelib_msw_fdrepdlg.obj \
|
||||
$(OBJS)\corelib_fontdlg.obj \
|
||||
|
|
@ -7952,6 +7956,9 @@ $(OBJS)\monodll_timectrl.obj: ..\..\src\msw\timectrl.cpp
|
|||
$(OBJS)\monodll_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
|
||||
|
||||
$(OBJS)\monodll_darkmode.obj: ..\..\src\msw\darkmode.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\darkmode.cpp
|
||||
|
||||
$(OBJS)\monodll_msw_checklst.obj: ..\..\src\msw\checklst.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\checklst.cpp
|
||||
|
||||
|
|
@ -10539,6 +10546,9 @@ $(OBJS)\monolib_timectrl.obj: ..\..\src\msw\timectrl.cpp
|
|||
$(OBJS)\monolib_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
|
||||
|
||||
$(OBJS)\monolib_darkmode.obj: ..\..\src\msw\darkmode.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\darkmode.cpp
|
||||
|
||||
$(OBJS)\monolib_msw_checklst.obj: ..\..\src\msw\checklst.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\checklst.cpp
|
||||
|
||||
|
|
@ -13552,6 +13562,9 @@ $(OBJS)\coredll_timectrl.obj: ..\..\src\msw\timectrl.cpp
|
|||
$(OBJS)\coredll_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
|
||||
|
||||
$(OBJS)\coredll_darkmode.obj: ..\..\src\msw\darkmode.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\darkmode.cpp
|
||||
|
||||
$(OBJS)\coredll_msw_checklst.obj: ..\..\src\msw\checklst.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\checklst.cpp
|
||||
|
||||
|
|
@ -15302,6 +15315,9 @@ $(OBJS)\corelib_timectrl.obj: ..\..\src\msw\timectrl.cpp
|
|||
$(OBJS)\corelib_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
|
||||
|
||||
$(OBJS)\corelib_darkmode.obj: ..\..\src\msw\darkmode.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\darkmode.cpp
|
||||
|
||||
$(OBJS)\corelib_msw_checklst.obj: ..\..\src\msw\checklst.cpp
|
||||
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\checklst.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -613,6 +613,7 @@
|
|||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)msw_%(Filename).obj</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='DLL Release|x64'">$(IntDir)msw_%(Filename).obj</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\msw\darkmode.cpp" />
|
||||
<ClCompile Include="..\..\src\msw\graphicsd2d.cpp" />
|
||||
<ClCompile Include="..\..\src\msw\ole\access.cpp" />
|
||||
<ClCompile Include="..\..\src\msw\ole\activex.cpp" />
|
||||
|
|
|
|||
|
|
@ -1074,6 +1074,9 @@
|
|||
<ClCompile Include="..\..\src\xrc\xmlreshandler.cpp">
|
||||
<Filter>Common Sources</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\msw\darkmode.cpp">
|
||||
<Filter>MSW Sources</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\..\src\msw\version.rc">
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ Changes in behaviour not resulting in compilation errors
|
|||
- Calling wxListCtrl::EditLabel() now asserts if the control doesn't have
|
||||
wxLC_EDIT_LABELS style: previously this silently didn't work in wxMSW.
|
||||
|
||||
- wxSystemAppearance::IsDark() now returns whether this application uses dark
|
||||
mode under MSW, use the new AreAppsDark() or IsSystemDark() to check if the
|
||||
other applications or the system are using dark mode.
|
||||
|
||||
|
||||
Changes in behaviour which may result in build errors
|
||||
-----------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ public:
|
|||
virtual void SetPrintMode(int mode) override { m_printMode = mode; }
|
||||
virtual int GetPrintMode() const { return m_printMode; }
|
||||
|
||||
// MSW-specific function to enable experimental dark mode support.
|
||||
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);
|
||||
|
||||
// implementation only
|
||||
void OnIdle(wxIdleEvent& event);
|
||||
void OnEndSession(wxCloseEvent& event);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public:
|
|||
protected:
|
||||
virtual wxSize DoGetBestClientSize() const override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
virtual void DoSet3StateValue(wxCheckBoxState value) override;
|
||||
virtual wxCheckBoxState DoGet3StateValue() const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ protected:
|
|||
int sizeFlags = wxSIZE_AUTO) override;
|
||||
virtual wxSize DoGetSizeFromTextSize(int xlen, int ylen = -1) const override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
// Show or hide the popup part of the control.
|
||||
void MSWDoPopupOrDismiss(bool show);
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,34 @@ protected:
|
|||
virtual wxWindow *MSWFindItem(long id, WXHWND hWnd) const override;
|
||||
|
||||
|
||||
// Struct used for MSWGetDarkModeSupport() below.
|
||||
struct MSWDarkModeSupport
|
||||
{
|
||||
// The name of the theme to use (also called "app name").
|
||||
const wchar_t* themeName = nullptr;
|
||||
|
||||
// The theme IDs to use. If neither this field nor the theme name is
|
||||
// set, no theme is applied to the window.
|
||||
const wchar_t* themeId = nullptr;
|
||||
|
||||
// For some controls we need to set the foreground explicitly, even if
|
||||
// they have some support for the dark theme.
|
||||
bool setForeground = false;
|
||||
};
|
||||
|
||||
// Called after creating the control to enable dark mode support if needed.
|
||||
//
|
||||
// If this function returns true, wxControl allows using dark mode for the
|
||||
// window and set its theme to the one specified by MSWDarkModeSupport
|
||||
// fields.
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const;
|
||||
|
||||
// Return the message that can be used to retrieve the tooltip window used
|
||||
// by a native control. If this message is non-zero and sending it returns
|
||||
// a valid HWND, the dark theme is also applied to it, if appropriate.
|
||||
virtual int MSWGetToolTipMessage() const { return 0; }
|
||||
|
||||
|
||||
// for controls like radiobuttons which are really composite this array
|
||||
// holds the ids (not HWNDs!) of the sub controls
|
||||
wxArrayLong m_subControls;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#include "wx/vector.h"
|
||||
|
||||
class wxMSWListItemData;
|
||||
class wxMSWListHeaderCustomDraw;
|
||||
class wxMSWHeaderCtrlCustomDraw;
|
||||
|
||||
// define this symbol to indicate the availability of SetColumnsOrder() and
|
||||
// related functions
|
||||
|
|
@ -366,10 +366,7 @@ public:
|
|||
|
||||
virtual bool ShouldInheritColours() const override { return false; }
|
||||
|
||||
virtual wxVisualAttributes GetDefaultAttributes() const override
|
||||
{
|
||||
return GetClassDefaultAttributes(GetWindowVariant());
|
||||
}
|
||||
virtual wxVisualAttributes GetDefaultAttributes() const override;
|
||||
|
||||
static wxVisualAttributes
|
||||
GetClassDefaultAttributes(wxWindowVariant variant = wxWINDOW_VARIANT_NORMAL);
|
||||
|
|
@ -401,6 +398,10 @@ protected:
|
|||
|
||||
virtual void MSWAfterReparent() override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
virtual int MSWGetToolTipMessage() const override;
|
||||
|
||||
void OnDPIChanged(wxDPIChangedEvent& event);
|
||||
|
||||
wxSize MSWGetBestViewRect(int x, int y) const;
|
||||
|
|
@ -443,6 +444,9 @@ private:
|
|||
// UpdateStyle()), only should be called if InReportView()
|
||||
void MSWSetExListStyles();
|
||||
|
||||
// Initialize the header control if it exists.
|
||||
void MSWInitHeader();
|
||||
|
||||
// initialize the (already created) m_textCtrl with the associated HWND
|
||||
void InitEditControl(WXHWND hWnd);
|
||||
|
||||
|
|
@ -460,7 +464,7 @@ private:
|
|||
void DrawSortArrow();
|
||||
|
||||
// Object using for header custom drawing if necessary, may be null.
|
||||
wxMSWListHeaderCustomDraw* m_headerCustomDraw;
|
||||
wxMSWHeaderCtrlCustomDraw* m_headerCustomDraw;
|
||||
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(wxListCtrl);
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ protected:
|
|||
// common part of all ctors
|
||||
void Init();
|
||||
|
||||
virtual int MSWGetToolTipMessage() const override;
|
||||
|
||||
// hides the currently shown page and shows the given one (if not -1) and
|
||||
// updates m_selection accordingly
|
||||
void UpdateSelection(int selNew);
|
||||
|
|
@ -181,6 +183,10 @@ protected:
|
|||
void OnEraseBackground(wxEraseEvent& event);
|
||||
void OnPaint(wxPaintEvent& event);
|
||||
|
||||
// Paint the notebook ourselves on the provided DC.
|
||||
void MSWNotebookPaint(wxDC& dc);
|
||||
|
||||
|
||||
// true if we have already subclassed our updown control
|
||||
bool m_hasSubclassedUpdown;
|
||||
|
||||
|
|
|
|||
|
|
@ -386,6 +386,21 @@ inline RECT wxGetClientRect(HWND hwnd)
|
|||
return rect;
|
||||
}
|
||||
|
||||
// Call MapWindowPoints() on a RECT: because a RECT is (intentionally) laid out
|
||||
// as 2 consecutive POINTs, the cast below is valid but we still prefer to hide
|
||||
// it in this function instead of writing it out in the rest of the code.
|
||||
inline void wxMapWindowPoints(HWND hwndFrom, HWND hwndTo, RECT* rc)
|
||||
{
|
||||
::MapWindowPoints(hwndFrom, hwndTo, reinterpret_cast<POINT *>(rc), 2);
|
||||
}
|
||||
|
||||
// For consistency also provide an overload taking a POINT, even if this one is
|
||||
// even more trivial.
|
||||
inline void wxMapWindowPoints(HWND hwndFrom, HWND hwndTo, POINT* pt)
|
||||
{
|
||||
::MapWindowPoints(hwndFrom, hwndTo, pt, 1);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// small helper classes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -421,38 +436,46 @@ private:
|
|||
|
||||
#endif // __WXMSW__
|
||||
|
||||
// create an instance of this class and use it as the HDC for screen, will
|
||||
// automatically release the DC going out of scope
|
||||
class ScreenHDC
|
||||
// RAII helper for releasing an HDC in its dtor.
|
||||
class AutoHDC
|
||||
{
|
||||
public:
|
||||
ScreenHDC() { m_hdc = ::GetDC(nullptr); }
|
||||
~ScreenHDC() { ::ReleaseDC(nullptr, m_hdc); }
|
||||
~AutoHDC() { if ( m_hdc ) { ::ReleaseDC(m_hwnd, m_hdc); } }
|
||||
|
||||
operator HDC() const { return m_hdc; }
|
||||
|
||||
protected:
|
||||
AutoHDC(HWND hwnd, HDC hdc) : m_hwnd(hwnd), m_hdc(hdc) { }
|
||||
|
||||
private:
|
||||
HWND m_hwnd;
|
||||
HDC m_hdc;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(ScreenHDC);
|
||||
wxDECLARE_NO_COPY_CLASS(AutoHDC);
|
||||
};
|
||||
|
||||
// the same as ScreenHDC but for window DCs (and if HWND is null, then exactly
|
||||
// the same as it)
|
||||
class WindowHDC
|
||||
// create an instance of this class and use it as the HDC for screen, will
|
||||
// automatically release the DC going out of scope
|
||||
class ScreenHDC : public AutoHDC
|
||||
{
|
||||
public:
|
||||
WindowHDC() : m_hwnd(nullptr), m_hdc(nullptr) { }
|
||||
WindowHDC(HWND hwnd) { m_hdc = ::GetDC(m_hwnd = hwnd); }
|
||||
~WindowHDC() { if ( m_hdc ) { ::ReleaseDC(m_hwnd, m_hdc); } }
|
||||
ScreenHDC() : AutoHDC(nullptr, ::GetDC(nullptr)) { }
|
||||
};
|
||||
|
||||
operator HDC() const { return m_hdc; }
|
||||
// the same as ScreenHDC but for client part of the window (if HWND is null,
|
||||
// then it's exactly the same as ScreenHDC)
|
||||
class ClientHDC : public AutoHDC
|
||||
{
|
||||
public:
|
||||
ClientHDC() : AutoHDC(nullptr, nullptr) { }
|
||||
explicit ClientHDC(HWND hwnd) : AutoHDC(hwnd, ::GetDC(hwnd)) { }
|
||||
};
|
||||
|
||||
private:
|
||||
HWND m_hwnd;
|
||||
HDC m_hdc;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(WindowHDC);
|
||||
// same as ClientHDC but includes the non-client part of the window
|
||||
class WindowHDC : public AutoHDC
|
||||
{
|
||||
public:
|
||||
explicit WindowHDC(HWND hwnd) : AutoHDC(hwnd, ::GetWindowDC(hwnd)) { }
|
||||
};
|
||||
|
||||
// the same as ScreenHDC but for memory DCs: creates the HDC compatible with
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "wx/itemattr.h"
|
||||
|
||||
#include "wx/msw/uxtheme.h"
|
||||
#include "wx/msw/wrapcctl.h"
|
||||
|
||||
namespace wxMSWImpl
|
||||
|
|
@ -56,4 +57,53 @@ private:
|
|||
|
||||
} // namespace wxMSWImpl
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxMSWHeaderCtrlCustomDraw: custom draw helper for header control
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class wxMSWHeaderCtrlCustomDraw : public wxMSWImpl::CustomDraw
|
||||
{
|
||||
public:
|
||||
wxMSWHeaderCtrlCustomDraw()
|
||||
{
|
||||
}
|
||||
|
||||
// Set the colours to the ones used by "Header" theme.
|
||||
//
|
||||
// This is required, for unknown reasons, when using dark mode, because
|
||||
// enabling it for the header control changes the background, but not the
|
||||
// foreground, making the text completely unreadable.
|
||||
//
|
||||
// If this is ever fixed in later Windows versions, this function wouldn't
|
||||
// need to be called any more.
|
||||
void UseHeaderThemeColors(HWND hwndHdr)
|
||||
{
|
||||
wxUxThemeHandle theme{hwndHdr, L"Header"};
|
||||
|
||||
m_attr.SetTextColour(theme.GetColour(HP_HEADERITEM, TMT_TEXTCOLOR));
|
||||
|
||||
// Note that TMT_FILLCOLOR doesn't seem to exist in this theme but the
|
||||
// correct background colour is already used in "ItemsView" theme by
|
||||
// default anyhow.
|
||||
}
|
||||
|
||||
// Make this field public to let the control update it directly when its
|
||||
// attributes change.
|
||||
wxItemAttr m_attr;
|
||||
|
||||
private:
|
||||
virtual bool HasCustomDrawnItems() const override
|
||||
{
|
||||
// We only exist if the header does need to be custom drawn.
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const wxItemAttr*
|
||||
GetItemAttr(DWORD_PTR WXUNUSED(dwItemSpec)) const override
|
||||
{
|
||||
// We use the same attribute for all items for now.
|
||||
return &m_attr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _WX_MSW_CUSTOMDRAW_H_
|
||||
|
|
|
|||
61
include/wx/msw/private/darkmode.h
Normal file
61
include/wx/msw/private/darkmode.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: wx/msw/private/darkmode.h
|
||||
// Purpose: Dark mode support in wxMSW
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2022-06-25
|
||||
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WX_MSW_PRIVATE_DARKMODE_H_
|
||||
#define _WX_MSW_PRIVATE_DARKMODE_H_
|
||||
|
||||
#include "wx/settings.h"
|
||||
|
||||
namespace wxMSWDarkMode
|
||||
{
|
||||
|
||||
// Return true if the application is using dark mode: note that this will only
|
||||
// be the case if wxApp::MSWEnableDarkMode() was called.
|
||||
bool IsActive();
|
||||
|
||||
// Enable dark mode for the given TLW if appropriate.
|
||||
void EnableForTLW(HWND hwnd);
|
||||
|
||||
// Set dark theme for the given (child) window if appropriate.
|
||||
//
|
||||
// Optional theme name and ID can be specified if something other than the
|
||||
// default "Explorer" should be used. If both theme name and theme ID are null,
|
||||
// no theme is set.
|
||||
void AllowForWindow(HWND hwnd,
|
||||
const wchar_t* themeName = L"Explorer",
|
||||
const wchar_t* themeId = nullptr);
|
||||
|
||||
// Return the colour value appropriate for dark mode if it's used or an invalid
|
||||
// colour if it isn't.
|
||||
wxColour GetColour(wxSystemColour index);
|
||||
|
||||
// Return the background brush to be used by default in dark mode.
|
||||
HBRUSH GetBackgroundBrush();
|
||||
|
||||
// If dark mode is active, paint the given window using inverted colours.
|
||||
// Otherwise just return false without doing anything.
|
||||
//
|
||||
// This can only be called from WM_PAINT handler for a native control and
|
||||
// assumes that this control handles WPARAM argument of WM_PAINT as HDC to
|
||||
// paint on.
|
||||
bool PaintIfNecessary(HWND hwnd, WXWNDPROC defWndProc);
|
||||
|
||||
// If dark mode is active and if the message is one of those used for menu
|
||||
// drawing, process it and return true, otherwise just return false without
|
||||
// doing anything.
|
||||
bool
|
||||
HandleMenuMessage(WXLRESULT* result,
|
||||
wxWindow* w,
|
||||
WXUINT nMsg,
|
||||
WXWPARAM wParam,
|
||||
WXLPARAM lParam);
|
||||
|
||||
} // namespace wxMSWDarkMode
|
||||
|
||||
#endif // _WX_MSW_PRIVATE_DARKMODE_H_
|
||||
|
|
@ -58,6 +58,8 @@ protected:
|
|||
virtual wxBorder GetDefaultBorder() const override { return wxBORDER_NONE; }
|
||||
virtual wxSize DoGetBestSize() const override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
// Implement wxMSWOwnerDrawnButtonBase methods.
|
||||
virtual int MSWGetButtonStyle() const override;
|
||||
virtual void MSWOnButtonResetOwnerDrawn() override;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ public:
|
|||
virtual void SetIncrement(int value) override;
|
||||
virtual int GetIncrement() const override;
|
||||
|
||||
virtual bool MSWShouldUseAutoDarkMode() const override;
|
||||
|
||||
protected:
|
||||
virtual wxSize DoGetBestSize() const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ public:
|
|||
protected:
|
||||
virtual wxWindowList GetCompositeWindowParts() const override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
// return the region with all the windows inside this static box excluded
|
||||
WXHRGN MSWGetRegionWithoutChildren();
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ protected:
|
|||
virtual bool MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result) override;
|
||||
#endif
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
// implementation of the public SetStatusWidths()
|
||||
void MSWUpdateFieldsWidths();
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ protected:
|
|||
// common part of all ctors
|
||||
void Init();
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
virtual int MSWGetToolTipMessage() const override;
|
||||
|
||||
// create the native toolbar control
|
||||
bool MSWCreateToolbar(const wxPoint& pos, const wxSize& size);
|
||||
|
||||
|
|
|
|||
|
|
@ -212,6 +212,8 @@ protected:
|
|||
|
||||
virtual bool MSWShouldSetDefaultFont() const override { return false; }
|
||||
|
||||
virtual int MSWGetToolTipMessage() const override;
|
||||
|
||||
virtual void OnImagesChanged() override;
|
||||
|
||||
// SetImageList helper
|
||||
|
|
|
|||
|
|
@ -169,9 +169,14 @@ WXDLLIMPEXP_CORE bool wxUxThemeIsActive();
|
|||
class wxUxThemeHandle
|
||||
{
|
||||
public:
|
||||
wxUxThemeHandle(const wxWindow *win, const wchar_t *classes)
|
||||
wxUxThemeHandle(HWND hwnd, const wchar_t *classes) :
|
||||
m_hTheme{::OpenThemeData(hwnd, classes)}
|
||||
{
|
||||
}
|
||||
|
||||
wxUxThemeHandle(const wxWindow *win, const wchar_t *classes) :
|
||||
wxUxThemeHandle(GetHwndOf(win), classes)
|
||||
{
|
||||
m_hTheme = (HTHEME)::OpenThemeData(GetHwndOf(win), classes);
|
||||
}
|
||||
|
||||
operator HTHEME() const { return m_hTheme; }
|
||||
|
|
@ -184,8 +189,14 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// Return the colour for the given part, property and state.
|
||||
//
|
||||
// Note that the order of arguments here is _not_ the same as for
|
||||
// GetThemeColor() because we want to default the state.
|
||||
wxColour GetColour(int part, int prop, int state = 0) const;
|
||||
|
||||
private:
|
||||
HTHEME m_hTheme;
|
||||
const HTHEME m_hTheme;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxUxThemeHandle);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -211,6 +211,13 @@ public:
|
|||
|
||||
void OnPaint(wxPaintEvent& event);
|
||||
|
||||
// Override this to return true to automatically invert the window colours
|
||||
// in dark mode.
|
||||
//
|
||||
// This doesn't result in visually great results, but may still be better
|
||||
// than using light background.
|
||||
virtual bool MSWShouldUseAutoDarkMode() const { return false; }
|
||||
|
||||
public:
|
||||
// Windows subclassing
|
||||
void SubclassWin(WXHWND hWnd);
|
||||
|
|
@ -475,21 +482,6 @@ public:
|
|||
// querying the parent windows via MSWGetBgBrushForChild() recursively
|
||||
WXHBRUSH MSWGetBgBrush(WXHDC hDC);
|
||||
|
||||
enum MSWThemeColour
|
||||
{
|
||||
ThemeColourText = 0,
|
||||
ThemeColourBackground,
|
||||
ThemeColourBorder
|
||||
};
|
||||
|
||||
// returns a specific theme colour, or if that is not possible then
|
||||
// wxSystemSettings::GetColour(fallback)
|
||||
wxColour MSWGetThemeColour(const wchar_t *themeName,
|
||||
int themePart,
|
||||
int themeState,
|
||||
MSWThemeColour themeColour,
|
||||
wxSystemColour fallback) const;
|
||||
|
||||
// gives the parent the possibility to draw its children background, e.g.
|
||||
// this is used by wxNotebook to do it using DrawThemeBackground()
|
||||
//
|
||||
|
|
|
|||
|
|
@ -172,8 +172,15 @@ public:
|
|||
// Return the name if available or empty string otherwise.
|
||||
wxString GetName() const;
|
||||
|
||||
// Return true if the current system there is explicitly recognized as
|
||||
// being a dark theme or if the default window background is dark.
|
||||
// Return true if the applications on this system use dark theme by default.
|
||||
bool AreAppsDark() const;
|
||||
|
||||
// Return true if the system elements use dark theme: this can only differ
|
||||
// from AreAppsDark() under MSW where it's possible to configure the system
|
||||
// (taskbar etc) to use a different theme.
|
||||
bool IsSystemDark() const;
|
||||
|
||||
// Return true if this application itself uses a dark theme.
|
||||
bool IsDark() const;
|
||||
|
||||
// Return true if the background is darker than foreground. This is used by
|
||||
|
|
@ -221,6 +228,12 @@ public:
|
|||
// get the object describing the current system appearance
|
||||
static wxSystemAppearance GetAppearance();
|
||||
|
||||
// get the first colour for light appearance and the second one for the dark
|
||||
static wxColour SelectLightDark(wxColour colForLight, wxColour colForDark)
|
||||
{
|
||||
return GetAppearance().IsDark() ? colForDark : colForLight;
|
||||
}
|
||||
|
||||
// return true if the port has certain feature
|
||||
static bool HasFeature(wxSystemFeature index);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,48 @@ public:
|
|||
|
||||
///@}
|
||||
|
||||
/**
|
||||
@name MSW-specific functions
|
||||
*/
|
||||
//@{
|
||||
|
||||
/**
|
||||
Enable experimental dark mode support for MSW applications.
|
||||
|
||||
This function uses @e undocumented, and unsupported by Microsoft,
|
||||
functions to enable dark mode support for the desktop applications
|
||||
under Windows 10 20H1 or later (including all Windows 11 versions).
|
||||
|
||||
Note that dark mode can also be enabled by setting the "msw.dark-mode"
|
||||
@ref wxSystemOptions "system option" via an environment variable from
|
||||
outside the application.
|
||||
|
||||
Known limitations of dark mode support include:
|
||||
|
||||
- wxMessageBox() contents doesn't use dark mode. Consider using
|
||||
wxGenericMessageDialog if dark mode support is more important
|
||||
than using the native dialog.
|
||||
- wxDatePickerCtrl and wxTimePickerCtrl don't support dark mode and
|
||||
use the same (light) background as by default in it.
|
||||
- Toolbar items for which wxToolBar::SetDropdownMenu() was called
|
||||
don't draw the menu drop-down correctly, making it almost
|
||||
invisible.
|
||||
|
||||
@param flags Can include @c wxApp::DarkMode_Always to force enabling
|
||||
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.
|
||||
|
||||
@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.
|
||||
|
||||
@onlyfor{wxmsw}
|
||||
|
||||
@since 3.3.0
|
||||
*/
|
||||
bool MSWEnableDarkMode(int flags = 0);
|
||||
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -271,6 +271,22 @@ enum wxSystemScreenType
|
|||
class wxSystemAppearance
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Return true if the applications on this system use dark theme by
|
||||
default.
|
||||
|
||||
This function returns @true if dark mode is enabled for the
|
||||
applications system-wide, even if it's not enabled for this particular
|
||||
application.
|
||||
|
||||
Note that for non-MSW platforms this is currently the same as IsDark(),
|
||||
but under MSW these two functions can return different values as dark
|
||||
mode requires to opt-in into it specifically.
|
||||
|
||||
@since 3.3.0
|
||||
*/
|
||||
bool AreAppsDark() const;
|
||||
|
||||
/**
|
||||
Return the name if available or empty string otherwise.
|
||||
|
||||
|
|
@ -286,9 +302,28 @@ public:
|
|||
|
||||
This method should be used to check whether custom colours more
|
||||
appropriate for the default (light) or dark appearance should be used.
|
||||
|
||||
Note that this checks the appearance of the current application and not
|
||||
the other applications on the system, so under MSW, for example, it
|
||||
will return @false even if dark mode is used system-wide unless the
|
||||
application opted in using dark mode using wxApp::MSWEnableDarkMode().
|
||||
You can use IsSystemDark() or AreAppsDark() to check if the system is
|
||||
using dark mode by default.
|
||||
*/
|
||||
bool IsDark() const;
|
||||
|
||||
/**
|
||||
Return true if the system UI uses dark theme.
|
||||
|
||||
This is the same as AreAppsDark() on the non-MSW platforms, but can be
|
||||
different from the other function under MSW as it is possible to
|
||||
configure default "Windows mode" and "app mode" to use different colour
|
||||
schemes under Windows.
|
||||
|
||||
@since 3.3.0
|
||||
*/
|
||||
bool IsSystemDark() const;
|
||||
|
||||
/**
|
||||
Return true if the default window background is significantly darker
|
||||
than foreground.
|
||||
|
|
@ -388,5 +423,20 @@ public:
|
|||
See the ::wxSystemFeature enum values.
|
||||
*/
|
||||
static bool HasFeature(wxSystemFeature index);
|
||||
|
||||
/**
|
||||
Select one of the two colours depending on whether light or dark mode
|
||||
is used.
|
||||
|
||||
This is just a convenient helper using wxSystemAppearance::IsDark() to
|
||||
select between the two colours.
|
||||
|
||||
@param colForLight Colour returned when using light appearance.
|
||||
@param colForDark Colour returned when using dark appearance, as
|
||||
detected by wxSystemAppearance::IsDark().
|
||||
|
||||
@since 3.3.0
|
||||
*/
|
||||
static wxColour SelectLightDark(wxColour colForLight, wxColour colForDark);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@
|
|||
If set to 1, disables the use of composited, i.e. double-buffered,
|
||||
windows by default in wxMSW. This is not recommended, but can be useful
|
||||
for debugging or working around redraw problems in the existing code.
|
||||
@flag{msw.dark-mode}
|
||||
If set to 1, enable experimental support of dark mode if the system is
|
||||
using it, i.e. this has the same effect as calling
|
||||
wxApp::MSWEnableDarkMode(). If set to 2, use dark mode unconditionally,
|
||||
as if this function were called with wxApp::DarkMode_Always argument.
|
||||
@endFlagTable
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -524,6 +524,13 @@ MyCanvas::MyCanvas(MyFrame *parent)
|
|||
m_useBuffer = false;
|
||||
m_showBBox = false;
|
||||
m_sizeDIP = wxSize(0, 0);
|
||||
|
||||
Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) {
|
||||
event.Skip();
|
||||
|
||||
if ( m_show == File_ShowSystemColours )
|
||||
Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
void MyCanvas::DrawTestBrushes(wxDC& dc)
|
||||
|
|
@ -1676,21 +1683,31 @@ void MyCanvas::DrawSystemColours(wxDC& dc)
|
|||
wxCoord x(FromDIP(10));
|
||||
wxRect r(textSize.GetWidth() + x, x, dc.FromDIP(100), lineHeight);
|
||||
|
||||
wxString title = "System colours";
|
||||
dc.DrawText("System colours", x, r.y);
|
||||
r.y += 2*lineHeight;
|
||||
|
||||
const wxSystemAppearance appearance = wxSystemSettings::GetAppearance();
|
||||
const wxString appearanceName = appearance.GetName();
|
||||
if ( !appearanceName.empty() )
|
||||
title += wxString::Format(" for \"%s\"", appearanceName);
|
||||
if ( appearance.IsDark() )
|
||||
title += " (using dark system theme)";
|
||||
dc.DrawText(title, x, r.y);
|
||||
r.y += 2*lineHeight;
|
||||
dc.DrawText(wxString::Format("Window background is %s",
|
||||
appearance.IsUsingDarkBackground() ? "dark"
|
||||
: "light"),
|
||||
x, r.y);
|
||||
r.y += 3*lineHeight;
|
||||
{
|
||||
dc.DrawText(wxString::Format("System appearance: %s", appearanceName),
|
||||
x, r.y);
|
||||
r.y += lineHeight;
|
||||
}
|
||||
|
||||
auto const showDarkOrLight = [&](const char* what, bool dark)
|
||||
{
|
||||
dc.DrawText(wxString::Format("%s: %s", what, dark ? "dark" : "light"),
|
||||
x, r.y);
|
||||
r.y += 1.5*lineHeight;
|
||||
};
|
||||
|
||||
showDarkOrLight("System", appearance.IsSystemDark());
|
||||
showDarkOrLight("App default", appearance.AreAppsDark());
|
||||
showDarkOrLight("Current app", appearance.IsDark());
|
||||
showDarkOrLight("Background", appearance.IsUsingDarkBackground());
|
||||
|
||||
r.y += lineHeight;
|
||||
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,21 @@ void wxSystemSettings::SetScreenType( wxSystemScreenType screen )
|
|||
// Trivial wxSystemAppearance implementation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// wxMSW has its own implementation of these functions.
|
||||
#if !defined(__WXMSW__)
|
||||
|
||||
bool wxSystemAppearance::AreAppsDark() const
|
||||
{
|
||||
return IsDark();
|
||||
}
|
||||
|
||||
bool wxSystemAppearance::IsSystemDark() const
|
||||
{
|
||||
return IsDark();
|
||||
}
|
||||
|
||||
#endif // !__WXMSW__
|
||||
|
||||
#if !defined(__WXOSX__)
|
||||
|
||||
wxString wxSystemAppearance::GetName() const
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/dcclient.h"
|
||||
#include "wx/settings.h"
|
||||
#include "wx/timer.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
|
|
@ -147,6 +148,9 @@ private:
|
|||
// the next position every time.
|
||||
gc->Rotate(m_frame*angle);
|
||||
|
||||
// Choose a contrasting background colour.
|
||||
wxColour colBg = wxSystemSettings::SelectLightDark(*wxBLACK, *wxWHITE);
|
||||
|
||||
const bool isEnabled = m_win->IsThisEnabled();
|
||||
for ( int n = 0; n < NUM_DOTS; n++ )
|
||||
{
|
||||
|
|
@ -159,7 +163,8 @@ private:
|
|||
// it in 0..wxALPHA_OPAQUE range.
|
||||
const int opacity = opacityIndex*(wxALPHA_OPAQUE + 1)/NUM_DOTS - 1;
|
||||
|
||||
gc->SetBrush(wxBrush(wxColour(0, 0, 0, opacity)));
|
||||
colBg.Set(colBg.Red(), colBg.Green(), colBg.Blue(), opacity);
|
||||
gc->SetBrush(colBg);
|
||||
|
||||
gc->FillPath(path);
|
||||
gc->Rotate(angle);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@
|
|||
|
||||
#if wxUSE_FONTPICKERCTRL
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/settings.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/fontpicker.h"
|
||||
|
||||
#include "wx/fontdlg.h"
|
||||
|
|
@ -68,7 +72,7 @@ bool wxGenericFontButton::Create( wxWindow *parent, wxWindowID id,
|
|||
void wxGenericFontButton::InitFontData()
|
||||
{
|
||||
m_data.SetAllowSymbols(true);
|
||||
m_data.SetColour(*wxBLACK);
|
||||
m_data.SetColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
|
||||
m_data.EnableEffects(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -541,7 +541,7 @@ protected:
|
|||
#endif
|
||||
#ifdef __WXMSW__
|
||||
cairo_surface_t* m_mswSurface;
|
||||
WindowHDC m_mswWindowHDC;
|
||||
ClientHDC m_mswWindowHDC;
|
||||
int m_mswStateSavedDC;
|
||||
#endif
|
||||
#ifdef __WXGTK__
|
||||
|
|
|
|||
|
|
@ -3031,11 +3031,7 @@ void wxGrid::Init()
|
|||
m_cellHighlightColour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
m_cellHighlightPenWidth = 2;
|
||||
m_cellHighlightROPenWidth = 1;
|
||||
if ( wxSystemSettings::GetAppearance().IsDark() )
|
||||
m_gridFrozenBorderColour = *wxWHITE;
|
||||
else
|
||||
m_gridFrozenBorderColour = *wxBLACK;
|
||||
|
||||
m_gridFrozenBorderColour = wxSystemSettings::SelectLightDark(*wxBLACK, *wxWHITE);
|
||||
m_gridFrozenBorderPenWidth = 2;
|
||||
|
||||
m_canDragRowMove = false;
|
||||
|
|
|
|||
|
|
@ -96,21 +96,15 @@ public:
|
|||
{
|
||||
titleFont.MakeLarger();
|
||||
|
||||
COLORREF c;
|
||||
if ( FAILED(::GetThemeColor
|
||||
(
|
||||
wxUxThemeHandle(parent, L"TOOLTIP"),
|
||||
TTP_BALLOONTITLE,
|
||||
0,
|
||||
TMT_TEXTCOLOR,
|
||||
&c
|
||||
)) )
|
||||
wxUxThemeHandle theme(parent, L"TOOLTIP");
|
||||
wxColour c = theme.GetColour(TTP_BALLOONTITLE, TMT_TEXTCOLOR);
|
||||
if ( !c.IsOk() )
|
||||
{
|
||||
// Use the standard value of this colour as fallback.
|
||||
c = 0x993300;
|
||||
c.Set(0x00, 0x33, 0x99);
|
||||
}
|
||||
|
||||
labelTitle->SetForegroundColour(wxRGBToColour(c));
|
||||
labelTitle->SetForegroundColour(c);
|
||||
}
|
||||
else
|
||||
#endif // HAVE_MSW_THEME
|
||||
|
|
@ -175,30 +169,14 @@ public:
|
|||
{
|
||||
wxUxThemeHandle hTheme(GetParent(), L"TOOLTIP");
|
||||
|
||||
COLORREF c1, c2;
|
||||
if ( FAILED(::GetThemeColor
|
||||
(
|
||||
hTheme,
|
||||
TTP_BALLOONTITLE,
|
||||
0,
|
||||
TMT_GRADIENTCOLOR1,
|
||||
&c1
|
||||
)) ||
|
||||
FAILED(::GetThemeColor
|
||||
(
|
||||
hTheme,
|
||||
TTP_BALLOONTITLE,
|
||||
0,
|
||||
TMT_GRADIENTCOLOR2,
|
||||
&c2
|
||||
)) )
|
||||
{
|
||||
c1 = 0xffffff;
|
||||
c2 = 0xf0e5e4;
|
||||
}
|
||||
colStart = hTheme.GetColour(TTP_BALLOONTITLE, TMT_GRADIENTCOLOR1);
|
||||
if ( !colStart.IsOk() )
|
||||
colStart = wxSystemSettings::SelectLightDark(*wxWHITE, *wxBLACK);
|
||||
|
||||
colStart = wxRGBToColour(c1);
|
||||
colEnd = wxRGBToColour(c2);
|
||||
colEnd = hTheme.GetColour(TTP_BALLOONTITLE, TMT_GRADIENTCOLOR2);
|
||||
if ( !colEnd.IsOk() )
|
||||
colEnd = wxSystemSettings::SelectLightDark({0xe4, 0xe5, 0xf0},
|
||||
{0x40, 0x40, 0x20});
|
||||
}
|
||||
else
|
||||
#endif // HAVE_MSW_THEME
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/dc.h"
|
||||
#include "wx/msw/ole/oleutils.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/timer.h"
|
||||
|
||||
#if wxUSE_TOOLTIPS
|
||||
|
|
@ -633,6 +634,14 @@ bool wxApp::Initialize(int& argc_, wxChar **argv_)
|
|||
|
||||
wxSetKeyboardHook(true);
|
||||
|
||||
// this is useful to allow users to enable dark mode for the applications
|
||||
// not enabling it themselves by setting the corresponding environment
|
||||
// variable
|
||||
if ( const int darkMode = wxSystemOptions::GetOptionInt("msw.dark-mode") )
|
||||
{
|
||||
MSWEnableDarkMode(darkMode > 1 ? DarkMode_Always : DarkMode_Auto);
|
||||
}
|
||||
|
||||
callBaseCleanup.Dismiss();
|
||||
|
||||
return true;
|
||||
|
|
@ -656,6 +665,15 @@ const wxChar *wxApp::GetRegisteredClassName(const wxChar *name,
|
|||
return gs_regClassesInfo[n].GetRequestedName(flags);
|
||||
}
|
||||
|
||||
// In dark mode, use the dark background brush instead of specified colour
|
||||
// which would result in light background.
|
||||
HBRUSH hbrBackground;
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
hbrBackground = wxMSWDarkMode::GetBackgroundBrush();
|
||||
else
|
||||
hbrBackground = (HBRUSH)wxUIntToPtr(bgBrushCol + 1);
|
||||
|
||||
|
||||
// we need to register this class
|
||||
WNDCLASS wndclass;
|
||||
wxZeroMemory(wndclass);
|
||||
|
|
@ -663,7 +681,7 @@ const wxChar *wxApp::GetRegisteredClassName(const wxChar *name,
|
|||
wndclass.lpfnWndProc = (WNDPROC)wxWndProc;
|
||||
wndclass.hInstance = wxGetInstance();
|
||||
wndclass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
|
||||
wndclass.hbrBackground = (HBRUSH)wxUIntToPtr(bgBrushCol + 1);
|
||||
wndclass.hbrBackground = hbrBackground;
|
||||
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | extraStyles;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,18 @@ WXDWORD wxCheckBox::MSWGetStyle(long style, WXDWORD *exstyle) const
|
|||
return msStyle;
|
||||
}
|
||||
|
||||
bool wxCheckBox::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// Just as radio buttons, check boxes have some dark theme support, but we
|
||||
// still need to change their foreground manually to make it readable in
|
||||
// dark mode.
|
||||
wxCheckBoxBase::MSWGetDarkModeSupport(support);
|
||||
|
||||
support.setForeground = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxCheckBox geometry
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
#include "wx/dynlib.h"
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/uxtheme.h"
|
||||
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
|
|
@ -193,15 +196,31 @@ wxChoice::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
|
|||
attrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
|
||||
// NB: use EDIT, not COMBOBOX (the latter works in XP but not Vista)
|
||||
attrs.colBg = wnd->MSWGetThemeColour(L"EDIT",
|
||||
EP_EDITTEXT,
|
||||
ETS_NORMAL,
|
||||
ThemeColourBackground,
|
||||
wxSYS_COLOUR_WINDOW);
|
||||
wxUxThemeHandle hTheme(wnd, L"EDIT");
|
||||
attrs.colBg = hTheme.GetColour(EP_EDITTEXT, TMT_FILLCOLOR, ETS_NORMAL);
|
||||
if ( !attrs.colBg.IsOk() )
|
||||
attrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
bool wxChoice::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
support.themeName = L"CFD";
|
||||
|
||||
// It is slightly improper to do this in a const function, but as we know
|
||||
// that this will only be called when we're using the dark mode, we also
|
||||
// use it to enable it for the drop down list, if any, to ensure that it
|
||||
// uses dark scrollbars.
|
||||
WinStruct<COMBOBOXINFO> info;
|
||||
if ( ::GetComboBoxInfo(GetHwnd(), &info) && info.hwndList )
|
||||
{
|
||||
wxMSWDarkMode::AllowForWindow(info.hwndList);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxChoice::~wxChoice()
|
||||
{
|
||||
Clear();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "wx/msw/uxtheme.h"
|
||||
#include "wx/msw/dc.h" // for wxDCTemp
|
||||
#include "wx/msw/ownerdrawnbutton.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/winstyle.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -137,6 +138,22 @@ bool wxControl::MSWCreateControl(const wxChar *classname,
|
|||
return false;
|
||||
}
|
||||
|
||||
MSWDarkModeSupport support;
|
||||
if ( wxMSWDarkMode::IsActive() && MSWGetDarkModeSupport(support) )
|
||||
{
|
||||
wxMSWDarkMode::AllowForWindow(m_hWnd, support.themeName, support.themeId);
|
||||
|
||||
if ( support.setForeground )
|
||||
SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT));
|
||||
|
||||
if ( const int msgTT = MSWGetToolTipMessage() )
|
||||
{
|
||||
const HWND hwndTT = (HWND)::SendMessage(GetHwnd(), msgTT, 0, 0);
|
||||
if ( ::IsWindow(hwndTT) )
|
||||
wxMSWDarkMode::AllowForWindow(hwndTT);
|
||||
}
|
||||
}
|
||||
|
||||
// saving the label in m_labelOrig to return it verbatim
|
||||
// later in GetLabel()
|
||||
m_labelOrig = label;
|
||||
|
|
@ -183,6 +200,16 @@ bool wxControl::MSWCreateControl(const wxChar *classname,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool wxControl::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// This theme works for a few controls (buttons, texts, comboboxes) and
|
||||
// doesn't seem to do any harm for those that don't support it, so use it
|
||||
// by default.
|
||||
support.themeName = L"Explorer";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// various accessors
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -291,6 +318,8 @@ WXHBRUSH wxControl::DoMSWControlColor(WXHDC pDC, wxColour colBg, WXHWND hWnd)
|
|||
{
|
||||
HDC hdc = (HDC)pDC;
|
||||
|
||||
wxColour colFg;
|
||||
|
||||
WXHBRUSH hbr = 0;
|
||||
if ( !colBg.IsOk() )
|
||||
{
|
||||
|
|
@ -325,11 +354,22 @@ WXHBRUSH wxControl::DoMSWControlColor(WXHDC pDC, wxColour colBg, WXHWND hWnd)
|
|||
if ( win )
|
||||
hbr = win->MSWGetBgBrush(pDC);
|
||||
|
||||
// if the control doesn't have any bg colour, foreground colour will be
|
||||
// ignored as the return value would be 0 -- so forcefully give it a
|
||||
// non default background brush in this case
|
||||
if ( !hbr && m_hasFgCol )
|
||||
colBg = GetBackgroundColour();
|
||||
if ( !hbr )
|
||||
{
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX);
|
||||
if ( !m_hasFgCol )
|
||||
colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT);
|
||||
}
|
||||
// if the control doesn't have any bg colour, foreground colour will be
|
||||
// ignored as the return value would be 0 -- so forcefully give it a
|
||||
// non default background brush in this case
|
||||
else if ( m_hasFgCol )
|
||||
{
|
||||
colBg = GetBackgroundColour();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use the background colour override if a valid colour is given: this is
|
||||
|
|
@ -347,7 +387,9 @@ WXHBRUSH wxControl::DoMSWControlColor(WXHDC pDC, wxColour colBg, WXHWND hWnd)
|
|||
// default just the simple black is used
|
||||
if ( hbr )
|
||||
{
|
||||
::SetTextColor(hdc, wxColourToRGB(GetForegroundColour()));
|
||||
if ( !colFg.IsOk() )
|
||||
colFg = GetForegroundColour();
|
||||
::SetTextColor(hdc, wxColourToRGB(colFg));
|
||||
}
|
||||
|
||||
// finally also set the background colour for text drawing: without this,
|
||||
|
|
|
|||
663
src/msw/darkmode.cpp
Normal file
663
src/msw/darkmode.cpp
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/msw/darkmode.cpp
|
||||
// Purpose: Support for dark mode in wxMSW
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2022-06-24
|
||||
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
The code in this file is based on the following sources:
|
||||
|
||||
- win32-darkmode by Richard Yu (https://github.com/ysc3839/win32-darkmode)
|
||||
- UAH menu by adzm (https://github.com/adzm/win32-custom-menubar-aero-theme)
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// for compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
// Allow predefining this as 0 to disable dark mode support completely.
|
||||
#ifndef wxUSE_DARK_MODE
|
||||
// Otherwise enable it by default.
|
||||
#define wxUSE_DARK_MODE 1
|
||||
#endif
|
||||
|
||||
#if wxUSE_DARK_MODE
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/app.h"
|
||||
#include "wx/bitmap.h"
|
||||
#include "wx/dcmemory.h"
|
||||
#include "wx/image.h"
|
||||
#include "wx/log.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/dynlib.h"
|
||||
#include "wx/module.h"
|
||||
|
||||
#include "wx/msw/dc.h"
|
||||
#include "wx/msw/uxtheme.h"
|
||||
|
||||
static const char* TRACE_DARKMODE = "msw-darkmode";
|
||||
|
||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Constants for use with SetPreferredAppMode().
|
||||
enum PreferredAppMode
|
||||
{
|
||||
AppMode_Default,
|
||||
AppMode_AllowDark,
|
||||
AppMode_ForceDark,
|
||||
AppMode_ForceLight
|
||||
};
|
||||
|
||||
PreferredAppMode gs_appMode = AppMode_Default;
|
||||
|
||||
template <typename T>
|
||||
bool TryLoadByOrd(T& func, const wxDynamicLibrary& lib, int ordinal)
|
||||
{
|
||||
func = (T)::GetProcAddress(lib.GetLibHandle(), MAKEINTRESOURCEA(ordinal));
|
||||
if ( !func )
|
||||
{
|
||||
wxLogTrace(TRACE_DARKMODE,
|
||||
"Required function with ordinal %d not found", ordinal);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
||||
namespace wxMSWImpl
|
||||
{
|
||||
|
||||
// Global pointers of the functions we use: they're not only undocumented, but
|
||||
// don't appear in the SDK headers at all.
|
||||
BOOL (WINAPI *ShouldAppsUseDarkMode)() = nullptr;
|
||||
BOOL (WINAPI *AllowDarkModeForWindow)(HWND hwnd, BOOL allow) = nullptr;
|
||||
DWORD (WINAPI *SetPreferredAppMode)(DWORD) = nullptr;
|
||||
|
||||
bool InitDarkMode()
|
||||
{
|
||||
// Note: for simplicity, we support dark mode only in Windows 10 v2004
|
||||
// ("20H1", build number 19041) and later, even if, in principle, it could
|
||||
// be supported as far back as v1809 (build 17763) -- but very few people
|
||||
// must still use it by now and so it just doesn't seem to be worth it.
|
||||
if ( !wxCheckOsVersion(10, 0, 19041) )
|
||||
{
|
||||
wxLogTrace(TRACE_DARKMODE, "Unsupported due to OS version");
|
||||
return false;
|
||||
}
|
||||
|
||||
wxLoadedDLL dllUxTheme(wxS("uxtheme.dll"));
|
||||
|
||||
// These functions are not only undocumented but are not even exported by
|
||||
// name, and have to be resolved using their ordinals.
|
||||
return TryLoadByOrd(ShouldAppsUseDarkMode, dllUxTheme, 132) &&
|
||||
TryLoadByOrd(AllowDarkModeForWindow, dllUxTheme, 133) &&
|
||||
TryLoadByOrd(SetPreferredAppMode, dllUxTheme, 135);
|
||||
}
|
||||
|
||||
// This function is only used in this file as it's more clear than using
|
||||
// IsActive() without the namespace name -- but in the rest of our code, it's
|
||||
// IsActive() which is more clear.
|
||||
bool ShouldUseDarkMode()
|
||||
{
|
||||
switch ( gs_appMode )
|
||||
{
|
||||
case AppMode_Default:
|
||||
// Dark mode support not enabled, don't try using dark mode.
|
||||
return false;
|
||||
|
||||
case AppMode_AllowDark:
|
||||
// Follow the global setting.
|
||||
return wxMSWImpl::ShouldAppsUseDarkMode();
|
||||
|
||||
case AppMode_ForceDark:
|
||||
return true;
|
||||
|
||||
case AppMode_ForceLight:
|
||||
return false;
|
||||
}
|
||||
|
||||
wxFAIL_MSG( "unreachable" );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace wxMSWImpl
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Module keeping dark mode-related data
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// This function is documented, but we still load it dynamically to avoid
|
||||
// having to link with dwmapi.lib.
|
||||
typedef HRESULT
|
||||
(WINAPI *DwmSetWindowAttribute_t)(HWND, DWORD, const void*, DWORD);
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class wxDarkModeModule : public wxModule
|
||||
{
|
||||
public:
|
||||
virtual bool OnInit() override { return true; }
|
||||
virtual void OnExit() override
|
||||
{
|
||||
ms_pfnDwmSetWindowAttribute = (DwmSetWindowAttribute_t)-1;
|
||||
ms_dllDWM.Unload();
|
||||
}
|
||||
|
||||
static DwmSetWindowAttribute_t GetDwmSetWindowAttribute()
|
||||
{
|
||||
if ( ms_pfnDwmSetWindowAttribute == (DwmSetWindowAttribute_t)-1 )
|
||||
{
|
||||
ms_dllDWM.Load(wxS("dwmapi.dll"), wxDL_VERBATIM | wxDL_QUIET);
|
||||
wxDL_INIT_FUNC(ms_pfn, DwmSetWindowAttribute, ms_dllDWM);
|
||||
}
|
||||
|
||||
return ms_pfnDwmSetWindowAttribute;
|
||||
}
|
||||
|
||||
private:
|
||||
static wxDynamicLibrary ms_dllDWM;
|
||||
static DwmSetWindowAttribute_t ms_pfnDwmSetWindowAttribute;
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(wxDarkModeModule);
|
||||
};
|
||||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(wxDarkModeModule, wxModule);
|
||||
|
||||
wxDynamicLibrary wxDarkModeModule::ms_dllDWM;
|
||||
|
||||
DwmSetWindowAttribute_t
|
||||
wxDarkModeModule::ms_pfnDwmSetWindowAttribute = (DwmSetWindowAttribute_t)-1;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool wxApp::MSWEnableDarkMode(int flags)
|
||||
{
|
||||
if ( !wxMSWImpl::InitDarkMode() )
|
||||
return false;
|
||||
|
||||
const PreferredAppMode mode = flags & DarkMode_Always ? AppMode_ForceDark
|
||||
: AppMode_AllowDark;
|
||||
const DWORD rc = wxMSWImpl::SetPreferredAppMode(mode);
|
||||
|
||||
// It's supposed to return the old mode normally.
|
||||
if ( rc != static_cast<DWORD>(gs_appMode) )
|
||||
{
|
||||
wxLogTrace(TRACE_DARKMODE,
|
||||
"SetPreferredAppMode(%d) unexpectedly returned %d",
|
||||
mode, rc);
|
||||
}
|
||||
|
||||
gs_appMode = mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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)
|
||||
{
|
||||
// This is not great at all, but better than using light mode colours that
|
||||
// are not appropriate for the dark mode.
|
||||
switch ( index )
|
||||
{
|
||||
case wxSYS_COLOUR_BTNSHADOW:
|
||||
return *wxBLACK;
|
||||
|
||||
case wxSYS_COLOUR_ACTIVECAPTION:
|
||||
case wxSYS_COLOUR_APPWORKSPACE:
|
||||
case wxSYS_COLOUR_INFOBK:
|
||||
case wxSYS_COLOUR_LISTBOX:
|
||||
case wxSYS_COLOUR_WINDOW:
|
||||
case wxSYS_COLOUR_BTNFACE:
|
||||
return wxColour(0x202020);
|
||||
|
||||
case wxSYS_COLOUR_BTNTEXT:
|
||||
case wxSYS_COLOUR_CAPTIONTEXT:
|
||||
case wxSYS_COLOUR_HIGHLIGHTTEXT:
|
||||
case wxSYS_COLOUR_INFOTEXT:
|
||||
case wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT:
|
||||
case wxSYS_COLOUR_LISTBOXTEXT:
|
||||
case wxSYS_COLOUR_MENUTEXT:
|
||||
case wxSYS_COLOUR_WINDOWTEXT:
|
||||
return wxColour(0xe0e0e0);
|
||||
|
||||
case wxSYS_COLOUR_HOTLIGHT:
|
||||
return wxColour(0x474747);
|
||||
|
||||
case wxSYS_COLOUR_SCROLLBAR:
|
||||
return wxColour(0x4d4d4d);
|
||||
|
||||
case wxSYS_COLOUR_INACTIVECAPTION:
|
||||
case wxSYS_COLOUR_MENU:
|
||||
return wxColour(0x2b2b2b);
|
||||
|
||||
case wxSYS_COLOUR_MENUBAR:
|
||||
return wxColour(0x626262);
|
||||
|
||||
case wxSYS_COLOUR_MENUHILIGHT:
|
||||
return wxColour(0x353535);
|
||||
|
||||
case wxSYS_COLOUR_HIGHLIGHT:
|
||||
return wxColour(0x777777);
|
||||
|
||||
case wxSYS_COLOUR_INACTIVECAPTIONTEXT:
|
||||
return wxColour(0xaaaaaa);
|
||||
|
||||
case wxSYS_COLOUR_3DDKSHADOW:
|
||||
case wxSYS_COLOUR_3DLIGHT:
|
||||
case wxSYS_COLOUR_ACTIVEBORDER:
|
||||
case wxSYS_COLOUR_BTNHIGHLIGHT:
|
||||
case wxSYS_COLOUR_DESKTOP:
|
||||
case wxSYS_COLOUR_GRADIENTACTIVECAPTION:
|
||||
case wxSYS_COLOUR_GRADIENTINACTIVECAPTION:
|
||||
case wxSYS_COLOUR_GRAYTEXT:
|
||||
case wxSYS_COLOUR_INACTIVEBORDER:
|
||||
case wxSYS_COLOUR_WINDOWFRAME:
|
||||
return wxColour();
|
||||
|
||||
case wxSYS_COLOUR_MAX:
|
||||
break;
|
||||
}
|
||||
|
||||
wxFAIL_MSG( "unreachable" );
|
||||
return wxColour();
|
||||
}
|
||||
|
||||
HBRUSH GetBackgroundBrush()
|
||||
{
|
||||
wxBrush* const brush =
|
||||
wxTheBrushList->FindOrCreateBrush(GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
||||
return brush ? GetHbrushOf(*brush) : 0;
|
||||
}
|
||||
|
||||
bool PaintIfNecessary(HWND hwnd, WXWNDPROC defWndProc)
|
||||
{
|
||||
#if wxUSE_IMAGE
|
||||
if ( !wxMSWImpl::ShouldUseDarkMode() )
|
||||
return false;
|
||||
|
||||
const RECT rc = wxGetClientRect(hwnd);
|
||||
const wxSize size{rc.right - rc.left, rc.bottom - rc.top};
|
||||
|
||||
// Don't bother doing anything with the empty windows.
|
||||
if ( size == wxSize() )
|
||||
return false;
|
||||
|
||||
// Ask the control to paint itself on the given bitmap.
|
||||
wxBitmap bmp(size);
|
||||
{
|
||||
wxMemoryDC mdc(bmp);
|
||||
|
||||
WPARAM wparam = (WPARAM)GetHdcOf(mdc);
|
||||
if ( defWndProc )
|
||||
::CallWindowProc(defWndProc, hwnd, WM_PAINT, wparam, 0);
|
||||
else
|
||||
::DefWindowProc(hwnd, WM_PAINT, wparam, 0);
|
||||
}
|
||||
|
||||
wxImage image = bmp.ConvertToImage();
|
||||
|
||||
unsigned char *data = image.GetData();
|
||||
for ( int i = 0; i < size.x*size.y; ++i, data += 3 )
|
||||
{
|
||||
wxImage::RGBValue rgb(data[0], data[1], data[2]);
|
||||
wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
|
||||
|
||||
// There is no really good way to convert normal colours to dark mode,
|
||||
// but try to do a bit better than just inverting the value because
|
||||
// this results in colours which are much too dark.
|
||||
hsv.value = sqrt(1.0 - hsv.value*hsv.value);
|
||||
|
||||
rgb = wxImage::HSVtoRGB(hsv);
|
||||
data[0] = rgb.red;
|
||||
data[1] = rgb.green;
|
||||
data[2] = rgb.blue;
|
||||
}
|
||||
|
||||
PAINTSTRUCT ps;
|
||||
wxDCTemp dc(::BeginPaint(hwnd, &ps), size);
|
||||
dc.DrawBitmap(wxBitmap(image), 0, 0);
|
||||
::EndPaint(hwnd, &ps);
|
||||
|
||||
return true;
|
||||
#else // !wxUSE_IMAGE
|
||||
wxUnusedVar(hwnd);
|
||||
wxUnusedVar(defWndProc);
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Menu bar custom drawing
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace wxMSWMenuImpl
|
||||
{
|
||||
|
||||
// Definitions for undocumented messages and structs used in this code.
|
||||
constexpr int WM_MENUBAR_DRAWMENU = 0x91;
|
||||
constexpr int WM_MENUBAR_DRAWMENUITEM = 0x92;
|
||||
|
||||
// This is passed via LPARAM of WM_MENUBAR_DRAWMENU.
|
||||
struct MenuBarDrawMenu
|
||||
{
|
||||
HMENU hmenu;
|
||||
HDC hdc;
|
||||
DWORD dwReserved;
|
||||
};
|
||||
|
||||
struct MenuBarMenuItem
|
||||
{
|
||||
int iPosition;
|
||||
|
||||
// There are more fields in this (undocumented) struct but we don't
|
||||
// currently need them, so don't bother with declaring them.
|
||||
};
|
||||
|
||||
struct MenuBarDrawMenuItem
|
||||
{
|
||||
DRAWITEMSTRUCT dis;
|
||||
MenuBarDrawMenu mbdm;
|
||||
MenuBarMenuItem mbmi;
|
||||
};
|
||||
|
||||
constexpr COLORREF COL_STANDARD = 0xffffff;
|
||||
constexpr COLORREF COL_DISABLED = 0x6d6d6d;
|
||||
constexpr COLORREF COL_MENU_HOT = 0x414141;
|
||||
|
||||
HBRUSH GetMenuBrush()
|
||||
{
|
||||
wxBrush* const brush =
|
||||
wxTheBrushList->FindOrCreateBrush(GetColour(wxSYS_COLOUR_MENU));
|
||||
|
||||
return brush ? GetHbrushOf(*brush) : 0;
|
||||
}
|
||||
|
||||
} // namespace wxMSWMenuImpl
|
||||
|
||||
bool
|
||||
HandleMenuMessage(WXLRESULT* result,
|
||||
wxWindow* w,
|
||||
WXUINT nMsg,
|
||||
WXWPARAM wParam,
|
||||
WXLPARAM lParam)
|
||||
{
|
||||
if ( !wxMSWImpl::ShouldUseDarkMode() )
|
||||
return false;
|
||||
|
||||
using namespace wxMSWMenuImpl;
|
||||
|
||||
switch ( nMsg )
|
||||
{
|
||||
case WM_MENUBAR_DRAWMENU:
|
||||
// Erase the menu bar background using custom brush.
|
||||
if ( auto* const drawMenu = (MenuBarDrawMenu*)lParam )
|
||||
{
|
||||
HWND hwnd = GetHwndOf(w);
|
||||
|
||||
WinStruct<MENUBARINFO> mbi;
|
||||
if ( !::GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi) )
|
||||
{
|
||||
wxLogLastError("GetMenuBarInfo");
|
||||
break;
|
||||
}
|
||||
|
||||
const RECT rcWindow = wxGetWindowRect(hwnd);
|
||||
|
||||
// rcBar is expressed in screen coordinates.
|
||||
::OffsetRect(&mbi.rcBar, -rcWindow.left, -rcWindow.top);
|
||||
|
||||
::FillRect(drawMenu->hdc, &mbi.rcBar, GetMenuBrush());
|
||||
}
|
||||
|
||||
*result = 0;
|
||||
return true;
|
||||
|
||||
case WM_NCPAINT:
|
||||
case WM_NCACTIVATE:
|
||||
// Drawing the menu bar background in WM_MENUBAR_DRAWMENU somehow
|
||||
// leaves a single pixel line unpainted (and increasing the size of
|
||||
// the rectangle doesn't help, i.e. drawing is clipped to an area
|
||||
// which is one pixel too small), so we have to draw over it here
|
||||
// to get rid of it.
|
||||
{
|
||||
*result = w->MSWDefWindowProc(nMsg, wParam, lParam);
|
||||
|
||||
HWND hwnd = GetHwndOf(w);
|
||||
WindowHDC hdc(hwnd);
|
||||
|
||||
// Create a RECT one pixel above the client area: note that we
|
||||
// have to use window (and not client) coordinates for this as
|
||||
// this is outside of the client area of the window.
|
||||
const RECT rcWindow = wxGetWindowRect(hwnd);
|
||||
RECT rc = wxGetClientRect(hwnd);
|
||||
|
||||
// Convert client coordinates to window ones.
|
||||
wxMapWindowPoints(hwnd, HWND_DESKTOP, &rc);
|
||||
::OffsetRect(&rc, -rcWindow.left, -rcWindow.top);
|
||||
|
||||
rc.bottom = rc.top;
|
||||
rc.top--;
|
||||
|
||||
::FillRect(hdc, &rc, GetMenuBrush());
|
||||
}
|
||||
return true;
|
||||
|
||||
case WM_MENUBAR_DRAWMENUITEM:
|
||||
if ( auto* const drawMenuItem = (MenuBarDrawMenuItem*)lParam )
|
||||
{
|
||||
const DRAWITEMSTRUCT& dis = drawMenuItem->dis;
|
||||
|
||||
// Just a sanity check.
|
||||
if ( dis.CtlType != ODT_MENU )
|
||||
break;
|
||||
|
||||
wchar_t buf[256];
|
||||
WinStruct<MENUITEMINFO> mii;
|
||||
mii.fMask = MIIM_STRING;
|
||||
mii.dwTypeData = buf;
|
||||
mii.cch = sizeof(buf) - 1;
|
||||
|
||||
// Note that we need to use the iPosition field of the
|
||||
// undocumented struct here because DRAWITEMSTRUCT::itemID is
|
||||
// not initialized in the struct passed to us here, so this is
|
||||
// the only way to identify the item we're dealing with.
|
||||
if ( !::GetMenuItemInfo((HMENU)dis.hwndItem,
|
||||
drawMenuItem->mbmi.iPosition,
|
||||
TRUE,
|
||||
&mii) )
|
||||
break;
|
||||
|
||||
const UINT itemState = dis.itemState;
|
||||
|
||||
HBRUSH hbr = 0;
|
||||
int partState = 0;
|
||||
if ( itemState & ODS_INACTIVE )
|
||||
{
|
||||
partState = MBI_DISABLED;
|
||||
}
|
||||
else if ( (itemState & ODS_GRAYED) && (itemState & ODS_HOTLIGHT) )
|
||||
{
|
||||
partState = MBI_DISABLEDHOT;
|
||||
}
|
||||
else if ( itemState & ODS_GRAYED )
|
||||
{
|
||||
partState = MBI_DISABLED;
|
||||
}
|
||||
else if ( itemState & (ODS_HOTLIGHT | ODS_SELECTED) )
|
||||
{
|
||||
partState = MBI_HOT;
|
||||
|
||||
auto* const
|
||||
brush = wxTheBrushList->FindOrCreateBrush(COL_MENU_HOT);
|
||||
if ( brush )
|
||||
hbr = GetHbrushOf(*brush);
|
||||
}
|
||||
else
|
||||
{
|
||||
partState = MBI_NORMAL;
|
||||
}
|
||||
|
||||
RECT* const rcItem = const_cast<RECT*>(&dis.rcItem);
|
||||
|
||||
// Don't use DrawThemeBackground() here, as it doesn't use the
|
||||
// correct colours in the dark mode, at least not when using
|
||||
// the "Menu" theme.
|
||||
::FillRect(dis.hDC, &dis.rcItem, hbr ? hbr : GetMenuBrush());
|
||||
|
||||
// We have to specify the text colour explicitly as by default
|
||||
// black would be used, making the menu label unreadable on the
|
||||
// (almost) black background.
|
||||
DTTOPTS textOpts;
|
||||
textOpts.dwSize = sizeof(textOpts);
|
||||
textOpts.dwFlags = DTT_TEXTCOLOR;
|
||||
textOpts.crText = itemState & (ODS_INACTIVE | ODS_GRAYED)
|
||||
? COL_DISABLED
|
||||
: COL_STANDARD;
|
||||
|
||||
DWORD drawTextFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER;
|
||||
if ( itemState & ODS_NOACCEL)
|
||||
drawTextFlags |= DT_HIDEPREFIX;
|
||||
|
||||
wxUxThemeHandle menuTheme(GetHwndOf(w), L"Menu");
|
||||
::DrawThemeTextEx(menuTheme, dis.hDC, MENU_BARITEM, partState,
|
||||
buf, mii.cch, drawTextFlags, rcItem,
|
||||
&textOpts);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace wxMSWDarkMode
|
||||
|
||||
#else // !wxUSE_DARK_MODE
|
||||
|
||||
bool wxApp::MSWEnableDarkMode(int WXUNUSED(flags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace wxMSWDarkMode
|
||||
{
|
||||
|
||||
bool IsActive()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnableForTLW(HWND WXUNUSED(hwnd))
|
||||
{
|
||||
}
|
||||
|
||||
void AllowForWindow(HWND WXUNUSED(hwnd), const wchar_t* WXUNUSED(themeClass))
|
||||
{
|
||||
}
|
||||
|
||||
wxColour GetColour(wxSystemColour WXUNUSED(index))
|
||||
{
|
||||
return wxColour();
|
||||
}
|
||||
|
||||
HBRUSH GetBackgroundBrush()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PaintIfNecessary(HWND WXUNUSED(hwnd), WXWNDPROC WXUNUSED(defWndProc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
HandleMenuMessage(WXLRESULT* WXUNUSED(result),
|
||||
wxWindow* WXUNUSED(w),
|
||||
WXUINT WXUNUSED(nMsg),
|
||||
WXWPARAM WXUNUSED(wParam),
|
||||
WXLPARAM WXUNUSED(lParam))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace wxMSWDarkMode
|
||||
|
||||
#endif // wxUSE_DARK_MODE/!wxUSE_DARK_MODE
|
||||
|
|
@ -36,6 +36,8 @@
|
|||
#endif
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
#include "wx/evtloop.h"
|
||||
#include "wx/scopedptr.h"
|
||||
|
||||
|
|
@ -209,6 +211,10 @@ void wxDialog::SetWindowStyleFlag(long style)
|
|||
{
|
||||
wxDialogBase::SetWindowStyleFlag(style);
|
||||
|
||||
// Don't do anything if we're setting the style before creating the dialog.
|
||||
if ( !GetHwnd() )
|
||||
return;
|
||||
|
||||
if ( HasFlag(wxRESIZE_BORDER) )
|
||||
CreateGripper();
|
||||
else
|
||||
|
|
@ -235,6 +241,8 @@ void wxDialog::CreateGripper()
|
|||
wxGetInstance(),
|
||||
nullptr
|
||||
);
|
||||
|
||||
wxMSWDarkMode::AllowForWindow((HWND)m_hGripper);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,6 +356,13 @@ WXLRESULT wxDialog::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lPar
|
|||
::InvalidateRect(GetHwnd(), nullptr, false /* erase bg */);
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CTLCOLORDLG:
|
||||
// We need to explicitly set the dark background colour when using
|
||||
// dark mode, otherwise we'd be using the default light background.
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
return (WXLRESULT)wxMSWDarkMode::GetBackgroundBrush();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !processed )
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
#include "wx/generic/statusbr.h"
|
||||
|
||||
|
|
@ -837,6 +838,12 @@ WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lPara
|
|||
WXLRESULT rc = 0;
|
||||
bool processed = false;
|
||||
|
||||
if ( GetMenuBar() &&
|
||||
wxMSWDarkMode::HandleMenuMessage(&rc, this, message, wParam, lParam) )
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
switch ( message )
|
||||
{
|
||||
case WM_CLOSE:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "wx/msw/wrapcctl.h"
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/customdraw.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/winstyle.h"
|
||||
|
||||
#ifndef HDM_SETBITMAPMARGIN
|
||||
|
|
@ -49,36 +50,6 @@
|
|||
// from src/msw/listctrl.cpp
|
||||
extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxMSWHeaderCtrlCustomDraw: our custom draw helper
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class wxMSWHeaderCtrlCustomDraw : public wxMSWImpl::CustomDraw
|
||||
{
|
||||
public:
|
||||
wxMSWHeaderCtrlCustomDraw()
|
||||
{
|
||||
}
|
||||
|
||||
// Make this field public to let wxHeaderCtrl update it directly when its
|
||||
// attributes change.
|
||||
wxItemAttr m_attr;
|
||||
|
||||
private:
|
||||
virtual bool HasCustomDrawnItems() const override
|
||||
{
|
||||
// We only exist if the header does need to be custom drawn.
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const wxItemAttr*
|
||||
GetItemAttr(DWORD_PTR WXUNUSED(dwItemSpec)) const override
|
||||
{
|
||||
// We use the same attribute for all items for now.
|
||||
return &m_attr;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxMSWHeaderCtrl: the native header control
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -123,6 +94,8 @@ protected:
|
|||
int sizeFlags = wxSIZE_AUTO) override;
|
||||
virtual void MSWUpdateFontOnDPIChange(const wxSize& newDPI) override;
|
||||
|
||||
virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override;
|
||||
|
||||
// This function can be used as event handle for wxEVT_DPI_CHANGED event.
|
||||
void WXHandleDPIChanged(wxDPIChangedEvent& event);
|
||||
|
||||
|
|
@ -246,6 +219,12 @@ bool wxMSWHeaderCtrl::Create(wxWindow *parent,
|
|||
if ( !MSWCreateControl(WC_HEADER, wxT(""), pos, size) )
|
||||
return false;
|
||||
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
m_customDraw = new wxMSWHeaderCtrlCustomDraw();
|
||||
m_customDraw->UseHeaderThemeColors(GetHwnd());
|
||||
}
|
||||
|
||||
// special hack for margins when using comctl32.dll v6 or later: the
|
||||
// default margin is too big and results in label truncation when the
|
||||
// column width is just about right to show it together with the sort
|
||||
|
|
@ -276,6 +255,13 @@ WXDWORD wxMSWHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
|
|||
return msStyle;
|
||||
}
|
||||
|
||||
bool wxMSWHeaderCtrl::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
support.themeName = L"ItemsView";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxMSWHeaderCtrl::~wxMSWHeaderCtrl()
|
||||
{
|
||||
delete m_imageList;
|
||||
|
|
|
|||
|
|
@ -569,7 +569,7 @@ void wxListBox::SetHorizontalExtent(const wxString& s)
|
|||
return;
|
||||
|
||||
|
||||
WindowHDC dc(GetHwnd());
|
||||
ClientHDC dc(GetHwnd());
|
||||
SelectInHDC selFont(dc, GetHfontOf(GetFont()));
|
||||
|
||||
TEXTMETRIC lpTextMetric;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@
|
|||
#include "wx/vector.h"
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/uxtheme.h"
|
||||
#include "wx/msw/private/customdraw.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/keyboard.h"
|
||||
|
||||
// Currently gcc doesn't define NMLVFINDITEM, and DMC only defines
|
||||
|
|
@ -214,34 +216,6 @@ public:
|
|||
wxDECLARE_NO_COPY_CLASS(wxMSWListItemData);
|
||||
};
|
||||
|
||||
// wxMSWListHeaderCustomDraw: custom draw helper for the header
|
||||
class wxMSWListHeaderCustomDraw : public wxMSWImpl::CustomDraw
|
||||
{
|
||||
public:
|
||||
wxMSWListHeaderCustomDraw()
|
||||
{
|
||||
}
|
||||
|
||||
// Make this field public to let wxListCtrl update it directly when its
|
||||
// header attributes change.
|
||||
wxItemAttr m_attr;
|
||||
|
||||
private:
|
||||
virtual bool HasCustomDrawnItems() const override
|
||||
{
|
||||
// We only exist if the header does need to be custom drawn.
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const wxItemAttr*
|
||||
GetItemAttr(DWORD_PTR WXUNUSED(dwItemSpec)) const override
|
||||
{
|
||||
// We use the same attribute for all items for now.
|
||||
return &m_attr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
wxBEGIN_EVENT_TABLE(wxListCtrl, wxListCtrlBase)
|
||||
EVT_PAINT(wxListCtrl::OnPaint)
|
||||
EVT_CHAR_HOOK(wxListCtrl::OnCharHook)
|
||||
|
|
@ -289,15 +263,30 @@ bool wxListCtrl::Create(wxWindow *parent,
|
|||
// this style for them.
|
||||
MSWDisableComposited();
|
||||
|
||||
EnableSystemThemeByDefault();
|
||||
const wxVisualAttributes& defAttrs = GetDefaultAttributes();
|
||||
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
MSWInitHeader();
|
||||
|
||||
// We also need to explicitly set the background colour as the value
|
||||
// returned by GetBackgroundColour() by default doesn't match the
|
||||
// actually used colour neither when using dark mode.
|
||||
SetBackgroundColour(defAttrs.colBg);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableSystemThemeByDefault();
|
||||
}
|
||||
|
||||
// explicitly say that we want to use Unicode because otherwise we get ANSI
|
||||
// versions of _some_ messages (notably LVN_GETDISPINFOA)
|
||||
wxSetCCUnicodeFormat(GetHwnd());
|
||||
|
||||
// We must set the default text colour to the system/theme color, otherwise
|
||||
// GetTextColour will always return black
|
||||
SetTextColour(GetDefaultAttributes().colFg);
|
||||
// GetTextColour will always return black even if this is not what is used
|
||||
// by default.
|
||||
SetTextColour(defAttrs.colFg);
|
||||
|
||||
if ( InReportView() )
|
||||
MSWSetExListStyles();
|
||||
|
|
@ -360,6 +349,28 @@ void wxListCtrl::MSWSetExListStyles()
|
|||
::SendMessage(GetHwnd(), LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle);
|
||||
}
|
||||
|
||||
void wxListCtrl::MSWInitHeader()
|
||||
{
|
||||
// Currently we only need to do something here in dark mode.
|
||||
if ( !wxMSWDarkMode::IsActive() )
|
||||
return;
|
||||
|
||||
// It's not an error if the header doesn't exist.
|
||||
HWND hwndHdr = ListView_GetHeader(GetHwnd());
|
||||
if ( !hwndHdr )
|
||||
return;
|
||||
|
||||
// But if it does, configure it to use dark mode.
|
||||
wxMSWDarkMode::AllowForWindow(hwndHdr, L"ItemsView");
|
||||
|
||||
// It's not clear why do we have to do it, but without using custom drawing
|
||||
// the header text is drawn in black, making it unreadable, so do use it.
|
||||
if ( !m_headerCustomDraw )
|
||||
m_headerCustomDraw = new wxMSWHeaderCtrlCustomDraw();
|
||||
|
||||
m_headerCustomDraw->UseHeaderThemeColors(hwndHdr);
|
||||
}
|
||||
|
||||
void wxListCtrl::MSWAfterReparent()
|
||||
{
|
||||
// We did it for the original parent in our Create(), but we need to do it
|
||||
|
|
@ -593,10 +604,14 @@ void wxListCtrl::SetWindowStyleFlag(long flag)
|
|||
m_windowStyle &= ~(wxHSCROLL | wxVSCROLL);
|
||||
|
||||
// if we switched to the report view, set the extended styles for
|
||||
// it too
|
||||
// it too and configure the header which hadn't existed before
|
||||
if ( !wasInReportView && InReportView() )
|
||||
{
|
||||
MSWSetExListStyles();
|
||||
|
||||
MSWInitHeader();
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -605,6 +620,48 @@ void wxListCtrl::SetWindowStyleFlag(long flag)
|
|||
// accessors
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool wxListCtrl::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// There doesn't seem to be any theme that works well here:
|
||||
// - "Explorer" draws bluish hover highlight rectangle which is not at
|
||||
// all like the greyish one used by the actual Explorer in dark mode.
|
||||
// - "DarkMode_Explorer" uses the same selection colours as the light mode
|
||||
// and doesn't draw hover rectangle at all.
|
||||
// - "ItemsView", which we use currently, draws the selection and hover as
|
||||
// expected, but uses light mode scrollbars.
|
||||
support.themeName = L"ItemsView";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int wxListCtrl::MSWGetToolTipMessage() const
|
||||
{
|
||||
return LVM_GETTOOLTIPS;
|
||||
}
|
||||
|
||||
wxVisualAttributes wxListCtrl::GetDefaultAttributes() const
|
||||
{
|
||||
wxVisualAttributes attrs = GetClassDefaultAttributes(GetWindowVariant());
|
||||
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
// Note that we intentionally do not use this window HWND for the
|
||||
// theme, as it doesn't have dark values for it -- but does have them
|
||||
// when we use null window.
|
||||
wxUxThemeHandle theme{HWND(0), L"ItemsView"};
|
||||
|
||||
wxColour col = theme.GetColour(0, TMT_TEXTCOLOR);
|
||||
if ( col.IsOk() )
|
||||
attrs.colFg = col;
|
||||
|
||||
col = theme.GetColour(0, TMT_FILLCOLOR);
|
||||
if ( col.IsOk() )
|
||||
attrs.colBg = col;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
/* static */ wxVisualAttributes
|
||||
wxListCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
|
||||
{
|
||||
|
|
@ -667,7 +724,7 @@ bool wxListCtrl::SetHeaderAttr(const wxItemAttr& attr)
|
|||
else // We do have custom attributes.
|
||||
{
|
||||
if ( !m_headerCustomDraw )
|
||||
m_headerCustomDraw = new wxMSWListHeaderCustomDraw();
|
||||
m_headerCustomDraw = new wxMSWHeaderCtrlCustomDraw();
|
||||
|
||||
if ( m_headerCustomDraw->m_attr == attr )
|
||||
{
|
||||
|
|
@ -3082,7 +3139,7 @@ static void HandleItemPostpaint(NMCUSTOMDRAW nmcd)
|
|||
RECT rc = GetCustomDrawnItemRect(nmcd);
|
||||
|
||||
// don't use the provided HDC, it's in some strange state by now
|
||||
::DrawFocusRect(WindowHDC(nmcd.hdr.hwndFrom), &rc);
|
||||
::DrawFocusRect(ClientHDC(nmcd.hdr.hwndFrom), &rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "wx/ptr_scpd.h"
|
||||
#include "wx/dynlib.h"
|
||||
#include "wx/msw/private/button.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/metrics.h"
|
||||
#include "wx/msw/private/msgdlg.h"
|
||||
#include "wx/modalhook.h"
|
||||
|
|
@ -86,10 +87,7 @@ wxMessageDialogMap& HookMap()
|
|||
void ScreenRectToClient(HWND hwnd, RECT& rc)
|
||||
{
|
||||
// map from desktop (i.e. screen) coordinates to ones of this window
|
||||
//
|
||||
// notice that a RECT is laid out as 2 consecutive POINTs so the cast is
|
||||
// valid
|
||||
::MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast<POINT *>(&rc), 2);
|
||||
wxMapWindowPoints(HWND_DESKTOP, hwnd, &rc);
|
||||
}
|
||||
|
||||
// set window position to the given rect
|
||||
|
|
@ -599,6 +597,24 @@ void wxMessageDialog::DoCentre(int dir)
|
|||
// Helpers of the wxMSWMessageDialog namespace
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
HRESULT CALLBACK
|
||||
wxTaskDialogCallback(HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR)
|
||||
{
|
||||
switch ( msg )
|
||||
{
|
||||
case TDN_DIALOG_CONSTRUCTED:
|
||||
wxMSWDarkMode::EnableForTLW(hwnd);
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg)
|
||||
: buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS])
|
||||
{
|
||||
|
|
@ -747,6 +763,8 @@ void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc)
|
|||
|
||||
AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel);
|
||||
}
|
||||
|
||||
tdc.pfCallback = wxTaskDialogCallback;
|
||||
}
|
||||
|
||||
void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc,
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@
|
|||
#include "wx/app.h"
|
||||
#include "wx/dcclient.h"
|
||||
#include "wx/dcmemory.h"
|
||||
#include "wx/control.h"
|
||||
#include "wx/panel.h"
|
||||
#include "wx/settings.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/imaglist.h"
|
||||
#include "wx/renderer.h"
|
||||
#include "wx/sysopt.h"
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
#include <windowsx.h>
|
||||
#include "wx/msw/winundef.h"
|
||||
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
#if wxUSE_UXTHEME
|
||||
#include "wx/msw/uxtheme.h"
|
||||
#endif
|
||||
|
|
@ -221,12 +223,34 @@ bool wxNotebook::Create(wxWindow *parent,
|
|||
if ( !MSWCreateControl(className, wxEmptyString, pos, size) )
|
||||
return false;
|
||||
|
||||
Bind(wxEVT_PAINT, &wxNotebook::OnPaint, this);
|
||||
|
||||
// Inherit parent attributes and, unlike the default, also inherit the
|
||||
// parent background colour in order to blend in with its background if
|
||||
// it's set to a non-default value.
|
||||
// it's set to a non-default value -- or if we're using dark mode, in which
|
||||
// the default colour always needs to be changed.
|
||||
InheritAttributes();
|
||||
if ( parent->InheritsBackgroundColour() && !UseBgCol() )
|
||||
SetBackgroundColour(parent->GetBackgroundColour());
|
||||
if ( !UseBgCol() )
|
||||
{
|
||||
wxColour colBg;
|
||||
if ( parent->InheritsBackgroundColour() )
|
||||
{
|
||||
colBg = parent->GetBackgroundColour();
|
||||
}
|
||||
else if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
|
||||
// We also need to change the foreground in this case to ensure a
|
||||
// good contrast with the dark background.
|
||||
SetForegroundColour(
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)
|
||||
);
|
||||
}
|
||||
|
||||
if ( colBg.IsOk() )
|
||||
SetBackgroundColour(colBg);
|
||||
}
|
||||
|
||||
#if wxUSE_UXTHEME
|
||||
if ( HasFlag(wxNB_NOPAGETHEME) ||
|
||||
|
|
@ -281,6 +305,11 @@ WXDWORD wxNotebook::MSWGetStyle(long style, WXDWORD *exstyle) const
|
|||
return tabStyle;
|
||||
}
|
||||
|
||||
int wxNotebook::MSWGetToolTipMessage() const
|
||||
{
|
||||
return TCM_GETTOOLTIPS;
|
||||
}
|
||||
|
||||
wxNotebook::~wxNotebook()
|
||||
{
|
||||
#if wxUSE_UXTHEME
|
||||
|
|
@ -976,8 +1005,16 @@ int wxNotebook::HitTest(const wxPoint& pt, long *flags) const
|
|||
LRESULT APIENTRY
|
||||
wxNotebookSpinBtnWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if ( message == WM_ERASEBKGND )
|
||||
return 0;
|
||||
switch ( message )
|
||||
{
|
||||
case WM_ERASEBKGND:
|
||||
return 0;
|
||||
|
||||
case WM_PAINT:
|
||||
if ( wxMSWDarkMode::PaintIfNecessary(hwnd, gs_wndprocNotebookSpinBtn) )
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebookSpinBtn,
|
||||
hwnd, message, wParam, lParam);
|
||||
|
|
@ -995,9 +1032,288 @@ void wxNotebook::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
|
|||
// do nothing here
|
||||
}
|
||||
|
||||
void wxNotebook::OnPaint(wxPaintEvent& WXUNUSED(event))
|
||||
namespace
|
||||
{
|
||||
|
||||
// Flags may include:
|
||||
// - wxCONTROL_SELECTED for the currently selected tab
|
||||
// - wxCONTROL_CURRENT for the "hot" tab, i.e. the one under mouse pointer
|
||||
// - wxCONTROL_SPECIAL for the first tab.
|
||||
void
|
||||
DrawNotebookTab(wxWindow* win,
|
||||
wxDC& dc,
|
||||
const wxRect& rectOrig,
|
||||
const wxString& text,
|
||||
const wxBitmap& image,
|
||||
wxDirection tabOrient,
|
||||
int flags = wxCONTROL_NONE)
|
||||
{
|
||||
// This colour is just an approximation which seems to look acceptable.
|
||||
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_MENUBAR));
|
||||
|
||||
const int selectedOffset = win->FromDIP(2);
|
||||
const int labelOffset = 3*selectedOffset;
|
||||
|
||||
wxRect rectTab = rectOrig;
|
||||
wxColour colTab;
|
||||
if ( flags & wxCONTROL_SELECTED )
|
||||
{
|
||||
// Selected tab literally stands out, so make it bigger -- but clip
|
||||
// drawing to ensure we don't draw the inner border of the inflated
|
||||
// selected tab rectangle, it shouldn't overflow into the notebook
|
||||
// page area.
|
||||
rectTab.Inflate(selectedOffset);
|
||||
|
||||
wxRect rectClip = rectTab;
|
||||
switch ( tabOrient )
|
||||
{
|
||||
case wxTOP:
|
||||
rectClip.height -= selectedOffset;
|
||||
break;
|
||||
|
||||
case wxBOTTOM:
|
||||
rectClip.y += selectedOffset;
|
||||
rectClip.height -= selectedOffset;
|
||||
break;
|
||||
|
||||
case wxLEFT:
|
||||
rectClip.width -= selectedOffset;
|
||||
break;
|
||||
|
||||
case wxRIGHT:
|
||||
rectClip.x += selectedOffset;
|
||||
rectClip.width -= selectedOffset;
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL_MSG("unreachable");
|
||||
}
|
||||
|
||||
dc.SetClippingRegion(rectClip);
|
||||
|
||||
colTab = win->GetBackgroundColour();
|
||||
}
|
||||
else // not the selected tab
|
||||
{
|
||||
// All tab rectangles overlap the previous one to avoid double pixel
|
||||
// borders between them in Windows 10 flat look, except for the first
|
||||
// one which has nothing to overlap.
|
||||
if ( !(flags & wxCONTROL_SPECIAL) )
|
||||
{
|
||||
switch ( tabOrient )
|
||||
{
|
||||
case wxTOP:
|
||||
case wxBOTTOM:
|
||||
rectTab.x--;
|
||||
rectTab.width++;
|
||||
break;
|
||||
|
||||
case wxLEFT:
|
||||
case wxRIGHT:
|
||||
rectTab.y--;
|
||||
rectTab.height++;
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL_MSG("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
if ( flags & wxCONTROL_CURRENT )
|
||||
colTab = wxSystemSettings::GetColour(wxSYS_COLOUR_HOTLIGHT);
|
||||
else
|
||||
colTab = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW);
|
||||
}
|
||||
|
||||
dc.SetBrush(colTab);
|
||||
dc.DrawRectangle(rectTab);
|
||||
|
||||
wxRect rectLabel = rectOrig;
|
||||
if ( flags & wxCONTROL_SELECTED )
|
||||
{
|
||||
dc.DestroyClippingRegion();
|
||||
|
||||
// Also shift the label to mimic the native control which makes it "pop
|
||||
// up" for the selected tab (with "up" being "in the tab direction").
|
||||
switch ( tabOrient )
|
||||
{
|
||||
case wxTOP:
|
||||
rectLabel.y -= selectedOffset;
|
||||
break;
|
||||
|
||||
case wxBOTTOM:
|
||||
rectLabel.y += selectedOffset;
|
||||
break;
|
||||
|
||||
case wxLEFT:
|
||||
rectLabel.x -= selectedOffset;
|
||||
break;
|
||||
|
||||
case wxRIGHT:
|
||||
rectLabel.x += selectedOffset;
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL_MSG("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
rectLabel.Deflate(labelOffset);
|
||||
|
||||
// Draw the label and the image, if any.
|
||||
switch ( tabOrient )
|
||||
{
|
||||
case wxTOP:
|
||||
case wxBOTTOM:
|
||||
// We can use an existing helper that will do everything for us.
|
||||
dc.DrawLabel(text, image, rectLabel,
|
||||
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
||||
break;
|
||||
|
||||
case wxLEFT:
|
||||
case wxRIGHT:
|
||||
{
|
||||
const wxSize textSize = dc.GetTextExtent(text);
|
||||
|
||||
// Exchange width and height because we're drawing text
|
||||
// vertically.
|
||||
wxSize totalSize{textSize.y, textSize.x};
|
||||
|
||||
int textOfs = 0;
|
||||
wxSize imageSize;
|
||||
if ( image.IsOk() )
|
||||
{
|
||||
imageSize = image.GetSize();
|
||||
|
||||
// Use label offset for the gap between image and the label
|
||||
// too because why not.
|
||||
totalSize.y += imageSize.y + labelOffset;
|
||||
|
||||
if ( imageSize.x > totalSize.x )
|
||||
{
|
||||
textOfs = (imageSize.x - totalSize.x) / 2;
|
||||
totalSize.x = imageSize.x;
|
||||
}
|
||||
}
|
||||
|
||||
// Native control actually draws text bottom/top aligned in the
|
||||
// first/only row but centers them if there is more than one
|
||||
// row of tabs. Don't bother with this, especially because it's
|
||||
// really not obvious that it looks any better and just center
|
||||
// them always.
|
||||
const wxRect rect = wxRect(totalSize).CentreIn(rectLabel);
|
||||
|
||||
if ( tabOrient == wxLEFT )
|
||||
{
|
||||
int y = rect.y + textSize.x;
|
||||
|
||||
dc.DrawRotatedText(text, rect.x + textOfs, y, 90.0);
|
||||
|
||||
if ( image.IsOk() )
|
||||
dc.DrawBitmap(image, rect.x, y + labelOffset, true);
|
||||
}
|
||||
else // tabOrient == wxRIGHT
|
||||
{
|
||||
int y = rect.y;
|
||||
|
||||
if ( image.IsOk() )
|
||||
{
|
||||
dc.DrawBitmap(image, rect.x, y, true);
|
||||
|
||||
y += imageSize.y + labelOffset;
|
||||
}
|
||||
|
||||
dc.DrawRotatedText(text, rect.GetRight() - textOfs, y, -90.0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL_MSG("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void wxNotebook::MSWNotebookPaint(wxDC& dc)
|
||||
{
|
||||
dc.Clear();
|
||||
|
||||
const wxDirection tabOrient = GetTabOrientation();
|
||||
|
||||
const wxSize sizeWindow = GetClientSize();
|
||||
const int selected = GetSelection();
|
||||
const wxPoint posCursor = ScreenToClient(wxGetMousePosition());
|
||||
|
||||
const auto drawTab = [this, &dc, tabOrient](wxRect rect, size_t n, int flags)
|
||||
{
|
||||
if ( n == 0 )
|
||||
flags |= wxCONTROL_SPECIAL;
|
||||
|
||||
DrawNotebookTab(this, dc, rect,
|
||||
GetPageText(n),
|
||||
GetImageBitmapFor(this, GetPageImage(n)),
|
||||
tabOrient,
|
||||
flags);
|
||||
};
|
||||
|
||||
const size_t pages = GetPageCount();
|
||||
for ( size_t n = 0; n < pages; ++n )
|
||||
{
|
||||
if ( static_cast<int>(n) == selected )
|
||||
{
|
||||
// We're going to draw this one after all the other ones as it
|
||||
// overlaps them.
|
||||
continue;
|
||||
}
|
||||
|
||||
const wxRect rect = GetTabRect(n);
|
||||
|
||||
// For horizontal tabs, some of them can be scrolled out of view, skip
|
||||
// drawing them just in case we have zillions of tabs to avoid drawing
|
||||
// the off-screen ones unnecessarily.
|
||||
if ( rect.x > sizeWindow.x )
|
||||
{
|
||||
// This tab, and all the remaining ones, can't be seen anyhow, so
|
||||
// don't bother drawing them.
|
||||
break;
|
||||
}
|
||||
|
||||
// We can't track the "hot" tab when using non-top tabs as the native
|
||||
// control doesn't refresh them on mouse move (it seems to switch to
|
||||
// comctl32.dll v5-like implementation in this case), so don't paint
|
||||
// them specially.
|
||||
int flags = wxCONTROL_NONE;
|
||||
if ( tabOrient == wxTOP && rect.Contains(posCursor) )
|
||||
flags |= wxCONTROL_CURRENT;
|
||||
|
||||
drawTab(rect, n, flags);
|
||||
}
|
||||
|
||||
if ( selected != wxNOT_FOUND )
|
||||
drawTab(GetTabRect(selected), selected, wxCONTROL_SELECTED);
|
||||
}
|
||||
|
||||
void wxNotebook::OnPaint(wxPaintEvent& event)
|
||||
{
|
||||
// We can rely on the default implementation if we don't have a custom
|
||||
// background colour (note that it is always set when using dark mode).
|
||||
if ( !m_hasBgCol )
|
||||
{
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
wxPaintDC dc(this);
|
||||
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
// We can't use default painting in dark mode, it just doesn't work
|
||||
// there, whichever theme we use, so draw everything ourselves.
|
||||
MSWNotebookPaint(dc);
|
||||
return;
|
||||
}
|
||||
|
||||
RECT rc;
|
||||
::GetClientRect(GetHwnd(), &rc);
|
||||
if ( !rc.right || !rc.bottom )
|
||||
|
|
@ -1306,11 +1622,9 @@ bool wxNotebook::SetBackgroundColour(const wxColour& colour)
|
|||
|
||||
#if USE_NOTEBOOK_ANTIFLICKER
|
||||
Unbind(wxEVT_ERASE_BACKGROUND, &wxNotebook::OnEraseBackground, this);
|
||||
Unbind(wxEVT_PAINT, &wxNotebook::OnPaint, this);
|
||||
if ( m_hasBgCol || !wxUxThemeIsActive() )
|
||||
{
|
||||
Bind(wxEVT_ERASE_BACKGROUND, &wxNotebook::OnEraseBackground, this);
|
||||
Bind(wxEVT_PAINT, &wxNotebook::OnPaint, this);
|
||||
}
|
||||
#endif // USE_NOTEBOOK_ANTIFLICKER
|
||||
|
||||
|
|
@ -1330,7 +1644,7 @@ WXHBRUSH wxNotebook::QueryBgBitmap()
|
|||
if ( !theme )
|
||||
return 0;
|
||||
|
||||
WindowHDC hDC(GetHwnd());
|
||||
ClientHDC hDC(GetHwnd());
|
||||
|
||||
RECT rcBg;
|
||||
::GetThemeBackgroundContentRect(theme,
|
||||
|
|
@ -1398,7 +1712,7 @@ bool wxNotebook::MSWPrintChild(WXHDC hDC, wxWindow *child)
|
|||
|
||||
// map rect to the coords of the window we're drawing in
|
||||
if ( child )
|
||||
::MapWindowPoints(GetHwnd(), GetHwndOf(child), (POINT *)&rc, 2);
|
||||
wxMapWindowPoints(GetHwnd(), GetHwndOf(child), &rc);
|
||||
|
||||
// If we're using a solid colour (for example if we've switched off
|
||||
// theming for this notebook), paint it
|
||||
|
|
@ -1457,14 +1771,8 @@ wxColour wxNotebook::GetThemeBackgroundColour() const
|
|||
// This is total guesswork.
|
||||
// See PlatformSDK\Include\Tmschema.h for values.
|
||||
// JACS: can also use 9 (TABP_PANE)
|
||||
COLORREF themeColor;
|
||||
bool success = (S_OK == ::GetThemeColor(
|
||||
hTheme,
|
||||
10 /* TABP_BODY */,
|
||||
1 /* NORMAL */,
|
||||
3821 /* FILLCOLORHINT */,
|
||||
&themeColor));
|
||||
if (!success)
|
||||
wxColour colour = hTheme.GetColour(TABP_BODY, TMT_FILLCOLORHINT, TIS_NORMAL);
|
||||
if ( !colour.IsOk() )
|
||||
return GetBackgroundColour();
|
||||
|
||||
/*
|
||||
|
|
@ -1476,17 +1784,8 @@ wxColour wxNotebook::GetThemeBackgroundColour() const
|
|||
This workaround potentially breaks appearance of some themes,
|
||||
but in practice it already fixes some themes.
|
||||
*/
|
||||
if (themeColor == 1)
|
||||
{
|
||||
::GetThemeColor(
|
||||
hTheme,
|
||||
10 /* TABP_BODY */,
|
||||
1 /* NORMAL */,
|
||||
3802 /* FILLCOLOR */,
|
||||
&themeColor);
|
||||
}
|
||||
|
||||
wxColour colour = wxRGBToColour(themeColor);
|
||||
if ( colour.GetRGB() == 1 )
|
||||
colour = hTheme.GetColour(TABP_BODY, TMT_FILLCOLOR, TIS_NORMAL);
|
||||
|
||||
// Under Vista, the tab background colour is reported incorrectly.
|
||||
// So for the default theme at least, hard-code the colour to something
|
||||
|
|
@ -1582,8 +1881,15 @@ bool wxNotebook::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result)
|
|||
// Change the selection before generating the event as its handler should
|
||||
// already see the new page selected.
|
||||
if ( hdr->code == TCN_SELCHANGE )
|
||||
{
|
||||
UpdateSelection(event.GetSelection());
|
||||
|
||||
// We need to update the tabs after the selection change when drawing
|
||||
// them ourselves, otherwise the previously selected tab is not redrawn.
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool processed = HandleWindowEvent(event);
|
||||
*result = !event.IsAllowed();
|
||||
return processed;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,19 @@ bool wxRadioButton::Create(wxWindow *parent,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool wxRadioButton::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// Weirdly enough, even though radio buttons support dark theme (they
|
||||
// notably change the way they draw the focus rectangle if we set it), they
|
||||
// still use the default black foreground colour in it, making their text
|
||||
// unreadable, so we need to change it manually.
|
||||
wxRadioButtonBase::MSWGetDarkModeSupport(support);
|
||||
|
||||
support.setForeground = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxRadioButton functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1223,23 +1223,11 @@ void wxRendererXP::DrawTextCtrl(wxWindow* win,
|
|||
return;
|
||||
}
|
||||
|
||||
wxColour fill;
|
||||
wxColour bdr;
|
||||
COLORREF cref;
|
||||
|
||||
::GetThemeColor(hTheme, EP_EDITTEXT,
|
||||
ETS_NORMAL, TMT_FILLCOLOR, &cref);
|
||||
fill = wxRGBToColour(cref);
|
||||
|
||||
int etsState;
|
||||
if ( flags & wxCONTROL_DISABLED )
|
||||
etsState = ETS_DISABLED;
|
||||
else
|
||||
etsState = ETS_NORMAL;
|
||||
|
||||
::GetThemeColor(hTheme, EP_EDITTEXT,
|
||||
etsState, TMT_BORDERCOLOR, &cref);
|
||||
bdr = wxRGBToColour(cref);
|
||||
wxColour fill = hTheme.GetColour(EP_EDITTEXT, TMT_FILLCOLOR, ETS_NORMAL);
|
||||
wxColour bdr = hTheme.GetColour(EP_EDITTEXT, TMT_BORDERCOLOR,
|
||||
flags & wxCONTROL_DISABLED
|
||||
? ETS_DISABLED
|
||||
: ETS_NORMAL);
|
||||
|
||||
wxDCPenChanger setPen(dc, bdr);
|
||||
wxDCBrushChanger setBrush(dc, fill);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/missing.h" // for SM_CXCURSOR, SM_CYCURSOR, SM_TABLETPC
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/metrics.h"
|
||||
#include "wx/msw/registry.h"
|
||||
|
||||
|
|
@ -98,6 +99,14 @@ void wxSystemSettingsModule::OnExit()
|
|||
|
||||
wxColour wxSystemSettingsNative::GetColour(wxSystemColour index)
|
||||
{
|
||||
// As GetSysColor() doesn't support dark mode, check for it before using it.
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
const wxColour colDark = wxMSWDarkMode::GetColour(index);
|
||||
if ( colDark.IsOk() )
|
||||
return colDark;
|
||||
}
|
||||
|
||||
if ( index == wxSYS_COLOUR_LISTBOXTEXT)
|
||||
{
|
||||
// there is no standard colour with this index, map to another one
|
||||
|
|
@ -362,22 +371,50 @@ extern wxFont wxGetCCDefaultFont()
|
|||
#endif // wxUSE_LISTCTRL || wxUSE_TREECTRL
|
||||
|
||||
// There is no official API for determining whether dark mode is being used,
|
||||
// but // HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize has
|
||||
// a value AppsUseLightTheme = 0 for dark mode and 1 for normal mode, so use it
|
||||
// and fall back to the generic algorithm in IsUsingDarkBackground() if it's
|
||||
// absent.
|
||||
// but HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
|
||||
// contains AppsUseLightTheme and SystemUsesLightTheme values determining
|
||||
// whether the applications/system use light or dark mode, so use them.
|
||||
//
|
||||
// Adapted from https://stackoverflow.com/a/51336913/15275 ("How to detect
|
||||
// Windows 10 light/dark mode in Win32 application?").
|
||||
bool wxSystemAppearance::IsDark() const
|
||||
namespace
|
||||
{
|
||||
|
||||
// Return false unless we are sure we're using the dark mode.
|
||||
bool IsUsingDarkTheme(const wxString& forWhat)
|
||||
{
|
||||
wxRegKey rk(wxRegKey::HKCU, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
|
||||
if ( rk.Exists() && rk.HasValue("AppsUseLightTheme") )
|
||||
if ( rk.Exists() && rk.HasValue(forWhat) )
|
||||
{
|
||||
long value = -1;
|
||||
if ( rk.QueryValue("AppsUseLightTheme", &value) )
|
||||
if ( rk.QueryValue(forWhat, &value) )
|
||||
return value <= 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool wxSystemAppearance::IsDark() const
|
||||
{
|
||||
// If the application opted in using dark mode, use the undocumented API
|
||||
// which we use for dark mode support directly.
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
return true;
|
||||
|
||||
// Note that we should _not_ check if the system is configured to use the
|
||||
// dark mode for the other applications here, what matters is whether this
|
||||
// application itself uses dark colour schema or not.
|
||||
return IsUsingDarkBackground();
|
||||
}
|
||||
|
||||
bool wxSystemAppearance::AreAppsDark() const
|
||||
{
|
||||
return IsUsingDarkTheme("AppsUseLightTheme");
|
||||
}
|
||||
|
||||
bool wxSystemAppearance::IsSystemDark() const
|
||||
{
|
||||
return IsUsingDarkTheme("SystemUsesLightTheme");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,13 @@ wxSpinButton::~wxSpinButton()
|
|||
{
|
||||
}
|
||||
|
||||
bool wxSpinButton::MSWShouldUseAutoDarkMode() const
|
||||
{
|
||||
// Native control doesn't seem to have any support for dark theme, so
|
||||
// invert it in dark mode -- this is not great, but better than nothing.
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// size calculation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -95,6 +95,15 @@ bool wxStaticBox::Create(wxWindow *parent,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool wxStaticBox::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// Static boxes don't seem to have any dark mode support, so just set the
|
||||
// foreground colour contrasting with the dark background for them.
|
||||
support.setForeground = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxStaticBox::ShouldUseCustomPaint() const
|
||||
{
|
||||
// When not using double buffering, we paint the box ourselves by default
|
||||
|
|
|
|||
|
|
@ -679,4 +679,15 @@ bool wxStatusBar::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM* W
|
|||
}
|
||||
#endif // wxUSE_TOOLTIPS
|
||||
|
||||
bool wxStatusBar::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
// This is not documented anywhere but seems to work.
|
||||
//
|
||||
// Note that we should _not_ set the theme name to "Explorer", this ID only
|
||||
// works if we do _not_ do it.
|
||||
support.themeId = L"ExplorerStatusBar";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // wxUSE_STATUSBAR && wxUSE_NATIVE_STATUSBAR
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@
|
|||
#include "wx/msw/uxtheme.h"
|
||||
#endif
|
||||
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// constants
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -334,6 +336,11 @@ static bool MSWShouldBeChecked(const wxToolBarToolBase *tool)
|
|||
return tool->IsToggled();
|
||||
}
|
||||
|
||||
static COLORREF wxSysColourToRGB(wxSystemColour syscol)
|
||||
{
|
||||
return wxColourToRGB(wxSystemSettings::GetColour(syscol));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
|
@ -463,6 +470,18 @@ bool wxToolBar::MSWCreateToolbar(const wxPoint& pos, const wxSize& size)
|
|||
}
|
||||
#endif // wxUSE_TOOLTIPS
|
||||
|
||||
// Change the color scheme when using the dark mode even though MSDN says
|
||||
// that it's not used with comctl32 v6, it actually still is for "3D"
|
||||
// separator above the toolbar, which is drawn partially in white by
|
||||
// default and so looks very ugly in dark mode.
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
COLORSCHEME colScheme{sizeof(COLORSCHEME)};
|
||||
colScheme.clrBtnHighlight =
|
||||
colScheme.clrBtnShadow = wxSysColourToRGB(wxSYS_COLOUR_WINDOW);
|
||||
::SendMessage(GetHwnd(), TB_SETCOLORSCHEME, 0, (LPARAM)&colScheme);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -714,6 +733,22 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const
|
|||
return msStyle;
|
||||
}
|
||||
|
||||
bool wxToolBar::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
||||
{
|
||||
wxToolBarBase::MSWGetDarkModeSupport(support);
|
||||
|
||||
// This ensures GetForegroundColour(), used in our custom draw code,
|
||||
// returns the correct colour.
|
||||
support.setForeground = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int wxToolBar::MSWGetToolTipMessage() const
|
||||
{
|
||||
return TB_GETTOOLTIPS;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// adding/removing tools
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -1611,7 +1646,7 @@ bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_)
|
|||
|
||||
bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl),
|
||||
WXLPARAM lParam,
|
||||
WXLPARAM *WXUNUSED(result))
|
||||
WXLPARAM *result)
|
||||
{
|
||||
LPNMHDR hdr = (LPNMHDR)lParam;
|
||||
if ( hdr->code == TBN_DROPDOWN )
|
||||
|
|
@ -1640,6 +1675,32 @@ bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl),
|
|||
return true;
|
||||
}
|
||||
|
||||
if ( hdr->code == NM_CUSTOMDRAW )
|
||||
{
|
||||
NMTBCUSTOMDRAW* const nmtbcd = (NMTBCUSTOMDRAW*)lParam;
|
||||
switch ( nmtbcd->nmcd.dwDrawStage )
|
||||
{
|
||||
case CDDS_PREPAINT:
|
||||
if ( !wxMSWDarkMode::IsActive() )
|
||||
break;
|
||||
|
||||
*result = CDRF_NOTIFYITEMDRAW;
|
||||
return true;
|
||||
|
||||
case CDDS_ITEMPREPAINT:
|
||||
// If we get here, we must have returned CDRF_NOTIFYITEMDRAW
|
||||
// from above, so we're using the dark mode and need to
|
||||
// customize the colours for it.
|
||||
nmtbcd->clrText =
|
||||
nmtbcd->clrTextHighlight = wxColourToRGB(GetForegroundColour());
|
||||
nmtbcd->clrHighlightHotTrack = wxSysColourToRGB(wxSYS_COLOUR_HOTLIGHT);
|
||||
|
||||
*result = CDRF_DODEFAULT | TBCDRF_USECDCOLORS | TBCDRF_HILITEHOTTRACK;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !HasFlag(wxTB_NO_TOOLTIPS) )
|
||||
{
|
||||
|
|
@ -2103,7 +2164,7 @@ bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam)
|
|||
{
|
||||
// erase the dummy separators region ourselves now as nobody painted
|
||||
// over them
|
||||
WindowHDC hdc(GetHwnd());
|
||||
ClientHDC hdc(GetHwnd());
|
||||
::SelectClipRgn(hdc, GetHrgnOf(rgnDummySeps));
|
||||
MSWDoEraseBackground(hdc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "wx/tokenzr.h"
|
||||
#include "wx/vector.h"
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
|
||||
#ifndef TTTOOLINFO_V1_SIZE
|
||||
#define TTTOOLINFO_V1_SIZE 0x28
|
||||
|
|
@ -314,6 +315,7 @@ WXHWND wxToolTip::GetToolTipCtrl()
|
|||
if ( ms_hwndTT )
|
||||
{
|
||||
HWND hwnd = (HWND)ms_hwndTT;
|
||||
wxMSWDarkMode::AllowForWindow(hwnd);
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include "wx/tooltip.h"
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/winstyle.h"
|
||||
|
||||
#include "wx/msw/winundef.h"
|
||||
|
|
@ -510,6 +511,8 @@ bool wxTopLevelWindowMSW::Create(wxWindow *parent,
|
|||
// focus rectangles) work under Win2k+
|
||||
MSWUpdateUIState(UIS_INITIALIZE);
|
||||
|
||||
wxMSWDarkMode::EnableForTLW(GetHwnd());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2220,6 +2220,11 @@ void wxTreeCtrl::SortChildren(const wxTreeItemId& item)
|
|||
// implementation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int wxTreeCtrl::MSWGetToolTipMessage() const
|
||||
{
|
||||
return TVM_GETTOOLTIPS;
|
||||
}
|
||||
|
||||
bool wxTreeCtrl::MSWShouldPreProcessMessage(WXMSG* msg)
|
||||
{
|
||||
if ( msg->message == WM_KEYDOWN )
|
||||
|
|
@ -3667,7 +3672,7 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
|
|||
POINT point;
|
||||
point.x = GET_X_LPARAM(pos);
|
||||
point.y = GET_Y_LPARAM(pos);
|
||||
::MapWindowPoints(HWND_DESKTOP, GetHwnd(), &point, 1);
|
||||
wxMapWindowPoints(HWND_DESKTOP, GetHwnd(), &point);
|
||||
int htFlags = 0;
|
||||
wxTreeItemId item = HitTest(wxPoint(point.x, point.y), htFlags);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,23 @@ bool wxUxThemeIsActive()
|
|||
{
|
||||
return ::IsAppThemed() && ::IsThemeActive();
|
||||
}
|
||||
|
||||
wxColour wxUxThemeHandle::GetColour(int part, int prop, int state) const
|
||||
{
|
||||
COLORREF col;
|
||||
|
||||
HRESULT hr = ::GetThemeColor(m_hTheme, part, state, prop, &col);
|
||||
if ( FAILED(hr) )
|
||||
{
|
||||
wxLogApiError(
|
||||
wxString::Format("GetThemeColor(%i, %i, %i)", part, state, prop),
|
||||
hr
|
||||
);
|
||||
return wxColour{};
|
||||
}
|
||||
|
||||
return wxRGBToColour(col);
|
||||
}
|
||||
#else
|
||||
bool wxUxThemeIsActive()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
#endif
|
||||
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/private/darkmode.h"
|
||||
#include "wx/msw/private/dpiaware.h"
|
||||
#include "wx/msw/private/keyboard.h"
|
||||
#include "wx/msw/private/paint.h"
|
||||
|
|
@ -1962,8 +1963,9 @@ void wxWindowMSW::DoGetPosition(int *x, int *y) const
|
|||
{
|
||||
// In RTL mode, we want the logical left x-coordinate,
|
||||
// which would be the physical right x-coordinate.
|
||||
::MapWindowPoints(nullptr, parent ? GetHwndOf(parent) : HWND_DESKTOP,
|
||||
(LPPOINT)&rect, 2);
|
||||
wxMapWindowPoints(HWND_DESKTOP,
|
||||
parent ? GetHwndOf(parent) : HWND_DESKTOP,
|
||||
&rect);
|
||||
}
|
||||
|
||||
pos.x = rect.left;
|
||||
|
|
@ -3145,7 +3147,11 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
|
|||
}
|
||||
else // no DC given
|
||||
{
|
||||
processed = HandlePaint();
|
||||
if ( MSWShouldUseAutoDarkMode() &&
|
||||
wxMSWDarkMode::PaintIfNecessary(GetHwnd(), m_oldWndProc) )
|
||||
processed = true;
|
||||
else
|
||||
processed = HandlePaint();
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -3824,7 +3830,7 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
|
|||
// it below if it fails.
|
||||
RECT rcClient;
|
||||
|
||||
WindowHDC hdc(GetHwnd());
|
||||
ClientHDC hdc(GetHwnd());
|
||||
|
||||
if ( ::GetThemeBackgroundContentRect
|
||||
(
|
||||
|
|
@ -4082,6 +4088,16 @@ bool wxWindowMSW::MSWCreate(const wxChar *wclass,
|
|||
return false;
|
||||
}
|
||||
|
||||
if ( wxMSWDarkMode::IsActive() )
|
||||
{
|
||||
// We currently allow customizing the theme at wxControl level as some
|
||||
// native controls require using a different theme, but for plain
|
||||
// windows it looks like the default ("Explorer") should always be used
|
||||
// and its only (but important) effect is to make their scrollbars
|
||||
// dark, if they're used.
|
||||
wxMSWDarkMode::AllowForWindow(m_hWnd);
|
||||
}
|
||||
|
||||
SubclassWin(m_hWnd);
|
||||
|
||||
return true;
|
||||
|
|
@ -4886,7 +4902,7 @@ wxSize wxWindowMSW::GetDPI() const
|
|||
|
||||
if ( !dpi.x || !dpi.y )
|
||||
{
|
||||
dpi = wxGetDPIofHDC(WindowHDC(hwnd));
|
||||
dpi = wxGetDPIofHDC(ClientHDC(hwnd));
|
||||
}
|
||||
|
||||
return dpi;
|
||||
|
|
@ -5103,6 +5119,13 @@ bool wxWindowMSW::HandleCaptureChanged(WXHWND hWndGainedCapture)
|
|||
|
||||
bool wxWindowMSW::HandleSettingChange(WXWPARAM wParam, WXLPARAM lParam)
|
||||
{
|
||||
// Check for the special case of changing the system light/dark mode.
|
||||
if ( lParam && wxStrcmp((TCHAR*)lParam, wxT("ImmersiveColorSet")) == 0 )
|
||||
{
|
||||
// Forward to the existing function generating an event for this.
|
||||
HandleSysColorChange();
|
||||
}
|
||||
|
||||
// despite MSDN saying "(This message cannot be sent directly to a window.)"
|
||||
// we need to send this to child windows (it is only sent to top-level
|
||||
// windows) so {list,tree}ctrls can adjust their font size if necessary
|
||||
|
|
@ -5252,68 +5275,6 @@ extern wxCOLORMAP *wxGetStdColourMap()
|
|||
return s_cmap;
|
||||
}
|
||||
|
||||
#if wxUSE_UXTHEME && !defined(TMT_FILLCOLOR)
|
||||
#define TMT_FILLCOLOR 3802
|
||||
#define TMT_TEXTCOLOR 3803
|
||||
#define TMT_BORDERCOLOR 3801
|
||||
#endif
|
||||
|
||||
wxColour wxWindowMSW::MSWGetThemeColour(const wchar_t *themeName,
|
||||
int themePart,
|
||||
int themeState,
|
||||
MSWThemeColour themeColour,
|
||||
wxSystemColour fallback) const
|
||||
{
|
||||
#if wxUSE_UXTHEME
|
||||
if ( wxUxThemeIsActive() )
|
||||
{
|
||||
int themeProperty = 0;
|
||||
|
||||
// TODO: Convert this into a table? Sure would be faster.
|
||||
switch ( themeColour )
|
||||
{
|
||||
case ThemeColourBackground:
|
||||
themeProperty = TMT_FILLCOLOR;
|
||||
break;
|
||||
case ThemeColourText:
|
||||
themeProperty = TMT_TEXTCOLOR;
|
||||
break;
|
||||
case ThemeColourBorder:
|
||||
themeProperty = TMT_BORDERCOLOR;
|
||||
break;
|
||||
default:
|
||||
wxFAIL_MSG(wxT("unsupported theme colour"));
|
||||
}
|
||||
|
||||
wxUxThemeHandle hTheme((const wxWindow *)this, themeName);
|
||||
COLORREF col;
|
||||
HRESULT hr = ::GetThemeColor
|
||||
(
|
||||
hTheme,
|
||||
themePart,
|
||||
themeState,
|
||||
themeProperty,
|
||||
&col
|
||||
);
|
||||
|
||||
if ( SUCCEEDED(hr) )
|
||||
return wxRGBToColour(col);
|
||||
|
||||
wxLogApiError(
|
||||
wxString::Format(
|
||||
"GetThemeColor(%s, %i, %i, %i)",
|
||||
themeName, themePart, themeState, themeProperty),
|
||||
hr);
|
||||
}
|
||||
#else
|
||||
wxUnusedVar(themeName);
|
||||
wxUnusedVar(themePart);
|
||||
wxUnusedVar(themeState);
|
||||
wxUnusedVar(themeColour);
|
||||
#endif
|
||||
return wxSystemSettings::GetColour(fallback);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// painting
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -5526,7 +5487,7 @@ wxWindowMSW::MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child)
|
|||
// uses RTL layout, which is exactly what we need here as the child
|
||||
// window origin is its _right_ top corner in this case and not the
|
||||
// left one.
|
||||
::MapWindowPoints(nullptr, GetHwnd(), (POINT *)&rc, 2);
|
||||
wxMapWindowPoints(HWND_DESKTOP, GetHwnd(), &rc);
|
||||
|
||||
int x = rc.left,
|
||||
y = rc.top;
|
||||
|
|
|
|||
|
|
@ -208,8 +208,7 @@ void FontPickerCtrlTestCase::ColourSelection()
|
|||
{
|
||||
wxColour selectedColour(0xFF4269UL);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("Default font picker color must be black",
|
||||
m_font->GetSelectedColour(), wxColour(*wxBLACK));
|
||||
CHECK( m_font->GetSelectedColour() != selectedColour );
|
||||
|
||||
m_font->SetSelectedColour(selectedColour);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue