From e2c6d747480391a100257e80fe93654bae4da1cb Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:21:17 +0100 Subject: [PATCH 01/34] Introduce GIImageDiffView files This view will be used very similarly to GIDiffView, as both are variably sized diffing cell contents. --- GitUpKit/GitUpKit.xcodeproj/project.pbxproj | 8 ++++++++ GitUpKit/Interface/GIImageDiffView.h | 4 ++++ GitUpKit/Interface/GIImageDiffView.m | 11 +++++++++++ GitUpKit/Interface/GIInterface.h | 1 + 4 files changed, 24 insertions(+) create mode 100644 GitUpKit/Interface/GIImageDiffView.h create mode 100644 GitUpKit/Interface/GIImageDiffView.m diff --git a/GitUpKit/GitUpKit.xcodeproj/project.pbxproj b/GitUpKit/GitUpKit.xcodeproj/project.pbxproj index 469cb984..130a6206 100644 --- a/GitUpKit/GitUpKit.xcodeproj/project.pbxproj +++ b/GitUpKit/GitUpKit.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 1DADC0E225A25D63008C2C35 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DBBEE64C256B094000F96DAF /* libz.tbd */; }; 1DF371CD22F5262300EF7BD9 /* GCLiveRepository+Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DF371CB22F5262300EF7BD9 /* GCLiveRepository+Utilities.h */; }; 1DF371CE22F5262300EF7BD9 /* GCLiveRepository+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DF371CC22F5262300EF7BD9 /* GCLiveRepository+Utilities.m */; }; + 6D8E3F0B25D90E1300AAFC17 /* GIImageDiffView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D8E3F0A25D90E1300AAFC17 /* GIImageDiffView.m */; }; + 6D8E3F1025D90E3400AAFC17 /* GIImageDiffView.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D8E3F0F25D90E3400AAFC17 /* GIImageDiffView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 743BF1841B871C0200E1CA49 /* GCOrderedSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 74EDB5D01B84D4C400F00E79 /* GCOrderedSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; 749335CA1B9B7FF200225513 /* GCOrderedSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */; }; 749786941B85AAB10065BD55 /* GCOrderedSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */; }; @@ -418,6 +420,8 @@ 0AC8525823A122C400479160 /* GILaunchServicesLocator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GILaunchServicesLocator.m; sourceTree = ""; }; 1DF371CB22F5262300EF7BD9 /* GCLiveRepository+Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GCLiveRepository+Utilities.h"; sourceTree = ""; }; 1DF371CC22F5262300EF7BD9 /* GCLiveRepository+Utilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "GCLiveRepository+Utilities.m"; sourceTree = ""; }; + 6D8E3F0A25D90E1300AAFC17 /* GIImageDiffView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GIImageDiffView.m; sourceTree = ""; }; + 6D8E3F0F25D90E3400AAFC17 /* GIImageDiffView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GIImageDiffView.h; sourceTree = ""; }; 74EDB5D01B84D4C400F00E79 /* GCOrderedSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCOrderedSet.h; sourceTree = ""; }; 74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCOrderedSet.m; sourceTree = ""; }; 74EDB5D51B84E06500F00E79 /* GCOrderedSet-Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GCOrderedSet-Tests.m"; sourceTree = ""; }; @@ -1001,6 +1005,8 @@ DBDFBC0B22B610F1003EEC6C /* Interface.xcassets */, E2D4DEAD1A4D592000B6AF66 /* GIBranch.h */, E2D4DEAE1A4D592000B6AF66 /* GIBranch.m */, + 6D8E3F0A25D90E1300AAFC17 /* GIImageDiffView.m */, + 6D8E3F0F25D90E3400AAFC17 /* GIImageDiffView.h */, E23186031B139CB900A93CCF /* GIConstants.h */, E2891AB41AE1684A00E58C77 /* GIDiffView.h */, E2891AB51AE1684A00E58C77 /* GIDiffView.m */, @@ -1159,6 +1165,7 @@ E267E2391B84DC3900BAB377 /* GISnapshotListViewController.h in Headers */, 1DF371CD22F5262300EF7BD9 /* GCLiveRepository+Utilities.h in Headers */, E267E23A1B84DC3900BAB377 /* GIUnifiedReflogViewController.h in Headers */, + 6D8E3F1025D90E3400AAFC17 /* GIImageDiffView.h in Headers */, E267E24D1B84DC7D00BAB377 /* GIAdvancedCommitViewController.h in Headers */, E267E24E1B84DC7D00BAB377 /* GICommitRewriterViewController.h in Headers */, E267E24F1B84DC7D00BAB377 /* GICommitSplitterViewController.h in Headers */, @@ -1475,6 +1482,7 @@ E267E2291B84DBF800BAB377 /* GIAppKit.m in Sources */, E267E22A1B84DBF800BAB377 /* GIColorView.m in Sources */, E267E22B1B84DBF800BAB377 /* GILinkButton.m in Sources */, + 6D8E3F0B25D90E1300AAFC17 /* GIImageDiffView.m in Sources */, DBDFBC1122B61135003EEC6C /* NSBundle+GitUpKit.m in Sources */, E267E22C1B84DBF800BAB377 /* GIModalView.m in Sources */, E267E22D1B84DBF800BAB377 /* GIViewController.m in Sources */, diff --git a/GitUpKit/Interface/GIImageDiffView.h b/GitUpKit/Interface/GIImageDiffView.h new file mode 100644 index 00000000..edfade63 --- /dev/null +++ b/GitUpKit/Interface/GIImageDiffView.h @@ -0,0 +1,4 @@ +#import + +@interface GIImageDiffView : NSView +@end diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m new file mode 100644 index 00000000..580d2030 --- /dev/null +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -0,0 +1,11 @@ +#if !__has_feature(objc_arc) +#error This file requires ARC +#endif + +#import "GIPrivate.h" + +@interface GIImageDiffView () +@end + +@implementation GIImageDiffView +@end diff --git a/GitUpKit/Interface/GIInterface.h b/GitUpKit/Interface/GIInterface.h index d6d850f9..b97c3646 100644 --- a/GitUpKit/Interface/GIInterface.h +++ b/GitUpKit/Interface/GIInterface.h @@ -39,4 +39,5 @@ #import "GISplitDiffView.h" #import "GIUnifiedDiffView.h" #import "NSColor+GINamedColors.h" +#import "GIImageDiffView.h" #endif From d18411be62847e10a4e74c32ebec52e1ccb35763 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:23:04 +0100 Subject: [PATCH 02/34] Attach GIImageDiffView to GIDiffContentData Again, similarly to the approach used for GIDiffView, we are attaching this content view to the diff data, so it can later be integrated into a cell. --- GitUpKit/Components/GIDiffContentsViewController.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 21f6029a..44723046 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -37,6 +37,7 @@ @interface GIDiffContentData : NSObject @property(nonatomic, strong) GCDiffDelta* delta; @property(nonatomic, strong) GCIndexConflict* conflict; @property(nonatomic, strong) GIDiffView* diffView; +@property(nonatomic, strong) GIImageDiffView* imageDiffView; @property(nonatomic, getter=isEmpty) BOOL empty; @end @@ -332,7 +333,12 @@ - (void)_reloadDeltas { GCDiffPatch* patch = [self.repository makePatchForDiffDelta:delta isBinary:&isBinary error:&error]; if (patch) { XLOG_DEBUG_CHECK(!isBinary || patch.empty); - if (patch.empty) { + + BOOL isImage = [[NSImage alloc] initWithContentsOfFile:[self.repository absolutePathForFile:delta.canonicalPath]] != nil; + if (isImage) { + GIImageDiffView* imageDiffView = [[GIImageDiffView alloc] initWithFrame:CGRectZero]; + data.imageDiffView = imageDiffView; + } else if (patch.empty) { data.empty = !isBinary; } else { GIDiffView* diffView = [[[self _diffViewClassForChange:delta.change] alloc] initWithFrame:NSZeroRect]; From 0dde98c6b7915b76a66394598ed0a3935fd5d826 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:29:11 +0100 Subject: [PATCH 03/34] Show a GIImageDiffCellView for image diffs Once again, this is mimicking GIDiffView and the associated GITextDiffCellView. For now we merely use the previously introduced imageDiffView as a way to identify whether an image cell should be shown. --- .../GIDiffContentsViewController.xib | 28 +++++++++++-------- .../Components/GIDiffContentsViewController.m | 9 ++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/GitUpKit/Components/Base.lproj/GIDiffContentsViewController.xib b/GitUpKit/Components/Base.lproj/GIDiffContentsViewController.xib index 3a396d00..48c4b6f7 100644 --- a/GitUpKit/Components/Base.lproj/GIDiffContentsViewController.xib +++ b/GitUpKit/Components/Base.lproj/GIDiffContentsViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -17,18 +17,18 @@ - + - + - + - + @@ -97,8 +97,12 @@ + + + + - + @@ -113,7 +117,7 @@ - + @@ -127,7 +131,7 @@ - + @@ -187,7 +191,7 @@ - + @@ -269,7 +273,7 @@ - + @@ -278,7 +282,7 @@ - + diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 44723046..66c8e8ae 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -55,6 +55,9 @@ @interface GITextDiffCellView : NSTableCellView @property(nonatomic, weak) GIDiffView* diffView; @end +@interface GIImageDiffCellView : NSTableCellView +@end + @interface GIBinaryDiffCellView : NSTableCellView @end @@ -145,6 +148,9 @@ @implementation GIEmptyDiffCellView @implementation GITextDiffCellView @end +@implementation GIImageDiffCellView +@end + @implementation GIBinaryDiffCellView @end @@ -491,6 +497,9 @@ - (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*) [view addSubview:data.diffView]; view.diffView = data.diffView; return view; + } else if (data.imageDiffView) { + GIImageDiffCellView* view = [_tableView makeViewWithIdentifier:@"image" owner:self]; + return view; } else if (data.empty) { GIEmptyDiffCellView* view = [_tableView makeViewWithIdentifier:@"empty" owner:self]; return view; From 7b534c741400fa8753301997c603dca6b68605e3 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:35:32 +0100 Subject: [PATCH 04/34] Allow GIImageDiffView to request a height This will later be necessary to request enough height to display images at their full resolution (or a fraction of that depending on width constraints). --- GitUpKit/Components/GIDiffContentsViewController.m | 2 ++ GitUpKit/Interface/GIImageDiffView.h | 1 + GitUpKit/Interface/GIImageDiffView.m | 3 +++ 3 files changed, 6 insertions(+) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 66c8e8ae..220b4dd4 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -641,6 +641,8 @@ - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row { GCDiffDelta* delta = data.delta; if (data.diffView) { return [data.diffView updateLayoutForWidth:[_tableView.tableColumns[0] width]]; + } else if (data.imageDiffView) { + return [data.imageDiffView desiredHeightForWidth:[_tableView.tableColumns[0] width]]; } else if (data.empty) { return _emptyViewHeight; } else if (data.conflict) { diff --git a/GitUpKit/Interface/GIImageDiffView.h b/GitUpKit/Interface/GIImageDiffView.h index edfade63..178066c9 100644 --- a/GitUpKit/Interface/GIImageDiffView.h +++ b/GitUpKit/Interface/GIImageDiffView.h @@ -1,4 +1,5 @@ #import @interface GIImageDiffView : NSView +- (CGFloat)desiredHeightForWidth:(CGFloat)width; @end diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 580d2030..1c258274 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -8,4 +8,7 @@ @interface GIImageDiffView () @end @implementation GIImageDiffView +- (CGFloat)desiredHeightForWidth:(CGFloat)width { + return width * 2; +} @end From 904c1568ed9954b6350a3318089b6582e35ae797 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:40:12 +0100 Subject: [PATCH 05/34] Inject GIImageDiffView into GIImageDiffCellView Following the example of GIDiffView, we attach (and detach) the content view to a designated cell. --- .../Components/GIDiffContentsViewController.m | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 220b4dd4..1d26c423 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -56,6 +56,7 @@ @interface GITextDiffCellView : NSTableCellView @end @interface GIImageDiffCellView : NSTableCellView +@property(nonatomic, weak) GIImageDiffView* imageDiffView; @end @interface GIBinaryDiffCellView : NSTableCellView @@ -450,10 +451,14 @@ - (void)tableView:(NSTableView*)tableView didRemoveRowView:(NSTableRowView*)rowV row -= 1; } if (row % 2) { - GITextDiffCellView* view = [rowView viewAtColumn:0]; - if ([view isKindOfClass:[GITextDiffCellView class]]) { - [view.diffView removeFromSuperview]; - view.diffView = nil; + GITextDiffCellView* textDiffView = [rowView viewAtColumn:0]; + GIImageDiffCellView* imageDiffView = [rowView viewAtColumn:0]; + if ([textDiffView isKindOfClass:[GITextDiffCellView class]]) { + [textDiffView.diffView removeFromSuperview]; + textDiffView.diffView = nil; + } else if ([imageDiffView isKindOfClass:[GIImageDiffCellView class]]) { + [imageDiffView.imageDiffView removeFromSuperview]; + imageDiffView.imageDiffView = nil; } } } @@ -499,6 +504,12 @@ - (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*) return view; } else if (data.imageDiffView) { GIImageDiffCellView* view = [_tableView makeViewWithIdentifier:@"image" owner:self]; + XLOG_DEBUG_CHECK(view.imageDiffView == nil); + XLOG_DEBUG_CHECK(data.imageDiffView.superview == nil); + data.imageDiffView.frame = view.bounds; + data.imageDiffView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [view addSubview:data.imageDiffView]; + view.imageDiffView = data.imageDiffView; return view; } else if (data.empty) { GIEmptyDiffCellView* view = [_tableView makeViewWithIdentifier:@"empty" owner:self]; From 116af158ab3b7dc7b202f865d7362a7327f94505 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:45:16 +0100 Subject: [PATCH 06/34] Inject the repository into each GIImageDiffView To be able to export blobs of images later on, we'll need access to the repository. This commit lays the necessary foundation for that step. --- GitUpKit/Components/GIDiffContentsViewController.m | 2 +- GitUpKit/Interface/GIImageDiffView.h | 1 + GitUpKit/Interface/GIImageDiffView.m | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 1d26c423..5847beb0 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -343,7 +343,7 @@ - (void)_reloadDeltas { BOOL isImage = [[NSImage alloc] initWithContentsOfFile:[self.repository absolutePathForFile:delta.canonicalPath]] != nil; if (isImage) { - GIImageDiffView* imageDiffView = [[GIImageDiffView alloc] initWithFrame:CGRectZero]; + GIImageDiffView* imageDiffView = [[GIImageDiffView alloc] initWithRepository:self.repository]; data.imageDiffView = imageDiffView; } else if (patch.empty) { data.empty = !isBinary; diff --git a/GitUpKit/Interface/GIImageDiffView.h b/GitUpKit/Interface/GIImageDiffView.h index 178066c9..0d7f5651 100644 --- a/GitUpKit/Interface/GIImageDiffView.h +++ b/GitUpKit/Interface/GIImageDiffView.h @@ -1,5 +1,6 @@ #import @interface GIImageDiffView : NSView +- (id)initWithRepository:(GCLiveRepository*)repository; - (CGFloat)desiredHeightForWidth:(CGFloat)width; @end diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 1c258274..70432560 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -5,9 +5,16 @@ #import "GIPrivate.h" @interface GIImageDiffView () +@property(nonatomic, strong) GCLiveRepository* repository; @end @implementation GIImageDiffView +- (id)initWithRepository:(GCLiveRepository*)repository { + self = [super initWithFrame:CGRectZero]; + self.repository = repository; + return self; +} + - (CGFloat)desiredHeightForWidth:(CGFloat)width { return width * 2; } From dbf71879a4ea9eac6dca0b506611d0f373a0fc85 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:47:59 +0100 Subject: [PATCH 07/34] Pass along the GCDiffDelta to GIImageDiffView Adds another necessary resource to be able to display images later on. --- GitUpKit/Components/GIDiffContentsViewController.m | 1 + GitUpKit/Interface/GIImageDiffView.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 5847beb0..68e0e870 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -344,6 +344,7 @@ - (void)_reloadDeltas { BOOL isImage = [[NSImage alloc] initWithContentsOfFile:[self.repository absolutePathForFile:delta.canonicalPath]] != nil; if (isImage) { GIImageDiffView* imageDiffView = [[GIImageDiffView alloc] initWithRepository:self.repository]; + imageDiffView.delta = delta; data.imageDiffView = imageDiffView; } else if (patch.empty) { data.empty = !isBinary; diff --git a/GitUpKit/Interface/GIImageDiffView.h b/GitUpKit/Interface/GIImageDiffView.h index 0d7f5651..dc8f4f32 100644 --- a/GitUpKit/Interface/GIImageDiffView.h +++ b/GitUpKit/Interface/GIImageDiffView.h @@ -1,6 +1,8 @@ #import @interface GIImageDiffView : NSView +@property(nonatomic, strong) GCDiffDelta* delta; + - (id)initWithRepository:(GCLiveRepository*)repository; - (CGFloat)desiredHeightForWidth:(CGFloat)width; @end From 0e0520a516d0a1ca468870bb5547c59917d98815 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:53:23 +0100 Subject: [PATCH 08/34] Add an image view for the current (new) image --- GitUpKit/Interface/GIImageDiffView.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 70432560..355ba46c 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -6,15 +6,22 @@ @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; +@property(nonatomic, strong) NSImageView* currentImageView; @end @implementation GIImageDiffView - (id)initWithRepository:(GCLiveRepository*)repository { self = [super initWithFrame:CGRectZero]; self.repository = repository; + [self setupView]; return self; } +- (void)setupView { + _currentImageView = [[NSImageView alloc] init]; + [self addSubview:_currentImageView]; +} + - (CGFloat)desiredHeightForWidth:(CGFloat)width { return width * 2; } From a6f936d79fb307747fb9458bf7e25d790c40038b Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 20 Feb 2021 23:53:44 +0100 Subject: [PATCH 09/34] Update the frame of the current image view --- GitUpKit/Interface/GIImageDiffView.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 355ba46c..6ee7b9fc 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -25,4 +25,12 @@ - (void)setupView { - (CGFloat)desiredHeightForWidth:(CGFloat)width { return width * 2; } + +- (void)drawRect:(NSRect)dirtyRect { + [self updateFrames]; +} + +- (void)updateFrames { + _currentImageView.frame = self.frame; +} @end From 0ca502eb42a6b3262ec198a597cb1eb7d43ba950 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:02:16 +0100 Subject: [PATCH 10/34] Update the current image when the delta is set This preliminary solution only covers showing the absolute newest version of the file and is therefore usually wrong. It does however establish the connection between delta, repository and the final image. --- GitUpKit/Interface/GIImageDiffView.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 6ee7b9fc..e35f5e60 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -22,6 +22,16 @@ - (void)setupView { [self addSubview:_currentImageView]; } +- (void)setDelta:(GCDiffDelta*)delta { + _delta = delta; + [self updateCurrentImage]; +} + +- (void)updateCurrentImage { + NSString* newPath = [self.repository absolutePathForFile:_delta.canonicalPath]; + _currentImageView.image = [[NSImage alloc] initWithContentsOfFile:newPath]; +} + - (CGFloat)desiredHeightForWidth:(CGFloat)width { return width * 2; } From c889fde8bdd7a9eef4dc90e1350808ba0330a8c3 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:03:41 +0100 Subject: [PATCH 11/34] Avoid unnecessary image updates --- GitUpKit/Interface/GIImageDiffView.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index e35f5e60..1fafb7ff 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -23,8 +23,10 @@ - (void)setupView { } - (void)setDelta:(GCDiffDelta*)delta { - _delta = delta; - [self updateCurrentImage]; + if (delta != _delta) { + _delta = delta; + [self updateCurrentImage]; + } } - (void)updateCurrentImage { From f987cdd47606cbb39b866d0d79253c6c8b4d813b Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:07:34 +0100 Subject: [PATCH 12/34] Show the proper new file for all cases This code is very similar to the viewDeltasInDiffTool utility method, as we are fundamentally doing the same thing (exporting the image file referenced by the SHA1 value and then displaying it). The most noteworthy detail is that we are defaulting back to the canonical path for untracked files. --- GitUpKit/Interface/GIImageDiffView.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 1fafb7ff..8067db63 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -3,6 +3,7 @@ #endif #import "GIPrivate.h" +#import "GILaunchServicesLocator.h" @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; @@ -30,7 +31,20 @@ - (void)setDelta:(GCDiffDelta*)delta { } - (void)updateCurrentImage { - NSString* newPath = [self.repository absolutePathForFile:_delta.canonicalPath]; + NSError* error; + NSString* newPath; + if (_delta.newFile.SHA1 != nil) { + newPath = [GILaunchServicesLocator.diffTemporaryDirectoryPath stringByAppendingPathComponent:_delta.newFile.SHA1]; + NSString* newExtension = _delta.newFile.path.pathExtension; + if (newExtension.length) { + newPath = [newPath stringByAppendingPathExtension:newExtension]; + } + if (![[NSFileManager defaultManager] fileExistsAtPath:newPath]) { + [self.repository exportBlobWithSHA1:_delta.newFile.SHA1 toPath:newPath error:&error]; + } + } else { + newPath = [self.repository absolutePathForFile:_delta.canonicalPath]; + } _currentImageView.image = [[NSImage alloc] initWithContentsOfFile:newPath]; } From f248c00d5da067888a2395ce8c706c1a35183d92 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:09:46 +0100 Subject: [PATCH 13/34] Add the second comparison image view --- GitUpKit/Interface/GIImageDiffView.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 8067db63..c214ca10 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -7,6 +7,7 @@ @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; +@property(nonatomic, strong) NSImageView* oldImageView; @property(nonatomic, strong) NSImageView* currentImageView; @end @@ -20,7 +21,9 @@ - (id)initWithRepository:(GCLiveRepository*)repository { - (void)setupView { _currentImageView = [[NSImageView alloc] init]; + _oldImageView = [[NSImageView alloc] init]; [self addSubview:_currentImageView]; + [self addSubview:_oldImageView]; } - (void)setDelta:(GCDiffDelta*)delta { From c415aaccf45d863f5d6cec1bcc78c876c6da62c7 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:13:09 +0100 Subject: [PATCH 14/34] Position the old image view alongside the new one --- GitUpKit/Interface/GIImageDiffView.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index c214ca10..3c380dc4 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -60,6 +60,13 @@ - (void)drawRect:(NSRect)dirtyRect { } - (void)updateFrames { - _currentImageView.frame = self.frame; + _oldImageView.frame = CGRectMake(self.frame.origin.x, + self.frame.origin.y, + self.frame.size.width, + self.frame.size.height / 2); + _currentImageView.frame = CGRectMake(self.frame.origin.x, + self.frame.origin.y + self.frame.size.height / 2, + self.frame.size.width, + self.frame.size.height / 2); } @end From fd0af86a2549b0217c24ea2b9c7d6f3560232f1a Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:15:58 +0100 Subject: [PATCH 15/34] Display the correct old comparison image This works analogous to the previously added updateNewImage method. The major difference is that we are looking at the oldFile and there is no fallback if it does not exist. --- GitUpKit/Interface/GIImageDiffView.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 3c380dc4..85873bb7 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -30,6 +30,7 @@ - (void)setDelta:(GCDiffDelta*)delta { if (delta != _delta) { _delta = delta; [self updateCurrentImage]; + [self updateOldImage]; } } @@ -51,6 +52,23 @@ - (void)updateCurrentImage { _currentImageView.image = [[NSImage alloc] initWithContentsOfFile:newPath]; } +- (void)updateOldImage { + NSError* error; + if (_delta.oldFile.SHA1 != nil) { + NSString* oldPath = [GILaunchServicesLocator.diffTemporaryDirectoryPath stringByAppendingPathComponent:_delta.oldFile.SHA1]; + NSString* oldExtension = _delta.oldFile.path.pathExtension; + if (oldExtension.length) { + oldPath = [oldPath stringByAppendingPathExtension:oldExtension]; + } + if (![[NSFileManager defaultManager] fileExistsAtPath:oldPath]) { + [self.repository exportBlobWithSHA1:_delta.oldFile.SHA1 toPath:oldPath error:&error]; + } + _oldImageView.image = [[NSImage alloc] initWithContentsOfFile:oldPath]; + } else { + _oldImageView.image = nil; + } +} + - (CGFloat)desiredHeightForWidth:(CGFloat)width { return width * 2; } From 555c2ea2cf946167ba9680d456cef8012c304645 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:25:34 +0100 Subject: [PATCH 16/34] Add a method to calculate the combined image size The goal is to make the GIImageDiffView a slide style view image comparison tool, in which the two images are displayed in the same area, but hidden or revealed by sliding a divider across them. This method allows us to calculate how big this combined area needs to be. --- GitUpKit/Interface/GIImageDiffView.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 85873bb7..d82e6f55 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -87,4 +87,10 @@ - (void)updateFrames { self.frame.size.width, self.frame.size.height / 2); } + +- (NSSize)originalDiffImageSize { + CGFloat maxHeight = MAX(_currentImageView.image.size.height, _oldImageView.image.size.height); + CGFloat maxWidth = MAX(_currentImageView.image.size.width, _oldImageView.image.size.width); + return NSMakeSize(maxWidth, maxHeight); +} @end From ddffca54101994ac385d8a8b4f92f33cb9667827 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:32:24 +0100 Subject: [PATCH 17/34] Add a method to calculate the comparison frame This method is designed to work within a given frame and will return a rectangle that will provide the correct dimensions to display both the old and new image in a shared area. --- GitUpKit/Interface/GIImageDiffView.m | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index d82e6f55..9affcab5 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -5,6 +5,8 @@ #import "GIPrivate.h" #import "GILaunchServicesLocator.h" +#define kImageInset 10 + @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; @property(nonatomic, strong) NSImageView* oldImageView; @@ -88,6 +90,27 @@ - (void)updateFrames { self.frame.size.height / 2); } +- (CGRect)fittedImageFrame { + CGFloat maxContentWidth = self.frame.size.width - 2 * kImageInset; + CGFloat maxContentHeight = self.frame.size.height - 2 * kImageInset; + CGFloat originalImageWidth = [self originalDiffImageSize].width; + CGFloat originalImageHeight = [self originalDiffImageSize].height; + + CGFloat scaledImageWidth = MIN(originalImageWidth, maxContentWidth); + CGFloat scaledImageHeight = MIN(originalImageHeight, maxContentHeight); + CGFloat widthScalingFactor = scaledImageWidth / originalImageWidth; + CGFloat heightScalingFactor = scaledImageHeight / originalImageHeight; + CGFloat minimumScalingFactor = MIN(widthScalingFactor, heightScalingFactor); + + CGFloat actualImageWidth = originalImageWidth * minimumScalingFactor; + CGFloat actualImageHeight = originalImageHeight * minimumScalingFactor; + + return CGRectMake((self.frame.size.width - actualImageWidth) / 2, + self.bounds.size.height - actualImageHeight - kImageInset, + actualImageWidth, + actualImageHeight); +} + - (NSSize)originalDiffImageSize { CGFloat maxHeight = MAX(_currentImageView.image.size.height, _oldImageView.image.size.height); CGFloat maxWidth = MAX(_currentImageView.image.size.width, _oldImageView.image.size.width); From 044b396cad57ba04daf37a50f03a521abe444a4f Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:36:13 +0100 Subject: [PATCH 18/34] Move old and new image into the shared area Currently this will overlay the old image over the new one. However, this is only setting up for the ability to partially hide both images. --- GitUpKit/Interface/GIImageDiffView.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 9affcab5..b0eb7799 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -80,14 +80,14 @@ - (void)drawRect:(NSRect)dirtyRect { } - (void)updateFrames { - _oldImageView.frame = CGRectMake(self.frame.origin.x, - self.frame.origin.y, - self.frame.size.width, - self.frame.size.height / 2); - _currentImageView.frame = CGRectMake(self.frame.origin.x, - self.frame.origin.y + self.frame.size.height / 2, - self.frame.size.width, - self.frame.size.height / 2); + CGRect fittedImageFrame = [self fittedImageFrame]; + _currentImageView.frame = fittedImageFrame; + if (_oldImageView.image != nil) { + _oldImageView.frame = fittedImageFrame; + [_oldImageView setHidden:false]; + } else { + [_oldImageView setHidden:true]; + } } - (CGRect)fittedImageFrame { From a65c640076b97ded5607f4f1d69065d870091e90 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:40:42 +0100 Subject: [PATCH 19/34] Make the image diff view ask for the proper height Instead of asking for a somewhat random, large height to fit the image, we can now ask for exactly the required height. To do this, we try to fit the image at full resolution, but scale down according to width constraints if necessary. --- GitUpKit/Interface/GIImageDiffView.m | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index b0eb7799..665362d9 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -72,7 +72,7 @@ - (void)updateOldImage { } - (CGFloat)desiredHeightForWidth:(CGFloat)width { - return width * 2; + return [self desiredImageFrame:width].size.height + 2 * kImageInset; } - (void)drawRect:(NSRect)dirtyRect { @@ -90,6 +90,18 @@ - (void)updateFrames { } } +- (CGRect)desiredImageFrame:(CGFloat)width { + CGFloat maxContentWidth = width - 2 * kImageInset; + CGFloat originalImageWidth = [self originalDiffImageSize].width; + CGFloat originalImageHeight = [self originalDiffImageSize].height; + + CGFloat scaledImageWidth = MIN(originalImageWidth, maxContentWidth); + CGFloat scaledImageHeight = originalImageHeight * scaledImageWidth / originalImageWidth; + + CGFloat x = (width - scaledImageWidth) / 2; + return CGRectMake(x, self.bounds.size.height - scaledImageHeight - kImageInset, scaledImageWidth, scaledImageHeight); +} + - (CGRect)fittedImageFrame { CGFloat maxContentWidth = self.frame.size.width - 2 * kImageInset; CGFloat maxContentHeight = self.frame.size.height - 2 * kImageInset; From e898f991076c3c68968a21ceccbee4021a47397f Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:44:48 +0100 Subject: [PATCH 20/34] Add percentage to keep track of shown fractions This property will be used to figure out how much to show of the old and new image. A value of 0 will represent showing just the new image, while a value of 1 will mean only the old image is shown. Everything in between will move the divider in between accordingly. --- GitUpKit/Interface/GIImageDiffView.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 665362d9..6fe59475 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -11,12 +11,14 @@ @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; @property(nonatomic, strong) NSImageView* oldImageView; @property(nonatomic, strong) NSImageView* currentImageView; +@property(nonatomic) CGFloat percentage; @end @implementation GIImageDiffView - (id)initWithRepository:(GCLiveRepository*)repository { self = [super initWithFrame:CGRectZero]; self.repository = repository; + _percentage = 0.5; [self setupView]; return self; } From 9b15865592bb71f73d29544fe8de81bd76f31925 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:47:45 +0100 Subject: [PATCH 21/34] Reset the percentage when delta is updated --- GitUpKit/Interface/GIImageDiffView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 6fe59475..60ef01e4 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -35,6 +35,7 @@ - (void)setDelta:(GCDiffDelta*)delta { _delta = delta; [self updateCurrentImage]; [self updateOldImage]; + self.percentage = 0.5; } } From 8bc7b5e5fc18018d1910a0d594ea62721b5cec63 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:51:29 +0100 Subject: [PATCH 22/34] Partially hide the images according to percentage By using masking layers, we can easily hide parts of the old and new images. Originally, we considered drawing the images directly, but the draw calls turned out to be very slow. This mask layer solution seems to be about four times as fast. --- GitUpKit/Interface/GIImageDiffView.m | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 60ef01e4..c52ed29c 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -11,6 +11,8 @@ @interface GIImageDiffView () @property(nonatomic, strong) GCLiveRepository* repository; @property(nonatomic, strong) NSImageView* oldImageView; @property(nonatomic, strong) NSImageView* currentImageView; +@property(nonatomic, strong) CALayer* oldImageMaskLayer; +@property(nonatomic, strong) CALayer* currentImageMaskLayer; @property(nonatomic) CGFloat percentage; @end @@ -28,6 +30,16 @@ - (void)setupView { _oldImageView = [[NSImageView alloc] init]; [self addSubview:_currentImageView]; [self addSubview:_oldImageView]; + + _oldImageMaskLayer = [[CALayer alloc] init]; + _oldImageMaskLayer.backgroundColor = NSColor.blackColor.CGColor; + _oldImageView.wantsLayer = true; + _oldImageView.layer.mask = _oldImageMaskLayer; + + _currentImageMaskLayer = [[CALayer alloc] init]; + _currentImageMaskLayer.backgroundColor = NSColor.blackColor.CGColor; + _currentImageView.wantsLayer = true; + _currentImageView.layer.mask = _currentImageMaskLayer; } - (void)setDelta:(GCDiffDelta*)delta { @@ -88,6 +100,15 @@ - (void)updateFrames { if (_oldImageView.image != nil) { _oldImageView.frame = fittedImageFrame; [_oldImageView setHidden:false]; + CGFloat dividerOffset = fittedImageFrame.size.width * _percentage; + _oldImageMaskLayer.frame = CGRectMake(0, + 0, + dividerOffset, + fittedImageFrame.size.height); + _currentImageMaskLayer.frame = CGRectMake(dividerOffset, + 0, + fittedImageFrame.size.width * (1 - _percentage), + fittedImageFrame.size.height); } else { [_oldImageView setHidden:true]; } From b0861e523c4a7c02fcedeca25c33858ec1b84199 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:54:26 +0100 Subject: [PATCH 23/34] Update the percentage according to mouse input By adding gesture recognizers to the view, we can use the entire image area as a large input field for the user to select the split between the old and new image. Whenever we change the percentage value we need to trigger a redraw. --- GitUpKit/Interface/GIImageDiffView.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index c52ed29c..4da37557 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -8,6 +8,8 @@ #define kImageInset 10 @interface GIImageDiffView () +@property(nonatomic, strong) NSPanGestureRecognizer* panGestureRecognizer; +@property(nonatomic, strong) NSClickGestureRecognizer* clickGestureRecognizer; @property(nonatomic, strong) GCLiveRepository* repository; @property(nonatomic, strong) NSImageView* oldImageView; @property(nonatomic, strong) NSImageView* currentImageView; @@ -40,6 +42,11 @@ - (void)setupView { _currentImageMaskLayer.backgroundColor = NSColor.blackColor.CGColor; _currentImageView.wantsLayer = true; _currentImageView.layer.mask = _currentImageMaskLayer; + + _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; + _clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; + [self addGestureRecognizer:_panGestureRecognizer]; + [self addGestureRecognizer:_clickGestureRecognizer]; } - (void)setDelta:(GCDiffDelta*)delta { @@ -51,6 +58,11 @@ - (void)setDelta:(GCDiffDelta*)delta { } } +- (void)setPercentage:(CGFloat)percentage { + _percentage = percentage; + [self setNeedsDisplay:true]; +} + - (void)updateCurrentImage { NSError* error; NSString* newPath; @@ -152,4 +164,10 @@ - (NSSize)originalDiffImageSize { CGFloat maxWidth = MAX(_currentImageView.image.size.width, _oldImageView.image.size.width); return NSMakeSize(maxWidth, maxHeight); } + +- (void)didMoveSplit:(NSGestureRecognizer*)gestureRecognizer { + CGRect imageFrame = [self fittedImageFrame]; + CGFloat unboundPercentage = ([gestureRecognizer locationInView:self].x - imageFrame.origin.x) / imageFrame.size.width; + self.percentage = MIN(1, MAX(0, unboundPercentage)); +} @end From 8a822234e4258f63481c813b454f0b3d735032bc Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:56:36 +0100 Subject: [PATCH 24/34] Avoid implicit layer animation By default, changing a CALayer's frame causes it to animate to the new position. This prevents that behavior, as we need the change to happen as fast as possible. --- GitUpKit/Interface/GIImageDiffView.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 4da37557..68ad532e 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -4,6 +4,7 @@ #import "GIPrivate.h" #import "GILaunchServicesLocator.h" +#import #define kImageInset 10 @@ -103,7 +104,10 @@ - (CGFloat)desiredHeightForWidth:(CGFloat)width { } - (void)drawRect:(NSRect)dirtyRect { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; [self updateFrames]; + [CATransaction commit]; } - (void)updateFrames { From 854d2fde68f5e89aaaddb812583ca1dedf153861 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 00:58:19 +0100 Subject: [PATCH 25/34] Show the entire new image if there is no old one --- GitUpKit/Interface/GIImageDiffView.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 68ad532e..1e754f7f 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -126,6 +126,10 @@ - (void)updateFrames { fittedImageFrame.size.width * (1 - _percentage), fittedImageFrame.size.height); } else { + _currentImageMaskLayer.frame = CGRectMake(0, + 0, + fittedImageFrame.size.width, + fittedImageFrame.size.height); [_oldImageView setHidden:true]; } } From 2c359786c81f73841b3aa7bd4a2676b63199e946 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 01:03:06 +0100 Subject: [PATCH 26/34] Add a transparency background layer This adds the commonly used checkerboard transparency indicator. The pattern had already been used by the GIMapViewController, so no new assets were necessary. --- GitUpKit/Interface/GIImageDiffView.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 1e754f7f..491c3834 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -16,6 +16,8 @@ @interface GIImageDiffView () @property(nonatomic, strong) NSImageView* currentImageView; @property(nonatomic, strong) CALayer* oldImageMaskLayer; @property(nonatomic, strong) CALayer* currentImageMaskLayer; +@property(nonatomic, strong) CALayer* transparencyCheckerboardLayer; +@property(nonatomic, strong) NSColor* checkerboardColor; @property(nonatomic) CGFloat percentage; @end @@ -29,6 +31,15 @@ - (id)initWithRepository:(GCLiveRepository*)repository { } - (void)setupView { + self.wantsLayer = true; + + _transparencyCheckerboardLayer = [[CALayer alloc] init]; + NSBundle* bundle = NSBundle.gitUpKitBundle; + NSImage* patternImage = [bundle imageForResource:@"background_pattern"]; + _checkerboardColor = [NSColor colorWithPatternImage:patternImage]; + _transparencyCheckerboardLayer.backgroundColor = _checkerboardColor.CGColor; + [self.layer addSublayer:_transparencyCheckerboardLayer]; + _currentImageView = [[NSImageView alloc] init]; _oldImageView = [[NSImageView alloc] init]; [self addSubview:_currentImageView]; @@ -112,6 +123,7 @@ - (void)drawRect:(NSRect)dirtyRect { - (void)updateFrames { CGRect fittedImageFrame = [self fittedImageFrame]; + _transparencyCheckerboardLayer.frame = fittedImageFrame; _currentImageView.frame = fittedImageFrame; if (_oldImageView.image != nil) { _oldImageView.frame = fittedImageFrame; From 23c0a87b106ff5f9be9a660ebff64ef8293acb83 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 01:06:54 +0100 Subject: [PATCH 27/34] Update colors on every draw call In order to reliably obey dark mode, it seems to be necessary to reassign the colors constantly. We tried to handle it via viewDidChangeEffectiveAppearance, but the behavior was unreliable. Since we couldn't notice any performance downsides to reassigning the colors with every draw call, we went with this solution. --- GitUpKit/Interface/GIImageDiffView.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 491c3834..5885e5ee 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -117,10 +117,15 @@ - (CGFloat)desiredHeightForWidth:(CGFloat)width { - (void)drawRect:(NSRect)dirtyRect { [CATransaction begin]; [CATransaction setDisableActions:YES]; + [self updateColors]; [self updateFrames]; [CATransaction commit]; } +- (void)updateColors { + _transparencyCheckerboardLayer.backgroundColor = _checkerboardColor.CGColor; +} + - (void)updateFrames { CGRect fittedImageFrame = [self fittedImageFrame]; _transparencyCheckerboardLayer.frame = fittedImageFrame; From 9c50cf4ccf36575589f95bd62694e5cf35da17a9 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 01:09:47 +0100 Subject: [PATCH 28/34] Add indicator borders Another common design element in slide style image comparison tools are red and green borders to separate old and new images. This is a simple implementation of that, which once again reassigns the colors on every draw call to handle dark mode properly. --- GitUpKit/Interface/GIImageDiffView.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 5885e5ee..2272be22 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -7,6 +7,7 @@ #import #define kImageInset 10 +#define kBorderWidth 8 @interface GIImageDiffView () @property(nonatomic, strong) NSPanGestureRecognizer* panGestureRecognizer; @@ -16,6 +17,8 @@ @interface GIImageDiffView () @property(nonatomic, strong) NSImageView* currentImageView; @property(nonatomic, strong) CALayer* oldImageMaskLayer; @property(nonatomic, strong) CALayer* currentImageMaskLayer; +@property(nonatomic, strong) CALayer* oldImageBorderLayer; +@property(nonatomic, strong) CALayer* currentImageBorderLayer; @property(nonatomic, strong) CALayer* transparencyCheckerboardLayer; @property(nonatomic, strong) NSColor* checkerboardColor; @property(nonatomic) CGFloat percentage; @@ -33,6 +36,11 @@ - (id)initWithRepository:(GCLiveRepository*)repository { - (void)setupView { self.wantsLayer = true; + _oldImageBorderLayer = [[CALayer alloc] init]; + _currentImageBorderLayer = [[CALayer alloc] init]; + [self.layer addSublayer:_oldImageBorderLayer]; + [self.layer addSublayer:_currentImageBorderLayer]; + _transparencyCheckerboardLayer = [[CALayer alloc] init]; NSBundle* bundle = NSBundle.gitUpKitBundle; NSImage* patternImage = [bundle imageForResource:@"background_pattern"]; @@ -123,6 +131,8 @@ - (void)drawRect:(NSRect)dirtyRect { } - (void)updateColors { + _oldImageBorderLayer.backgroundColor = NSColor.gitUpDiffDeletedTextHighlightColor.CGColor; + _currentImageBorderLayer.backgroundColor = NSColor.gitUpDiffAddedTextHighlightColor.CGColor; _transparencyCheckerboardLayer.backgroundColor = _checkerboardColor.CGColor; } @@ -142,6 +152,14 @@ - (void)updateFrames { 0, fittedImageFrame.size.width * (1 - _percentage), fittedImageFrame.size.height); + _oldImageBorderLayer.frame = CGRectMake(fittedImageFrame.origin.x - kBorderWidth, + fittedImageFrame.origin.y - kBorderWidth, + dividerOffset + kBorderWidth, + fittedImageFrame.size.height + 2 * kBorderWidth); + _currentImageBorderLayer.frame = CGRectMake(fittedImageFrame.origin.x + dividerOffset, + fittedImageFrame.origin.y - kBorderWidth, + fittedImageFrame.size.width * (1 - _percentage) + kBorderWidth, + fittedImageFrame.size.height + 2 * kBorderWidth); } else { _currentImageMaskLayer.frame = CGRectMake(0, 0, From ac7d2d7359cfb3e96420579b016d58f7005a0d70 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 21 Feb 2021 01:11:07 +0100 Subject: [PATCH 29/34] Add a divider line The final common design element of slider style image comparison tools. To further indicate where the old and new images meet, we add a thin line right between them. --- GitUpKit/Interface/GIImageDiffView.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 2272be22..f2651587 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -8,6 +8,7 @@ #define kImageInset 10 #define kBorderWidth 8 +#define kDividerWidth 2 @interface GIImageDiffView () @property(nonatomic, strong) NSPanGestureRecognizer* panGestureRecognizer; @@ -19,6 +20,7 @@ @interface GIImageDiffView () @property(nonatomic, strong) CALayer* currentImageMaskLayer; @property(nonatomic, strong) CALayer* oldImageBorderLayer; @property(nonatomic, strong) CALayer* currentImageBorderLayer; +@property(nonatomic, strong) NSView* dividerView; @property(nonatomic, strong) CALayer* transparencyCheckerboardLayer; @property(nonatomic, strong) NSColor* checkerboardColor; @property(nonatomic) CGFloat percentage; @@ -63,6 +65,9 @@ - (void)setupView { _currentImageView.wantsLayer = true; _currentImageView.layer.mask = _currentImageMaskLayer; + _dividerView = [[NSView alloc] init]; + [self addSubview:_dividerView]; + _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; _clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; [self addGestureRecognizer:_panGestureRecognizer]; @@ -133,6 +138,7 @@ - (void)drawRect:(NSRect)dirtyRect { - (void)updateColors { _oldImageBorderLayer.backgroundColor = NSColor.gitUpDiffDeletedTextHighlightColor.CGColor; _currentImageBorderLayer.backgroundColor = NSColor.gitUpDiffAddedTextHighlightColor.CGColor; + _dividerView.layer.backgroundColor = NSColor.gitUpDiffModifiedBackgroundColor.CGColor; _transparencyCheckerboardLayer.backgroundColor = _checkerboardColor.CGColor; } @@ -160,6 +166,10 @@ - (void)updateFrames { fittedImageFrame.origin.y - kBorderWidth, fittedImageFrame.size.width * (1 - _percentage) + kBorderWidth, fittedImageFrame.size.height + 2 * kBorderWidth); + _dividerView.frame = CGRectMake(fittedImageFrame.origin.x + dividerOffset - kDividerWidth / 2, + fittedImageFrame.origin.y - kBorderWidth, + kDividerWidth, + fittedImageFrame.size.height + 2 * kBorderWidth); } else { _currentImageMaskLayer.frame = CGRectMake(0, 0, From c4f52a4fbc19dd030341c6b220c7bf75d31b5216 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 5 Jun 2021 17:07:11 +0200 Subject: [PATCH 30/34] Infer file type based on UTI The previous approach of initializing an NSImage to figure out whether a file is an image was questionable when taking large binaries into account. This new solution uses the file ending to determine the uniform type identifier of a file, which can be used to check for image files. --- GitUpKit/Components/GIDiffContentsViewController.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index 68e0e870..c9c84be6 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -341,7 +341,11 @@ - (void)_reloadDeltas { if (patch) { XLOG_DEBUG_CHECK(!isBinary || patch.empty); - BOOL isImage = [[NSImage alloc] initWithContentsOfFile:[self.repository absolutePathForFile:delta.canonicalPath]] != nil; + CFStringRef fileExtension = CFBridgingRetain(delta.canonicalPath.pathExtension); + CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL); + BOOL isImage = UTTypeConformsTo(fileUTI, kUTTypeImage); + CFRelease(fileUTI); + CFRelease(fileExtension); if (isImage) { GIImageDiffView* imageDiffView = [[GIImageDiffView alloc] initWithRepository:self.repository]; imageDiffView.delta = delta; From 991f88908737795da132a25a4a2fef40e01c0364 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sat, 5 Jun 2021 17:08:46 +0200 Subject: [PATCH 31/34] Limit maximum size of images in memory To ensure fast performance and a reasonable memory impact even for very large image files, we now create thumbnails of a limited size. --- GitUpKit/Interface/GIImageDiffView.m | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index f2651587..6a8f7976 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -9,6 +9,7 @@ #define kImageInset 10 #define kBorderWidth 8 #define kDividerWidth 2 +#define kMaxImageDimension 4000 @interface GIImageDiffView () @property(nonatomic, strong) NSPanGestureRecognizer* panGestureRecognizer; @@ -103,7 +104,7 @@ - (void)updateCurrentImage { } else { newPath = [self.repository absolutePathForFile:_delta.canonicalPath]; } - _currentImageView.image = [[NSImage alloc] initWithContentsOfFile:newPath]; + _currentImageView.image = [self generateLimitedSizeImageFromPath:newPath]; } - (void)updateOldImage { @@ -117,12 +118,33 @@ - (void)updateOldImage { if (![[NSFileManager defaultManager] fileExistsAtPath:oldPath]) { [self.repository exportBlobWithSHA1:_delta.oldFile.SHA1 toPath:oldPath error:&error]; } - _oldImageView.image = [[NSImage alloc] initWithContentsOfFile:oldPath]; + _oldImageView.image = [self generateLimitedSizeImageFromPath:oldPath]; } else { _oldImageView.image = nil; } } +- (NSImage*)generateLimitedSizeImageFromPath:(NSString*)path { + CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL); + if (!imageSource) { + return nil; + } + + CFDictionaryRef options = (CFDictionaryRef)CFBridgingRetain(@{ + (id)kCGImageSourceCreateThumbnailWithTransform : @YES, + (id)kCGImageSourceCreateThumbnailFromImageAlways : @YES, + (id)kCGImageSourceThumbnailMaxPixelSize : @(kMaxImageDimension) + }); + CGImageRef image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options); + NSImage* convertedImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize]; + + CGImageRelease(image); + CFRelease(options); + CFRelease(imageSource); + + return convertedImage; +} + - (CGFloat)desiredHeightForWidth:(CGFloat)width { return [self desiredImageFrame:width].size.height + 2 * kImageInset; } From bae1fc2d943165e92308ef3d57e030c3abd54e96 Mon Sep 17 00:00:00 2001 From: Stengo Date: Mon, 7 Jun 2021 09:22:17 +0200 Subject: [PATCH 32/34] Restrict image diffing to NSImage compatible types --- GitUpKit/Components/GIDiffContentsViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitUpKit/Components/GIDiffContentsViewController.m b/GitUpKit/Components/GIDiffContentsViewController.m index c9c84be6..75ec551c 100644 --- a/GitUpKit/Components/GIDiffContentsViewController.m +++ b/GitUpKit/Components/GIDiffContentsViewController.m @@ -343,7 +343,7 @@ - (void)_reloadDeltas { CFStringRef fileExtension = CFBridgingRetain(delta.canonicalPath.pathExtension); CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL); - BOOL isImage = UTTypeConformsTo(fileUTI, kUTTypeImage); + BOOL isImage = [NSImage.imageTypes containsObject:(__bridge NSString * _Nonnull)(fileUTI)]; CFRelease(fileUTI); CFRelease(fileExtension); if (isImage) { From febb9950ceab945fc066d2fc0d3b61c397f07b28 Mon Sep 17 00:00:00 2001 From: Stengo Date: Sun, 26 Sep 2021 16:03:59 +0200 Subject: [PATCH 33/34] Separate image loading and cell size calculation To ensure maximum performance we now offload loading the image into memory to a separate thread. As we still need the image size to lay out cells properly, we query just this information before starting to load the full image. --- GitUpKit/Interface/GIImageDiffView.m | 73 ++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 6a8f7976..47a2b109 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -5,6 +5,7 @@ #import "GIPrivate.h" #import "GILaunchServicesLocator.h" #import +#import #define kImageInset 10 #define kBorderWidth 8 @@ -24,7 +25,10 @@ @interface GIImageDiffView () @property(nonatomic, strong) NSView* dividerView; @property(nonatomic, strong) CALayer* transparencyCheckerboardLayer; @property(nonatomic, strong) NSColor* checkerboardColor; +@property(nonatomic, strong) NSProgressIndicator* progressIndicator; @property(nonatomic) CGFloat percentage; +@property(nonatomic) NSSize oldImageSize; +@property(nonatomic) NSSize currentImageSize; @end @implementation GIImageDiffView @@ -69,6 +73,12 @@ - (void)setupView { _dividerView = [[NSView alloc] init]; [self addSubview:_dividerView]; + _progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 30, 30)]; + _progressIndicator.style = NSProgressIndicatorStyleSpinning; + [self addSubview:_progressIndicator]; + [_progressIndicator startAnimation:self]; + _progressIndicator.hidden = true; + _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; _clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(didMoveSplit:)]; [self addGestureRecognizer:_panGestureRecognizer]; @@ -104,7 +114,14 @@ - (void)updateCurrentImage { } else { newPath = [self.repository absolutePathForFile:_delta.canonicalPath]; } - _currentImageView.image = [self generateLimitedSizeImageFromPath:newPath]; + _currentImageSize = [self imageSizeWithoutLoadingFromPath:newPath]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSImage* limitedSizeImage = [self generateLimitedSizeImageFromPath:newPath]; + dispatch_async(dispatch_get_main_queue(), ^{ + _currentImageView.image = limitedSizeImage; + [self setNeedsDisplay:true]; + }); + }); } - (void)updateOldImage { @@ -118,12 +135,54 @@ - (void)updateOldImage { if (![[NSFileManager defaultManager] fileExistsAtPath:oldPath]) { [self.repository exportBlobWithSHA1:_delta.oldFile.SHA1 toPath:oldPath error:&error]; } - _oldImageView.image = [self generateLimitedSizeImageFromPath:oldPath]; + _oldImageSize = [self imageSizeWithoutLoadingFromPath:oldPath]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSImage* limitedSizeImage = [self generateLimitedSizeImageFromPath:oldPath]; + dispatch_async(dispatch_get_main_queue(), ^{ + _oldImageView.image = limitedSizeImage; + [self setNeedsDisplay:true]; + }); + }); } else { _oldImageView.image = nil; } } +- (NSSize)imageSizeWithoutLoadingFromPath:(NSString*)path { + NSURL* imageFileURL = [NSURL fileURLWithPath:path]; + CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL); + if (imageSource == NULL) { + return NSZeroSize; + } + CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); + CFRelease(imageSource); + + CGFloat width = 0.0f; + CGFloat height = 0.0f; + if (imageProperties != NULL) { + CFNumberRef widthNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth); + if (widthNum != NULL) { + CFNumberGetValue(widthNum, kCFNumberCGFloatType, &width); + } + CFNumberRef heightNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight); + if (heightNum != NULL) { + CFNumberGetValue(heightNum, kCFNumberCGFloatType, &height); + } + CFNumberRef orientationNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation); + if (orientationNum != NULL) { + int orientation; + CFNumberGetValue(orientationNum, kCFNumberIntType, &orientation); + if (orientation > 4) { + CGFloat temp = width; + width = height; + height = temp; + } + } + CFRelease(imageProperties); + } + return NSMakeSize(width, height); +} + - (NSImage*)generateLimitedSizeImageFromPath:(NSString*)path { CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL); if (!imageSource) { @@ -154,6 +213,7 @@ - (void)drawRect:(NSRect)dirtyRect { [CATransaction setDisableActions:YES]; [self updateColors]; [self updateFrames]; + _progressIndicator.hidden = _currentImageView.image != nil || _oldImageView.image != nil; [CATransaction commit]; } @@ -166,6 +226,11 @@ - (void)updateColors { - (void)updateFrames { CGRect fittedImageFrame = [self fittedImageFrame]; + _progressIndicator.frame = CGRectMake( + (fittedImageFrame.size.width - _progressIndicator.frame.size.width) / 2, + (fittedImageFrame.size.height - _progressIndicator.frame.size.height) / 2, + _progressIndicator.frame.size.width, + _progressIndicator.frame.size.height); _transparencyCheckerboardLayer.frame = fittedImageFrame; _currentImageView.frame = fittedImageFrame; if (_oldImageView.image != nil) { @@ -235,8 +300,8 @@ - (CGRect)fittedImageFrame { } - (NSSize)originalDiffImageSize { - CGFloat maxHeight = MAX(_currentImageView.image.size.height, _oldImageView.image.size.height); - CGFloat maxWidth = MAX(_currentImageView.image.size.width, _oldImageView.image.size.width); + CGFloat maxHeight = MAX(_currentImageSize.height, _oldImageSize.height); + CGFloat maxWidth = MAX(_currentImageSize.width, _oldImageSize.width); return NSMakeSize(maxWidth, maxHeight); } From f29bc233981e1c55a1943a1ac6c7dcf2084d3173 Mon Sep 17 00:00:00 2001 From: Stengo Date: Thu, 7 Oct 2021 16:34:04 +0200 Subject: [PATCH 34/34] Show the entire deleted image --- GitUpKit/Interface/GIImageDiffView.m | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/GitUpKit/Interface/GIImageDiffView.m b/GitUpKit/Interface/GIImageDiffView.m index 47a2b109..b9a85884 100644 --- a/GitUpKit/Interface/GIImageDiffView.m +++ b/GitUpKit/Interface/GIImageDiffView.m @@ -233,7 +233,7 @@ - (void)updateFrames { _progressIndicator.frame.size.height); _transparencyCheckerboardLayer.frame = fittedImageFrame; _currentImageView.frame = fittedImageFrame; - if (_oldImageView.image != nil) { + if (_oldImageView.image != nil && _currentImageView.image != nil) { _oldImageView.frame = fittedImageFrame; [_oldImageView setHidden:false]; CGFloat dividerOffset = fittedImageFrame.size.width * _percentage; @@ -257,6 +257,17 @@ - (void)updateFrames { fittedImageFrame.origin.y - kBorderWidth, kDividerWidth, fittedImageFrame.size.height + 2 * kBorderWidth); + } else if (_oldImageView.image != nil) { + [_oldImageView setHidden:false]; + _oldImageView.frame = fittedImageFrame; + _oldImageMaskLayer.frame = CGRectMake(0, + 0, + fittedImageFrame.size.width, + fittedImageFrame.size.height); + _oldImageBorderLayer.frame = CGRectMake(fittedImageFrame.origin.x - kBorderWidth, + fittedImageFrame.origin.y - kBorderWidth, + fittedImageFrame.size.width + 2 * kBorderWidth, + fittedImageFrame.size.height + 2 * kBorderWidth); } else { _currentImageMaskLayer.frame = CGRectMake(0, 0,