diff --git a/include/eepp/ui/css/stylesheet.hpp b/include/eepp/ui/css/stylesheet.hpp index 2a96a4dd7..36826bff1 100644 --- a/include/eepp/ui/css/stylesheet.hpp +++ b/include/eepp/ui/css/stylesheet.hpp @@ -27,6 +27,8 @@ class EE_API StyleSheet { const std::vector>& getStyles() const; + std::shared_ptr getStyleFromSelector( const std::string& selector ) const; + bool updateMediaLists( const MediaFeatures& features ); bool isMediaQueryListEmpty() const; diff --git a/include/eepp/ui/css/stylesheetstyle.hpp b/include/eepp/ui/css/stylesheetstyle.hpp index 2ba0f5c37..d0aaffcab 100644 --- a/include/eepp/ui/css/stylesheetstyle.hpp +++ b/include/eepp/ui/css/stylesheetstyle.hpp @@ -43,6 +43,8 @@ class EE_API StyleSheetStyle { bool hasVariables() const; + bool hasVariable( const std::string& name ) const; + StyleSheetVariable getVariableByName( const std::string& name ) const; void setVariable( const StyleSheetVariable& variable ); diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index a23c97ada..83acf776e 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -156,6 +156,8 @@ class EE_API UISceneNode : public SceneNode { void nodeToWorldTranslation( Vector2f& Pos ) const; + void reloadStyle( bool disableAnimations = false, bool forceReApplyProperties = false ); + protected: friend class EE::UI::UIWindow; friend class EE::UI::UIWidget; @@ -205,8 +207,6 @@ class EE_API UISceneNode : public SceneNode { virtual void setInternalSize( const Sizef& size ); - void reloadStyle( const bool& disableAnimations = false ); - bool onMediaChanged( bool forceReApplyStyles = false ); virtual void onChildCountChange( Node* child, const bool& removed ); diff --git a/include/eepp/ui/uitheme.hpp b/include/eepp/ui/uitheme.hpp index d2b36456f..69f380a69 100644 --- a/include/eepp/ui/uitheme.hpp +++ b/include/eepp/ui/uitheme.hpp @@ -68,6 +68,8 @@ class EE_API UITheme : protected ResourceManagerMulti { void setDefaultFont( Font* font ); + CSS::StyleSheet& getStyleSheet(); + const CSS::StyleSheet& getStyleSheet() const; void setStyleSheet( const CSS::StyleSheet& styleSheet ); diff --git a/src/eepp/ui/css/stylesheet.cpp b/src/eepp/ui/css/stylesheet.cpp index 953904512..18dbef9e7 100644 --- a/src/eepp/ui/css/stylesheet.cpp +++ b/src/eepp/ui/css/stylesheet.cpp @@ -251,6 +251,14 @@ const std::vector>& StyleSheet::getStyles() con return mNodes; } +std::shared_ptr +StyleSheet::getStyleFromSelector( const std::string& selector ) const { + for ( const auto& node : mNodes ) + if ( node->getSelector().getName() == selector ) + return node; + return {}; +} + bool StyleSheet::updateMediaLists( const MediaFeatures& features ) { if ( mMediaQueryList.empty() ) return false; diff --git a/src/eepp/ui/css/stylesheetstyle.cpp b/src/eepp/ui/css/stylesheetstyle.cpp index ce48ff9e9..c60a4db7e 100644 --- a/src/eepp/ui/css/stylesheetstyle.cpp +++ b/src/eepp/ui/css/stylesheetstyle.cpp @@ -152,6 +152,10 @@ bool StyleSheetStyle::hasVariables() const { return !mVariables.empty(); } +bool StyleSheetStyle::hasVariable( const std::string& name ) const { + return !getVariableByName( name ).isEmpty(); +} + StyleSheetVariable StyleSheetStyle::getVariableByName( const std::string& name ) const { auto it = mVariables.find( String::hash( name ) ); diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 165b56b46..2d073493d 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -387,13 +387,14 @@ bool UISceneNode::hasStyleSheet() { return !mStyleSheet.isEmpty(); } -void UISceneNode::reloadStyle( const bool& disableAnimations ) { +void UISceneNode::reloadStyle( bool disableAnimations, bool forceReApplyProperties ) { if ( NULL != mChild ) { Node* child = mChild; while ( NULL != child ) { if ( child->isWidget() ) { - child->asType()->reloadStyle( true, disableAnimations ); + child->asType()->reloadStyle( true, disableAnimations, true, + forceReApplyProperties ); } child = child->getNextNode(); diff --git a/src/eepp/ui/uitheme.cpp b/src/eepp/ui/uitheme.cpp index 5df06ffed..e368c99c4 100644 --- a/src/eepp/ui/uitheme.cpp +++ b/src/eepp/ui/uitheme.cpp @@ -317,6 +317,10 @@ UISkin* UITheme::getSkin( const std::string& widgetName ) { return getByName( mAbbr + "_" + widgetName ); } +CSS::StyleSheet& UITheme::getStyleSheet() { + return mStyleSheet; +} + const CSS::StyleSheet& UITheme::getStyleSheet() const { return mStyleSheet; } diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 5e1007ad2..3c02eed59 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -93,6 +93,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, ui.terminalFont = ini.getValue( "ui", "terminal_font", "fonts/DejaVuSansMonoNerdFontComplete.ttf" ); ui.fallbackFont = ini.getValue( "ui", "fallback_font", "fonts/DroidSansFallbackFull.ttf" ); + ui.theme = ini.getValue( "ui", "theme" ); ui.colorScheme = ini.getValue( "ui", "ui_color_scheme", "dark" ) == "light" ? ColorSchemePreference::Light : ColorSchemePreference::Dark; @@ -214,6 +215,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "ui", "serif_font", ui.serifFont ); ini.setValue( "ui", "monospace_font", ui.monospaceFont ); ini.setValue( "ui", "terminal_font", ui.terminalFont ); + ini.setValue( "ui", "theme", ui.theme ); ini.setValue( "ui", "fallback_font", ui.fallbackFont ); ini.setValue( "ui", "ui_color_scheme", ui.colorScheme == ColorSchemePreference::Light ? "light" : "dark" ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index f557ce699..0cca01acf 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -35,6 +35,7 @@ struct UIConfig { std::string terminalFont; std::string fallbackFont; ColorSchemePreference colorScheme{ ColorSchemePreference::Dark }; + std::string theme; }; struct WindowStateConfig { diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 5631c3f45..dfe3fdc2c 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -438,6 +438,7 @@ void App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s FileSystem::dirAddSlashAtEnd( mConfigPath ); mPluginsPath = mConfigPath + "plugins"; mLanguagesPath = mConfigPath + "languages"; + mThemesPath = mConfigPath + "themes"; mColorSchemesPath = mConfigPath + "editor" + FileSystem::getOSSlash() + "colorschemes" + FileSystem::getOSSlash(); mTerminalManager = std::make_unique( this ); @@ -454,6 +455,10 @@ void App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s FileSystem::makeDir( mLanguagesPath ); FileSystem::dirAddSlashAtEnd( mLanguagesPath ); + if ( !FileSystem::fileExists( mThemesPath ) ) + FileSystem::makeDir( mThemesPath ); + FileSystem::dirAddSlashAtEnd( mThemesPath ); + #ifndef EE_DEBUG Log::create( mConfigPath + "ecode.log", logLevel, false, true ); #else @@ -1484,6 +1489,74 @@ void App::loadFileDelayed() { mFileToOpen.clear(); } +const std::string& App::getThemesPath() const { + return mThemesPath; +} + +std::string App::getThemePath() const { + if ( mConfig.ui.theme.empty() || "default_theme" == mConfig.ui.theme ) + return getDefaultThemePath(); + + auto themePath( mThemesPath + mConfig.ui.theme + ".css" ); + if ( !FileSystem::fileExists( themePath ) ) + return getDefaultThemePath(); + + return themePath; +} + +std::string App::getDefaultThemePath() const { + return mResPath + "ui/breeze.css"; +} + +void App::setTheme( const std::string& path ) { + UITheme* theme = UITheme::load( "uitheme", "uitheme", "", mFont, path ); + theme->setDefaultFontSize( mConfig.ui.fontSize.asDp( 0, Sizef(), mDisplayDPI ) ); + + if ( path != getDefaultThemePath() ) { + auto style = theme->getStyleSheet().getStyleFromSelector( ":root" ); + if ( style ) { + auto inheritsBaseTheme = style->getVariableByName( "--inherit-base-theme" ); + if ( !inheritsBaseTheme.isEmpty() ) { + StyleSheetParser parser; + parser.loadFromFile( getDefaultThemePath() ); + for ( auto& tstyle : parser.getStyleSheet().getStyles() ) { + if ( tstyle->getSelector().getName() != ":root" ) { + theme->getStyleSheet().addStyle( tstyle ); + } else { + // Add root variables that arent defined in the custom theme + auto root = theme->getStyleSheet().getStyleFromSelector( ":root" ); + if ( root ) { + for ( const auto& var : tstyle->getVariables() ) { + if ( !root->hasVariable( var.second.getName() ) ) + root->setVariable( var.second ); + } + } + } + } + theme->getStyleSheet().addKeyframes( parser.getStyleSheet().getKeyframes() ); + } + } + } + + mUISceneNode->setStyleSheet( theme->getStyleSheet() ); + mUISceneNode + ->getUIThemeManager() + //->setDefaultEffectsEnabled( true ) + ->setDefaultTheme( theme ) + ->setDefaultFont( mFont ) + ->setDefaultFontSize( mConfig.ui.fontSize.asDp( 0, Sizef(), mDisplayDPI ) ) + ->add( theme ); + + mUISceneNode->getRoot()->addClass( "appbackground" ); + + if ( mTheme ) + mUISceneNode->getUIThemeManager()->remove( mTheme ); + + mTheme = theme; + + mUISceneNode->reloadStyle( true, true ); +} + void App::onRealDocumentLoaded( UICodeEditor* editor, const std::string& path ) { updateEditorTitle( editor ); if ( mSplitter->curEditorExistsAndFocused() && editor == mSplitter->getCurEditor() ) @@ -2807,19 +2880,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe SceneManager::instance()->add( mUISceneNode ); - UITheme* theme = - UITheme::load( "uitheme", "uitheme", "", mFont, mResPath + "ui/breeze.css" ); - theme->setDefaultFontSize( mConfig.ui.fontSize.asDp( 0, Sizef(), mDisplayDPI ) ); - mUISceneNode->setStyleSheet( theme->getStyleSheet() ); - mUISceneNode - ->getUIThemeManager() - //->setDefaultEffectsEnabled( true ) - ->setDefaultTheme( theme ) - ->setDefaultFont( mFont ) - ->setDefaultFontSize( mConfig.ui.fontSize.asDp( 0, Sizef(), mDisplayDPI ) ) - ->add( theme ); - - mUISceneNode->getRoot()->addClass( "appbackground" ); + setTheme( getThemePath() ); if ( !css.empty() && FileSystem::fileExists( css ) ) { CSS::StyleSheetParser parser; diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index b177c7c7d..1184c47cb 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -326,6 +326,14 @@ class App : public UICodeEditorSplitter::Client { const std::vector& getRecentFolders() const; + const std::string& getThemesPath() const; + + std::string getThemePath() const; + + std::string getDefaultThemePath() const; + + void setTheme( const std::string& path ); + protected: std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; @@ -355,6 +363,7 @@ class App : public UICodeEditorSplitter::Client { std::string mKeybindingsPath; std::string mResPath; std::string mLanguagesPath; + std::string mThemesPath; Float mDisplayDPI{ 96 }; std::shared_ptr mThreadPool; std::shared_ptr mDirTree; @@ -391,6 +400,7 @@ class App : public UICodeEditorSplitter::Client { std::unique_ptr mPluginManager; std::unique_ptr mSettings; std::string mFileToOpen; + UITheme* mTheme{ nullptr }; void saveAllProcess(); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index db36825a4..31658526f 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -856,6 +856,8 @@ UIMenu* SettingsMenu::createWindowMenu() { } ); mWindowMenu->addSubMenu( i18n( "ui_prefes_color_scheme", "UI Prefers Color Scheme" ), findIcon( "color-scheme" ), colorsMenu ); + mWindowMenu->addSubMenu( i18n( "ui_thene", "UI Theme" ), findIcon( "palette" ), + createThemesMenu() ); mWindowMenu->addSubMenu( i18n( "ui_renderer", "Renderer" ), findIcon( "renderer" ), createRendererMenu() ); mWindowMenu @@ -1291,6 +1293,36 @@ UIMenu* SettingsMenu::createHelpMenu() { return helpMenu; } +UIMenu* SettingsMenu::createThemesMenu() { + UIPopUpMenu* menu = UIPopUpMenu::New(); + + const std::string& curTheme = mApp->getConfig().ui.theme; + + menu->addRadioButton( i18n( "default_theme", "Default Theme" ), + curTheme.empty() || "default_theme" == curTheme ) + ->setId( "default_theme" ); + + auto files = FileSystem::filesInfoGetInPath( mApp->getThemesPath(), true, true, true ); + + for ( const auto& file : files ) { + if ( file.getExtension() != "css" ) + continue; + auto name( FileSystem::fileRemoveExtension( file.getFileName() ) ); + menu->addRadioButton( name, curTheme == name )->setId( name ); + } + + menu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + auto id = event->getNode()->getId(); + mApp->getConfig().ui.theme = "default_theme" != id ? id : ""; + std::string path( mApp->getConfig().ui.theme.empty() + ? mApp->getDefaultThemePath() + : mApp->getThemesPath() + id + ".css" ); + mApp->setTheme( path ); + } ); + + return menu; +} + void SettingsMenu::toggleSettingsMenu() { if ( ( !mSettingsMenu->isVisible() || mSettingsMenu->isHiding() ) && mSettingsMenu->getInactiveTime().getElapsedTime().asMilliseconds() > 1 ) { diff --git a/src/tools/ecode/settingsmenu.hpp b/src/tools/ecode/settingsmenu.hpp index 52865cb4f..e937c8c3f 100644 --- a/src/tools/ecode/settingsmenu.hpp +++ b/src/tools/ecode/settingsmenu.hpp @@ -38,6 +38,8 @@ class SettingsMenu { UIMenu* createHelpMenu(); + UIMenu* createThemesMenu(); + void updateTerminalMenu(); void updateProjectSettingsMenu();