Add IconImage to encapsulate our icon processing logic

This commit is contained in:
Maurice Parker
2019-11-05 18:05:57 -06:00
parent 05e0e34f6b
commit 560f36621f
46 changed files with 336 additions and 323 deletions

View File

@@ -32,7 +32,6 @@ import RSCore
}()
static func customSharingServices(for items: [Any]) -> [NSSharingService] {
let customServices = sendToCommands.compactMap { (sendToCommand) -> NSSharingService? in
guard let object = items.first else {
@@ -42,7 +41,7 @@ import RSCore
return nil
}
let image = sendToCommand.image ?? AppAssets.genericFeedImage ?? NSImage()
let image = sendToCommand.image ?? AppAssets.genericFeedImage?.image ?? NSImage()
return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) {
sendToCommand.sendObject(object, selectedText: nil)
}

View File

@@ -81,8 +81,8 @@ class SidebarCell : NSTableCellView {
}()
private let faviconImageView: NSImageView = {
let image = AppAssets.genericFeedImage
let imageView = image != nil ? NSImageView(image: image!) : NSImageView(frame: NSRect.zero)
let iconImage = AppAssets.genericFeedImage
let imageView = iconImage != nil ? NSImageView(image: iconImage!.image) : NSImageView(frame: NSRect.zero)
imageView.animates = false
imageView.imageAlignment = .alignCenter
imageView.imageScaling = .scaleProportionallyDown

View File

@@ -526,14 +526,14 @@ private extension SidebarViewController {
}
func configureFavicon(_ cell: SidebarCell, _ node: Node) {
cell.image = imageFor(node)
cell.image = imageFor(node)?.image
}
func configureGroupCell(_ cell: NSTableCellView, _ node: Node) {
cell.textField?.stringValue = nameFor(node)
}
func imageFor(_ node: Node) -> NSImage? {
func imageFor(_ node: Node) -> IconImage? {
if let smallIconProvider = node.representedObject as? SmallIconProvider {
return smallIconProvider.smallIcon
}

View File

@@ -10,7 +10,7 @@ import AppKit
struct TimelineCellAppearance: Equatable {
let showAvatar: Bool
let showIcon: Bool
let cellPadding = NSEdgeInsets(top: 8.0, left: 18.0, bottom: 10.0, right: 18.0)
@@ -35,15 +35,15 @@ struct TimelineCellAppearance: Equatable {
let drawsGrid = false
let avatarSize = NSSize(width: 48, height: 48)
let avatarMarginLeft: CGFloat = 8.0
let avatarMarginRight: CGFloat = 8.0
let avatarAdjustmentTop: CGFloat = 4.0
let avatarCornerRadius: CGFloat = 4.0
let iconSize = NSSize(width: 48, height: 48)
let iconMarginLeft: CGFloat = 8.0
let iconMarginRight: CGFloat = 8.0
let iconAdjustmentTop: CGFloat = 4.0
let iconCornerRadius: CGFloat = 4.0
let boxLeftMargin: CGFloat
init(showAvatar: Bool, fontSize: FontSize) {
init(showIcon: Bool, fontSize: FontSize) {
let actualFontSize = AppDefaults.actualFontSize(for: fontSize)
let smallItemFontSize = actualFontSize //floor(actualFontSize * 0.95)
@@ -55,7 +55,7 @@ struct TimelineCellAppearance: Equatable {
self.textFont = NSFont.systemFont(ofSize: largeItemFontSize)
self.textOnlyFont = NSFont.systemFont(ofSize: largeItemFontSize)
self.showAvatar = showAvatar
self.showIcon = showIcon
let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight
self.boxLeftMargin = margin

View File

@@ -16,13 +16,13 @@ struct TimelineCellData {
let dateString: String
let feedName: String
let showFeedName: Bool
let avatar: NSImage? // feed icon, user avatar, or favicon
let showAvatar: Bool // Make space even when avatar is nil
let iconImage: IconImage? // feed icon, user avatar, or favicon
let showIcon: Bool // Make space even when icon is nil
let featuredImage: NSImage? // image from within the article
let read: Bool
let starred: Bool
init(article: Article, showFeedName: Bool, feedName: String?, avatar: NSImage?, showAvatar: Bool, featuredImage: NSImage?) {
init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) {
self.title = ArticleStringFormatter.truncatedTitle(article)
self.text = ArticleStringFormatter.truncatedSummary(article)
@@ -38,8 +38,8 @@ struct TimelineCellData {
self.showFeedName = showFeedName
self.showAvatar = showAvatar
self.avatar = avatar
self.showIcon = showIcon
self.iconImage = iconImage
self.featuredImage = featuredImage
self.read = article.status.read
@@ -52,8 +52,8 @@ struct TimelineCellData {
self.dateString = ""
self.feedName = ""
self.showFeedName = false
self.showAvatar = false
self.avatar = nil
self.showIcon = false
self.iconImage = nil
self.featuredImage = nil
self.read = true
self.starred = false

View File

@@ -21,11 +21,11 @@ struct TimelineCellLayout {
let textRect: NSRect
let unreadIndicatorRect: NSRect
let starRect: NSRect
let avatarImageRect: NSRect
let iconImageRect: NSRect
let separatorRect: NSRect
let paddingBottom: CGFloat
init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) {
init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, iconImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) {
self.width = width
self.feedNameRect = feedNameRect
@@ -36,7 +36,7 @@ struct TimelineCellLayout {
self.textRect = textRect
self.unreadIndicatorRect = unreadIndicatorRect
self.starRect = starRect
self.avatarImageRect = avatarImageRect
self.iconImageRect = iconImageRect
self.separatorRect = separatorRect
self.paddingBottom = paddingBottom
@@ -44,16 +44,16 @@ struct TimelineCellLayout {
self.height = height
}
else {
self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, iconImageRect].maxY() + paddingBottom
}
}
init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasAvatar: Bool) {
init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasIcon: Bool) {
// If height == 0.0, then height is calculated.
let showAvatar = cellData.showAvatar
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, showAvatar, width)
let showIcon = cellData.showIcon
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, showIcon, width)
let (titleRect, numberOfLinesForTitle) = TimelineCellLayout.rectForTitle(textBoxRect, appearance, cellData)
let summaryRect = numberOfLinesForTitle > 0 ? TimelineCellLayout.rectForSummary(textBoxRect, titleRect, numberOfLinesForTitle, appearance, cellData) : NSRect.zero
@@ -72,19 +72,19 @@ struct TimelineCellLayout {
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
textBoxRect.size.height = ceil([titleRect, summaryRect, textRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, showAvatar, textBoxRect, width, height)
let iconImageRect = TimelineCellLayout.rectForIcon(cellData, appearance, showIcon, textBoxRect, width, height)
let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, textBoxRect)
let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
let separatorRect = TimelineCellLayout.rectForSeparator(cellData, appearance, showAvatar ? avatarImageRect : titleRect, width, height)
let separatorRect = TimelineCellLayout.rectForSeparator(cellData, appearance, showIcon ? iconImageRect : titleRect, width, height)
let paddingBottom = appearance.cellPadding.bottom
self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, separatorRect: separatorRect, paddingBottom: paddingBottom)
self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, iconImageRect: iconImageRect, separatorRect: separatorRect, paddingBottom: paddingBottom)
}
static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasAvatar: true)
let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasIcon: true)
return layout.height
}
}
@@ -93,12 +93,12 @@ struct TimelineCellLayout {
private extension TimelineCellLayout {
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showAvatar: Bool, _ width: CGFloat) -> NSRect {
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showIcon: Bool, _ width: CGFloat) -> NSRect {
// Returned height is a placeholder. Not needed when this is calculated.
let avatarSpace = showAvatar ? appearance.avatarSize.width + appearance.avatarMarginRight : 0.0
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight + avatarSpace
let iconSpace = showIcon ? appearance.iconSize.width + appearance.iconMarginRight : 0.0
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight + iconSpace
let textBoxMaxX = floor(width - appearance.cellPadding.right)
let textBoxWidth = floor(textBoxMaxX - textBoxOriginX)
let textBoxRect = NSRect(x: textBoxOriginX, y: appearance.cellPadding.top, width: textBoxWidth, height: 1000000)
@@ -201,15 +201,15 @@ private extension TimelineCellLayout {
return r
}
static func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showAvatar: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
static func rectForIcon(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showIcon: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
var r = NSRect.zero
if !showAvatar {
if !showIcon {
return r
}
r.size = appearance.avatarSize
r.size = appearance.iconSize
r.origin.x = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
r.origin.y = textBoxRect.origin.y + appearance.avatarAdjustmentTop
r.origin.y = textBoxRect.origin.y + appearance.iconAdjustmentTop
return r
}

View File

@@ -1,5 +1,5 @@
//
// TimelineAvatarView.swift
// TimelineIconView.swift
// NetNewsWire
//
// Created by Brent Simmons on 9/15/19.
@@ -8,12 +8,12 @@
import AppKit
final class TimelineAvatarView: NSView {
final class TimelineIconView: NSView {
var image: NSImage? = nil {
var iconImage: IconImage? = nil {
didSet {
if image !== oldValue {
imageView.image = image
if iconImage !== oldValue {
imageView.image = iconImage?.image
needsDisplay = true
needsLayout = true
}
@@ -36,8 +36,8 @@ final class TimelineAvatarView: NSView {
return imageView.frame.size.height < bounds.size.height
}
private static var lightBackgroundColor = AppAssets.avatarLightBackgroundColor
private static var darkBackgroundColor = AppAssets.avatarDarkBackgroundColor
private static var lightBackgroundColor = AppAssets.iconLightBackgroundColor
private static var darkBackgroundColor = AppAssets.iconDarkBackgroundColor
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
@@ -71,13 +71,13 @@ final class TimelineAvatarView: NSView {
return
}
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineAvatarView.darkBackgroundColor : TimelineAvatarView.lightBackgroundColor
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineIconView.darkBackgroundColor : TimelineIconView.lightBackgroundColor
color.set()
dirtyRect.fill()
}
}
private extension TimelineAvatarView {
private extension TimelineIconView {
func commonInit() {
addSubview(imageView)
@@ -85,7 +85,7 @@ private extension TimelineAvatarView {
}
func rectForImageView() -> NSRect {
guard let image = image else {
guard let image = iconImage?.image else {
return NSRect.zero
}

View File

@@ -18,7 +18,7 @@ class TimelineTableCellView: NSTableCellView {
private let dateView = TimelineTableCellView.singleLineTextField()
private let feedNameView = TimelineTableCellView.singleLineTextField()
private lazy var avatarView = TimelineAvatarView()
private lazy var iconView = TimelineIconView()
private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone)
private let separatorView = TimelineTableCellView.separatorView()
@@ -37,7 +37,7 @@ class TimelineTableCellView: NSTableCellView {
didSet {
if cellAppearance != oldValue {
updateTextFieldFonts()
avatarView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
iconView.layer?.cornerRadius = cellAppearance.iconCornerRadius
needsLayout = true
}
}
@@ -119,7 +119,7 @@ class TimelineTableCellView: NSTableCellView {
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
avatarView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
iconView.rs_setFrameIfNotEqual(layoutRects.iconImageRect)
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
separatorView.rs_setFrameIfNotEqual(layoutRects.separatorRect)
}
@@ -207,7 +207,7 @@ private extension TimelineTableCellView {
addSubviewAtInit(unreadIndicatorView, hidden: true)
addSubviewAtInit(dateView, hidden: false)
addSubviewAtInit(feedNameView, hidden: true)
addSubviewAtInit(avatarView, hidden: true)
addSubviewAtInit(iconView, hidden: true)
addSubviewAtInit(starView, hidden: true)
addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators)
@@ -216,7 +216,7 @@ private extension TimelineTableCellView {
func updatedLayoutRects() -> TimelineCellLayout {
return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarView.image != nil)
return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasIcon: iconView.iconImage != nil)
}
func updateTitleView() {
@@ -265,25 +265,25 @@ private extension TimelineTableCellView {
showOrHideView(starView, !cellData.starred)
}
func updateAvatar() {
guard let image = cellData.avatar, cellData.showAvatar else {
makeAvatarEmpty()
func updateIcon() {
guard let iconImage = cellData.iconImage, cellData.showIcon else {
makeIconEmpty()
return
}
showView(avatarView)
if avatarView.image !== image {
avatarView.image = image
showView(iconView)
if iconView.iconImage !== iconImage {
iconView.iconImage = iconImage
needsLayout = true
}
}
func makeAvatarEmpty() {
if avatarView.image != nil {
avatarView.image = nil
func makeIconEmpty() {
if iconView.iconImage != nil {
iconView.iconImage = nil
needsLayout = true
}
hideView(avatarView)
hideView(iconView)
}
func hideView(_ view: NSView) {
@@ -310,7 +310,7 @@ private extension TimelineTableCellView {
updateFeedNameView()
updateUnreadIndicator()
updateStarView()
updateAvatar()
updateIcon()
}
}

View File

@@ -79,7 +79,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
reloadVisibleCells()
return
}
updateShowAvatars()
updateShowIcons()
articleRowMap = [String: Int]()
tableView.reloadData()
}
@@ -98,18 +98,18 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
private let fetchRequestQueue = FetchRequestQueue()
private var articleRowMap = [String: Int]() // articleID: rowIndex
private var cellAppearance: TimelineCellAppearance!
private var cellAppearanceWithAvatar: TimelineCellAppearance!
private var cellAppearanceWithIcon: TimelineCellAppearance!
private var showFeedNames = false {
didSet {
if showFeedNames != oldValue {
updateShowAvatars()
updateShowIcons()
updateTableViewRowHeight()
reloadVisibleCells()
}
}
}
private var showAvatars = false
private var showIcons = false
private var rowHeightWithFeedName: CGFloat = 0.0
private var rowHeightWithoutFeedName: CGFloat = 0.0
@@ -156,8 +156,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
override func viewDidLoad() {
cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize)
cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize)
cellAppearance = TimelineCellAppearance(showIcon: false, fontSize: fontSize)
cellAppearanceWithIcon = TimelineCellAppearance(showIcon: true, fontSize: fontSize)
updateRowHeights()
tableView.rowHeight = currentRowHeight
@@ -438,7 +438,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard showAvatars, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
return
}
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
@@ -453,7 +453,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func avatarDidBecomeAvailable(_ note: Notification) {
guard showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
guard showIcons, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
return
}
@@ -474,7 +474,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func faviconDidBecomeAvailable(_ note: Notification) {
if showAvatars {
if showIcons {
queueReloadAvailableCells()
}
}
@@ -570,7 +570,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", avatar: nil, showAvatar: false, featuredImage: nil)
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil)
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
return height
}
@@ -674,7 +674,7 @@ extension TimelineViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
func configure(_ cell: TimelineTableCellView) {
cell.cellAppearance = showAvatars ? cellAppearanceWithAvatar : cellAppearance
cell.cellAppearance = showIcons ? cellAppearanceWithIcon : cellAppearance
if let article = articles.articleAtRow(row) {
configureTimelineCell(cell, article: article)
}
@@ -716,12 +716,12 @@ extension TimelineViewController: NSTableViewDelegate {
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
cell.objectValue = article
let avatar = article.avatarImage()
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatars, featuredImage: nil)
let iconImage = article.iconImage()
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
}
private func avatarFor(_ article: Article) -> NSImage? {
if !showAvatars {
private func iconFor(_ article: Article) -> IconImage? {
if !showIcons {
return nil
}
@@ -741,14 +741,14 @@ extension TimelineViewController: NSTableViewDelegate {
return feedIcon
}
if let favicon = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
if let favicon = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
return favicon
}
return FaviconGenerator.favicon(feed)
}
private func avatarForAuthor(_ author: Author) -> NSImage? {
private func avatarForAuthor(_ author: Author) -> IconImage? {
return appDelegate.authorAvatarDownloader.image(for: author)
}
@@ -842,9 +842,9 @@ private extension TimelineViewController {
tableView.rowHeight = currentRowHeight
}
func updateShowAvatars() {
func updateShowIcons() {
if showFeedNames {
self.showAvatars = true
self.showIcons = true
return
}
@@ -852,14 +852,14 @@ private extension TimelineViewController {
if let authors = article.authors {
for author in authors {
if author.avatarURL != nil {
self.showAvatars = true
self.showIcons = true
return
}
}
}
}
self.showAvatars = false
self.showIcons = false
}
func emptyTheTimeline() {
@@ -935,8 +935,8 @@ private extension TimelineViewController {
// MARK: - Appearance Change
private func fontSizeDidChange() {
cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize)
cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize)
cellAppearance = TimelineCellAppearance(showIcon: false, fontSize: fontSize)
cellAppearanceWithIcon = TimelineCellAppearance(showIcon: true, fontSize: fontSize)
updateRowHeights()
performBlockAndRestoreSelection {
tableView.reloadData()