diff --git a/bin/unit_tests/assets/html/eepp-ui-background-atlas.webp b/bin/unit_tests/assets/html/eepp-ui-background-atlas.webp
index 82ae7a50c..992b3c4e7 100644
Binary files a/bin/unit_tests/assets/html/eepp-ui-background-atlas.webp and b/bin/unit_tests/assets/html/eepp-ui-background-atlas.webp differ
diff --git a/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp b/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp
index 1772856a9..dcbdb989b 100644
Binary files a/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp and b/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp differ
diff --git a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp
index 3ca34f91c..c0027e6cd 100644
Binary files a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp and b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp differ
diff --git a/include/eepp/ui/tools/uidiffview.hpp b/include/eepp/ui/tools/uidiffview.hpp
index 4231763a6..1dd7e4ba5 100644
--- a/include/eepp/ui/tools/uidiffview.hpp
+++ b/include/eepp/ui/tools/uidiffview.hpp
@@ -6,12 +6,19 @@
#include
#include
-namespace EE { namespace UI {
+namespace EE {
+
+namespace Graphics {
+class Sprite;
+}
+
+namespace UI {
class UIScrollView;
namespace Tools {
+class UIImageViewer;
class UIDiffEditorPlugin;
class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
@@ -21,7 +28,8 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
static UIDiffView* New();
- static UIScrollView* NewMultiFileDiffViewer( const std::string& patchText );
+ static UIScrollView* NewMultiFileDiffViewer( const std::string& patchText,
+ const std::string& repoPath = "" );
static std::vector splitDiff( const std::string& multiFileDiff );
@@ -32,7 +40,8 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
virtual Uint32 getType() const override;
virtual bool isType( const Uint32& type ) const override;
- void loadFromPatch( const std::string& patchText, const std::string& originalFilePath = "" );
+ void loadFromPatch( const std::string& patchText, const std::string& originalFilePath = "",
+ const std::string& oldFilePath = "", const std::string& repoPath = "" );
void loadFromStrings( const std::string& oldText, const std::string& newText,
const std::string& originalFilePath = "" );
void loadFromFile( const std::string& oldFilePath, const std::string& newFilePath );
@@ -40,6 +49,8 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
UICodeEditor* getEditor() const { return mEditor; }
UICodeEditor* getLeftEditor() const { return mLeftEditor; }
UICodeEditor* getRightEditor() const { return mRightEditor; }
+ UIImageViewer* getLeftImageViewer() const { return mLeftImageViewer; }
+ UIImageViewer* getRightImageViewer() const { return mRightImageViewer; }
enum class DiffLineType { Common, Added, Removed, Header };
struct DiffLine {
@@ -76,12 +87,20 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
const String& getFileName() const { return mFileName; }
+ bool isImageDiff() const { return mIsImageDiff; }
+
+ void setAutoDeleteOldTempImage( bool set ) { mAutoDeleteOldTempImage = set; }
+
protected:
UICodeEditor* mEditor{ nullptr };
UICodeEditor* mLeftEditor{ nullptr };
UICodeEditor* mRightEditor{ nullptr };
+ UIImageViewer* mLeftImageViewer{ nullptr };
+ UIImageViewer* mRightImageViewer{ nullptr };
+ UIImageViewer* mDiffImageViewer{ nullptr };
UISelectButton* mModeToggle{ nullptr };
UISelectButton* mCompleteViewToggle{ nullptr };
+ Sprite* mSprite{ nullptr };
std::unique_ptr mPlugin;
std::unique_ptr mLeftPlugin;
std::unique_ptr mRightPlugin;
@@ -93,8 +112,12 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
bool mShowCompleteView{ false };
bool mCompleteViewToggleVisible{ true };
bool mHeadersVisible{ false };
+ bool mIsImageDiff{ false };
+ bool mAutoDeleteOldTempImage{ false };
std::shared_ptr mSyntaxDef;
String mFileName;
+ std::string mImageDiffOldPath;
+ std::string mImageDiffNewPath;
UIDiffView();
@@ -112,9 +135,16 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
void computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine );
void updateEditorsText();
void updateButtonsText();
+ void createImageViewers();
+ bool loadImageDiffFromPaths( const std::string& oldFilePath, const std::string& newFilePath );
+ void updateImageDiffView();
+ void resetToTextDiffView();
+ void imageDisplayState( bool& displayDiffImage, bool& displayLeftImage );
+ void updateImagesPosAndSize();
};
} // namespace Tools
-}} // namespace EE::UI
+} // namespace UI
+} // namespace EE
#endif // EE_UI_TOOLS_UIDIFFVIEW_HPP
diff --git a/include/eepp/ui/tools/uiimageviewer.hpp b/include/eepp/ui/tools/uiimageviewer.hpp
index 6e6125b5d..0bbec2580 100644
--- a/include/eepp/ui/tools/uiimageviewer.hpp
+++ b/include/eepp/ui/tools/uiimageviewer.hpp
@@ -74,6 +74,12 @@ class EE_API UIImageViewer : public UIWidget {
virtual std::vector getPropertiesImplemented() const override;
+ void resetImageView();
+
+ bool isLoading() const { return mLoading; }
+
+ bool hasImage() const;
+
protected:
UILoader* mLoader{ nullptr };
UIImage* mImage{ nullptr };
@@ -104,8 +110,6 @@ class EE_API UIImageViewer : public UIWidget {
virtual Uint32 onKeyDown( const KeyEvent& event ) override;
void updateTextDisplay();
-
- void resetImageView();
};
} // namespace EE::UI::Tools
diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp
index f53a0b450..c87e8766c 100644
--- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp
+++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp
@@ -577,8 +577,10 @@ UICodeEditorSplitter::createWidget( UIWidget* widget, const std::string& tabName
UITabWidget* tabWidget = nullptr;
UIWidget* curWidget = getCurWidget();
- if ( !curWidget )
+ if ( !curWidget ) {
+ Log::error( "UICodeEditorSplitter::createWidget curWidget was nullptr!" );
return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr );
+ }
tabWidget = tabWidgetFromWidget( curWidget );
if ( !tabWidget ) {
@@ -600,6 +602,11 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget*
return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr );
UITab* tab = tabWidget->add( tabName, widget );
widget->setData( (UintPtr)tab );
+ // We use both events because there was an strange behavior that sometimes OnFocusWithin was not
+ // enough, so this is just in case.
+ widget->on( Event::OnFocus, [this]( const Event* event ) {
+ setCurrentWidget( event->getNode()->asType() );
+ } );
widget->on( Event::OnFocusWithin, [this]( const Event* event ) {
setCurrentWidget( event->getNode()->asType() );
} );
diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp
index 9fb860a05..47afee452 100644
--- a/src/eepp/ui/tools/uidiffview.cpp
+++ b/src/eepp/ui/tools/uidiffview.cpp
@@ -1,11 +1,16 @@
#include
+#include
#include
+#include
#include
+#include
#include
#include
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -16,7 +21,63 @@
namespace EE { namespace UI { namespace Tools {
-UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText ) {
+static bool imagesHaveSameDimensions( const std::string& oldFilePath,
+ const std::string& newFilePath ) {
+ int oldWidth = 0;
+ int oldHeight = 0;
+ int oldChannels = 0;
+ int newWidth = 0;
+ int newHeight = 0;
+ int newChannels = 0;
+
+ return Image::getInfo( oldFilePath, &oldWidth, &oldHeight, &oldChannels ) &&
+ Image::getInfo( newFilePath, &newWidth, &newHeight, &newChannels ) &&
+ oldWidth == newWidth && oldHeight == newHeight;
+}
+
+static bool setImageViewerImageSize( UIImageViewer* viewer ) {
+ if ( !viewer || !viewer->getImage() || !viewer->getImage()->getDrawable() )
+ return false;
+
+ auto imageSize( viewer->getImage()->getDrawable()->getPixelsSize() );
+ auto viewerSize( viewer->getPixelsSize() );
+ auto scale(
+ viewerSize.x > 0 && viewerSize.y > 0 &&
+ ( imageSize.x > viewerSize.x || imageSize.y > viewerSize.y )
+ ? eemin( viewerSize.x / imageSize.getWidth(), viewerSize.y / imageSize.getHeight() )
+ : 1.f );
+ viewer->getImage()->setPixelsSize( eefloor( imageSize.x * scale ),
+ eefloor( imageSize.y * scale ) );
+ viewer->getImage()->setScale( 1 );
+ viewer->getImage()->setPixelsPosition( Vector2f::Zero );
+ viewer->getImage()->setRotation( 0 );
+ viewer->getImage()->setVisible( true );
+ viewer->getImage()->center();
+ return true;
+}
+
+static Sprite* setImageViewerImage( UIImageViewer* viewer, Image* image ) {
+ if ( !viewer || !image || !image->getPixelsPtr() )
+ return nullptr;
+
+ auto texture = TextureFactory::instance()->loadFromPixels(
+ image->getPixelsPtr(), image->getWidth(), image->getHeight(), image->getChannels() );
+ if ( !texture )
+ return nullptr;
+
+ auto sprite = Sprite::New();
+ sprite->createStatic( texture );
+ sprite->setAsTextureOwner( true );
+ sprite->setAsTextureRegionOwner( true );
+
+ viewer->reset();
+ viewer->getImage()->setDrawable( sprite, true );
+ setImageViewerImageSize( viewer );
+ return sprite;
+}
+
+UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText,
+ const std::string& repoPath ) {
auto scrollView = UIScrollView::New();
auto vbox = UILinearLayout::NewVertical();
vbox->setParent( scrollView );
@@ -29,7 +90,7 @@ UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText )
diffView->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
diffView->setParent( vbox );
diffView->setHeadersVisible( true );
- diffView->loadFromPatch( diff );
+ diffView->loadFromPatch( diff, "", "", repoPath );
}
return scrollView;
@@ -302,6 +363,7 @@ UIDiffView::UIDiffView() :
createEditor( mEditor, mPlugin );
createEditor( mLeftEditor, mLeftPlugin );
createEditor( mRightEditor, mRightPlugin );
+ createImageViewers();
mEditor->on( Event::OnFontChanged, [this]( auto ) { mPlugin->registerUpdate( mEditor ); } );
mLeftEditor->on( Event::OnFontChanged, [this]( auto ) {
@@ -351,6 +413,7 @@ UIDiffView::UIDiffView() :
mCompleteViewToggle->on( Event::OnSizeChange, [this]( auto ) { updateModeButton(); } );
updateButtonsText();
+ updateImagesPosAndSize();
}
UIDiffView::~UIDiffView() {
@@ -362,6 +425,9 @@ UIDiffView::~UIDiffView() {
if ( mRightEditor && mRightPlugin )
mRightEditor->unregisterPlugin( mRightPlugin.get() );
+
+ if ( mAutoDeleteOldTempImage )
+ FileSystem::fileRemove( mImageDiffOldPath );
}
Uint32 UIDiffView::getType() const {
@@ -384,12 +450,50 @@ void UIDiffView::createEditor( UICodeEditor*& editor,
editor->registerPlugin( plugin.get() );
}
+void UIDiffView::createImageViewers() {
+ const auto initImageView = [this] {
+ auto iv = UIImageViewer::New();
+ iv->setParent( this );
+ iv->setVisible( false );
+ iv->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ iv->setDisplayOptions( UIImageViewer::DisplayDimensions );
+ return iv;
+ };
+ mLeftImageViewer = initImageView();
+ mRightImageViewer = initImageView();
+ mDiffImageViewer = initImageView();
+}
+
+void UIDiffView::resetToTextDiffView() {
+ mIsImageDiff = false;
+ mImageDiffOldPath.clear();
+ mImageDiffNewPath.clear();
+ if ( mLeftImageViewer ) {
+ mLeftImageViewer->reset();
+ mLeftImageViewer->setVisible( false );
+ }
+ if ( mRightImageViewer ) {
+ mRightImageViewer->reset();
+ mRightImageViewer->setVisible( false );
+ }
+ mEditor->setVisible( mViewMode == ViewMode::Unified );
+ mLeftEditor->setVisible( mViewMode == ViewMode::SideBySide );
+ mRightEditor->setVisible( mViewMode == ViewMode::SideBySide );
+}
+
void UIDiffView::setViewMode( ViewMode mode ) {
if ( mViewMode == mode )
return;
mViewMode = mode;
+ if ( mIsImageDiff ) {
+ onSizeChange();
+ updateImageDiffView();
+ updateButtonsText();
+ return;
+ }
+
mLeftPlugin->registerUpdate( mLeftEditor );
mRightPlugin->registerUpdate( mRightEditor );
@@ -422,6 +526,10 @@ void UIDiffView::setCompleteView( bool complete ) {
return;
mShowCompleteView = complete;
updateButtonsText();
+ if ( mIsImageDiff ) {
+ updateImageDiffView();
+ return;
+ }
updateEditorsText();
}
@@ -463,23 +571,83 @@ void UIDiffView::updateModeButton() {
}
void UIDiffView::onSizePolicyChange() {
- if ( mHeightPolicy == SizePolicy::WrapContent && mEditor && mLeftEditor ) {
- mEditor->setLayoutHeightPolicy( mHeightPolicy );
- mLeftEditor->setLayoutHeightPolicy( mHeightPolicy );
- mRightEditor->setLayoutHeightPolicy( mHeightPolicy );
+ if ( mHeightPolicy == SizePolicy::WrapContent ) {
+ if ( mEditor )
+ mEditor->setLayoutHeightPolicy( mHeightPolicy );
+ if ( mLeftEditor )
+ mLeftEditor->setLayoutHeightPolicy( mHeightPolicy );
+ if ( mRightEditor )
+ mRightEditor->setLayoutHeightPolicy( mHeightPolicy );
+
onAutoSize();
}
}
void UIDiffView::onAutoSize() {
- if ( mHeightPolicy == SizePolicy::WrapContent && mEditor && mLeftEditor ) {
+ if ( mHeightPolicy != SizePolicy::WrapContent )
+ return;
+
+ if ( mEditor && mLeftEditor && !mIsImageDiff ) {
setPixelsSize( getPixelsSize().getWidth(), mViewMode == ViewMode::Unified
? mEditor->getPixelsSize().getHeight()
: mLeftEditor->getPixelsSize().getHeight() );
}
+
+ if ( mIsImageDiff && mLeftImageViewer && mRightImageViewer && mDiffImageViewer ) {
+ bool displayDiffImage;
+ bool displayLeftImage;
+ imageDisplayState( displayDiffImage, displayLeftImage );
+ Float height = PixelDensity::dpToPx( 64 ); // force a min height
+ auto viewImageHeight = []( auto iv ) -> Float {
+ if ( iv && iv->getImage() && iv->getImage()->getDrawable() )
+ return iv->getImage()->getDrawable()->getPixelsSize().getHeight();
+ return 0;
+ };
+
+ height = std::max( height, viewImageHeight( mLeftImageViewer ) );
+ height = std::max( height, viewImageHeight( mRightImageViewer ) );
+
+ if ( displayDiffImage )
+ height = std::max( height, viewImageHeight( mDiffImageViewer ) );
+
+ setPixelsSize( getPixelsSize().getWidth(), height );
+ }
+}
+
+void UIDiffView::updateImagesPosAndSize() {
+ const Sizef size( getPixelsSize() );
+
+ bool displayDiffImage;
+ bool displayLeftImage;
+ imageDisplayState( displayDiffImage, displayLeftImage );
+
+ mLeftImageViewer->setVisible( true );
+ mLeftImageViewer->setPixelsPosition( 0, 0 );
+ mLeftImageViewer->setPixelsSize( { size.getWidth() * 0.5f, size.getHeight() } );
+ setImageViewerImageSize( mLeftImageViewer );
+
+ mRightImageViewer->setVisible( true );
+ mRightImageViewer->setPixelsSize(
+ displayLeftImage ? Sizef{ size.getWidth() * 0.5f, size.getHeight() } : size );
+ mRightImageViewer->setPixelsPosition(
+ displayLeftImage ? std::floor( size.getWidth() * 0.5f ) : 0.f, 0.f );
+ setImageViewerImageSize( mRightImageViewer );
+
+ mDiffImageViewer->setVisible( true );
+ mDiffImageViewer->setPixelsPosition( 0, 0 );
+ mDiffImageViewer->setPixelsSize( size );
+ setImageViewerImageSize( mDiffImageViewer );
+
+ onAutoSize();
+ updateModeButton();
}
void UIDiffView::onSizeChange() {
+ if ( mIsImageDiff ) {
+ updateImagesPosAndSize();
+ return;
+ }
+
if ( mViewMode == ViewMode::Unified ) {
mEditor->setPixelsSize(
{ getPixelsSize().getWidth(), mHeightPolicy == SizePolicy::WrapContent
@@ -559,6 +727,110 @@ void UIDiffView::updateEditorsText() {
}
}
+bool UIDiffView::loadImageDiffFromPaths( const std::string& oldFilePath,
+ const std::string& newFilePath ) {
+ const bool hasOldImage = !oldFilePath.empty() && FileSystem::fileExists( oldFilePath ) &&
+ Image::isImage( oldFilePath );
+ const bool hasNewImage = !newFilePath.empty() && FileSystem::fileExists( newFilePath ) &&
+ Image::isImage( newFilePath );
+ if ( !hasOldImage && !hasNewImage )
+ return false;
+
+ mLines.clear();
+ mViewLines.clear();
+ mSyntaxDef.reset();
+ mImageDiffOldPath = hasOldImage ? oldFilePath : "";
+ mImageDiffNewPath = hasNewImage ? newFilePath : "";
+ mIsImageDiff = true;
+
+ mEditor->setVisible( false );
+ mLeftEditor->setVisible( false );
+ mRightEditor->setVisible( false );
+
+ if ( !mImageDiffNewPath.empty() )
+ mFileName = FileSystem::fileNameFromPath( mImageDiffNewPath );
+ else
+ mFileName = FileSystem::fileNameFromPath( mImageDiffOldPath );
+
+ if ( !mImageDiffOldPath.empty() && !mImageDiffNewPath.empty() )
+ mViewMode = ViewMode::SideBySide;
+ else
+ mViewMode = ViewMode::Unified;
+
+ setCompleteViewToggleVisible( !mImageDiffOldPath.empty() && !mImageDiffNewPath.empty() );
+ updateButtonsText();
+ onSizeChange();
+ updateImageDiffView();
+ return true;
+}
+
+void UIDiffView::imageDisplayState( bool& displayDiffImage, bool& displayLeftImage ) {
+ if ( !mIsImageDiff ) {
+ displayDiffImage = displayLeftImage = false;
+ return;
+ }
+
+ const bool showVisualDiff =
+ !mShowCompleteView && !mImageDiffOldPath.empty() && !mImageDiffNewPath.empty();
+ const bool sameDimension = imagesHaveSameDimensions( mImageDiffOldPath, mImageDiffNewPath );
+ displayDiffImage = showVisualDiff && sameDimension && mViewMode == ViewMode::Unified;
+ displayLeftImage =
+ !displayDiffImage && !mImageDiffOldPath.empty() && mViewMode == ViewMode::SideBySide;
+}
+
+void UIDiffView::updateImageDiffView() {
+ if ( !mIsImageDiff )
+ return;
+
+ bool displayDiffImage;
+ bool displayLeftImage;
+ imageDisplayState( displayDiffImage, displayLeftImage );
+
+ mEditor->setVisible( false );
+ mLeftEditor->setVisible( false );
+ mRightEditor->setVisible( false );
+
+ mDiffImageViewer->setVisible( false );
+
+ if ( displayLeftImage ) {
+ mLeftImageViewer->setVisible( true );
+ if ( !mLeftImageViewer->hasImage() )
+ mLeftImageViewer->loadImageAsync( mImageDiffOldPath, false, false );
+ } else {
+ mLeftImageViewer->reset();
+ mLeftImageViewer->setVisible( false );
+ }
+
+ if ( displayDiffImage ) {
+ if ( nullptr == mSprite ) {
+ Image oldImage( mImageDiffOldPath, 4 );
+ Image newImage( mImageDiffNewPath, 4 );
+ if ( oldImage.getPixelsPtr() && newImage.getPixelsPtr() ) {
+ auto diff = oldImage.diff( newImage );
+ if ( diff.diffImage )
+ mSprite = setImageViewerImage( mDiffImageViewer, diff.diffImage );
+ }
+ }
+
+ mLeftImageViewer->setVisible( false );
+ mRightImageViewer->setVisible( false );
+ mDiffImageViewer->setVisible( true );
+ return;
+ }
+
+ const std::string& displayPath =
+ mImageDiffNewPath.empty() ? mImageDiffOldPath : mImageDiffNewPath;
+
+ if ( !displayPath.empty() ) {
+ mRightImageViewer->setVisible( true );
+ if ( !mRightImageViewer->hasImage() )
+ mRightImageViewer->loadImageAsync( displayPath, false, false );
+ } else {
+ mRightImageViewer->reset();
+ mRightImageViewer->setVisible( false );
+ }
+}
+
void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) {
dtl::Diff diff( oldLine.text.view(),
newLine.text.view() );
@@ -660,8 +932,127 @@ static void applySubLineDiff(
}
}
-void UIDiffView::loadFromPatch( const std::string& patchText,
- const std::string& originalFilePath ) {
+struct BinaryImagePatch {
+ std::string oldPath;
+ std::string newPath;
+ std::string fileName;
+ bool isBinary{ false };
+};
+
+static std::string stripDiffPathPrefix( std::string path ) {
+ path = String::trim( path );
+ if ( String::startsWith( path, "\"" ) && String::endsWith( path, "\"" ) && path.size() > 1 )
+ path = path.substr( 1, path.size() - 2 );
+ if ( String::startsWith( path, "a/" ) || String::startsWith( path, "b/" ) )
+ path = path.substr( 2 );
+ return path;
+}
+
+static bool isPatchNullFile( const std::string& path ) {
+ return stripDiffPathPrefix( path ) == "/dev/null";
+}
+
+static bool isImagePath( const std::string& path ) {
+ return !path.empty() && !isPatchNullFile( path ) && Image::isImageExtension( path );
+}
+
+static std::string resolveImagePatchPath( const std::string& patchPath,
+ const std::string& originalFilePath,
+ bool preferOriginalFile, const std::string& repoPath ) {
+ if ( patchPath.empty() || isPatchNullFile( patchPath ) )
+ return "";
+
+ const std::string cleanPath( stripDiffPathPrefix( patchPath ) );
+
+ if ( preferOriginalFile && !originalFilePath.empty() &&
+ Image::isImageExtension( originalFilePath ) &&
+ FileSystem::fileExists( originalFilePath ) ) {
+ return originalFilePath;
+ }
+
+ if ( FileSystem::fileExists( patchPath ) )
+ return patchPath;
+ if ( FileSystem::fileExists( cleanPath ) )
+ return cleanPath;
+
+ if ( !repoPath.empty() ) {
+ std::string fullPath = repoPath + cleanPath;
+ if ( FileSystem::fileExists( fullPath ) )
+ return fullPath;
+ }
+
+ if ( !originalFilePath.empty() ) {
+ std::string basePath( FileSystem::fileRemoveFileName( originalFilePath ) );
+ if ( !basePath.empty() ) {
+ std::string path( basePath + cleanPath );
+ if ( FileSystem::fileExists( path ) )
+ return path;
+ path = basePath + patchPath;
+ if ( FileSystem::fileExists( path ) )
+ return path;
+ }
+ }
+
+ return "";
+}
+
+static BinaryImagePatch parseBinaryImagePatch( const std::vector& lines,
+ const std::string& originalFilePath ) {
+ BinaryImagePatch patch;
+
+ for ( const auto& line : lines ) {
+ if ( String::startsWith( line, "diff --git " ) ) {
+ auto paths = String::split( line.substr( 11 ), ' ', true );
+ if ( paths.size() >= 2 ) {
+ patch.oldPath = paths[0];
+ patch.newPath = paths[1];
+ }
+ } else if ( String::startsWith( line, "--- " ) ) {
+ patch.oldPath = line.substr( 4 );
+ } else if ( String::startsWith( line, "+++ " ) ) {
+ patch.newPath = line.substr( 4 );
+ } else if ( String::startsWith( line, "Binary files " ) ) {
+ patch.isBinary = true;
+ const size_t oldStart = sizeof( "Binary files " ) - 1;
+ const size_t andPos = line.find( " and ", oldStart );
+ const size_t differPos = line.rfind( " differ" );
+ if ( andPos != std::string::npos && differPos != std::string::npos &&
+ andPos < differPos ) {
+ patch.oldPath = line.substr( oldStart, andPos - oldStart );
+ patch.newPath = line.substr( andPos + 5, differPos - andPos - 5 );
+ }
+ } else if ( line == "GIT binary patch" || String::startsWith( line, "literal " ) ) {
+ patch.isBinary = true;
+ }
+ }
+
+ if ( !patch.newPath.empty() && !isPatchNullFile( patch.newPath ) )
+ patch.fileName = FileSystem::fileNameFromPath( stripDiffPathPrefix( patch.newPath ) );
+ else if ( !patch.oldPath.empty() && !isPatchNullFile( patch.oldPath ) )
+ patch.fileName = FileSystem::fileNameFromPath( stripDiffPathPrefix( patch.oldPath ) );
+ else if ( !originalFilePath.empty() )
+ patch.fileName = FileSystem::fileNameFromPath( originalFilePath );
+
+ if ( !patch.isBinary && ( isImagePath( patch.oldPath ) || isImagePath( patch.newPath ) ) ) {
+ for ( const auto& line : lines ) {
+ if ( String::startsWith( line, "Binary " ) || line == "GIT binary patch" ) {
+ patch.isBinary = true;
+ break;
+ }
+ }
+ }
+
+ if ( patch.isBinary && !isImagePath( patch.oldPath ) && !isImagePath( patch.newPath ) &&
+ !Image::isImageExtension( originalFilePath ) ) {
+ patch.isBinary = false;
+ }
+
+ return patch;
+}
+
+void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& originalFilePath,
+ const std::string& oldFilePath, const std::string& repoPath ) {
+ resetToTextDiffView();
mLines.clear();
auto lines = String::split( patchText, '\n', true );
@@ -680,6 +1071,26 @@ void UIDiffView::loadFromPatch( const std::string& patchText,
Int64 expectedNewLineNum = 1;
std::string filename;
+ auto imagePatch = parseBinaryImagePatch( lines, originalFilePath );
+ if ( imagePatch.isBinary ) {
+ std::string oldImagePath( oldFilePath );
+ if ( oldImagePath.empty() ) {
+ oldImagePath =
+ resolveImagePatchPath( imagePatch.oldPath, originalFilePath, false, repoPath );
+ }
+ std::string newImagePath(
+ resolveImagePatchPath( imagePatch.newPath, originalFilePath, true, repoPath ) );
+
+ if ( oldImagePath == newImagePath )
+ oldImagePath.clear();
+
+ if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) {
+ if ( !imagePatch.fileName.empty() )
+ mFileName = std::move( imagePatch.fileName );
+ return;
+ }
+ }
+
for ( const auto& line : lines ) {
if ( String::startsWith( line, "diff " ) || String::startsWith( line, "index " ) ||
String::startsWith( line, "--- " ) || String::startsWith( line, "+++ " ) ) {
@@ -795,6 +1206,7 @@ void UIDiffView::loadFromPatch( const std::string& patchText,
void UIDiffView::loadFromStrings( const std::string& oldText, const std::string& newText,
const std::string& originalFilePath ) {
+ resetToTextDiffView();
mLines.clear();
std::vector leftLines = String::split( oldText, '\n', true );
@@ -845,6 +1257,13 @@ void UIDiffView::loadFromStrings( const std::string& oldText, const std::string&
}
void UIDiffView::loadFromFile( const std::string& oldFilePath, const std::string& newFilePath ) {
+ if ( ( oldFilePath.empty() || Image::isImageExtension( oldFilePath ) ) &&
+ ( newFilePath.empty() || Image::isImageExtension( newFilePath ) ) &&
+ loadImageDiffFromPaths( oldFilePath, newFilePath ) ) {
+ return;
+ }
+
+ resetToTextDiffView();
std::string oldText, newText;
FileSystem::fileGet( oldFilePath, oldText );
FileSystem::fileGet( newFilePath, newText );
@@ -864,6 +1283,7 @@ void UIDiffView::updateButtonsText() {
mModeToggle->setText( i18n( "diffview_side_by_side", "Side by Side" ) );
mModeToggle->setSelected( mViewMode != ViewMode::Unified );
mCompleteViewToggle->setText( i18n( "diffview_compact", "Compact" ) );
+ mCompleteViewToggle->setVisible( !mIsImageDiff );
mCompleteViewToggle->setSelected( !mShowCompleteView );
}
diff --git a/src/eepp/ui/tools/uiimageviewer.cpp b/src/eepp/ui/tools/uiimageviewer.cpp
index 8c7b5f09d..866c82076 100644
--- a/src/eepp/ui/tools/uiimageviewer.cpp
+++ b/src/eepp/ui/tools/uiimageviewer.cpp
@@ -341,7 +341,7 @@ Uint32 UIImageViewer::onKeyDown( const KeyEvent& event ) {
} else if ( event.getKeyCode() == KEY_R ) {
mImage->setRotation( mImage->getRotation() + 90.0f );
} else if ( event.getKeyCode() == KEY_T ) {
- resetImageView();
+ resetImageView();
}
return UIWidget::onKeyDown( event );
}
@@ -458,4 +458,8 @@ std::vector UIImageViewer::getPropertiesImplemented() const {
return props;
}
+bool UIImageViewer::hasImage() const {
+ return mLoading || ( mImage && mImage->getDrawable() != nullptr );
+}
+
} // namespace EE::UI::Tools
diff --git a/src/tests/unit_tests/uidiffview_tests.cpp b/src/tests/unit_tests/uidiffview_tests.cpp
index 5c365ad9d..06da314cd 100644
--- a/src/tests/unit_tests/uidiffview_tests.cpp
+++ b/src/tests/unit_tests/uidiffview_tests.cpp
@@ -1,7 +1,9 @@
#include "utest.h"
+#include
#include
#include
#include
+#include
#include
#include
@@ -100,3 +102,60 @@ UTEST( UIDiffView, LoadFromPatchAndVerifyCleanText ) {
eeDelete( diffView );
}
+
+UTEST( UIDiffView, LoadFromFileImageDiffUsesImageViewers ) {
+ UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } );
+
+ std::string oldImagePath( "/tmp/eepp-uidiffview-old.png" );
+ std::string newImagePath( "/tmp/eepp-uidiffview-new.png" );
+ Graphics::Image oldImage( 8, 8, 4, Color::Red );
+ Graphics::Image newImage( 8, 8, 4, Color::Blue );
+ ASSERT_TRUE( oldImage.saveToFile( oldImagePath, Graphics::Image::SaveType::PNG ) );
+ ASSERT_TRUE( newImage.saveToFile( newImagePath, Graphics::Image::SaveType::PNG ) );
+
+ UIDiffView* diffView = UIDiffView::New();
+ diffView->setPixelsSize( 400, 200 );
+ diffView->loadFromFile( oldImagePath, newImagePath );
+
+ EXPECT_TRUE( diffView->isImageDiff() );
+ EXPECT_TRUE( diffView->getLeftImageViewer()->isVisible() );
+ EXPECT_TRUE( diffView->getRightImageViewer()->isVisible() );
+ EXPECT_FALSE( diffView->getEditor()->isVisible() );
+ EXPECT_FALSE( diffView->getLeftEditor()->isVisible() );
+ EXPECT_FALSE( diffView->getRightEditor()->isVisible() );
+ EXPECT_TRUE( diffView->getFileName().toUtf8() == "eepp-uidiffview-new.png" );
+
+ eeDelete( diffView );
+ FileSystem::fileRemove( oldImagePath );
+ FileSystem::fileRemove( newImagePath );
+}
+
+UTEST( UIDiffView, LoadFromPatchImageDiffUsesProvidedOldFile ) {
+ UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } );
+
+ std::string oldImagePath( "/tmp/eepp-uidiffview-patch-old.png" );
+ std::string newImagePath( "/tmp/eepp-uidiffview-patch-new.png" );
+ Graphics::Image oldImage( 8, 8, 4, Color::Red );
+ Graphics::Image newImage( 8, 8, 4, Color::Blue );
+ ASSERT_TRUE( oldImage.saveToFile( oldImagePath, Graphics::Image::SaveType::PNG ) );
+ ASSERT_TRUE( newImage.saveToFile( newImagePath, Graphics::Image::SaveType::PNG ) );
+
+ std::string patchText = R"patch(diff --git a/assets/logo.png b/assets/logo.png
+index 1234567..89abcde 100644
+Binary files a/assets/logo.png and b/assets/logo.png differ
+)patch";
+
+ UIDiffView* diffView = UIDiffView::New();
+ diffView->setPixelsSize( 400, 200 );
+ diffView->loadFromPatch( patchText, newImagePath, oldImagePath );
+
+ EXPECT_TRUE( diffView->isImageDiff() );
+ EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffView->getViewMode() );
+ EXPECT_TRUE( diffView->getLeftImageViewer()->isVisible() );
+ EXPECT_TRUE( diffView->getRightImageViewer()->isVisible() );
+ EXPECT_TRUE( diffView->getFileName().toUtf8() == "logo.png" );
+
+ eeDelete( diffView );
+ FileSystem::fileRemove( oldImagePath );
+ FileSystem::fileRemove( newImagePath );
+}
diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp
index 4f1c5f7ca..51f94df75 100644
--- a/src/tests/unit_tests/uihtml_tests.cpp
+++ b/src/tests/unit_tests/uihtml_tests.cpp
@@ -50,7 +50,7 @@ static void init_ui_test() {
UTEST( UIHTMLTable, complexLayout ) {
auto win = Engine::instance()->createWindow(
- WindowSettings( 1024, 653, "HTML Tables Test", WindowStyle::Default, WindowBackend::Default,
+ WindowSettings( 1024, 656, "HTML Tables Test", WindowStyle::Default, WindowBackend::Default,
32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
@@ -1578,7 +1578,7 @@ UTEST( UIHTMLDetails, lobstersInlineBlockCachesWidth ) {
UTEST( UIBorder, renderingVariations ) {
auto win = Engine::instance()->createWindow(
- WindowSettings( 1024, 653, "Border Rendering Test", WindowStyle::Default,
+ WindowSettings( 1024, 656, "Border Rendering Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
@@ -2093,7 +2093,7 @@ UTEST( UIHTML, ContactFormLayout ) {
UTEST( UIBackground, imageAtlasPositioning ) {
auto win = Engine::instance()->createWindow(
- WindowSettings( 1024, 653, "Background Atlas Test", WindowStyle::Default,
+ WindowSettings( 1024, 656, "Background Atlas Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index add723a08..d3b490245 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -2637,14 +2637,15 @@ void App::loadAudioFromPath( const std::string& path, bool autoPlay ) {
audioPlayer->loadFromPath( path, autoPlay );
}
-void App::loadDiffFromMemory( const std::string& content, const std::string& originalFilePath ) {
+void App::loadDiffFromMemory( const std::string& content, const std::string& originalFilePath,
+ const std::string& oldFilePath, const std::string& repoPath ) {
if ( UIDiffView::isMultiFileDiff( content ) ) {
auto diffViewTitle = i18n( "diff_viewer", "Diff Viewer" ) + ": " + originalFilePath;
UIIcon* icon = getUISceneNode()->findIcon( "filetype-diff" );
if ( !icon )
icon = getUISceneNode()->findIcon( "file" );
- auto scrollView = UIDiffView::NewMultiFileDiffViewer( content );
+ auto scrollView = UIDiffView::NewMultiFileDiffViewer( content, repoPath );
auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle );
if ( icon )
tab->setIcon( icon->getSize( getMenuIconSize() ) );
@@ -2662,7 +2663,10 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori
auto diffViewTitle = i18n( "diff_viewer", "Diff Viewer" );
auto* diffView = Tools::UIDiffView::New();
+ diffView->setAutoDeleteOldTempImage( true );
auto [tab, iv] = getSplitter()->createWidget( diffView, diffViewTitle );
+ if ( !tab )
+ return;
if ( !originalFilePath.empty() ) {
std::string fileName = FileSystem::fileNameFromPath( originalFilePath );
tab->setText( diffViewTitle + ": " + fileName );
@@ -2676,7 +2680,7 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori
if ( icon )
tab->setIcon( icon->getSize( getMenuIconSize() ) );
diffView->setHeadersVisible( true );
- diffView->loadFromPatch( content, originalFilePath );
+ diffView->loadFromPatch( content, originalFilePath, oldFilePath );
diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
registerUnlockedCommands( *diffView );
}
diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp
index 091ef28d6..aaad561f1 100644
--- a/src/tools/ecode/ecode.hpp
+++ b/src/tools/ecode/ecode.hpp
@@ -608,7 +608,9 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void loadDiffFromPaths( const std::string& oldPath, const std::string& newPath );
- void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "" );
+ void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "",
+ const std::string& oldFilePath = "",
+ const std::string& repoPath = "" );
void loadDiffFromStrings( const std::string& str, const std::string& otherStr );
diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp
index 2b3250412..f75176e4a 100644
--- a/src/tools/ecode/plugins/git/git.cpp
+++ b/src/tools/ecode/plugins/git/git.cpp
@@ -249,6 +249,17 @@ Git::Result Git::diff( const std::string& file, bool isStaged, const std::string
projectDir );
}
+Git::Result Git::showFile( const std::string& file, const std::string& ref,
+ const std::string& projectDir ) {
+ std::string relativePath( file );
+ const std::string& repoPath = projectDir.empty() ? mProjectPath : projectDir;
+ if ( !repoPath.empty() )
+ FileSystem::filePathRemoveBasePath( repoPath, relativePath );
+
+ std::string refSpec( ref == ":" ? ":" + relativePath : ref + ":" + relativePath );
+ return gitSimple( String::format( "show \"%s\"", refSpec ), projectDir );
+}
+
Git::Result Git::createBranch( const std::string& branchName, bool _checkout,
const std::string& projectDir ) {
auto res = gitSimple( String::format( "branch --no-track %s", branchName ), projectDir );
diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp
index 0e85c1678..875a7e0fd 100644
--- a/src/tools/ecode/plugins/git/git.hpp
+++ b/src/tools/ecode/plugins/git/git.hpp
@@ -269,6 +269,9 @@ class Git {
Result diff( const std::string& file, bool isStaged, const std::string& projectDir = "" );
+ Result showFile( const std::string& file, const std::string& ref,
+ const std::string& projectDir = "" );
+
Result createBranch( const std::string& branchName, bool checkout = false,
const std::string& projectDir = "" );
diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp
index ddc6c117a..1401dcac0 100644
--- a/src/tools/ecode/plugins/git/gitplugin.cpp
+++ b/src/tools/ecode/plugins/git/gitplugin.cpp
@@ -1,11 +1,13 @@
#include "gitplugin.hpp"
#include "gitbranchmodel.hpp"
#include "gitstatusmodel.hpp"
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -34,6 +36,19 @@ namespace ecode {
static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv;
static constexpr auto GIT_STATUS_UPDATE_TAG = String::hash( "git::status-update" );
+static std::string writeGitBlobTempFile( const std::string& contents,
+ const std::string& sourceFilePath ) {
+ if ( contents.empty() )
+ return "";
+
+ std::string ext( FileSystem::fileExtension( sourceFilePath ) );
+ std::string path( Sys::getTempPath() + ".ecode-git-diff-" + String::randString( 16 ) );
+ if ( !ext.empty() )
+ path += "." + ext;
+
+ return FileSystem::fileWrite( path, contents ) ? path : "";
+}
+
std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) {
switch ( type ) {
case Git::GitStatusType::Untracked:
@@ -1087,7 +1102,7 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
return;
std::string repoName = this->repoName( repoPath );
- getUISceneNode()->runOnMainThread( [this, mode, res, repoName] {
+ getUISceneNode()->runOnMainThread( [this, mode, res, repoName, repoPath] {
std::string modeName;
switch ( mode ) {
case Git::DiffHead: {
@@ -1099,7 +1114,8 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
break;
}
getPluginContext()->loadDiffFromMemory(
- res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "" );
+ res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "",
+ repoPath );
} );
} );
}
@@ -1119,9 +1135,27 @@ void GitPlugin::diff( const std::string& file, Git::GitStatusType status ) {
return;
auto result = std::move( res.result );
+ std::string oldImagePath;
+ std::string newImagePath( filePath );
+ if ( EE::Graphics::Image::isImageExtension( filePath ) ) {
+ std::string repoPath( mGit->repoPath( file ) );
+ auto oldBlob = mGit->showFile(
+ filePath, status == Git::GitStatusType::Staged ? "HEAD" : ":", repoPath );
+ if ( oldBlob.success() )
+ oldImagePath = writeGitBlobTempFile( oldBlob.result, filePath );
+
+ if ( status == Git::GitStatusType::Staged ) {
+ auto newBlob = mGit->showFile( filePath, ":", repoPath );
+ if ( newBlob.success() )
+ newImagePath = writeGitBlobTempFile( newBlob.result, filePath );
+ }
+ }
+
getUISceneNode()->runOnMainThread(
- [this, result = std::move( result ), filePath = std::move( filePath )] {
- getPluginContext()->loadDiffFromMemory( result, filePath );
+ [this, result = std::move( result ), filePath = std::move( filePath ),
+ oldImagePath = std::move( oldImagePath ), newImagePath = std::move( newImagePath )] {
+ getPluginContext()->loadDiffFromMemory(
+ result, newImagePath.empty() ? filePath : newImagePath, oldImagePath );
} );
} );
}
diff --git a/src/tools/ecode/plugins/plugincontextprovider.hpp b/src/tools/ecode/plugins/plugincontextprovider.hpp
index aa2809a3d..0e4c116b0 100644
--- a/src/tools/ecode/plugins/plugincontextprovider.hpp
+++ b/src/tools/ecode/plugins/plugincontextprovider.hpp
@@ -158,7 +158,9 @@ class PluginContextProvider {
virtual void loadDiffFromPath( const std::string& path ) = 0;
virtual void loadDiffFromMemory( const std::string& content,
- const std::string& originalFilePath = "" ) = 0;
+ const std::string& originalFilePath = "",
+ const std::string& oldFilePath = "",
+ const std::string& repoPath = "" ) = 0;
virtual void loadFolder( std::string path, bool forceNewWindow = false ) = 0;