diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 81cf035d46..b8303b407b 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -151,6 +151,8 @@ static const wxChar HotkeysDumper[] = wxT( "HotkeysDumper" ); static const wxChar DrawBoundingBoxes[] = wxT( "DrawBoundingBoxes" ); +static const wxChar AllowDarkMode[] = wxT( "AllowDarkMode" ); + } // namespace KEYS @@ -252,6 +254,7 @@ ADVANCED_CFG::ADVANCED_CFG() m_SmallDrillMarkSize = 0.35; m_HotkeysDumper = false; m_DrawBoundingBoxes = false; + m_AllowDarkMode = false; loadFromConfigFile(); } @@ -342,6 +345,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::DrawBoundingBoxes, &m_DrawBoundingBoxes, false ) ); + configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::AllowDarkMode, + &m_AllowDarkMode, false ) ); + wxConfigLoadSetups( &aCfg, configParams ); for( PARAM_CFG* param : configParams ) diff --git a/common/bitmap.cpp b/common/bitmap.cpp index 2407b6bcb5..3a65a4461a 100644 --- a/common/bitmap.cpp +++ b/common/bitmap.cpp @@ -85,7 +85,12 @@ namespace std { } -BITMAP_STORE* GetStore() +static std::unordered_map s_ScaledBitmapCache; + +static std::mutex s_BitmapCacheMutex; + + +BITMAP_STORE* GetBitmapStore() { if( !s_BitmapStore ) { @@ -99,7 +104,7 @@ BITMAP_STORE* GetStore() wxBitmap KiBitmap( BITMAPS aBitmap ) { - return GetStore()->GetBitmap( aBitmap ); + return GetBitmapStore()->GetBitmap( aBitmap ); } @@ -143,32 +148,37 @@ static int get_scale_factor( wxWindow* aWindow ) wxBitmap KiScaledBitmap( BITMAPS aBitmap, wxWindow* aWindow, int aHeight ) { // Bitmap conversions are cached because they can be slow. - static std::unordered_map bitmap_cache; - static std::mutex bitmap_cache_mutex; const int scale = get_scale_factor( aWindow ); SCALED_BITMAP_ID id = { static_cast( aBitmap ), scale }; - std::lock_guard guard( bitmap_cache_mutex ); - auto it = bitmap_cache.find( id ); + std::lock_guard guard( s_BitmapCacheMutex ); + auto it = s_ScaledBitmapCache.find( id ); - if( it != bitmap_cache.end() ) + if( it != s_ScaledBitmapCache.end() ) { return it->second; } else { - wxBitmap bitmap = GetStore()->GetBitmapScaled( aBitmap, scale, aHeight ); - return bitmap_cache.emplace( id, bitmap ).first->second; + wxBitmap bitmap = GetBitmapStore()->GetBitmapScaled( aBitmap, scale, aHeight ); + return s_ScaledBitmapCache.emplace( id, bitmap ).first->second; } } +void ClearScaledBitmapCache() +{ + std::lock_guard guard( s_BitmapCacheMutex ); + s_ScaledBitmapCache.clear(); +} + + wxBitmap KiScaledBitmap( const wxBitmap& aBitmap, wxWindow* aWindow ) { const int scale = get_scale_factor( aWindow ); - if( scale == 4) + if( scale == 4 ) { return wxBitmap( aBitmap ); } @@ -185,7 +195,7 @@ wxBitmap KiScaledBitmap( const wxBitmap& aBitmap, wxWindow* aWindow ) wxBitmap* KiBitmapNew( BITMAPS aBitmap ) { - wxBitmap* bitmap = new wxBitmap( GetStore()->GetBitmap( aBitmap ) ); + wxBitmap* bitmap = new wxBitmap( GetBitmapStore()->GetBitmap( aBitmap ) ); return bitmap; } diff --git a/common/bitmap_store.cpp b/common/bitmap_store.cpp index b41c068d34..ef7cc8c074 100644 --- a/common/bitmap_store.cpp +++ b/common/bitmap_store.cpp @@ -23,12 +23,15 @@ #include #include +#include #include #include #include #include #include #include +#include +#include /// A question-mark icon shown when we can't find a given bitmap in the archive @@ -102,9 +105,9 @@ BITMAP_STORE::BITMAP_STORE() m_archive = std::make_unique( path.GetFullPath() ); - m_theme = KIPLATFORM::UI::IsDarkTheme() ? wxT( "dark" ) : wxT( "light" ); - buildBitmapInfoCache(); + + ThemeChanged(); } @@ -158,9 +161,31 @@ wxImage BITMAP_STORE::getImage( BITMAPS aBitmapId, int aHeight ) } -void BITMAP_STORE::ThemeChanged() +bool BITMAP_STORE::ThemeChanged() { + COMMON_SETTINGS* settings = Pgm().GetCommonSettings(); + + wxString oldTheme = m_theme; + + if( ADVANCED_CFG::GetCfg().m_AllowDarkMode ) + { + switch( settings->m_Appearance.icon_theme ) + { + case ICON_THEME::LIGHT: m_theme = wxT( "light" ); break; + case ICON_THEME::DARK: m_theme = wxT( "dark" ); break; + case ICON_THEME::AUTO: + m_theme = KIPLATFORM::UI::IsDarkTheme() ? wxT( "dark" ) : wxT( "light" ); + break; + } + } + else + { + m_theme = wxT( "light" ); + } + m_bitmapNameCache.clear(); + + return !oldTheme.IsSameAs( m_theme ); } diff --git a/common/dialogs/panel_common_settings.cpp b/common/dialogs/panel_common_settings.cpp index f2ce8eceeb..f4cd5265e4 100644 --- a/common/dialogs/panel_common_settings.cpp +++ b/common/dialogs/panel_common_settings.cpp @@ -23,6 +23,7 @@ #include +#include #include #include #include @@ -71,6 +72,14 @@ PANEL_COMMON_SETTINGS::PANEL_COMMON_SETTINGS( DIALOG_SHIM* aDialog, wxWindow* aP m_antialiasingFallbackLabel->Show( false ); #endif + if( !ADVANCED_CFG::GetCfg().m_AllowDarkMode ) + { + m_rbIconThemeLight->Hide(); + m_rbIconThemeDark->Hide(); + m_rbIconThemeAuto->Hide(); + m_stIconTheme->Hide(); + } + m_textEditorBtn->SetBitmap( KiBitmap( BITMAPS::small_folder ) ); m_pdfViewerBtn->SetBitmap( KiBitmap( BITMAPS::small_folder ) ); @@ -123,6 +132,13 @@ bool PANEL_COMMON_SETTINGS::TransferDataFromWindow() dpi.SetDpiConfig( m_canvasScaleAuto->GetValue(), m_canvasScaleCtrl->GetValue() ); } + if( m_rbIconThemeLight->GetValue() ) + commonSettings->m_Appearance.icon_theme = ICON_THEME::LIGHT; + else if( m_rbIconThemeDark->GetValue() ) + commonSettings->m_Appearance.icon_theme = ICON_THEME::DARK; + else if( m_rbIconThemeAuto->GetValue() ) + commonSettings->m_Appearance.icon_theme = ICON_THEME::AUTO; + commonSettings->m_Appearance.use_icons_in_menus = m_checkBoxIconsInMenus->GetValue(); commonSettings->m_Input.immediate_actions = !m_NonImmediateActions->GetValue(); @@ -199,6 +215,13 @@ void PANEL_COMMON_SETTINGS::applySettingsToPanel( COMMON_SETTINGS& aSettings ) m_canvasScaleAuto->SetValue( dpi.GetCanvasIsAutoScaled() ); } + switch( aSettings.m_Appearance.icon_theme ) + { + case ICON_THEME::LIGHT: m_rbIconThemeLight->SetValue( true ); break; + case ICON_THEME::DARK: m_rbIconThemeDark->SetValue( true ); break; + case ICON_THEME::AUTO: m_rbIconThemeAuto->SetValue( true ); break; + } + m_checkBoxIconsInMenus->SetValue( aSettings.m_Appearance.use_icons_in_menus ); m_warpMouseOnMove->SetValue( aSettings.m_Input.warp_mouse_on_move ); diff --git a/common/dialogs/panel_common_settings_base.cpp b/common/dialogs/panel_common_settings_base.cpp index 7ff9bdddb2..076d7b5c5c 100644 --- a/common/dialogs/panel_common_settings_base.cpp +++ b/common/dialogs/panel_common_settings_base.cpp @@ -181,7 +181,27 @@ PANEL_COMMON_SETTINGS_BASE::PANEL_COMMON_SETTINGS_BASE( wxWindow* parent, wxWind gbSizer4->Add( m_canvasScaleAuto, wxGBPosition( 1, 2 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT|wxLEFT, 15 ); m_checkBoxIconsInMenus = new wxCheckBox( sbSizerIconsOpts->GetStaticBox(), wxID_ANY, _("Show icons in menus"), wxDefaultPosition, wxDefaultSize, 0 ); - gbSizer4->Add( m_checkBoxIconsInMenus, wxGBPosition( 2, 0 ), wxGBSpan( 1, 3 ), wxALIGN_CENTER_VERTICAL|wxTOP|wxLEFT, 5 ); + gbSizer4->Add( m_checkBoxIconsInMenus, wxGBPosition( 2, 0 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxTOP|wxLEFT, 5 ); + + m_stIconTheme = new wxStaticText( sbSizerIconsOpts->GetStaticBox(), wxID_ANY, _("Icon theme:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stIconTheme->Wrap( -1 ); + gbSizer4->Add( m_stIconTheme, wxGBPosition( 3, 0 ), wxGBSpan( 1, 3 ), wxLEFT|wxRIGHT, 5 ); + + m_rbIconThemeLight = new wxRadioButton( sbSizerIconsOpts->GetStaticBox(), wxID_ANY, _("Light"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + m_rbIconThemeLight->SetToolTip( _("Use icons designed for light window backgrounds") ); + + gbSizer4->Add( m_rbIconThemeLight, wxGBPosition( 4, 0 ), wxGBSpan( 1, 1 ), wxLEFT|wxRIGHT, 5 ); + + m_rbIconThemeDark = new wxRadioButton( sbSizerIconsOpts->GetStaticBox(), wxID_ANY, _("Dark"), wxDefaultPosition, wxDefaultSize, 0 ); + m_rbIconThemeDark->SetToolTip( _("Use icons designed for dark window backgrounds") ); + + gbSizer4->Add( m_rbIconThemeDark, wxGBPosition( 4, 1 ), wxGBSpan( 1, 1 ), wxLEFT|wxRIGHT, 5 ); + + m_rbIconThemeAuto = new wxRadioButton( sbSizerIconsOpts->GetStaticBox(), wxID_ANY, _("Automatic"), wxDefaultPosition, wxDefaultSize, 0 ); + m_rbIconThemeAuto->SetValue( true ); + m_rbIconThemeAuto->SetToolTip( _("Automatically choose light or dark icons based on the system color theme") ); + + gbSizer4->Add( m_rbIconThemeAuto, wxGBPosition( 4, 2 ), wxGBSpan( 1, 1 ), wxLEFT|wxRIGHT, 5 ); gbSizer4->AddGrowableCol( 1 ); diff --git a/common/dialogs/panel_common_settings_base.fbp b/common/dialogs/panel_common_settings_base.fbp index 05b066b397..544efd0397 100644 --- a/common/dialogs/panel_common_settings_base.fbp +++ b/common/dialogs/panel_common_settings_base.fbp @@ -1824,7 +1824,7 @@ 5 - 3 + 2 0 wxALIGN_CENTER_VERTICAL|wxTOP|wxLEFT 2 @@ -1889,6 +1889,271 @@ + + 5 + 3 + 0 + wxLEFT|wxRIGHT + 3 + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Icon theme: + 0 + + 0 + + + 0 + + 1 + m_stIconTheme + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + 1 + 0 + wxLEFT|wxRIGHT + 4 + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Light + + 0 + + + 0 + + 1 + m_rbIconThemeLight + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + Use icons designed for light window backgrounds + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + 1 + 1 + wxLEFT|wxRIGHT + 4 + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Dark + + 0 + + + 0 + + 1 + m_rbIconThemeDark + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Use icons designed for dark window backgrounds + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + 1 + 2 + wxLEFT|wxRIGHT + 4 + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Automatic + + 0 + + + 0 + + 1 + m_rbIconThemeAuto + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Automatically choose light or dark icons based on the system color theme + + wxFILTER_NONE + wxDefaultValidator + + 1 + + + + + diff --git a/common/dialogs/panel_common_settings_base.h b/common/dialogs/panel_common_settings_base.h index 0bc66351b5..4fcea32695 100644 --- a/common/dialogs/panel_common_settings_base.h +++ b/common/dialogs/panel_common_settings_base.h @@ -67,6 +67,10 @@ class PANEL_COMMON_SETTINGS_BASE : public RESETTABLE_PANEL wxSpinCtrlDouble* m_canvasScaleCtrl; wxCheckBox* m_canvasScaleAuto; wxCheckBox* m_checkBoxIconsInMenus; + wxStaticText* m_stIconTheme; + wxRadioButton* m_rbIconThemeLight; + wxRadioButton* m_rbIconThemeDark; + wxRadioButton* m_rbIconThemeAuto; wxCheckBox* m_warpMouseOnMove; wxCheckBox* m_NonImmediateActions; wxCheckBox* m_cbBackupEnabled; diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp index 2246e93edc..9e9992b2b8 100644 --- a/common/eda_base_frame.cpp +++ b/common/eda_base_frame.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +55,7 @@ #include #include #include +#include #include @@ -446,6 +449,19 @@ void EDA_BASE_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVars m_fileHistory->SetMaxFiles( (unsigned) std::max( 0, historySize ) ); } + if( GetBitmapStore()->ThemeChanged() ) + { + ClearScaledBitmapCache(); + + wxAuiPaneInfoArray panes = m_auimgr.GetAllPanes(); + + for( size_t i = 0; i < panes.GetCount(); ++i ) + { + if( ACTION_TOOLBAR* toolbar = dynamic_cast( panes[i].window ) ) + toolbar->RefreshBitmaps(); + } + } + if( GetMenuBar() ) { // For icons in menus, icon scaling & hotkeys diff --git a/common/settings/common_settings.cpp b/common/settings/common_settings.cpp index 58a5915ddf..8f5979d570 100644 --- a/common/settings/common_settings.cpp +++ b/common/settings/common_settings.cpp @@ -68,6 +68,9 @@ COMMON_SETTINGS::COMMON_SETTINGS() : m_params.emplace_back( new PARAM( "appearance.icon_scale", &m_Appearance.icon_scale, 0 ) ); + m_params.emplace_back( new PARAM_ENUM( "appearance.icon_theme", + &m_Appearance.icon_theme, ICON_THEME::AUTO, ICON_THEME::LIGHT, ICON_THEME::AUTO ) ); + m_params.emplace_back( new PARAM( "appearance.use_icons_in_menus", &m_Appearance.use_icons_in_menus, defaultUseIconsInMenus ) ); diff --git a/common/tool/action_toolbar.cpp b/common/tool/action_toolbar.cpp index cd6bcecd6a..0453d49dff 100644 --- a/common/tool/action_toolbar.cpp +++ b/common/tool/action_toolbar.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -170,14 +171,13 @@ ACTION_TOOLBAR::ACTION_TOOLBAR( EDA_BASE_FRAME* parent, wxWindowID id, const wxP { m_paletteTimer = new wxTimer( this ); - // Enable this once dark icon switching is available. Without dark theme icons, this just - // makes things (even) harder to see -#ifdef NOTYET #if !wxCHECK_VERSION( 3, 1, 0 ) // Custom art provider makes dark mode work on wx < 3.1 - WX_AUI_TOOLBAR_ART* newArt = new WX_AUI_TOOLBAR_ART(); - SetArtProvider( newArt ); -#endif + if( ADVANCED_CFG::GetCfg().m_AllowDarkMode ) + { + WX_AUI_TOOLBAR_ART* newArt = new WX_AUI_TOOLBAR_ART(); + SetArtProvider( newArt ); + } #endif Connect( wxEVT_COMMAND_TOOL_CLICKED, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolEvent ), @@ -759,3 +759,19 @@ bool ACTION_TOOLBAR::KiRealize() Refresh( false ); return retval; } + + +void ACTION_TOOLBAR::RefreshBitmaps() +{ + for( const std::pair pair : m_toolActions ) + { + wxAuiToolBarItem* tool = FindTool( pair.first ); + + wxBitmap bmp = KiScaledBitmap( pair.second->GetIcon(), GetParent() ); + + tool->SetBitmap( bmp ); + tool->SetDisabledBitmap( bmp.ConvertToDisabled() ); + } + + Refresh(); +} diff --git a/include/advanced_config.h b/include/advanced_config.h index bd4f4f3497..9122d7c248 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -160,6 +160,11 @@ public: */ bool m_DrawBoundingBoxes; + /** + * Enable detection of dark mode and automatic switch to dark-mode icon theme + */ + bool m_AllowDarkMode; + private: ADVANCED_CFG(); diff --git a/include/bitmap_store.h b/include/bitmap_store.h index 0e92dbed26..31d4455c9d 100644 --- a/include/bitmap_store.h +++ b/include/bitmap_store.h @@ -26,6 +26,7 @@ #include class ASSET_ARCHIVE; +class wxImage; namespace std @@ -72,8 +73,11 @@ public: /** * Notifies the store that the icon theme has been changed by the user, so caches must be * invalidated. + * @return true if the new theme is different than what was previously in use */ - void ThemeChanged(); + bool ThemeChanged(); + + bool IsDarkTheme() const { return m_theme == wxT( "dark" ); } private: diff --git a/include/bitmaps/bitmap_types.h b/include/bitmaps/bitmap_types.h index f3d5d0d2a0..6844e3c9cb 100644 --- a/include/bitmaps/bitmap_types.h +++ b/include/bitmaps/bitmap_types.h @@ -33,11 +33,14 @@ class wxBitmap; // only to define wxBitmap class EDA_DRAW_FRAME; class wxWindow; struct BITMAP_OPAQUE; +class BITMAP_STORE; enum class BITMAPS : unsigned int; #include // wxBitmapType +BITMAP_STORE* GetBitmapStore(); + /** * Construct a wxBitmap from an image identifier * Returns the image from the active theme if the image has multiple theme variants. @@ -50,6 +53,11 @@ wxBitmap KiBitmap( BITMAPS aBitmap ); */ wxBitmap KiBitmap( const BITMAP_OPAQUE* aBitmap ); +/** + * Wipes out the scaled bitmap cache so that the icon theme can be changed. + * TODO: move scaling system into BITMAP_STORE so this global doesn't need to exist + */ +void ClearScaledBitmapCache(); /** * Construct a wxBitmap from a memory record, scaling it if device DPI demands it. diff --git a/include/settings/common_settings.h b/include/settings/common_settings.h index 58b1802014..8c5c8b9fa2 100644 --- a/include/settings/common_settings.h +++ b/include/settings/common_settings.h @@ -35,15 +35,23 @@ enum class MOUSE_DRAG_ACTION NONE }; +enum class ICON_THEME +{ + LIGHT, + DARK, + AUTO +}; + class COMMON_SETTINGS : public JSON_SETTINGS { public: struct APPEARANCE { - double canvas_scale; - int icon_scale; - bool use_icons_in_menus; + double canvas_scale; + int icon_scale; + ICON_THEME icon_theme; + bool use_icons_in_menus; }; struct AUTO_BACKUP diff --git a/include/tool/action_toolbar.h b/include/tool/action_toolbar.h index 330b1f1cef..a31dcf7cf1 100644 --- a/include/tool/action_toolbar.h +++ b/include/tool/action_toolbar.h @@ -284,6 +284,11 @@ public: */ bool KiRealize(); + /** + * Reload all the bitmaps for the tools (e.g. when switching icon themes) + */ + void RefreshBitmaps(); + static constexpr bool TOGGLE = true; static constexpr bool CANCEL = true;