diff --git a/Evergreen/AppDelegate.swift b/Evergreen/AppDelegate.swift
index 25e298b3c..13c8fc80b 100644
--- a/Evergreen/AppDelegate.swift
+++ b/Evergreen/AppDelegate.swift
@@ -164,7 +164,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
func applicationDidResignActive(_ notification: Notification) {
- TimelineCellData.emptyCache()
timelineEmptyCaches()
saveState()
diff --git a/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
index 85b5da751..0f15b7676 100644
--- a/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
@@ -90,7 +90,17 @@ private extension MultilineTextFieldSizer {
return newSizer
}
- func sizeInfo(for string: String, width: Int) -> Int {
+ func sizeInfo(for string: String, width: Int) -> TextFieldSizeInfo {
+
+ let textFieldHeight = height(for: string, width: width)
+ let numberOfLinesUsed = numberOfLines(for: textFieldHeight)
+
+ let size = NSSize(width: width, height: textFieldHeight)
+ let sizeInfo = TextFieldSizeInfo(size: size, numberOfLinesUsed: numberOfLinesUsed)
+ return sizeInfo
+ }
+
+ func height(for string: String, width: Int) -> Int {
if cache[string] == nil {
cache[string] = WidthHeightCache()
@@ -135,6 +145,15 @@ private extension MultilineTextFieldSizer {
return Int(ceil(size.height))
}
+ func numberOfLines(for height: Int) -> Int {
+
+ // We’ll have to see if this really works reliably.
+
+ let averageHeight = CGFloat(doubleLineHeightEstimate) / 2.0
+ let lines = Int(round(CGFloat(height) / averageHeight))
+ return lines
+ }
+
func heightIsProbablySingleLineHeight(_ height: Int) -> Bool {
return heightIsProbablyEqualToEstimate(height, singleLineHeightEstimate)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
index ca40fb244..a552efeee 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
@@ -23,7 +23,7 @@ struct TimelineCellAppearance: Equatable {
let titleColor: NSColor
let titleFont: NSFont
let titleBottomMargin: CGFloat
- let titleNumberOfLines = 2
+ let titleNumberOfLines: Int
let textColor: NSColor
let textFont: NSFont
@@ -67,7 +67,8 @@ struct TimelineCellAppearance: Equatable {
self.titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor")
self.titleFont = NSFont.systemFont(ofSize: largeItemFontSize, weight: NSFont.Weight.semibold)
self.titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom")
-
+ self.titleNumberOfLines = theme.integer(forKey: "MainWindow.Timeline.cell.titleMaximumLines")
+
self.textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor")
self.textFont = NSFont.systemFont(ofSize: largeItemFontSize)
@@ -91,12 +92,10 @@ struct TimelineCellAppearance: Equatable {
self.showAvatar = showAvatar
let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight
-// if showAvatar {
-// margin += (self.avatarSize.width + self.avatarMarginRight)
-// }
self.boxLeftMargin = margin
}
+ // TODO: update the below
static func ==(lhs: TimelineCellAppearance, rhs: TimelineCellAppearance) -> Bool {
return lhs.boxLeftMargin == rhs.boxLeftMargin && lhs.showAvatar == rhs.showAvatar && lhs.cellPadding == rhs.cellPadding && lhs.feedNameColor == rhs.feedNameColor && lhs.feedNameFont == rhs.feedNameFont && lhs.dateColor == rhs.dateColor && lhs.dateMarginLeft == rhs.dateMarginLeft && lhs.dateFont == rhs.dateFont && lhs.titleColor == rhs.titleColor && lhs.titleFont == rhs.titleFont && lhs.titleBottomMargin == rhs.titleBottomMargin && lhs.textColor == rhs.textColor && lhs.textFont == rhs.textFont && lhs.unreadCircleColor == rhs.unreadCircleColor && lhs.unreadCircleDimension == rhs.unreadCircleDimension && lhs.unreadCircleMarginRight == rhs.unreadCircleMarginRight && lhs.gridColor == rhs.gridColor && lhs.avatarSize == rhs.avatarSize && lhs.avatarMarginRight == rhs.avatarMarginRight && lhs.avatarAdjustmentTop == rhs.avatarAdjustmentTop && lhs.avatarCornerRadius == rhs.avatarCornerRadius
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
index cfe2f33ab..c063859be 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
@@ -9,15 +9,10 @@
import AppKit
import Data
-var attributedTitleCache = [String: NSAttributedString]()
-var attributedDateCache = [String: NSAttributedString]()
-var attributedFeedNameCache = [String: NSAttributedString]()
-
struct TimelineCellData {
let title: String
let text: String
- let attributedTitle: NSAttributedString //title + text
let dateString: String
let feedName: String
let showFeedName: Bool
@@ -32,15 +27,6 @@ struct TimelineCellData {
self.title = timelineTruncatedTitle(article)
self.text = timelineTruncatedSummary(article)
- let attributedTitleCacheKey = "_title: " + self.title + "_text: " + self.text
- if let s = attributedTitleCache[attributedTitleCacheKey] {
- self.attributedTitle = s
- }
- else {
- self.attributedTitle = attributedTitleString(title, text, appearance)
- attributedTitleCache[attributedTitleCacheKey] = self.attributedTitle
- }
-
self.dateString = timelineDateString(article.logicalDatePublished)
if let feedName = feedName {
@@ -63,7 +49,6 @@ struct TimelineCellData {
init() { //Empty
self.title = ""
- self.attributedTitle = NSAttributedString(string: "")
self.text = ""
self.dateString = ""
self.feedName = ""
@@ -74,31 +59,4 @@ struct TimelineCellData {
self.read = true
self.starred = false
}
-
- static func emptyCache() {
-
- attributedTitleCache = [String: NSAttributedString]()
- attributedDateCache = [String: NSAttributedString]()
- attributedFeedNameCache = [String: NSAttributedString]()
- }
}
-
-let emptyCellData = TimelineCellData()
-
-private func attributedTitleString(_ title: String, _ text: String, _ appearance: TimelineCellAppearance) -> NSAttributedString {
-
- if !title.isEmpty && !text.isEmpty {
-
- let titleMutable = NSMutableAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor: appearance.titleColor, NSAttributedStringKey.font: appearance.titleFont])
-// let attributedText = NSAttributedString(string: " " + text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textColor, NSAttributedStringKey.font: appearance.textFont])
-// titleMutable.append(attributedText)
- return titleMutable
- }
-
- if !title.isEmpty && text.isEmpty {
- return NSAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor: appearance.titleColor, NSAttributedStringKey.font: appearance.titleFont])
- }
-
- return NSAttributedString(string: text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textOnlyColor, NSAttributedStringKey.font: appearance.textOnlyFont])
-}
-
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index ab86a7b10..18392af84 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -16,41 +16,49 @@ struct TimelineCellLayout {
let feedNameRect: NSRect
let dateRect: NSRect
let titleRect: NSRect
+ let numberOfLinesForTitle: Int
+ let summaryRect: NSRect
+ let textRect: NSRect
let unreadIndicatorRect: NSRect
let starRect: NSRect
let avatarImageRect: NSRect
let paddingBottom: CGFloat
- init(width: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) {
+ init(width: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) {
self.width = width
self.feedNameRect = feedNameRect
self.dateRect = dateRect
self.titleRect = titleRect
+ self.numberOfLinesForTitle = numberOfLinesForTitle
+ self.summaryRect = summaryRect
+ self.textRect = textRect
self.unreadIndicatorRect = unreadIndicatorRect
self.starRect = starRect
self.avatarImageRect = avatarImageRect
self.paddingBottom = paddingBottom
- self.height = [feedNameRect, dateRect, titleRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
+ self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
}
init(width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) {
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, width)
- let titleRect = TimelineCellLayout.rectForTitle(textBoxRect, cellData)
+ let (titleRect, numberOfLinesForTitle) = TimelineCellLayout.rectForTitle(textBoxRect, appearance, cellData)
+ let summaryRect = numberOfLinesForTitle > 0 ? TimelineCellLayout.rectForSummary(textBoxRect, titleRect, numberOfLinesForTitle, appearance, cellData) : NSRect.zero
+ let textRect = numberOfLinesForTitle > 0 ? NSRect.zero : TimelineCellLayout.rectForText(textBoxRect, appearance, cellData)
let dateRect = TimelineCellLayout.rectForDate(textBoxRect, titleRect, appearance, cellData)
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
- textBoxRect.size.height = ceil([titleRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
+ textBoxRect.size.height = ceil([titleRect, summaryRect, textRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, textBoxRect, width)
- let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, titleRect)
+ let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, textBoxRect)
let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
let paddingBottom = appearance.cellPadding.bottom
- self.init(width: width, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, paddingBottom: paddingBottom)
+ self.init(width: width, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, paddingBottom: paddingBottom)
}
static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
@@ -76,18 +84,52 @@ private extension TimelineCellLayout {
return textBoxRect
}
- static func rectForTitle(_ textBoxRect: NSRect, _ cellData: TimelineCellData) -> NSRect {
+ static func rectForTitle(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> (NSRect, Int) {
var r = textBoxRect
- let height = MultilineTextFieldSizer.size(for: cellData.title, font: appearance.titleFont, numberOfLines: 2, width: Int(textBoxRect.width))
- r.size.height = CGFloat(height)
+ if cellData.title.isEmpty {
+ r.size.height = 0
+ return (r, 0)
+ }
+
+ let sizeInfo = MultilineTextFieldSizer.size(for: cellData.title, font: appearance.titleFont, numberOfLines: appearance.titleNumberOfLines, width: Int(textBoxRect.width))
+ r.size.height = sizeInfo.size.height
+ if sizeInfo.numberOfLinesUsed < 1 {
+ r.size.height = 0
+ }
+ return (r, sizeInfo.numberOfLinesUsed)
+ }
+
+ static func rectForSummary(_ textBoxRect: NSRect, _ titleRect: NSRect, _ titleNumberOfLines: Int, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
+
+ if titleNumberOfLines >= appearance.titleNumberOfLines || cellData.text.isEmpty {
+ return NSRect.zero
+ }
+
+ return rectOfLineBelow(titleRect, titleRect, 0, cellData.text, appearance.textFont)
+ }
+
+ static func rectForText(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
+
+ var r = textBoxRect
+
+ if cellData.text.isEmpty {
+ r.size.height = 0
+ return r
+ }
+
+ let sizeInfo = MultilineTextFieldSizer.size(for: cellData.text, font: appearance.textOnlyFont, numberOfLines: appearance.titleNumberOfLines, width: Int(textBoxRect.width))
+ r.size.height = sizeInfo.size.height
+ if sizeInfo.numberOfLinesUsed < 1 {
+ r.size.height = 0
+ }
return r
}
- static func rectForDate(_ textBoxRect: NSRect, _ titleRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
+ static func rectForDate(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
- return rectOfLineBelow(textBoxRect, titleRect, appearance.titleBottomMargin, cellData.dateString, appearance.dateFont)
+ return rectOfLineBelow(textBoxRect, rectAbove, appearance.titleBottomMargin, cellData.dateString, appearance.dateFont)
}
static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
index 6dd0acfdd..0d09d1a99 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
@@ -10,12 +10,13 @@ import Foundation
import Data
import RSParser
+// TODO: Don’t make all this at top level.
+
private var truncatedFeedNameCache = [String: String]()
private let truncatedTitleCache = NSMutableDictionary()
private let normalizedTextCache = NSMutableDictionary()
private let textCache = NSMutableDictionary()
private let summaryCache = NSMutableDictionary()
-//private var summaryCache = [String: String]()
func timelineEmptyCaches() {
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 4dae483b2..9b6c5fe44 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -146,7 +146,7 @@ private extension TimelineTableCellView {
textField.usesSingleLineMode = false
textField.maximumNumberOfLines = 2
textField.isEditable = false
- textField.lineBreakMode = .byTruncatingTail
+// textField.lineBreakMode = .byTruncatingTail
textField.cell?.truncatesLastVisibleLine = true
textField.allowsDefaultTighteningForTruncation = false
return textField
@@ -171,6 +171,7 @@ private extension TimelineTableCellView {
else {
feedNameView.textColor = cellAppearance.feedNameColor
dateView.textColor = cellAppearance.dateColor
+ titleView.textColor = cellAppearance.titleColor
}
}
@@ -178,6 +179,7 @@ private extension TimelineTableCellView {
feedNameView.font = cellAppearance.feedNameFont
dateView.font = cellAppearance.dateFont
+ titleView.font = cellAppearance.titleFont
}
func updateTextFields() {
@@ -222,17 +224,7 @@ private extension TimelineTableCellView {
func updateTitleView() {
- if isEmphasized && isSelected {
- if let attributedTitle = cellData?.attributedTitle {
- titleView.attributedStringValue = attributedTitle.rs_attributedStringByMakingTextWhite()
- }
- }
- else {
- if let attributedTitle = cellData?.attributedTitle {
- titleView.attributedStringValue = attributedTitle
- }
- }
-
+ titleView.stringValue = cellData?.title ?? ""
needsLayout = true
}
diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
index d445e0150..a870518ef 100644
--- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
@@ -133,8 +133,6 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
private func fontSizeDidChange() {
- TimelineCellData.emptyCache()
-
cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize)
cellAppearanceWithAvatar = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: true, fontSize: fontSize)
updateRowHeights()
@@ -591,7 +589,7 @@ extension TimelineViewController: NSTableViewDelegate {
private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) {
cell.objectValue = nil
- cell.cellData = emptyCellData
+ cell.cellData = TimelineCellData()
}
}
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 888b6c830..9e50e9602 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -121,6 +121,8 @@
7
starDimension
13
+ titleMaximumLines
+ 2
Detail