From f3b6fcacf435aa39ccb4b3ad7aea93cc9812f756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 22 Dec 2025 01:16:05 -0300 Subject: [PATCH] Implementation for "Light/dark theme should follow system default" (issue SpartanJ/ecode#754). --- include/eepp/system/sys.hpp | 3 + include/eepp/ui/colorschemepreferences.hpp | 34 +++ include/eepp/ui/uiscenenode.hpp | 5 +- src/eepp/system/sys.cpp | 257 ++++++++++++++++++ src/eepp/ui/colorschemepreferences.cpp | 59 ++++ src/eepp/ui/iconmanager.cpp | 2 + src/eepp/ui/uiscenenode.cpp | 17 ++ src/eepp/ui/uistyle.cpp | 1 + src/tools/ecode/appconfig.cpp | 9 +- src/tools/ecode/appconfig.hpp | 2 +- src/tools/ecode/ecode.cpp | 26 +- src/tools/ecode/ecode.hpp | 8 +- src/tools/ecode/settingsmenu.cpp | 23 +- src/tools/ecode/statusappoutputcontroller.cpp | 1 + .../ecode/statusbuildoutputcontroller.cpp | 1 + src/tools/ecode/uiwelcomescreen.cpp | 34 ++- src/tools/uieditor/uieditor.cpp | 24 +- src/tools/uieditor/uieditor.hpp | 2 +- 18 files changed, 467 insertions(+), 41 deletions(-) create mode 100644 include/eepp/ui/colorschemepreferences.hpp create mode 100644 src/eepp/ui/colorschemepreferences.cpp diff --git a/include/eepp/system/sys.hpp b/include/eepp/system/sys.hpp index 43ded9f17..dc7ab815a 100644 --- a/include/eepp/system/sys.hpp +++ b/include/eepp/system/sys.hpp @@ -144,6 +144,9 @@ class EE_API Sys { /** @returns True if the process has any childrens */ static bool processHasChildren( ProcessID pid ); + + /** @returns True if the operating system is using a dark color scheme */ + static bool isOSUsingDarkColorScheme( bool allowUsingCached = true ); }; }} // namespace EE::System diff --git a/include/eepp/ui/colorschemepreferences.hpp b/include/eepp/ui/colorschemepreferences.hpp new file mode 100644 index 000000000..4a4c7b694 --- /dev/null +++ b/include/eepp/ui/colorschemepreferences.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +namespace EE::UI { + +enum class ColorSchemePreference { Light, Dark }; + +enum class ColorSchemeExtPreference : std::underlying_type_t { + Light = + static_cast>( ColorSchemePreference::Light ), + Dark = + static_cast>( ColorSchemePreference::Dark ), + System +}; + +struct EE_API ColorSchemePreferences { + static ColorSchemePreference fromString( std::string_view str ); + + static ColorSchemeExtPreference fromStringExt( std::string_view str ); + + static ColorSchemePreference fromExt( ColorSchemeExtPreference pref ); + + static ColorSchemeExtPreference toExt( ColorSchemePreference pref ); + + static std::string toString( ColorSchemePreference pref ); + + static std::string toString( ColorSchemeExtPreference pref ); +}; + +} // namespace EE::UI diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index fc67be50a..03c48fac1 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -23,8 +24,6 @@ class UIWidget; class UILayout; class UIIcon; -enum class ColorSchemePreference { Light, Dark }; - class EE_API UISceneNode : public SceneNode { public: static UISceneNode* New( EE::Window::Window* window = NULL ); @@ -149,6 +148,8 @@ class EE_API UISceneNode : public SceneNode { ColorSchemePreference getColorSchemePreference() const; + void setColorSchemePreference( const ColorSchemeExtPreference& colorSchemePreference ); + void setColorSchemePreference( const ColorSchemePreference& colorSchemePreference ); const Uint32& getMaxInvalidationDepth() const; diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index 7a71b65a6..062d8cd66 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -21,7 +21,9 @@ // This taints the System module! #if EE_PLATFORM == EE_PLATFORM_ANDROID +#include #include +#include #endif #ifdef EE_PLATFORM_POSIX @@ -41,6 +43,7 @@ #endif #include #include +#include #undef GetDiskFreeSpace #undef GetTempPath @@ -105,6 +108,11 @@ typedef DWORD( WINAPI* GetModuleBaseName_t )( HANDLE, HMODULE, LPSTR, DWORD ); using namespace EE::Network; #endif +#if EE_PLATFORM == EE_PLATFORM_IOS +#include +#include +#endif + #ifndef PATH_MAX #define PATH_MAX 4096 #endif @@ -2008,4 +2016,253 @@ bool Sys::processHasChildren( ProcessID pid ) { return false; } +static bool _isOSUsingDarkColorScheme() { +#if EE_PLATFORM == EE_PLATFORM_WIN + // Checks the registry for the "AppsUseLightTheme" key. + // 0 = Dark, 1 = Light. If key is missing (Win 7/8), default to Light (false). + + HKEY hKey; + const wchar_t* subKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + const wchar_t* valueName = L"AppsUseLightTheme"; + + if ( RegOpenKeyExW( HKEY_CURRENT_USER, subKey, 0, KEY_READ, &hKey ) != ERROR_SUCCESS ) { + return false; + } + + DWORD value = 1; // Default to Light + DWORD size = sizeof( DWORD ); + LSTATUS status = RegQueryValueExW( hKey, valueName, nullptr, nullptr, + reinterpret_cast( &value ), &size ); + + RegCloseKey( hKey ); + + if ( status == ERROR_SUCCESS ) { + return ( value == 0 ); + } + return false; + +#elif EE_PLATFORM == EE_PLATFORM_IOS + // Logic: [UIScreen.mainScreen.traitCollection userInterfaceStyle] == 2 (Dark) + + // 1. Get UIScreen Class + Class uiScreenClass = objc_getClass( "UIScreen" ); + if ( !uiScreenClass ) + return false; // Should not happen on iOS + + // 2. Call [UIScreen mainScreen] + // We must cast objc_msgSend to the correct function signature + typedef id ( *MainScreenMethod )( Class, SEL ); + SEL mainScreenSel = sel_registerName( "mainScreen" ); + MainScreenMethod getMainScreen = (MainScreenMethod)objc_msgSend; + id mainScreen = getMainScreen( uiScreenClass, mainScreenSel ); + if ( !mainScreen ) + return false; + + // 3. Call [screen traitCollection] + typedef id ( *TraitCollectionMethod )( id, SEL ); + SEL traitCollectionSel = sel_registerName( "traitCollection" ); + TraitCollectionMethod getTraitCollection = (TraitCollectionMethod)objc_msgSend; + id traits = getTraitCollection( mainScreen, traitCollectionSel ); + if ( !traits ) + return false; + + // 4. Call [traits userInterfaceStyle] + // Return type is NSInteger (long) + typedef long ( *UserInterfaceStyleMethod )( id, SEL ); + SEL styleSel = sel_registerName( "userInterfaceStyle" ); + UserInterfaceStyleMethod getStyle = (UserInterfaceStyleMethod)objc_msgSend; + long style = getStyle( traits, styleSel ); + + // 5. Check constants: UIUserInterfaceStyleLight = 1, UIUserInterfaceStyleDark = 2 + return ( style == 2 ); + +#elif EE_PLATFORM == EE_PLATFORM_MACOS + // Checks global user preferences via CoreFoundation (C-API). + // This avoids complex Obj-C Runtime casting and works in CLI apps / before UI init. + // Key: "AppleInterfaceStyle". Value: "Dark" (exists) or null (Light). + + bool isDark = false; + CFStringRef key = CFSTR( "AppleInterfaceStyle" ); + CFPropertyListRef propertyValue = + CFPreferencesCopyAppValue( key, kCFPreferencesAnyApplication ); + + if ( propertyValue ) { + if ( CFGetTypeID( propertyValue ) == CFStringGetTypeID() ) { + CFStringRef style = static_cast( propertyValue ); + // Check if string contains "Dark" + if ( CFStringCompare( style, CFSTR( "Dark" ), 0 ) == kCFCompareEqualTo ) { + isDark = true; + } + } + CFRelease( propertyValue ); + } + return isDark; +#elif EE_PLATFORM == EE_PLATFORM_ANDROID + // Logic: ActivityThread.currentApplication().getResources().getConfiguration().uiMode + + // 1. Retrieve the environment from your engine + void* rawEnv = Window::Engine::instance()->getPlatformHelper()->getJNIEnv(); + if ( !rawEnv ) + return false; + + // Cast void* to JNIEnv* + JNIEnv* env = static_cast( rawEnv ); + + bool isDark = false; + + // 2. Reflection to get the Application Context without passing it as an argument + // Note: We use the hidden class "android.app.ActivityThread" to get the current app. + jclass activityThreadCls = env->FindClass( "android/app/ActivityThread" ); + + if ( activityThreadCls ) { + jmethodID currentAppMethod = env->GetStaticMethodID( + activityThreadCls, "currentApplication", "()Landroid/app/Application;" ); + + if ( currentAppMethod ) { + jobject context = env->CallStaticObjectMethod( activityThreadCls, currentAppMethod ); + + if ( context ) { + // 3. context.getResources() + jclass contextCls = env->GetObjectClass( context ); + jmethodID getResMethod = env->GetMethodID( contextCls, "getResources", + "()Landroid/content/res/Resources;" ); + jobject resources = env->CallObjectMethod( context, getResMethod ); + + if ( resources ) { + // 4. resources.getConfiguration() + jclass resCls = env->GetObjectClass( resources ); + jmethodID getConfigMethod = env->GetMethodID( + resCls, "getConfiguration", "()Landroid/content/res/Configuration;" ); + jobject config = env->CallObjectMethod( resources, getConfigMethod ); + + if ( config ) { + // 5. config.uiMode & UI_MODE_NIGHT_MASK + jclass configCls = env->GetObjectClass( config ); + jfieldID uiModeField = env->GetFieldID( configCls, "uiMode", "I" ); + int uiMode = env->GetIntField( config, uiModeField ); + + // UI_MODE_NIGHT_MASK (0x30) check against UI_MODE_NIGHT_YES (0x20) + if ( ( uiMode & 0x30 ) == 0x20 ) { + isDark = true; + } + + // Cleanup local references to avoid JNI table overflow + env->DeleteLocalRef( config ); + env->DeleteLocalRef( configCls ); + } + env->DeleteLocalRef( resources ); + env->DeleteLocalRef( resCls ); + } + env->DeleteLocalRef( context ); + env->DeleteLocalRef( contextCls ); + } + } + env->DeleteLocalRef( activityThreadCls ); + } + + // Safety: Clear any exceptions if something wasn't found (e.g. old Android versions) + if ( env->ExceptionCheck() ) { + env->ExceptionClear(); + } + + return isDark; + +#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_BSD + // Helper to parse INI-style files robustly + auto checkFileForDarkTheme = [&]( const std::string& path, const std::string& key ) -> bool { + if ( access( path.c_str(), R_OK ) != 0 ) + return false; + + std::ifstream file( path ); + std::string line; + while ( std::getline( file, line ) ) { + // 1. Trim leading whitespace (handle indented keys) + size_t keyStart = line.find_first_not_of( " \t" ); + if ( keyStart == std::string::npos ) + continue; // Empty line + + // 2. Skip comments + if ( line[keyStart] == '#' || line[keyStart] == ';' ) + continue; + + // 3. Check if the line starts specifically with our key + if ( line.compare( keyStart, key.length(), key ) == 0 ) { + // 4. Look for '=' after the key + size_t afterKeyIndex = keyStart + key.length(); + size_t eqPos = line.find( '=', afterKeyIndex ); + + if ( eqPos != std::string::npos ) { + // 5. strict check: ensure only whitespace exists between key and '=' + // This prevents "Color" matching "ColorScheme" or "gtk-theme" matching + // "gtk-theme-backup" + bool isValidKey = true; + for ( size_t i = afterKeyIndex; i < eqPos; ++i ) { + if ( line[i] != ' ' && line[i] != '\t' ) { + isValidKey = false; + break; + } + } + + if ( isValidKey ) { + std::string value = line.substr( eqPos + 1 ); + value = String::toLower( value ); + if ( value.find( "dark" ) != std::string::npos ) { + return true; + } + } + } + } + } + return false; + }; + + // 1. Check Environment Variables (GTK_THEME overrides config files) + const char* gtkTheme = std::getenv( "GTK_THEME" ); + if ( gtkTheme ) { + std::string theme( gtkTheme ); + theme = String::toLower( theme ); + if ( theme.find( "dark" ) != std::string::npos ) + return true; + } + + const char* home = std::getenv( "HOME" ); + if ( !home ) + return false; + std::string homeStr( home ); + + // 2. KDE Plasma + if ( checkFileForDarkTheme( homeStr + "/.config/kdeglobals", "ColorScheme" ) ) { + return true; + } + + // 3. GTK 3/4 (Gnome, XFCE, Mate, Cinnamon) + if ( checkFileForDarkTheme( homeStr + "/.config/gtk-3.0/settings.ini", "gtk-theme-name" ) ) { + return true; + } + if ( checkFileForDarkTheme( homeStr + "/.config/gtk-4.0/settings.ini", "gtk-theme-name" ) ) { + return true; + } + + return false; // Default to light +#elif EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + // Executes JavaScript: window.matchMedia('(prefers-color-scheme: dark)').matches + return EM_ASM_INT({ + if (typeof window !== 'undefined' && window.matchMedia) { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 1 : 0; + } + return 0; + }) != 0; +#else + return true; // Any other OS default to dark +#endif +} + +bool Sys::isOSUsingDarkColorScheme( bool allowUsingCached ) { + static bool isUsingDarkColorScheme = _isOSUsingDarkColorScheme(); + if ( allowUsingCached ) + return isUsingDarkColorScheme; + isUsingDarkColorScheme = _isOSUsingDarkColorScheme(); + return isUsingDarkColorScheme; +} + }} // namespace EE::System diff --git a/src/eepp/ui/colorschemepreferences.cpp b/src/eepp/ui/colorschemepreferences.cpp new file mode 100644 index 000000000..1e11f6e11 --- /dev/null +++ b/src/eepp/ui/colorschemepreferences.cpp @@ -0,0 +1,59 @@ +#include +#include + +using namespace EE::System; + +using namespace std::literals; + +namespace EE::UI { + +ColorSchemePreference ColorSchemePreferences::fromString( std::string_view str ) { + if ( str == "light"sv ) + return ColorSchemePreference::Light; + return ColorSchemePreference::Dark; +} + +ColorSchemeExtPreference ColorSchemePreferences::fromStringExt( std::string_view str ) { + if ( str == "system"sv ) + return ColorSchemeExtPreference::System; + if ( str == "light"sv ) + return ColorSchemeExtPreference::Light; + return ColorSchemeExtPreference::Dark; +} + +ColorSchemePreference ColorSchemePreferences::fromExt( ColorSchemeExtPreference pref ) { + switch ( pref ) { + case ColorSchemeExtPreference::Light: + return ColorSchemePreference::Light; + case ColorSchemeExtPreference::Dark: + return ColorSchemePreference::Dark; + case ColorSchemeExtPreference::System: + break; + } + return Sys::isOSUsingDarkColorScheme() ? ColorSchemePreference::Dark + : ColorSchemePreference::Light; +} + +ColorSchemeExtPreference ColorSchemePreferences::toExt( ColorSchemePreference pref ) { + if ( pref == ColorSchemePreference::Light ) + return ColorSchemeExtPreference::Light; + if ( pref == ColorSchemePreference::Dark ) + return ColorSchemeExtPreference::Dark; + return ColorSchemeExtPreference::System; +} + +std::string ColorSchemePreferences::toString( ColorSchemePreference pref ) { + if ( pref == ColorSchemePreference::Light ) + return "light"; + return "dark"; +} + +std::string ColorSchemePreferences::toString( ColorSchemeExtPreference pref ) { + if ( pref == ColorSchemeExtPreference::Light ) + return "light"; + if ( pref == ColorSchemeExtPreference::Dark ) + return "dark"; + return "system"; +} + +} // namespace EE::UI diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp index 9352bd284..5c8074441 100644 --- a/src/eepp/ui/iconmanager.cpp +++ b/src/eepp/ui/iconmanager.cpp @@ -116,6 +116,8 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType* { "pause-fill", 0xEFD7 }, { "spy-line", 0xF17F }, { "input-field", 0xF47A }, + { "sun", 0xF1BF }, + { "moon", 0xEF75 }, } ) { iconTheme->add( UIGlyphIcon::New( icon.first, remixIconFont, icon.second ) ); } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 0c96dfbbc..b34b303d9 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -1078,6 +1078,23 @@ ColorSchemePreference UISceneNode::getColorSchemePreference() const { return mColorSchemePreference; } +void UISceneNode::setColorSchemePreference( + const ColorSchemeExtPreference& colorSchemePreference ) { + switch ( colorSchemePreference ) { + case ColorSchemeExtPreference::Light: + setColorSchemePreference( ColorSchemePreference::Light ); + break; + case ColorSchemeExtPreference::Dark: + setColorSchemePreference( ColorSchemePreference::Dark ); + break; + case ColorSchemeExtPreference::System: + setColorSchemePreference( Sys::isOSUsingDarkColorScheme() + ? ColorSchemePreference::Dark + : ColorSchemePreference::Light ); + break; + } +} + void UISceneNode::setColorSchemePreference( const ColorSchemePreference& colorSchemePreference ) { if ( mColorSchemePreference != colorSchemePreference ) { mColorSchemePreference = colorSchemePreference; diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index 636a00b64..c1e4cfb90 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -214,6 +214,7 @@ void UIStyle::applyLightDarkValue( std::string& value ) { std::string::size_type tokenEnd = 0; while ( true ) { + // TODO: add support to inner-function calls like: light-dark(rgba(0,0,0,1), rgba(1,1,1,1)) tokenStart = value.find( "light-dark(", tokenStart ); if ( tokenStart != std::string::npos ) { tokenEnd = String::findCloseBracket( value, tokenStart, '(', ')' ); diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 7351e6c96..cd3dab998 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -136,9 +136,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, ui.fallbackFont = ini.getValue( "ui", "fallback_font", "fonts/DroidSansFallbackFull.ttf" ); ui.theme = ini.getValue( "ui", "theme" ); ui.language = ini.getValue( "ui", "language" ); - ui.colorScheme = ini.getValue( "ui", "ui_color_scheme", "dark" ) == "light" - ? ColorSchemePreference::Light - : ColorSchemePreference::Dark; + ui.colorScheme = + ColorSchemePreferences::fromStringExt( ini.getValue( "ui", "ui_color_scheme", "system" ) ); ui.fontHinting = FontTrueType::fontHintingFromString( ini.getValue( "ui", "font_hinting", "full" ) ); ui.fontAntialiasing = FontTrueType::fontAntialiasingFromString( @@ -335,9 +334,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "ui", "theme", ui.theme ); ini.setValue( "ui", "language", ui.language ); ini.setValue( "ui", "fallback_font", ui.fallbackFont ); - ini.setValue( - "ui", "ui_color_scheme", - std::string_view{ ui.colorScheme == ColorSchemePreference::Light ? "light" : "dark" } ); + ini.setValue( "ui", "ui_color_scheme", ColorSchemePreferences::toString( ui.colorScheme ) ); ini.setValue( "ui", "font_hinting", FontTrueType::fontHintingToString( ui.fontHinting ) ); ini.setValue( "ui", "font_antialiasing", FontTrueType::fontAntialiasingToString( ui.fontAntialiasing ) ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index c26823c51..3ec62a38c 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -47,7 +47,7 @@ struct UIConfig { std::string monospaceFont; std::string terminalFont; std::string fallbackFont; - ColorSchemePreference colorScheme{ ColorSchemePreference::Dark }; + ColorSchemeExtPreference colorScheme{ ColorSchemeExtPreference::Dark }; std::string theme; std::string language; FontHinting fontHinting{ FontHinting::Full }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index e171a8840..2f1a587c7 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1203,7 +1203,7 @@ void App::panelPosition( const PanelPosition& panelPosition ) { mSettings->updateViewMenu(); } -void App::setUIColorScheme( const ColorSchemePreference& colorScheme ) { +void App::setUIColorScheme( const ColorSchemeExtPreference& colorScheme ) { if ( colorScheme == mUIColorScheme ) return; mUIColorScheme = mConfig.ui.colorScheme = colorScheme; @@ -1212,7 +1212,19 @@ void App::setUIColorScheme( const ColorSchemePreference& colorScheme ) { mPluginManager->setUIThemeReloaded(); } -ColorSchemePreference App::getUIColorScheme() const { +void App::setUIColorSchemeFromUserInteraction( const ColorSchemeExtPreference& colorSchemeExt ) { + setUIColorScheme( colorSchemeExt ); + ColorSchemePreference colorScheme = ColorSchemePreferences::fromExt( colorSchemeExt ); + if ( colorScheme == ColorSchemePreference::Light && + mSplitter->getCurrentColorSchemeName() == "eepp" ) { + mSplitter->setColorScheme( "github" ); + } else if ( colorScheme == ColorSchemePreference::Dark && + mSplitter->getCurrentColorSchemeName() == "github" ) { + mSplitter->setColorScheme( "eepp" ); + } +} + +ColorSchemeExtPreference App::getUIColorScheme() const { return mUIColorScheme; } @@ -4202,8 +4214,11 @@ void App::init( InitParameters& params ) { mUISceneNode->getTranslator().loadFromFile( langPath ); if ( !params.colorScheme.empty() ) { - mUIColorScheme = params.colorScheme == "light" ? ColorSchemePreference::Light - : ColorSchemePreference::Dark; + mUIColorScheme = + params.colorScheme == "light" + ? ColorSchemeExtPreference::Light + : ( params.colorScheme == "dark" ? ColorSchemeExtPreference::Dark + : ColorSchemeExtPreference::System ); } mUISceneNode->setColorSchemePreference( mUIColorScheme ); @@ -4556,7 +4571,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { "Set default application pixel density", { 'd', "pixel-density" } ); args::ValueFlag prefersColorScheme( - parser, "prefers-color-scheme", "Set the preferred color scheme (\"light\" or \"dark\")", + parser, "prefers-color-scheme", + "Set the preferred color scheme (\"light\", \"dark\" or \"system\")", { 'c', "prefers-color-scheme" } ); args::ValueFlag css( parser, "css", diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 2d61258ff..4ef473159 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -453,9 +453,11 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { void setFocusEditorOnClose( UIMessageBox* msgBox ); - void setUIColorScheme( const ColorSchemePreference& colorScheme ); + void setUIColorScheme( const ColorSchemeExtPreference& colorScheme ); - ColorSchemePreference getUIColorScheme() const; + void setUIColorSchemeFromUserInteraction( const ColorSchemeExtPreference& colorSchemeExt ); + + ColorSchemeExtPreference getUIColorScheme() const; EE::Window::Window* getWindow() const; @@ -663,7 +665,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::unique_ptr mStatusAppOutputController; std::unique_ptr mProjectBuildManager; std::string mLastFileFolder; - ColorSchemePreference mUIColorScheme; + ColorSchemeExtPreference mUIColorScheme; std::unique_ptr mTerminalManager; std::unique_ptr mPluginManager; std::unique_ptr mSettings; diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 02cda6007..5c8186ff6 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1267,29 +1267,30 @@ UIMenu* SettingsMenu::createWindowMenu() { mWindowMenu = UIPopUpMenu::New(); auto shouldCloseCb = []( UIMenuItem* ) -> bool { return false; }; UIPopUpMenu* colorsMenu = UIPopUpMenu::New(); + colorsMenu + ->addRadioButton( i18n( "system", "System" ), + mApp->getUIColorScheme() == ColorSchemeExtPreference::System ) + ->setOnShouldCloseCb( shouldCloseCb ) + ->setTooltipText( i18n( + "prefers_color_scheme_system_tooltip", + "System options will try to pick the system-wide currently preferred color scheme." ) ) + ->setId( "system" ); colorsMenu ->addRadioButton( i18n( "light", "Light" ), - mApp->getUIColorScheme() == ColorSchemePreference::Light ) + mApp->getUIColorScheme() == ColorSchemeExtPreference::Light ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "light" ); colorsMenu ->addRadioButton( i18n( "dark", "Dark" ), - mApp->getUIColorScheme() == ColorSchemePreference::Dark ) + mApp->getUIColorScheme() == ColorSchemeExtPreference::Dark ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "dark" ); colorsMenu->on( Event::OnItemClicked, [this]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; UIMenuItem* item = event->getNode()->asType(); - mApp->setUIColorScheme( item->getId() == "light" ? ColorSchemePreference::Light - : ColorSchemePreference::Dark ); - - if ( item->getId() == "light" && mSplitter->getCurrentColorSchemeName() == "eepp" ) { - mSplitter->setColorScheme( "github" ); - } else if ( item->getId() == "dark" && - mSplitter->getCurrentColorSchemeName() == "github" ) { - mSplitter->setColorScheme( "eepp" ); - } + auto colorSchemeExt = ColorSchemePreferences::fromStringExt( item->getId() ); + mApp->setUIColorSchemeFromUserInteraction( colorSchemeExt ); } ); mWindowMenu->addSubMenu( i18n( "ui_language", "UI Language" ), findIcon( "globe" ), createLanguagesMenu() ); diff --git a/src/tools/ecode/statusappoutputcontroller.cpp b/src/tools/ecode/statusappoutputcontroller.cpp index 882d5c6ed..4c0fa3581 100644 --- a/src/tools/ecode/statusappoutputcontroller.cpp +++ b/src/tools/ecode/statusappoutputcontroller.cpp @@ -182,6 +182,7 @@ void StatusAppOutputController::createContainer() { editor->setShowLineNumber( false ); editor->getDocument().reset(); editor->setScrollY( editor->getMaxScroll().y ); + editor->setColorScheme( mContext->getSplitter()->getCurrentColorScheme() ); mAppOutput = editor; mAppOutput->on( Event::OnScrollChange, [this]( auto ) { mScrollLocked = mAppOutput->getMaxScroll().y == mAppOutput->getScroll().y; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index 505ae640a..ce2f6ac90 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -464,6 +464,7 @@ void StatusBuildOutputController::createContainer() { editor->getDocument().textInput( mContext->i18n( "no_build_has_been_run", "No build has been run" ), false ); editor->setScrollY( editor->getMaxScroll().y ); + editor->setColorScheme( mContext->getSplitter()->getCurrentColorScheme() ); mButOutput = mContainer->find( "but_build_output_output" ); mButIssues = mContainer->find( "but_build_output_issues" ); mTableIssues = mContainer->find( "build_output_issues" ); diff --git a/src/tools/ecode/uiwelcomescreen.cpp b/src/tools/ecode/uiwelcomescreen.cpp index 5d6aeaa78..b65a87dc1 100644 --- a/src/tools/ecode/uiwelcomescreen.cpp +++ b/src/tools/ecode/uiwelcomescreen.cpp @@ -40,7 +40,7 @@ static const auto LAYOUT = R"xml( #welcome_ecode #version_number { cursor: pointer; font-family: DejaVuSansMono; - text-shadow-color: rgba(0,0,0,0.4); + text-shadow-color: light-dark(#00000019, #00000066); } #welcome_ecode #version_number { font-style: shadow; @@ -87,6 +87,26 @@ static const auto LAYOUT = R"xml( #welcome_ecode .right > PushButton:last-of-type { margin-bottom: 0dp; } +#welcome_ecode .color_scheme_icon { + lw: 24dp; + lh: 24dp; + background-color: transparent; + border-radius: 12dp; + foreground-position: center; + foreground-tint: var(--font); + transition: all 0.1s; +} +#welcome_ecode .color_scheme_icon:hover { + background-color: var(--primary); + foreground-tint: light-dark(var(--back), var(--font)); + cursor: hand; +} +#welcome_ecode #light_color_scheme_but { + foreground-image: icon("sun", 16dp); +} +#welcome_ecode #dark_color_scheme_but { + foreground-image: icon("moon", 16dp); +} ]]> @@ -125,6 +145,10 @@ static const auto LAYOUT = R"xml( + + + + @@ -254,6 +278,14 @@ UIWelcomeScreen::UIWelcomeScreen( App* app ) : find( "open_file_shortcut" )->setText( mApp->getKeybind( "open-file" ) ); + find( "light_color_scheme_but" )->onClick( [this]( auto ) { + mApp->setUIColorSchemeFromUserInteraction( ColorSchemeExtPreference::Light ); + } ); + + find( "dark_color_scheme_but" )->onClick( [this]( auto ) { + mApp->setUIColorSchemeFromUserInteraction( ColorSchemeExtPreference::Dark ); + } ); + auto welcomeDisabledChk = find( "disable_welcome_screen" ); welcomeDisabledChk->on( Event::OnValueChange, [welcomeDisabledChk, this]( auto ) { mApp->getConfig().ui.welcomeScreen = !welcomeDisabledChk->isChecked(); diff --git a/src/tools/uieditor/uieditor.cpp b/src/tools/uieditor/uieditor.cpp index 37fc7a865..cb25918b6 100644 --- a/src/tools/uieditor/uieditor.cpp +++ b/src/tools/uieditor/uieditor.cpp @@ -435,10 +435,8 @@ void App::refreshStyleSheet() { setUserDefaultTheme(); } - if ( !mCurrentLayout.empty() && FileSystem::fileExists( mCurrentLayout ) && - mUIContainer != NULL ) { - loadLayout( mCurrentLayout ); - } + mInvalidationLayout = InvalidationType::Memory; + reloadLayout(); mUpdateStyleSheet = false; mUpdateBaseStyleSheet = false; @@ -1157,17 +1155,21 @@ void App::createAppMenu() { UIPopUpMenu* colorsMenu = UIPopUpMenu::New(); colorsMenu - ->addRadioButton( i18n( "light", "Light" ), mUIColorScheme == ColorSchemePreference::Light ) + ->addRadioButton( i18n( "system", "System" ), + mUIColorScheme == ColorSchemeExtPreference::System ) + ->setId( "system" ); + colorsMenu + ->addRadioButton( i18n( "light", "Light" ), + mUIColorScheme == ColorSchemeExtPreference::Light ) ->setId( "light" ); colorsMenu - ->addRadioButton( i18n( "dark", "Dark" ), mUIColorScheme == ColorSchemePreference::Dark ) + ->addRadioButton( i18n( "dark", "Dark" ), mUIColorScheme == ColorSchemeExtPreference::Dark ) ->setId( "dark" ); colorsMenu->on( Event::OnItemClicked, [this]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; UIMenuItem* item = event->getNode()->asType(); - mUIColorScheme = - item->getId() == "light" ? ColorSchemePreference::Light : ColorSchemePreference::Dark; + mUIColorScheme = ColorSchemePreferences::fromStringExt( item->getId() ); mUISceneNode->setColorSchemePreference( mUIColorScheme ); updateLayoutFunc( InvalidationType::Memory ); } ); @@ -1289,8 +1291,7 @@ void App::init( const Float& pixelDensityConf, const bool& useAppTheme, const st mAppUISceneNode->enableDrawInvalidation(); mUISceneNode->enableDrawInvalidation(); - mUIColorScheme = - colorScheme == "light" ? ColorSchemePreference::Light : ColorSchemePreference::Dark; + mUIColorScheme = ColorSchemePreferences::fromStringExt( colorScheme ); mUISceneNode->setColorSchemePreference( mUIColorScheme ); FontTrueType* remixIconFont = loadFont( "icon", "fonts/remixicon.ttf" ); @@ -1544,7 +1545,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { "Use the default application theme in the editor.", { 'u', "use-app-theme" } ); args::ValueFlag prefersColorScheme( - parser, "prefers-color-scheme", "Set the preferred color scheme (\"light\" or \"dark\")", + parser, "prefers-color-scheme", + "Set the preferred color scheme (\"light\", \"dark\" or \"system\")", { 'c', "prefers-color-scheme" } ); try { diff --git a/src/tools/uieditor/uieditor.hpp b/src/tools/uieditor/uieditor.hpp index 7e4f42dcc..960d50a2d 100644 --- a/src/tools/uieditor/uieditor.hpp +++ b/src/tools/uieditor/uieditor.hpp @@ -205,7 +205,7 @@ class App : public UICodeEditorSplitter::Client { UILayout* mPreviewLayout{ nullptr }; UIWidget* mSidePanel{ nullptr }; std::unordered_set mTmpDocs; - ColorSchemePreference mUIColorScheme; + ColorSchemeExtPreference mUIColorScheme; Drawable* findIcon( const std::string& icon ); };