From 8a2d1f5f6a05a0952692e1e3a6b8f6a7c325ee59 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 15 Sep 2019 23:02:13 -0700 Subject: [PATCH] Create TimelineAvatarView, which draws the background for images that need it. --- .../Timeline/Cell/TimelineAvatarView.swift | 118 ++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 16 ++- Shared/Extensions/RSImage-Extensions.swift | 6 +- 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift diff --git a/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift new file mode 100644 index 000000000..f6f5192ef --- /dev/null +++ b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift @@ -0,0 +1,118 @@ +// +// TimelineAvatarView.swift +// NetNewsWire +// +// Created by Brent Simmons on 9/15/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import AppKit + +final class TimelineAvatarView: NSView { + + var image: NSImage? = nil { + didSet { + imageView.image = image + updateHasExposedBackground() + needsDisplay = true + needsLayout = true + } + } + + override var isFlipped: Bool { + return true + } + + private let imageView: NSImageView = { + let imageView = NSImageView(frame: NSRect.zero) + imageView.animates = false + imageView.imageAlignment = .alignCenter + imageView.imageScaling = .scaleProportionallyDown + return imageView + }() + + private var hasExposedBackground = true { + didSet { + if oldValue != hasExposedBackground { + needsDisplay = true + } + } + } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + convenience init() { + self.init(frame: NSRect.zero) + } + + override func viewDidMoveToSuperview() { + needsLayout = true + needsDisplay = true + } + + override func layout() { + resizeSubviews(withOldSize: NSZeroSize) + } + + override func resizeSubviews(withOldSize oldSize: NSSize) { + imageView.rs_setFrameIfNotEqual(rectForImageView()) + } + + override func draw(_ dirtyRect: NSRect) { + guard hasExposedBackground else { + return + } + let rImage = imageView.frame + if rImage.contains(dirtyRect) { + return + } + + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? AppAssets.avatarDarkBackgroundColor : AppAssets.avatarLightBackgroundColor + color.set() + dirtyRect.fill() + } +} + +private extension TimelineAvatarView { + + func commonInit() { + addSubview(imageView) + } + + func rectForImageView() -> NSRect { + guard let image = image else { + return NSRect.zero + } + + let imageSize = image.size + let viewSize = bounds.size + if imageSize.height == imageSize.width { + return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) + } + else if imageSize.height > imageSize.width { + let factor = viewSize.height / imageSize.height + let width = imageSize.width * factor + let originX = floor((viewSize.width - width) / 2.0) + return NSMakeRect(originX, 0.0, width, viewSize.height) + } + + // Wider than tall: imageSize.width > imageSize.height + let factor = viewSize.width / imageSize.width + let height = imageSize.height * factor + let originY = floor((viewSize.height - height) / 2.0) + return NSMakeRect(0.0, originY, viewSize.width, height) + } + + func updateHasExposedBackground() { + let rImage = rectForImageView() + hasExposedBackground = rImage.size.height < bounds.size.height || rImage.size.width < bounds.size.width + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index c13da6839..64b281494 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -187,6 +187,7 @@ 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; + 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */; }; 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; @@ -797,6 +798,7 @@ 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = ""; }; + 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAvatarView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; @@ -1422,6 +1424,7 @@ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, + 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */, ); path = Cell; sourceTree = ""; @@ -1960,12 +1963,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = M8L2WTLA8W; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -1981,8 +1984,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -1991,7 +1994,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = 9C84TZ7Q6Z; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2430,6 +2433,7 @@ files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, + 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, diff --git a/Shared/Extensions/RSImage-Extensions.swift b/Shared/Extensions/RSImage-Extensions.swift index 3406a959b..0b566b632 100644 --- a/Shared/Extensions/RSImage-Extensions.swift +++ b/Shared/Extensions/RSImage-Extensions.swift @@ -28,9 +28,9 @@ extension RSImage { return nil } - if cgImage.width < avatarSize || cgImage.height < avatarSize { - cgImage = RSImage.compositeAvatar(cgImage) - } +// if cgImage.width < avatarSize || cgImage.height < avatarSize { +// cgImage = RSImage.compositeAvatar(cgImage) +// } #if os(iOS) return RSImage(cgImage: cgImage)