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 ) {