diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index 0983dc14e..2a59daffe 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -280,6 +280,9 @@ Soll es jetzt heruntergeladen werden? Farbwähler aktivieren Aktiviert den Farbwähler bei Doppelklick auf farbenrepräsentierende Wörter. + Farbkästen aktivieren + Rendert ein Hintergrundfeld mit der analysierten Farbe direkt hinter dem +Text selbst. Farbvorschau aktivieren Aktiviert die Farbvorschau beim Halten der Maus über farbenrepräsentierenden Wörtern. diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index ca24ef83d..7947b92cd 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -264,6 +264,9 @@ Do you want to download it now? Enable Color Picker Enables the color picker tool when a double click selection is done over a word representing a color. + Enable Color Boxes + Renders a background box of the parsed color directly behind the +text itself. Enable Color Preview Enables a quick preview of a color when the mouse is hover a word that represents a color. diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index 80239e4c0..faadb18f7 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -272,6 +272,9 @@ Voulez-vous le télécharger maintenant ? Activer le rechargement automatique Activer le sélecteur de couleurs Active l'outil de sélection de couleurs lorsqu'un double clic est effectué sur un mot représentant une couleur. + Activer les boîtes de couleur + Affiche une zone d'arrière-plan de la couleur analysée directement derrière le +texte lui-même. Activer l'aperçu des couleurs Activer un aperçu rapide d'une couleur lorsque la souris survole un mot qui représente une couleur. Activer la barre de défilement horizontale diff --git a/bin/assets/i18n/zh_CN.xml b/bin/assets/i18n/zh_CN.xml index cdb379828..e9c9fc722 100644 --- a/bin/assets/i18n/zh_CN.xml +++ b/bin/assets/i18n/zh_CN.xml @@ -197,6 +197,8 @@ 开启颜色选择器 开启颜色选择工具 当光标悬浮在颜色字符串上时 + 启用色框 + 直接在文本后面呈现已解析颜色的背景框。 开启颜色预览 允许颜色预览 当鼠标悬停在颜色字符串上时 diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 96633e6a4..b77a3cc8f 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -312,6 +312,13 @@ class EE_API String { */ static bool startsWith( const String& haystack, const String& needle ); + /** Compare two strings from its beginning. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool startsWith( String::View haystack, String::View needle ); + /** Compare two strings from its beginning. * @param haystack The string to search in. * @param needle The searched string. @@ -326,6 +333,41 @@ class EE_API String { */ static bool startsWith( std::string_view haystack, std::string_view needle ); + /** Compare two strings from its beginning. Case-insensitive check. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool istartsWith( const std::string& haystack, const std::string& needle ); + + /** Compare two strings from its beginning. Case-insensitive check. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool istartsWith( const String& haystack, const String& needle ); + + /** Compare two strings from its beginning. Case-insensitive check. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool istartsWith( String::View haystack, String::View needle ); + + /** Compare two strings from its beginning. Case-insensitive check. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool istartsWith( const char* haystack, const char* needle ); + + /** Compare two strings from its beginning. Case-insensitive check. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool istartsWith( std::string_view haystack, std::string_view needle ); + /** Compare two strings from its end. * @param haystack The string to search in. * @param needle The searched string. @@ -340,6 +382,13 @@ class EE_API String { */ static bool endsWith( const String& haystack, const String& needle ); + /** Compare two strings from its end. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool endsWith( String::View haystack, String::View needle ); + /** @return True if a string contains a substring. * @param haystack The string to search in. * @param needle The searched string. diff --git a/include/eepp/system/color.hpp b/include/eepp/system/color.hpp index a304923b9..0ba9c0749 100644 --- a/include/eepp/system/color.hpp +++ b/include/eepp/system/color.hpp @@ -143,6 +143,18 @@ template class tColor { } tRGB toRGB() { return tRGB( r, g, b ); } + + /** + * @brief Calculates the perceived luminance (Luma) of the color. + * Uses the Rec. 601 luma formula: L = 0.299*R + 0.587*G + 0.114*B. + * Perceived luminance aligns with human vision, making it ideal for UI contrast. + * @note Note on HSL (Colorf): If this object is being used to store HSL values + * (for instance, a Colorf returned by Color::toHsl()), do NOT call this method + * directly. Perceived luminance mathematically requires RGB components. + * You must either call this on the original RGB object, or convert the HSL + * Colorf back to RGB first (e.g., using Color::fromHsl(hslColor)). + */ + T perceivedLuminance() const { return static_cast( 0.299f * r + 0.587f * g + 0.114f * b ); } }; typedef tColor ColorAf; @@ -208,13 +220,17 @@ class EE_API Color : public tColor { static Color fromString( std::string str ); - static bool isColorString( std::string str ); + static bool isColorString( std::string str, bool searchColorNames = true ); + + static bool isColorString( String::View str, bool searchColorNames = true ); static void registerColor( const std::string& name, const Color& color ); static bool unregisterColor( const std::string& name ); - static bool validHexColorString( const std::string& hexColor ); + static bool validHexColorString( std::string_view hexColor ); + + static bool validHexColorString( String::View hexColor ); static const Color Transparent; static const Color Black; diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 36bc06195..edea80312 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -423,6 +423,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void setEnableColorPickerOnSelection( const bool& enableColorPickerOnSelection ); + const bool& getEnableInlineColorBoxes() const; + + void setEnableInlineColorBoxes( const bool& enableInlineColorBoxes ); + void setSyntaxDefinition( const SyntaxDefinition& definition ); void resetSyntaxDefinition(); @@ -862,6 +866,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mHighlightMatchingBracket{ true }; bool mHighlightSelectionMatch{ true }; bool mEnableColorPickerOnSelection{ false }; + bool mEnableInlineColorBoxes{ false }; bool mVerticalScrollBarEnabled{ true }; bool mHorizontalScrollBarEnabled{ true }; bool mLongestLineWidthDirty{ true }; @@ -949,6 +954,15 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { MinimapConfig mMinimapConfig; Int64 mMinimapScrollOffset{ 0 }; std::unordered_map> mLinesWidthCache; + + struct ColorBoxData { + Int64 startColumn; + Int64 endColumn; + Color color; + }; + std::unordered_map>> + mColorBoxesCache; + Tools::UIDocFindReplace* mFindReplace{ nullptr }; struct PluginRequestedSpace { UICodeEditorPlugin* plugin; diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index db370f4ea..333633cf0 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -1293,6 +1293,11 @@ bool String::startsWith( const String& haystack, const String& needle ) { std::equal( needle.begin(), needle.end(), haystack.begin() ); } +bool String::startsWith( String::View haystack, String::View needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin() ); +} + bool String::startsWith( const char* haystack, const char* needle ) { return strncmp( needle, haystack, strlen( needle ) ) == 0; } @@ -1302,6 +1307,44 @@ bool String::startsWith( std::string_view haystack, std::string_view needle ) { std::equal( needle.begin(), needle.end(), haystack.begin() ); } +bool String::istartsWith( const std::string& haystack, const std::string& needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin(), []( char c1, char c2 ) { + return std::tolower( c1 ) == std::tolower( c2 ); + } ); +} + +bool String::istartsWith( const String& haystack, const String& needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin(), + []( String::StringBaseType c1, String::StringBaseType c2 ) { + return std::tolower( c1 ) == std::tolower( c2 ); + } ); +} + +bool String::istartsWith( String::View haystack, String::View needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin(), + []( String::StringBaseType c1, String::StringBaseType c2 ) { + return std::tolower( c1 ) == std::tolower( c2 ); + } ); +} + +bool String::istartsWith( const char* haystack, const char* needle ) { + size_t needleLen = strlen( needle ); + if ( needleLen > strlen( haystack ) ) return false; + return std::equal( needle, needle + needleLen, haystack, []( char c1, char c2 ) { + return std::tolower( c1 ) == std::tolower( c2 ); + } ); +} + +bool String::istartsWith( std::string_view haystack, std::string_view needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin(), []( char c1, char c2 ) { + return std::tolower( c1 ) == std::tolower( c2 ); + } ); +} + bool String::endsWith( const std::string& haystack, const std::string& needle ) { return needle.length() <= haystack.length() && haystack.compare( haystack.size() - needle.size(), needle.size(), needle ) == 0; @@ -1312,6 +1355,11 @@ bool String::endsWith( const String& haystack, const String& needle ) { haystack.compare( haystack.size() - needle.size(), needle.size(), needle ) == 0; } +bool String::endsWith( String::View haystack, String::View needle ) { + return needle.length() <= haystack.length() && + haystack.compare( haystack.size() - needle.size(), needle.size(), needle ) == 0; +} + bool String::contains( const std::string& haystack, const std::string& needle ) { return haystack.find( needle ) != std::string::npos; } diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index 8f89a2c51..8817f4ec6 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -714,32 +714,34 @@ Color Color::fromString( std::string str ) { return Color::Transparent; } -bool Color::isColorString( std::string str ) { +bool Color::isColorString( std::string str, bool searchColorNames ) { if ( str.empty() ) return false; if ( str[0] == '#' ) return true; - String::toLowerInPlace( str ); + if ( searchColorNames ) { + String::toLowerInPlace( str ); + initColorMap(); + auto it = sColorMap.find( String::hash( str ) ); + if ( it != sColorMap.end() ) + return true; + } - initColorMap(); - auto it = sColorMap.find( String::hash( str ) ); - if ( it != sColorMap.end() ) + if ( String::istartsWith( str, "rgb(" ) ) return true; - if ( String::startsWith( str, "rgb(" ) ) + else if ( String::istartsWith( str, "rgba(" ) ) return true; - else if ( String::startsWith( str, "rgba(" ) ) + else if ( String::istartsWith( str, "hsl(" ) ) return true; - else if ( String::startsWith( str, "hsl(" ) ) + else if ( String::istartsWith( str, "hsla(" ) ) return true; - else if ( String::startsWith( str, "hsla(" ) ) + else if ( String::istartsWith( str, "hsv(" ) ) return true; - else if ( String::startsWith( str, "hsv(" ) ) + else if ( String::istartsWith( str, "hsva(" ) ) return true; - else if ( String::startsWith( str, "hsva(" ) ) - return true; - else if ( String::startsWith( str, "@color/" ) ) + else if ( String::istartsWith( str, "@color/" ) ) return true; else if ( sColors.find( str ) != sColors.end() ) return true; @@ -747,6 +749,47 @@ bool Color::isColorString( std::string str ) { return false; } +bool Color::isColorString( String::View str, bool searchColorNames ) { + if ( str.empty() ) + return false; + + if ( str[0] == '#' ) + return true; + + if ( searchColorNames && str.size() <= 32 ) { + initColorMap(); + + String::HashType hash = 5381; + for ( size_t i = 0; i < str.size(); ++i ) + hash = ( ( hash << 5 ) + hash ) + std::tolower( str[i] ); + auto it = sColorMap.find( hash ); + if ( it != sColorMap.end() ) + return true; + + std::string lowerStr( String( str ).toUtf8() ); + String::toLowerInPlace( lowerStr ); + if ( sColors.find( lowerStr ) != sColors.end() ) + return true; + } + + if ( String::istartsWith( str, "rgb(" ) ) + return true; + else if ( String::istartsWith( str, "rgba(" ) ) + return true; + else if ( String::istartsWith( str, "hsl(" ) ) + return true; + else if ( String::istartsWith( str, "hsla(" ) ) + return true; + else if ( String::istartsWith( str, "hsv(" ) ) + return true; + else if ( String::istartsWith( str, "hsva(" ) ) + return true; + else if ( String::istartsWith( str, "@color/" ) ) + return true; + + return false; +} + void Color::registerColor( const std::string& name, const Color& color ) { sColors[String::toLower( name )] = color; } @@ -755,7 +798,21 @@ bool Color::unregisterColor( const std::string& name ) { return sColors.erase( String::toLower( name ) ) > 0; } -bool Color::validHexColorString( const std::string& hexColor ) { +bool Color::validHexColorString( String::View hexColor ) { + if ( hexColor.size() < 2 || hexColor[0] != '#' ) + return false; + + for ( size_t i = 1; i < hexColor.size(); i++ ) { + if ( !( String::isNumber( hexColor[i] ) || ( hexColor[i] >= 'a' && hexColor[i] <= 'f' ) || + ( hexColor[i] >= 'A' && hexColor[i] <= 'F' ) ) ) { + return false; + } + } + + return true; +} + +bool Color::validHexColorString( std::string_view hexColor ) { if ( hexColor.size() < 2 || hexColor[0] != '#' ) return false; diff --git a/src/eepp/ui/doc/languages/configfile.cpp b/src/eepp/ui/doc/languages/configfile.cpp index b32288187..d1ecf68fe 100644 --- a/src/eepp/ui/doc/languages/configfile.cpp +++ b/src/eepp/ui/doc/languages/configfile.cpp @@ -11,16 +11,16 @@ void addConfigFile() { { "%.ini$", "%.conf$", "%.desktop$", "%.service$", "%.cfg$", "%.properties$", "%.wrap$", "%.dev$", "Doxyfile", "%.timer$", "%.rules$" }, { - { { "%s*#%x%x%x%x%x%x%x%x" }, "string" }, - { { "%s*#%x%x%x%x%x%x" }, "string" }, + { { "%f[^%s=,]#%x%x%x%x%x%x%x%x" }, "string" }, + { { "%f[^%s=,]#%x%x%x%x%x%x" }, "string" }, { { "^#.-\n" }, "comment" }, { { "^;.-\n" }, "comment" }, - { { "%s#.-\n" }, "comment" }, + { { "%f[^%s]#.-\n" }, "comment" }, { { "[%a_][%w-+_%s%p]-%f[=]" }, "keyword" }, { { "\"", "\"", "\\" }, "string" }, { { "'", "'", "\\" }, "string" }, { { "^%[.-%]" }, "type" }, - { { "%s%[.-%]" }, "type" }, + { { "%f[^%s]%[.-%]" }, "type" }, { { "^%s*(%w[%w%d_-]+)%s?%f[{]" }, "keyword" }, { { "[={}]" }, "operator" }, { { "https?://[%w_.~!*:@&+$/?%%#-]-%w[-.%w]*%.%w%w%w?%w?:?%d*/?[%w_.~!*:@&+$/" diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 4971a0124..268d7c378 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -695,6 +695,8 @@ void UICodeEditor::onDocumentReset( TextDocument* ) { DocEvent event( this, mDoc.get(), Event::OnDocumentReset ); sendEvent( &event ); mDocView.clear(); + mLinesWidthCache.clear(); + mColorBoxesCache.clear(); invalidateEditor(); invalidateDraw(); invalidateLongestLineWidth(); @@ -741,6 +743,7 @@ void UICodeEditor::disableEditorFeatures( bool useDefaultStyle ) { mHighlightMatchingBracket = false; mHighlightSelectionMatch = false; mEnableColorPickerOnSelection = false; + mEnableInlineColorBoxes = false; mMinimapEnabled = false; mFindReplaceEnabled = false; mLineBreakingColumn = 0; @@ -1257,6 +1260,8 @@ std::string UICodeEditor::getCodeEditorFlags( bool enabled ) const { flags += "highlightselectionmatch|"; if ( mEnableColorPickerOnSelection == enabled ) flags += "colorpickeronselection|"; + if ( mEnableInlineColorBoxes == enabled ) + flags += "inlinecolorboxes|"; if ( getVerticalScrollBarEnabled() == enabled ) flags += "verticalscrollbar|"; if ( mColorPreview == enabled ) @@ -1990,7 +1995,7 @@ void UICodeEditor::checkColorPickerAction() { mDoc->replaceSelection( isRgba || color.a != 255 ? color.toRgbaString() : color.toRgbString() ); } ); - colorPicker->setColor( Color::fromString( mDoc->getSelectedText() ) ); + colorPicker->setColor( Color::fromString( mDoc->getSelectedText().toUtf8() ) ); } } if ( colorPicker ) @@ -2343,6 +2348,33 @@ void UICodeEditor::onDocumentLineMove( const Int64& fromLine, const Int64& toLin const Int64& numLines ) { mDocView.updateCache( fromLine, toLine, numLines ); + if ( !mColorBoxesCache.empty() ) { + Int64 linesCount = mDoc->linesCount(); + if ( numLines > 0 ) { + for ( Int64 i = linesCount - 1; i >= fromLine; --i ) { + auto lineIt = mColorBoxesCache.find( i - numLines ); + if ( lineIt != mColorBoxesCache.end() ) { + const auto& line = lineIt->second; + if ( line.first == mDoc->getLineHash( i ) ) { + auto nl = mColorBoxesCache.extract( lineIt ); + nl.key() = i; + mColorBoxesCache.insert( std::move( nl ) ); + } + } + } + } else if ( numLines < 0 ) { + for ( Int64 i = fromLine; i < linesCount; i++ ) { + auto lineIt = mColorBoxesCache.find( i - numLines ); + if ( lineIt != mColorBoxesCache.end() && + lineIt->second.first == mDoc->getLineHash( i ) ) { + auto nl = mColorBoxesCache.extract( lineIt ); + nl.key() = i; + mColorBoxesCache[i] = std::move( nl.mapped() ); + } + } + } + } + if ( !mFont || mFont->isMonospace() || mLinesWidthCache.empty() ) return; @@ -3162,6 +3194,13 @@ void UICodeEditor::setEnableColorPickerOnSelection( const bool& enableColorPicke mEnableColorPickerOnSelection = enableColorPickerOnSelection; } +void UICodeEditor::setEnableInlineColorBoxes( const bool& enableInlineColorBoxes ) { + if ( enableInlineColorBoxes != mEnableInlineColorBoxes ) { + mEnableInlineColorBoxes = enableInlineColorBoxes; + invalidateDraw(); + } +} + void UICodeEditor::setSyntaxDefinition( const SyntaxDefinition& definition ) { if ( &definition == &mDoc->getSyntaxDefinition() ) return; @@ -4020,6 +4059,43 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo ttf->setEnableFallbackFont( false ); ttf->setEnableEmojiFallback( false ); } + + const std::vector* colorBoxesPtr = nullptr; + size_t colorBoxIdx = 0; + if ( mEnableInlineColorBoxes ) { + auto it = mColorBoxesCache.find( line ); + if ( it == mColorBoxesCache.end() || it->second.first != mDoc->getLineHash( line ) ) { + std::vector colorBoxes; + size_t cachePos = 0; + for ( const auto& token : tokens ) { + if ( token.type == "string"_sst || token.type == "number"_sst || + token.type == "literal"_sst ) { + String::View text = strLine.view().substr( cachePos, token.len ); + String::View str = String::trim( text ); + if ( str.size() >= 4 ) { + if ( str[0] == '"' || str[0] == '\'' ) + str = str.substr( 1 ); + if ( !str.empty() && ( str.back() == '"' || str.back() == '\'' ) ) + str = str.substr( 0, str.size() - 1 ); + if ( Color::isColorString( str, false ) || + Color::validHexColorString( str ) ) { + ColorBoxData data; + data.startColumn = cachePos; + data.endColumn = cachePos + token.len; + data.color = Color::fromString( String( str ).toUtf8() ); + colorBoxes.push_back( data ); + } + } + } + cachePos += token.len; + } + mColorBoxesCache[line] = { mDoc->getLineHash( line ), std::move( colorBoxes ) }; + it = mColorBoxesCache.find( line ); + } + if ( !it->second.second.empty() ) + colorBoxesPtr = &it->second.second; + } + WhitespaceDisplayConfig whitespaceDisplayConfig = mShowWhitespaces ? WhitespaceDisplayConfig{ L'·', mTabIndentCharacter, mTabIndentAlignment, Color( mWhitespaceColor ).blendAlpha( mAlpha ), @@ -4068,6 +4144,34 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo } } + if ( colorBoxesPtr ) { + while ( colorBoxIdx < colorBoxesPtr->size() && + (Int64)pos >= ( *colorBoxesPtr )[colorBoxIdx].endColumn ) { + colorBoxIdx++; + } + if ( colorBoxIdx < colorBoxesPtr->size() ) { + const auto& colorBox = ( *colorBoxesPtr )[colorBoxIdx]; + if ( (Int64)pos >= colorBox.startColumn && + (Int64)pos < colorBox.endColumn ) { + primitives.setColor( colorBox.color ); + primitives.drawRectangle( Rectf( + position, + Sizef( getTextWidth( text, isMonospace, + mTabStops ? position.x - initX + : std::optional{} ), + lineHeight ) ) ); + + Color textColor; + if ( colorBox.color.perceivedLuminance() > 128 ) { + textColor = Color::Black; + } else { + textColor = Color::White; + } + fontStyle.FontColor = textColor.blendAlpha( mAlpha ); + } + } + } + if ( mTabStops ) whitespaceDisplayConfig.tabOffset = position.x - initX; @@ -4136,6 +4240,31 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo } } + if ( colorBoxesPtr ) { + while ( colorBoxIdx < colorBoxesPtr->size() && + (Int64)( pos - token.len ) >= + ( *colorBoxesPtr )[colorBoxIdx].endColumn ) { + colorBoxIdx++; + } + if ( colorBoxIdx < colorBoxesPtr->size() ) { + const auto& colorBox = ( *colorBoxesPtr )[colorBoxIdx]; + if ( (Int64)( pos - token.len ) >= colorBox.startColumn && + (Int64)( pos - token.len ) < colorBox.endColumn ) { + primitives.setColor( colorBox.color ); + primitives.drawRectangle( + Rectf( position, Sizef( textWidth, lineHeight ) ) ); + + Color textColor; + if ( colorBox.color.perceivedLuminance() > 128 ) { + textColor = Color::Black; + } else { + textColor = Color::White; + } + fontStyle.FontColor = textColor.blendAlpha( mAlpha ); + } + } + } + if ( isMonospace && curPositionChar + curChar + curCharsWidth > curMaxPositionChar ) { if ( curChar < curPositionChar ) { @@ -4731,7 +4860,7 @@ void UICodeEditor::checkMouseOverColor( const Vector2i& position ) { } } if ( found ) { - mPreviewColor = Color::fromString( word ); + mPreviewColor = Color::fromString( word.toUtf8() ); mPreviewColorRange = wordPos; invalidateDraw(); } else { diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index bd4c84a37..195ea417b 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -170,6 +170,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, editor.highlightSelectionMatch = ini.getValueB( "editor", "highlight_selection_match", true ); editor.colorPickerSelection = ini.getValueB( "editor", "color_picker_selection", true ); editor.colorPreview = ini.getValueB( "editor", "color_preview", true ); + editor.inlineColorBoxes = ini.getValueB( "editor", "inline_color_boxes", false ); editor.minimap = ini.getValueB( "editor", "minimap", true ); editor.showDocInfo = ini.getValueB( "editor", "show_doc_info", true ); editor.hideTabBarOnSingleTab = ini.getValueB( "editor", "hide_tab_bar_on_single_tab", false ); @@ -360,6 +361,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "editor", "highlight_selection_match", editor.highlightSelectionMatch ); ini.setValueB( "editor", "color_picker_selection", editor.colorPickerSelection ); ini.setValueB( "editor", "color_preview", editor.colorPreview ); + ini.setValueB( "editor", "inline_color_boxes", editor.inlineColorBoxes ); ini.setValueB( "editor", "minimap", editor.minimap ); ini.setValueB( "editor", "show_doc_info", editor.showDocInfo ); ini.setValueB( "editor", "hide_tab_bar_on_single_tab", editor.hideTabBarOnSingleTab ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index abbebb34f..6fd9fb20e 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -82,6 +82,7 @@ struct CodeEditorConfig { bool highlightSelectionMatch{ true }; bool colorPickerSelection{ false }; bool colorPreview{ false }; + bool inlineColorBoxes{ false }; bool minimap{ true }; bool showDocInfo{ true }; bool hideTabBarOnSingleTab{ true }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 00e73d12d..f5450688f 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2935,6 +2935,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); editor->setTabStops( mConfig.doc.tabStops ); + editor->setEnableInlineColorBoxes( config.inlineColorBoxes ); doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() ); doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) ); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index d3a207eed..e3db403e8 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1933,6 +1933,12 @@ UIMenu* SettingsMenu::createViewMenu() { mViewMenu->addCheckBox( i18n( "enable_horizontal_scrollbar", "Enable Horizontal ScrollBar" ) ) ->setActive( mApp->getConfig().editor.horizontalScrollbar ) ->setId( "enable-horizontal-scrollbar" ); + mViewMenu->addCheckBox( i18n( "enable_inline_color_boxes", "Enable Color Boxes" ) ) + ->setActive( mApp->getConfig().editor.inlineColorBoxes ) + ->setTooltipText( i18n( "enable_inline_color_boxes_tooltip", + "Renders a background box of the parsed color directly behind the\n" + "text itself." ) ) + ->setId( "enable-inline-color-boxes" ); mViewMenu->addCheckBox( i18n( "enable_color_preview", "Enable Color Preview" ) ) ->setActive( mApp->getConfig().editor.colorPreview ) ->setTooltipText( i18n( "enable_color_preview_tooltip", @@ -2061,6 +2067,11 @@ UIMenu* SettingsMenu::createViewMenu() { editor->setHorizontalScrollBarEnabled( mApp->getConfig().editor.horizontalScrollbar ); } ); + } else if ( item->getId() == "enable-inline-color-boxes" ) { + mApp->getConfig().editor.inlineColorBoxes = item->asType()->isActive(); + mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setEnableInlineColorBoxes( mApp->getConfig().editor.inlineColorBoxes ); + } ); } else if ( item->getId() == "enable-color-preview" ) { mApp->getConfig().editor.colorPreview = item->asType()->isActive(); mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {