From 3fd6e5637b74079cf3981e9c62d29bdb25649454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 5 Apr 2026 13:17:57 -0300 Subject: [PATCH] Fix SVG align rendering defaults to match HTML. Fix accidental reflow on UIRichText. --- include/eepp/graphics/richtext.hpp | 5 +++ include/eepp/ui/tools/uidiffview.hpp | 3 +- include/eepp/ui/uirichtext.hpp | 2 +- src/eepp/graphics/richtext.cpp | 20 ++++++---- src/eepp/ui/tools/uidiffview.cpp | 5 ++- src/eepp/ui/uihtmltable.cpp | 3 +- src/eepp/ui/uiimage.cpp | 16 ++++---- src/eepp/ui/uirichtext.cpp | 55 +++++++++++----------------- src/thirdparty/nanosvg/nanosvg.h | 6 ++- src/tools/ecode/ecode.cpp | 4 ++ 10 files changed, 64 insertions(+), 55 deletions(-) diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp index 375e4a3b1..f087fed29 100644 --- a/include/eepp/graphics/richtext.hpp +++ b/include/eepp/graphics/richtext.hpp @@ -168,8 +168,13 @@ class EE_API RichText : public Drawable { /** @return The current selection as a string. */ String getSelectionString() const; + /** Tries to update the layout if has been invalidated. This is automatically called before + * draw. */ void updateLayout(); + /** Invalidates the current layout */ + void invalidateLayout(); + protected: std::vector mBlocks; std::vector mLines; diff --git a/include/eepp/ui/tools/uidiffview.hpp b/include/eepp/ui/tools/uidiffview.hpp index c6d6e6ba2..4231763a6 100644 --- a/include/eepp/ui/tools/uidiffview.hpp +++ b/include/eepp/ui/tools/uidiffview.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace EE { namespace UI { @@ -13,7 +14,7 @@ namespace Tools { class UIDiffEditorPlugin; -class EE_API UIDiffView : public UIWidget { +class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { public: enum class ViewMode { Unified, SideBySide }; enum class SubLineDiffAlgorithm { LCS, SES }; diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp index e12a3978c..f5a01c756 100644 --- a/include/eepp/ui/uirichtext.hpp +++ b/include/eepp/ui/uirichtext.hpp @@ -150,7 +150,7 @@ class EE_API UIRichText : public UILayout { Int64 selCurEnd() const { return mSelCurEnd; } enum class IntrinsicMode { None, Min, Max }; - void rebuildRichText( IntrinsicMode mode = IntrinsicMode::None ); + void rebuildRichText( RichText& richText, IntrinsicMode mode = IntrinsicMode::None ); void positionChildren(); void updateDefaultSpansStyle(); }; diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index 221be8f89..a6f9fb19c 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -272,19 +272,19 @@ void RichText::addSpan( const String& text, const FontStyleConfig& style ) { span->setString( text ); span->setStyleConfig( style ); mBlocks.push_back( span ); // Implicitly constructs the variant's Text alternative - mNeedsLayoutUpdate = true; + invalidateLayout(); } void RichText::addDrawable( std::shared_ptr drawable ) { if ( !drawable ) return; mBlocks.push_back( drawable ); - mNeedsLayoutUpdate = true; + invalidateLayout(); } void RichText::addCustomSize( const Sizef& size ) { mBlocks.push_back( size ); - mNeedsLayoutUpdate = true; + invalidateLayout(); } void RichText::addSpan( const String& text, Font* font, Uint32 characterSize, Color color, @@ -307,30 +307,30 @@ void RichText::clear() { mBlocks.clear(); mLines.clear(); mSelection = { 0, 0 }; - mNeedsLayoutUpdate = true; + invalidateLayout(); } void RichText::setFontStyleConfig( const FontStyleConfig& styleConfig ) { mDefaultStyle = styleConfig; - mNeedsLayoutUpdate = true; + invalidateLayout(); } void RichText::setAlign( Uint32 align ) { if ( mAlign != align ) { mAlign = align; - mNeedsLayoutUpdate = true; + invalidateLayout(); } } void RichText::setMaxWidth( Float width ) { if ( mMaxWidth != width ) { mMaxWidth = width; - mNeedsLayoutUpdate = true; + invalidateLayout(); } } void RichText::invalidate() { - mNeedsLayoutUpdate = true; + invalidateLayout(); for ( auto& block : mBlocks ) { if ( auto pText = std::get_if>( &block ) ) { if ( *pText ) @@ -574,4 +574,8 @@ Sizef RichText::getSize() { return mSize; } +void RichText::invalidateLayout() { + mNeedsLayoutUpdate = true; +} + }} // namespace EE::Graphics diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index 17f54c518..e33ee1a5c 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -294,7 +295,9 @@ std::vector UIDiffView::splitDiff( const std::string& multiFileDiff return diffs; } -UIDiffView::UIDiffView() : UIWidget( "diffview" ) { +UIDiffView::UIDiffView() : + UIWidget( "diffview" ), + WidgetCommandExecuter( KeyBindings{ getUISceneNode()->getWindow()->getInput() } ) { setFlags( UI_AUTO_SIZE ); createEditor( mEditor, mPlugin ); createEditor( mLeftEditor, mLeftPlugin ); diff --git a/src/eepp/ui/uihtmltable.cpp b/src/eepp/ui/uihtmltable.cpp index 25fc4bbdf..db89c9a21 100644 --- a/src/eepp/ui/uihtmltable.cpp +++ b/src/eepp/ui/uihtmltable.cpp @@ -164,7 +164,8 @@ void UIHTMLTable::computeIntrinsicWidths() const { if ( colspan == 1 && colIndex < maxCols ) { if ( cellSpecified > 0.f ) { - mColSpecifiedWidths[colIndex] = std::max( mColSpecifiedWidths[colIndex], cellSpecified ); + mColSpecifiedWidths[colIndex] = + std::max( mColSpecifiedWidths[colIndex], cellSpecified ); } } colIndex += colspan; diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 6fbff60f3..dfd57f256 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -186,16 +186,14 @@ void UIImage::calcDestSize() { void UIImage::draw() { UINode::draw(); - if ( mVisible ) { - if ( NULL != mDrawable && 0.f != mAlpha ) { - calcDestSize(); + if ( mVisible && NULL != mDrawable && 0.f != mAlpha ) { + calcDestSize(); - mDrawable->setColor( mColor ); - mDrawable->draw( Vector2f( std::trunc( mScreenPos.x ) + std::trunc( mAlignOffset.x ), - std::trunc( mScreenPos.y ) + std::trunc( mAlignOffset.y ) ), - mDestSize ); - mDrawable->clearColor(); - } + mDrawable->setColor( mColor ); + mDrawable->draw( Vector2f( std::trunc( mScreenPos.x ) + std::trunc( mAlignOffset.x ), + std::trunc( mScreenPos.y ) + std::trunc( mAlignOffset.y ) ), + mDestSize ); + mDrawable->clearColor(); } } diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 3fbea7b28..f08b9f777 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -10,7 +11,6 @@ #include #define PUGIXML_HEADER_ONLY -#include #include namespace EE { namespace UI { @@ -388,12 +388,7 @@ void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) { for ( pugi::xml_node child = node.first_child(); child; child = child.next_sibling() ) { if ( child.type() == pugi::node_element ) { - if ( String::iequals( child.name(), "span" ) || - String::iequals( child.name(), "textspan" ) ) { - UITextSpan* span = UITextSpan::New(); - span->setParent( this ); - span->loadFromXmlNode( child ); - } else if ( mTag == "pre" && String::iequals( child.name(), "code" ) ) { + if ( mTag == "pre" && String::iequals( child.name(), "code" ) ) { // Use a UICodeEditor for

 				UICodeEditor* editor = UICodeEditor::New();
 				if ( editor ) {
@@ -477,24 +472,24 @@ void UIRichText::onAlphaChange() {
 	UILayout::onAlphaChange();
 }
 
-void UIRichText::rebuildRichText( IntrinsicMode mode ) {
-	mRichText.clear();
+void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
+	richText.clear();
 
 	// Calculate maximum layout width for the RichText block
 	Float maxWidth = mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right;
 	if ( maxWidth < 0 )
 		maxWidth = 0;
 	if ( mWidthPolicy == SizePolicy::WrapContent || mode != IntrinsicMode::None ) {
-		mRichText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later
+		richText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later
 	} else {
-		mRichText.setMaxWidth( maxWidth );
+		richText.setMaxWidth( maxWidth );
 	}
 
 	auto processWidget = [&]( UIWidget* widget, auto& processWidgetRef ) -> void {
 		if ( widget->isType( UI_TYPE_TEXTSPAN ) ) {
 			UITextSpan* span = widget->asType();
 			if ( !span->getText().empty() ) {
-				mRichText.addSpan( span->getText(), span->getFontStyleConfig() );
+				richText.addSpan( span->getText(), span->getFontStyleConfig() );
 			}
 			Node* spanChild = span->getFirstChild();
 			while ( spanChild != NULL ) {
@@ -504,8 +499,8 @@ void UIRichText::rebuildRichText( IntrinsicMode mode ) {
 				spanChild = spanChild->getNextNode();
 			}
 		} else if ( widget->isType( UI_TYPE_BR ) ) {
-			mRichText.addSpan( "\n",
-							   widget->asType()->getRichText().getFontStyleConfig() );
+			richText.addSpan( "\n",
+							  widget->asType()->getRichText().getFontStyleConfig() );
 		} else {
 			Rectf margin = widget->getLayoutPixelsMargin();
 
@@ -532,8 +527,8 @@ void UIRichText::rebuildRichText( IntrinsicMode mode ) {
 				size = widget->getPixelsSize();
 			}
 
-			mRichText.addCustomSize( Sizef( size.getWidth() + margin.Left + margin.Right,
-											size.getHeight() + margin.Top + margin.Bottom ) );
+			richText.addCustomSize( Sizef( size.getWidth() + margin.Left + margin.Right,
+										   size.getHeight() + margin.Top + margin.Bottom ) );
 		}
 	};
 
@@ -705,7 +700,7 @@ void UIRichText::updateLayout() {
 		setInternalPixelsSize( { lengthFromValue( *prop ), mSize.getHeight() } );
 	}
 
-	rebuildRichText();
+	rebuildRichText( mRichText );
 
 	mRichText.updateLayout();
 
@@ -735,14 +730,11 @@ Float UIRichText::getMinIntrinsicWidth() const {
 	}
 
 	if ( mIntrinsicWidthsDirty ) {
-		const_cast( this )->rebuildRichText( IntrinsicMode::Min );
-		mMinIntrinsicWidth = const_cast( mRichText ).getMinIntrinsicWidth() +
-							 mPaddingPx.Left + mPaddingPx.Right;
-		const_cast( this )->rebuildRichText( IntrinsicMode::Max );
-		mMaxIntrinsicWidth = const_cast( mRichText ).getMaxIntrinsicWidth() +
-							 mPaddingPx.Left + mPaddingPx.Right;
-		// We need to rebuild the rich text with the original state, otherwise layout will be broken
-		const_cast( this )->rebuildRichText( IntrinsicMode::None );
+		RichText richText( mRichText );
+		const_cast( this )->rebuildRichText( richText, IntrinsicMode::Min );
+		mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
+		const_cast( this )->rebuildRichText( richText, IntrinsicMode::Max );
+		mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
 		mIntrinsicWidthsDirty = false;
 	}
 	return mMinIntrinsicWidth;
@@ -754,14 +746,11 @@ Float UIRichText::getMaxIntrinsicWidth() const {
 	}
 
 	if ( mIntrinsicWidthsDirty ) {
-		const_cast( this )->rebuildRichText( IntrinsicMode::Min );
-		mMinIntrinsicWidth = const_cast( mRichText ).getMinIntrinsicWidth() +
-							 mPaddingPx.Left + mPaddingPx.Right;
-		const_cast( this )->rebuildRichText( IntrinsicMode::Max );
-		mMaxIntrinsicWidth = const_cast( mRichText ).getMaxIntrinsicWidth() +
-							 mPaddingPx.Left + mPaddingPx.Right;
-		// We need to rebuild the rich text with the original state, otherwise layout will be broken
-		const_cast( this )->rebuildRichText( IntrinsicMode::None );
+		RichText richText( mRichText );
+		const_cast( this )->rebuildRichText( richText, IntrinsicMode::Min );
+		mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
+		const_cast( this )->rebuildRichText( richText, IntrinsicMode::Max );
+		mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
 		mIntrinsicWidthsDirty = false;
 	}
 	return mMaxIntrinsicWidth;
diff --git a/src/thirdparty/nanosvg/nanosvg.h b/src/thirdparty/nanosvg/nanosvg.h
index e681444d5..a78090f59 100644
--- a/src/thirdparty/nanosvg/nanosvg.h
+++ b/src/thirdparty/nanosvg/nanosvg.h
@@ -165,7 +165,7 @@ typedef struct NSVGimage
 {
 	float width;				// Width of the image.
 	float height;				// Height of the image.
-	unsigned current_color;			// For "currentColor"
+	unsigned current_color;		// For "currentColor"
 	NSVGshape* shapes;			// Linked list of shapes in the image.
 } NSVGimage;
 
@@ -649,6 +649,10 @@ static NSVGparser* nsvg__createParser(unsigned current_color)
 
 	p->image->current_color = current_color;
 
+	p->alignX = NSVG_ALIGN_MID;
+	p->alignY = NSVG_ALIGN_MID;
+	p->alignType = NSVG_ALIGN_MEET;
+
 	return p;
 
 error:
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index f5450688f..d061520ab 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -2675,6 +2675,7 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori
 	diffView->setHeadersVisible( true );
 	diffView->loadFromPatch( content, originalFilePath );
 	diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
+	registerUnlockedCommands( *diffView );
 }
 
 void App::loadDiffFromPath( const std::string& path ) {
@@ -2721,6 +2722,7 @@ void App::loadDiffFromPath( const std::string& path ) {
 	diffView->setHeadersVisible( true );
 	diffView->loadFromPatch( content, path );
 	diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
+	registerUnlockedCommands( *diffView );
 }
 
 void App::loadDiffFromPaths( const std::string& oldPath, const std::string& newPath ) {
@@ -2740,6 +2742,7 @@ void App::loadDiffFromPaths( const std::string& oldPath, const std::string& newP
 	diffView->setHeadersVisible( true );
 	diffView->loadFromFile( oldPath, newPath );
 	diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
+	registerUnlockedCommands( *diffView );
 }
 
 void App::loadDiffFromStrings( const std::string& str, const std::string& otherStr ) {
@@ -2752,6 +2755,7 @@ void App::loadDiffFromStrings( const std::string& str, const std::string& otherS
 	diffView->setHeadersVisible( true );
 	diffView->loadFromStrings( str, otherStr );
 	diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
+	registerUnlockedCommands( *diffView );
 }
 
 void App::openFileFromPath( const std::string& path ) {