diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index c60d7e59c..cb1f0b9a6 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -78,8 +78,13 @@ class EE_API FontTrueType : public Font { bool isColorEmojiFont() const; + /** @return True if the font identifies itself as a monospace font and currently does not hold + * any non-monospaced glyph (from a fallback font) */ bool isMonospace() const; + /** @return True if the font identifies itself as a monospace font */ + bool isIdentifiedAsMonospace() const; + bool isScalable() const; bool isEmojiFont() const; diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index d2603c299..b51be3348 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -21,6 +21,10 @@ class EE_API Text { Shadow = 1 << 4 ///< Draw a shadow below the text }; + enum DrawHints { + AllAscii = 1 << 0, + }; + static std::string styleFlagToString( const Uint32& flags ); static Uint32 stringToStyleFlag( const std::string& str ); @@ -43,19 +47,22 @@ class EE_API Text { const Color& fontColor, Uint32 style = 0, Float outlineThickness = 0.f, const Color& outlineColor = Color::Black, const Color& shadowColor = Color::Black, - const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4 ); + const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, + Uint32 textDrawHints = 0 ); static Sizef draw( const String& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, Uint32 textDrawHints = 0 ); static Sizef draw( const String::View& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style = 0, Float outlineThickness = 0.f, const Color& outlineColor = Color::Black, const Color& shadowColor = Color::Black, - const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4 ); + const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, + Uint32 textDrawHints = 0 ); static Sizef draw( const String::View& string, const Vector2f& pos, - const FontStyleConfig& config, const Uint32& tabWidth = 4 ); + const FontStyleConfig& config, const Uint32& tabWidth = 4, + Uint32 textDrawHints = 0 ); static void drawUnderline( const Vector2f& pos, Float width, Font* font, Float fontSize, const Color& fontColor, const Uint32& style, Float outlineThickness, @@ -63,9 +70,9 @@ class EE_API Text { const Vector2f& shadowOffset ); static void drawStrikeThrough( const Vector2f& pos, Float width, Font* font, Float fontSize, - const Color& fontColor, const Uint32& style, - Float outlineThickness, const Color& outlineColor, - const Color& shadowColor, const Vector2f& shadowOffset ); + const Color& fontColor, const Uint32& style, + Float outlineThickness, const Color& outlineColor, + const Color& shadowColor, const Vector2f& shadowOffset ); static Int32 findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font, const Uint32& fontSize, const String& string, @@ -312,11 +319,12 @@ class EE_API Text { const Color& fontColor, Uint32 style = 0, Float outlineThickness = 0.f, const Color& outlineColor = Color::Black, const Color& shadowColor = Color::Black, - const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4 ); + const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, + Uint32 textDrawHints = 0 ); template static Sizef draw( const StringType& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, Uint32 textDrawHints = 0 ); template static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, diff --git a/include/eepp/scene/scenenode.hpp b/include/eepp/scene/scenenode.hpp index a50b61163..641390f0c 100644 --- a/include/eepp/scene/scenenode.hpp +++ b/include/eepp/scene/scenenode.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace EE { namespace Graphics { class FrameBuffer; @@ -114,7 +115,7 @@ class EE_API SceneNode : public Node { protected: friend class Node; - typedef UnorderedSet CloseList; + typedef std::unordered_set CloseList; EE::Window::Window* mWindow; ActionManager* mActionManager; diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp index 9947ad44c..4ce3b741e 100644 --- a/include/eepp/ui/css/propertydefinition.hpp +++ b/include/eepp/ui/css/propertydefinition.hpp @@ -220,6 +220,10 @@ enum class PropertyId : Uint32 { RowValign = String::hash( "row-valign" ), TextOverflow = String::hash( "text-overflow" ), CheckMode = String::hash( "check-mode" ), + EnableCodeEditorFlags = String::hash( "enable-editor-flags" ), + DisableCodeEditorFlags = String::hash( "disable-editor-flags" ), + LineWrapMode = String::hash( "line-wrap-mode" ), + LineWrapType = String::hash( "line-wrap-type" ), }; enum class PropertyType : Uint32 { diff --git a/include/eepp/ui/doc/textdocumentline.hpp b/include/eepp/ui/doc/textdocumentline.hpp index bb82f2e90..976de6d4c 100644 --- a/include/eepp/ui/doc/textdocumentline.hpp +++ b/include/eepp/ui/doc/textdocumentline.hpp @@ -64,6 +64,8 @@ class EE_API TextDocumentLine { std::string toUtf8() const { return mText.toUtf8(); } + bool isAscii() const { return ( mFlags & AllAscii ) != 0; } + protected: String mText; String::HashType mHash; diff --git a/include/eepp/ui/uiapplication.hpp b/include/eepp/ui/uiapplication.hpp index 2164d6e2b..a795a65a3 100644 --- a/include/eepp/ui/uiapplication.hpp +++ b/include/eepp/ui/uiapplication.hpp @@ -27,6 +27,9 @@ class EE_API UIApplication { //! The default base font for the UI. If not provided it will load NotoSans-Regular ( will //! look at "assets/fonts/NotoSans-Regular.ttf" ) Font* baseFont{ nullptr }; + //! The default base monospace font for the UI. If not provided it will load DejaVuSansMono + //! ( will look at "assets/fonts/DejaVuSansMono.ttf" ) + Font* monospaceFont{ nullptr }; //! The style sheet path is the path of the base UI theme stylesheet ( will look at //! "assets/ui/breeze.css" by default ) std::optional baseStyleSheetPath; diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index fbc681e84..d8f45b13d 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -1096,7 +1096,12 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { template size_t characterWidth( const StringType& str ) const; - template Float getTextWidth( const StringType& text ) const; + template + Float getTextWidth( const StringType& text, bool fromMonospaceLine ) const; + + Float getTextWidth( const String& text, bool fromMonospaceLine ) const; + + Float getTextWidth( const String::View& text, bool fromMonospaceLine ) const; void updateIMELocation(); @@ -1113,6 +1118,12 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool isNotMonospace() const; void flashCursor(); + + void setCodeEditorFlags( std::string flags, bool enable ); + + std::string getCodeEditorFlags( bool enabled ) const; + + bool isMonospaceLine( Int64 lineIndex ) const; }; }} // namespace EE::UI diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 997d175d4..cff592493 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -2127,10 +2127,18 @@ void String::readBySeparator( std::string_view buf, std::function onSepChunkRead, char sep ) { auto lastNL = 0; auto nextNL = buf.find_first_of( sep ); - while ( nextNL != std::string_view::npos ) { - onSepChunkRead( buf.substr( lastNL, nextNL - lastNL ) ); - lastNL = nextNL + 1; - nextNL = buf.find_first_of( sep, nextNL + 1 ); + if ( nextNL != std::string_view::npos ) { + while ( nextNL != std::string_view::npos ) { + onSepChunkRead( buf.substr( lastNL, nextNL - lastNL ) ); + lastNL = nextNL + 1; + nextNL = buf.find_first_of( sep, nextNL + 1 ); + } + + if ( lastNL < static_cast( buf.size() ) ) { + onSepChunkRead( buf.substr( lastNL ) ); + } + } else { + onSepChunkRead( buf ); } } diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 0da21e790..efba5cb0e 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -337,11 +337,11 @@ const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSiz if ( !mIsColorEmojiFont && FontManager::instance()->getColorEmojiFont() != nullptr && FontManager::instance()->getColorEmojiFont()->getType() == FontType::TTF ) { - if ( isMonospace() && maxWidth == 0.f && !Text::TextShaperEnabled ) { - const Glyph& monospaceGlyph = - getGlyph( ' ', characterSize, bold, italic, outlineThickness ); - maxWidth = monospaceGlyph.advance; - } + // if ( isMonospace() && maxWidth == 0.f && !Text::TextShaperEnabled ) { + // const Glyph& monospaceGlyph = + // getGlyph( ' ', characterSize, bold, italic, outlineThickness ); + // maxWidth = monospaceGlyph.advance; + // } FontTrueType* fontEmoji = static_cast( FontManager::instance()->getColorEmojiFont() ); @@ -357,11 +357,11 @@ const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSiz } else if ( !mIsEmojiFont && FontManager::instance()->getEmojiFont() != nullptr && FontManager::instance()->getEmojiFont()->getType() == FontType::TTF ) { - if ( isMonospace() && maxWidth == 0.f && !Text::TextShaperEnabled ) { - const Glyph& monospaceGlyph = - getGlyph( ' ', characterSize, bold, italic, outlineThickness ); - maxWidth = monospaceGlyph.advance; - } + // if ( isMonospace() && maxWidth == 0.f && !Text::TextShaperEnabled ) { + // const Glyph& monospaceGlyph = + // getGlyph( ' ', characterSize, bold, italic, outlineThickness ); + // maxWidth = monospaceGlyph.advance; + // } FontTrueType* fontEmoji = static_cast( FontManager::instance()->getEmojiFont() ); @@ -1408,6 +1408,10 @@ bool FontTrueType::isMonospace() const { return mIsMonospaceComplete; } +bool FontTrueType::isIdentifiedAsMonospace() const { + return mIsMonospace; +} + bool FontTrueType::isScalable() const { return FT_IS_SCALABLE( static_cast( mFace ) ); } diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index d42d0cf2b..4f296e219 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -241,27 +241,27 @@ Float Text::getTextWidth( const String::View& string, const FontStyleConfig& con Sizef Text::draw( const String& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth ) { + const Uint32& tabWidth, Uint32 textDrawHints ) { return draw( string, pos, font, fontSize, fontColor, style, outlineThickness, - outlineColor, shadowColor, shadowOffset, tabWidth ); + outlineColor, shadowColor, shadowOffset, tabWidth, textDrawHints ); } Sizef Text::draw( const String& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth ) { - return draw( string, pos, config, tabWidth ); + const Uint32& tabWidth, Uint32 textDrawHints ) { + return draw( string, pos, config, tabWidth, textDrawHints ); } Sizef Text::draw( const String::View& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth ) { + const Uint32& tabWidth, Uint32 textDrawHints ) { return draw( string, pos, font, fontSize, fontColor, style, outlineThickness, - outlineColor, shadowColor, shadowOffset, tabWidth ); + outlineColor, shadowColor, shadowOffset, tabWidth, textDrawHints ); } Sizef Text::draw( const String::View& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth ) { - return draw( string, pos, config, tabWidth ); + const Uint32& tabWidth, Uint32 textDrawHints ) { + return draw( string, pos, config, tabWidth, textDrawHints ); } Text* Text::New() { @@ -376,12 +376,21 @@ template Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth ) { + const Uint32& tabWidth, Uint32 textDrawHints ) { Vector2f cpos{ pos }; String::StringBaseType ch; String::StringBaseType prevChar = 0; bool isBold = ( style & Text::Bold ) != 0; bool isItalic = ( style & Text::Italic ) != 0; + bool fallbacksToColorEmoji = + font && font->getType() == FontType::TTF && + !static_cast( font )->isColorEmojiFont() && + FontManager::instance()->getColorEmojiFont() != nullptr && + FontManager::instance()->getColorEmojiFont()->getType() == FontType::TTF; + bool isMonospace = font && ( font->isMonospace() || + ( font->getType() == FontType::TTF && + static_cast( font )->isIdentifiedAsMonospace() && + ( textDrawHints & DrawHints::AllAscii ) != 0 ) ); Float kerning = 0; Float width = 0; Float height = font->getFontHeight( fontSize ); @@ -431,7 +440,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo curGlyph.codepoint, fontSize, isBold, isItalic, 0, rFont->getPage( fontSize ) ); if ( gd ) { - if ( !font->isMonospace() ) { + if ( !isMonospace ) { kerning = font->getKerningFromGlyphIndex( prevGlyphIndex, curGlyph.codepoint, fontSize, isBold, isItalic, outlineThickness ); @@ -439,7 +448,12 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo width += kerning; } - drawGlyph( BR, gd, cpos, fontColor, isItalic ); + drawGlyph( BR, gd, cpos, + fallbacksToColorEmoji && + Font::isEmojiCodePoint( ch ) + ? Color::White + : fontColor, + isItalic ); Float advance = font->isColorEmojiFont() && ' ' != ch ? gd->getPixelsSize().getWidth() @@ -546,14 +560,17 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo auto* gd = font->getGlyphDrawable( ch, fontSize, isBold, isItalic ); if ( gd ) { - if ( !font->isMonospace() ) { + if ( !isMonospace ) { kerning = font->getKerning( prevChar, ch, fontSize, isBold, isItalic, outlineThickness ); cpos.x += kerning; width += kerning; } - drawGlyph( BR, gd, cpos, fontColor, isItalic ); + drawGlyph( BR, gd, cpos, + fallbacksToColorEmoji && Font::isEmojiCodePoint( ch ) ? Color::White + : fontColor, + isItalic ); cpos.x += gd->getAdvance(); width += gd->getAdvance(); @@ -581,10 +598,10 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo template Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth ) { + const Uint32& tabWidth, Uint32 textDrawHints ) { return draw( string, pos, config.Font, config.CharacterSize, config.FontColor, config.Style, config.OutlineThickness, config.OutlineColor, - config.ShadowColor, config.ShadowOffset, tabWidth ); + config.ShadowColor, config.ShadowOffset, tabWidth, textDrawHints ); } template diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index 4d2ddb1d1..8cac60ae3 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -138,18 +138,16 @@ void Node::setInternalSize( const Sizef& size ) { mSize = size; mNodeFlags |= NODE_FLAG_POLYGON_DIRTY; updateCenter(); + onSizeChange(); sendCommonEvent( Event::OnSizeChange ); invalidateDraw(); } void Node::scheduledUpdate( const Time& ) {} -Node* Node::setSize( const Sizef& Size ) { - if ( Size != mSize ) { - setInternalSize( Size ); - - onSizeChange(); - } +Node* Node::setSize( const Sizef& size ) { + if ( size != mSize ) + setInternalSize( size ); return this; } diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index b9cc58761..9b983d138 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -414,6 +414,12 @@ void StyleSheetSpecification::registerDefaultProperties() { registerProperty( "check-mode", "element" ).setType( PropertyType::String ); + registerProperty( "enable-editor-flags", "" ).setType( PropertyType::String ); + registerProperty( "disable-editor-flags", "" ).setType( PropertyType::String ); + + registerProperty( "line-wrap-mode", "nowrap" ).setType( PropertyType::String ); + registerProperty( "line-wrap-type", "viewport" ).setType( PropertyType::String ); + // Shorthands registerShorthand( "margin", { "margin-top", "margin-right", "margin-bottom", "margin-left" }, "box" ); diff --git a/src/eepp/ui/uiapplication.cpp b/src/eepp/ui/uiapplication.cpp index 0efb1026d..df90ad6d7 100644 --- a/src/eepp/ui/uiapplication.cpp +++ b/src/eepp/ui/uiapplication.cpp @@ -52,6 +52,13 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin if ( font && font->getType() == FontType::TTF ) FontFamily::loadFromRegular( static_cast( font ) ); + Font* monospaceFont = appSettings.monospaceFont + ? appSettings.monospaceFont + : FontTrueType::New( "monospace", "assets/fonts/DejaVuSansMono.ttf" ); + + if ( monospaceFont && monospaceFont->getType() == FontType::TTF ) + FontFamily::loadFromRegular( static_cast( monospaceFont ) ); + if ( appSettings.emojiFont == nullptr ) FontTrueType::New( "NotoEmoji-Regular", "assets/fonts/NotoEmoji-Regular.ttf" ); diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 100612f70..78e809bc0 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1102,11 +1102,11 @@ size_t UICodeEditor::getTotalVisibleLines() const { } Uint32 UICodeEditor::onTextEditing( const TextEditingEvent& event ) { - UIWidget::onTextEditing( event ); mLastActivity.restart(); mDoc->imeTextEditing( event.getText() ); updateIMELocation(); invalidateDraw(); + UIWidget::onTextEditing( event ); return 1; } @@ -1132,6 +1132,118 @@ void UICodeEditor::flashCursor() { Actions::Close::New() ) ); } +void UICodeEditor::setCodeEditorFlags( std::string flags, bool enable ) { + String::toLowerInPlace( flags ); + String::readBySeparator( + std::string_view{ flags }, + [this, enable]( std::string_view flag ) { + if ( "linenumber" == flag ) { + mShowLineNumber = enable; + } else if ( "foldingregion" == flag ) { + mShowFoldingRegion = enable; + } else if ( "whitespaces" == flag ) { + mShowWhitespaces = enable; + } else if ( "lineendings" == flag ) { + mShowLineEndings = enable; + } else if ( "highlightcurrentline" == flag ) { + mHighlightCurrentLine = enable; + } else if ( "highlightmatchingbracket" == flag ) { + mHighlightMatchingBracket = enable; + } else if ( "highlightselectionmatch" == flag ) { + mHighlightSelectionMatch = enable; + } else if ( "colorpickeronselection" == flag ) { + mEnableColorPickerOnSelection = enable; + } else if ( "verticalscrollbar" == flag ) { + setVerticalScrollBarEnabled( enable ); + } else if ( "colorpreview" == flag ) { + mColorPreview = enable; + } else if ( "interactivelinks" == flag ) { + mInteractiveLinks = enable; + } else if ( "displayloader" == flag ) { + mDisplayLoaderIfDocumentLoading = enable; + } else if ( "defaultcontextmenu" == flag ) { + mCreateDefaultContextMenuOptions = enable; + } else if ( "minimap" == flag ) { + mMinimapEnabled = enable; + } else if ( "autoclosexmltags" == flag ) { + mAutoCloseXMLTags = enable; + } else if ( "findreplace" == flag ) { + mFindReplaceEnabled = enable; + } else if ( "showindentationguides" == flag ) { + mShowIndentationGuides = enable; + } else if ( "linesrelativeposition" == flag ) { + mShowLinesRelativePosition = enable; + } else if ( "lockedicon" == flag ) { + mDisplayLockedIcon = enable; + } else if ( "foldsalwaysvisible" == flag ) { + mFoldsAlwaysVisible = enable; + } else if ( "foldsvisible" == flag ) { + mFoldsVisible = enable; + } else if ( "flashcursor" == flag ) { + mEnableFlashCursor = enable; + } else if ( "defaultstyle" == flag ) { + mUseDefaultStyle = enable; + } else if ( !enable && "editorfeatures" == flag ) { + disableEditorFeatures(); + } + }, + '|' ); +} + +std::string UICodeEditor::getCodeEditorFlags( bool enabled ) const { + std::string flags; + if ( mShowLineNumber == enabled ) + flags += "linenumber|"; + if ( mShowFoldingRegion == enabled ) + flags += "foldingregion|"; + if ( mShowWhitespaces == enabled ) + flags += "whitespaces|"; + if ( mShowLineEndings == enabled ) + flags += "lineendings|"; + if ( mHighlightCurrentLine == enabled ) + flags += "highlightcurrentline|"; + if ( mHighlightMatchingBracket == enabled ) + flags += "highlightmatchingbracket|"; + if ( mHighlightSelectionMatch == enabled ) + flags += "highlightselectionmatch|"; + if ( mEnableColorPickerOnSelection == enabled ) + flags += "colorpickeronselection|"; + if ( getVerticalScrollBarEnabled() == enabled ) + flags += "verticalscrollbar|"; + if ( mColorPreview == enabled ) + flags += "colorpreview|"; + if ( mInteractiveLinks == enabled ) + flags += "interactivelinks|"; + if ( mDisplayLoaderIfDocumentLoading == enabled ) + flags += "displayloader|"; + if ( mCreateDefaultContextMenuOptions == enabled ) + flags += "defaultcontextmenu|"; + if ( mMinimapEnabled == enabled ) + flags += "minimap|"; + if ( mAutoCloseXMLTags == enabled ) + flags += "autoclosexmltags|"; + if ( mFindReplaceEnabled == enabled ) + flags += "findreplace|"; + if ( mShowIndentationGuides == enabled ) + flags += "showindentationguides|"; + if ( mShowLinesRelativePosition == enabled ) + flags += "linesrelativeposition|"; + if ( mDisplayLockedIcon == enabled ) + flags += "lockedicon|"; + if ( mFoldsAlwaysVisible == enabled ) + flags += "foldsalwaysvisible|"; + if ( mFoldsVisible == enabled ) + flags += "foldsvisible|"; + if ( mEnableFlashCursor == enabled ) + flags += "flashcursor|"; + if ( mUseDefaultStyle == enabled ) + flags += "defaultstyle|"; + + if ( !flags.empty() ) + flags.pop_back(); + return flags; +} + Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { if ( getUISceneNode()->getWindow()->getIME().isEditing() ) return 0; @@ -1790,19 +1902,19 @@ void UICodeEditor::drawCursor( const Vector2f& startScroll, const Float& lineHei } void UICodeEditor::onSizeChange() { - UIWidget::onSizeChange(); invalidateEditor( false ); invalidateLineWrapMaxWidth( false ); if ( mDocView.isWrapEnabled() ) invalidateLongestLineWidth(); + UIWidget::onSizeChange(); } void UICodeEditor::onPaddingChange() { - UIWidget::onPaddingChange(); invalidateEditor( false ); invalidateLineWrapMaxWidth( false ); if ( mDocView.isWrapEnabled() ) invalidateLongestLineWidth(); + UIWidget::onPaddingChange(); } std::pair UICodeEditor::findLongestLineInRange( const TextRange& range ) { @@ -1833,12 +1945,15 @@ void UICodeEditor::findLongestLine() { Float UICodeEditor::getLineWidth( const Int64& docLine ) { if ( docLine >= (Int64)mDoc->linesCount() || !mDocView.isLineVisible( docLine ) ) return 0; + + bool isMonospaceLine = this->isMonospaceLine( docLine ); + if ( mDocView.isWrappedLine( docLine ) ) { auto vline = mDocView.getVisibleLineInfo( docLine ); auto& line = mDoc->line( docLine ).getText(); Float width = 0; - if ( isNotMonospace() ) { + if ( !isMonospaceLine ) { auto& line = mDoc->line( docLine ); auto found = mLinesWidthCache.find( docLine ); if ( found != mLinesWidthCache.end() && line.getHash() == found->second.first ) @@ -1850,18 +1965,18 @@ Float UICodeEditor::getLineWidth( const Int64& docLine ) { auto len = i + 1 < vline.visualLines.size() ? vline.visualLines[i + 1].column() : line.size(); auto vlineStr = line.view().substr( pos, len - pos ); - auto curWidth = getTextWidth( vlineStr ); + auto curWidth = getTextWidth( vlineStr, isMonospaceLine ); width = eemax( width, curWidth ); } - if ( isNotMonospace() ) { + if ( !isMonospaceLine ) { mLinesWidthCache[docLine] = { line.getHash(), width }; } return width; } - if ( isNotMonospace() ) { + if ( !isMonospaceLine ) { auto& line = mDoc->line( docLine ); auto found = mLinesWidthCache.find( docLine ); if ( found != mLinesWidthCache.end() && line.getHash() == found->second.first ) @@ -1870,7 +1985,8 @@ Float UICodeEditor::getLineWidth( const Int64& docLine ) { mLinesWidthCache[docLine] = { line.getHash(), width }; return width; } - return getTextWidth( mDoc->line( docLine ).getText() ); + + return getTextWidth( mDoc->line( docLine ).getText(), isMonospaceLine ); } void UICodeEditor::updateScrollBar() { @@ -2283,7 +2399,7 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, ( info.visibleIndex != firstWrappedIndex ? mDocView.getLinePadding( position.line() ) : 0.f ); double offsetY = mDocView.getLineYOffset( info.visibleIndex, lh ); - if ( isNotMonospace() ) { + if ( !isMonospaceLine( position.line() ) ) { if ( !info.range.isValid() ) return {}; const auto& line = mDoc->line( position.line() ).getText(); @@ -2317,7 +2433,7 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, } double offsetY = mDocView.getLineYOffset( position.line(), lh ); - if ( isNotMonospace() ) { + if ( !isMonospaceLine( position.line() ) ) { bool isLastChar = position.column() == (Int64)mDoc->line( position.line() ).getText().size(); Float x = Text::findCharacterPos( @@ -2357,7 +2473,7 @@ size_t UICodeEditor::characterWidth( const String& str ) const { } Float UICodeEditor::getTextWidth( const String& text ) const { - return getTextWidth( text ); + return getTextWidth( text, false ); } size_t UICodeEditor::characterWidth( const String::View& str ) const { @@ -2365,11 +2481,20 @@ size_t UICodeEditor::characterWidth( const String::View& str ) const { } Float UICodeEditor::getTextWidth( const String::View& text ) const { - return getTextWidth( text ); + return getTextWidth( text, false ); } -template Float UICodeEditor::getTextWidth( const StringType& line ) const { - if ( isNotMonospace() ) { +Float UICodeEditor::getTextWidth( const String& text, bool fromMonospaceLine ) const { + return getTextWidth( text, fromMonospaceLine ); +} + +Float UICodeEditor::getTextWidth( const String::View& text, bool fromMonospaceLine ) const { + return getTextWidth( text, fromMonospaceLine ); +} + +template +Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine ) const { + if ( !fromMonospaceLine && isNotMonospace() ) { return Text::getTextWidth( mFont, getCharacterSize(), line, mFontStyleConfig.Style, mTabWidth ); } @@ -2691,6 +2816,18 @@ bool UICodeEditor::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::LineSpacing: setLineSpacing( attribute.asStyleSheetLength() ); break; + case PropertyId::EnableCodeEditorFlags: + setCodeEditorFlags( attribute.asString(), true ); + break; + case PropertyId::DisableCodeEditorFlags: + setCodeEditorFlags( attribute.asString(), false ); + break; + case PropertyId::LineWrapMode: + setLineWrapMode( DocumentView::toLineWrapMode( attribute.asString() ) ); + break; + case PropertyId::LineWrapType: + setLineWrapType( DocumentView::toLineWrapType( attribute.asString() ) ); + break; default: return UIWidget::applyProperty( attribute ); } @@ -2731,6 +2868,14 @@ std::string UICodeEditor::getPropertyString( const PropertyDefinition* propertyD return isTextSelectionEnabled() ? "true" : "false"; case PropertyId::LineSpacing: return getLineSpacing().toString(); + case PropertyId::EnableCodeEditorFlags: + return getCodeEditorFlags( true ); + case PropertyId::DisableCodeEditorFlags: + return getCodeEditorFlags( false ); + case PropertyId::LineWrapMode: + return DocumentView::fromLineWrapMode( getLineWrapMode() ); + case PropertyId::LineWrapType: + return DocumentView::fromLineWrapType( getLineWrapType() ); default: return UIWidget::getPropertyString( propertyDef, propertyIndex ); } @@ -2738,12 +2883,23 @@ std::string UICodeEditor::getPropertyString( const PropertyDefinition* propertyD std::vector UICodeEditor::getPropertiesImplemented() const { auto props = UIWidget::getPropertiesImplemented(); - auto local = { - PropertyId::Locked, PropertyId::Color, PropertyId::TextShadowColor, - PropertyId::TextShadowOffset, PropertyId::SelectionColor, PropertyId::SelectionBackColor, - PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle, - PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, PropertyId::TextSelection, - PropertyId::LineSpacing }; + auto local = { PropertyId::Locked, + PropertyId::Color, + PropertyId::TextShadowColor, + PropertyId::TextShadowOffset, + PropertyId::SelectionColor, + PropertyId::SelectionBackColor, + PropertyId::FontFamily, + PropertyId::FontSize, + PropertyId::FontStyle, + PropertyId::TextStrokeWidth, + PropertyId::TextStrokeColor, + PropertyId::TextSelection, + PropertyId::LineSpacing, + PropertyId::EnableCodeEditorFlags, + PropertyId::DisableCodeEditorFlags, + PropertyId::LineWrapMode, + PropertyId::LineWrapType }; props.insert( props.end(), local.begin(), local.end() ); return props; } @@ -2893,7 +3049,7 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x .view() .substr( visibleIndexRange.start().column(), visibleIndexRange.length() ); - if ( isNotMonospace() ) { + if ( !isMonospaceLine( pos.line() ) ) { return visibleIndexRange.start().column() + Text::findCharacterFromPos( Vector2i( eemax( -xOffset + x, 0.f ), 0 ), true, mFont, getCharacterSize(), line, @@ -2920,7 +3076,7 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x return visibleIndexRange.start().column() + static_cast( line.size() ) - 1; } - if ( isNotMonospace() ) { + if ( !isMonospaceLine( pos.line() ) ) { return Text::findCharacterFromPos( Vector2i( x, 0 ), true, mFont, getCharacterSize(), mDoc->line( pos.line() ).getText(), mFontStyleConfig.Style, mTabWidth ); @@ -3598,16 +3754,18 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo const DocumentViewLineRange& visibleLineRange ) { Vector2f originalPosition( position ); const auto& tokens = mDoc->getHighlighter()->getLine( line ); - const String& strLine = mDoc->line( line ).getText(); + const auto& docLine = mDoc->line( line ); + const String& strLine = docLine.getText(); Primitives primitives; Int64 curChar = 0; Int64 maxWidth = eeceil( mSize.getWidth() / getGlyphWidth() + 1 ); - bool isMonospace = mFont->isMonospace(); + bool isMonospace = isMonospaceLine( line ); bool isFallbackFont = false; bool isEmojiFallbackFont = false; bool ended = false; Float lineOffset = getLineOffset(); size_t pos = 0; + Uint32 drawHints = docLine.isAscii() ? Text::DrawHints::AllAscii : 0; if ( mDoc->mightBeBinary() && mFont->getType() == FontType::TTF ) { FontTrueType* ttf = static_cast( mFont ); isFallbackFont = ttf->isFallbackFontEnabled(); @@ -3691,12 +3849,13 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo if ( style.background != Color::Transparent ) { primitives.setColor( Color( style.background ).blendAlpha( mAlpha ) ); primitives.drawRectangle( - Rectf( position, Sizef( getTextWidth( text ), lineHeight ) ) ); + Rectf( position, + Sizef( getTextWidth( text, isMonospace ), lineHeight ) ) ); } } position.x += Text::draw( text, { position.x, position.y + lineOffset }, - fontStyle, mTabWidth ) + fontStyle, mTabWidth, drawHints ) .getWidth(); } @@ -3732,7 +3891,7 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo } pos += token.len; - Float textWidth = isMonospace ? getTextWidth( text ) : 0; + Float textWidth = isMonospace ? getTextWidth( text, isMonospace ) : 0; if ( !isMonospace || ( position.x + textWidth >= mScreenPos.x && position.x <= mScreenPos.x + mSize.getWidth() ) ) { Int64 curCharsWidth = text.size(); @@ -3766,18 +3925,18 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo size = Text::draw( text.substr( start, end ), { position.x + start * getGlyphWidth(), position.y + lineOffset }, - fontStyle, mTabWidth ); + fontStyle, mTabWidth, drawHints ); if ( minimumCharsToCoverScreen == end ) break; } } else { size = Text::draw( text.substr( 0, eemin( curCharsWidth, maxWidth ) ), { position.x, position.y + lineOffset }, fontStyle, - mTabWidth ); + mTabWidth, drawHints ); } } else { size = Text::draw( text, { position.x, position.y + lineOffset }, fontStyle, - mTabWidth ); + mTabWidth, drawHints ); } if ( !isMonospace ) @@ -4070,7 +4229,7 @@ void UICodeEditor::drawWhitespaces( const DocumentLineRange& lineRange, const Ve const Float& lineHeight, const DocumentViewLineRange& visibleLineRange ) { static const String tab = "\t"; - Float tabWidth = getTextWidth( tab ); + Float tabWidth = getTextWidth( tab, true ); Float glyphW = getGlyphWidth(); Color color( Color( mWhitespaceColor ).blendAlpha( mAlpha ) ); unsigned int fontSize = getCharacterSize(); @@ -4103,7 +4262,7 @@ void UICodeEditor::drawWhitespaces( const DocumentLineRange& lineRange, const Ve continue; const auto& text = mDoc->line( index ).getText(); - if ( mDocView.isWrappedLine( index ) || !mFont->isMonospace() ) { + if ( mDocView.isWrappedLine( index ) || !isMonospaceLine( index ) ) { size_t startCol = visibleDocRange.start().line() == index ? visibleDocRange.start().column() : 0; size_t endCol = visibleDocRange.end().line() == index ? visibleDocRange.end().column() @@ -5080,4 +5239,11 @@ void UICodeEditor::setTabIndentAlignment( CharacterAlignment alignment ) { } } +bool UICodeEditor::isMonospaceLine( Int64 lineIndex ) const { + return mFont && ( mFont->isMonospace() || + ( mFont->getType() == FontType::TTF && + static_cast( mFont )->isIdentifiedAsMonospace() && + mDoc->line( lineIndex ).isAscii() ) ); +} + }} // namespace EE::UI diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index cf0d9d075..2595de255 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -151,6 +151,7 @@ void UINode::setInternalSize( const Sizef& size ) { mSize = PixelDensity::dpToPx( s ); mNodeFlags |= NODE_FLAG_POLYGON_DIRTY; updateCenter(); + onSizeChange(); sendCommonEvent( Event::OnSizeChange ); invalidateDraw(); } @@ -171,6 +172,7 @@ void UINode::setInternalPixelsSize( const Sizef& size ) { mSize = s; mNodeFlags |= NODE_FLAG_POLYGON_DIRTY; updateCenter(); + onSizeChange(); sendCommonEvent( Event::OnSizeChange ); invalidateDraw(); } @@ -190,8 +192,6 @@ Node* UINode::setSize( const Sizef& size ) { setInternalSize( s ); - onSizeChange(); - if ( reportSizeChangeToChilds() ) { sendParentSizeChange( sizeChange ); } @@ -219,8 +219,6 @@ UINode* UINode::setPixelsSize( const Sizef& size ) { setInternalPixelsSize( s ); - onSizeChange(); - if ( reportSizeChangeToChilds() ) { sendParentSizeChange( PixelDensity::pxToDp( sizeChange ) ); } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index bba0e684a..1bf1e6ba0 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -524,6 +524,7 @@ void UISceneNode::setInternalSize( const Sizef& size ) { mDpSize = size; mSize = PixelDensity::dpToPx( size ); updateCenter(); + onSizeChange(); sendCommonEvent( Event::OnSizeChange ); invalidateDraw(); } @@ -535,8 +536,6 @@ Node* UISceneNode::setSize( const Sizef& Size ) { setInternalSize( Size ); - onSizeChange(); - if ( reportSizeChangeToChilds() ) { sendParentSizeChange( sizeChange ); } @@ -559,8 +558,6 @@ UISceneNode* UISceneNode::setPixelsSize( const Sizef& size ) { setInternalPixelsSize( size ); - onSizeChange(); - if ( reportSizeChangeToChilds() ) { sendParentSizeChange( PixelDensity::pxToDp( sizeChange ) ); } @@ -1009,6 +1006,7 @@ void UISceneNode::setInternalPixelsSize( const Sizef& size ) { mSize = s; mNodeFlags |= NODE_FLAG_POLYGON_DIRTY; updateCenter(); + onSizeChange(); sendCommonEvent( Event::OnSizeChange ); invalidateDraw(); }