From f11604df4843f04784efb2119fc2fec761da89fe Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 15:56:15 -0800
Subject: [PATCH 01/52] Switch to using NSTextField for timeline date and feed
name views.
---
Evergreen.xcodeproj/project.pbxproj | 4 +
.../Sidebar/Cell/SidebarCellLayout.swift | 6 +-
.../Cell/SingleLineTextFieldSizer.swift | 60 +++++++++++++++
.../Timeline/Cell/TimelineCellLayout.swift | 8 +-
.../Timeline/Cell/TimelineTableCellView.swift | 77 +++++++++++++------
.../Timeline/TimelineTableRowView.swift | 18 ++---
6 files changed, 135 insertions(+), 38 deletions(-)
create mode 100644 Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj
index 8e85b9f37..633bb9b75 100644
--- a/Evergreen.xcodeproj/project.pbxproj
+++ b/Evergreen.xcodeproj/project.pbxproj
@@ -140,6 +140,7 @@
84D5BA20201E8FB6009092BD /* SidebarGearMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */; };
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
84DAEE321F870B390058304B /* DockBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE311F870B390058304B /* DockBadge.swift */; };
+ 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; };
@@ -661,6 +662,7 @@
84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarGearMenuDelegate.swift; sourceTree = ""; };
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = ""; };
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = ""; };
+ 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = ""; };
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDefaults.swift; path = Evergreen/AppDefaults.swift; sourceTree = ""; };
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = ""; };
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = ""; };
@@ -1008,6 +1010,7 @@
849A97741ED9EC04007D329B /* TimelineTableCellView.swift */,
849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */,
849A97721ED9EC04007D329B /* TimelineCellLayout.swift */,
+ 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */,
849A97711ED9EC04007D329B /* TimelineCellData.swift */,
849A97731ED9EC04007D329B /* TimelineStringUtilities.swift */,
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
@@ -2001,6 +2004,7 @@
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */,
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */,
+ 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */,
8414AD251FCF5A1E00955102 /* TimelineHeaderView.swift in Sources */,
849EE71F20391DF20082A1EA /* MainWindowToolbarDelegate.swift in Sources */,
849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */,
diff --git a/Evergreen/MainWindow/Sidebar/Cell/SidebarCellLayout.swift b/Evergreen/MainWindow/Sidebar/Cell/SidebarCellLayout.swift
index a05f59c8e..77141bd18 100644
--- a/Evergreen/MainWindow/Sidebar/Cell/SidebarCellLayout.swift
+++ b/Evergreen/MainWindow/Sidebar/Cell/SidebarCellLayout.swift
@@ -28,8 +28,10 @@ struct SidebarCellLayout {
}
self.faviconRect = rFavicon
- textField.sizeToFit()
- let textFieldSize = textField.frame.size
+// textField.sizeToFit()
+// let textFieldSize = textField.fittingSize//frame.size
+ let textFieldSize = SingleLineTextFieldSizer.size(for: textField.stringValue, font: textField.font!)
+
var rTextField = NSRect(x: 0.0, y: 0.0, width: textFieldSize.width, height: textFieldSize.height)
if shouldShowImage {
rTextField.origin.x = NSMaxX(rFavicon) + appearance.imageMarginRight
diff --git a/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift b/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
new file mode 100644
index 000000000..99b9434c4
--- /dev/null
+++ b/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
@@ -0,0 +1,60 @@
+//
+// SingleLineTextFieldSizer.swift
+// Evergreen
+//
+// Created by Brent Simmons on 2/19/18.
+// Copyright © 2018 Ranchero Software. All rights reserved.
+//
+
+import AppKit
+
+// Get the size of an NSTextField configured with a specific font with a specific size.
+// Uses a cache.
+
+final class SingleLineTextFieldSizer {
+
+ let font: NSFont
+ private let textField: NSTextField
+ private var cache = [String: NSSize]()
+
+ init(font: NSFont) {
+
+ self.textField = NSTextField(labelWithString: "")
+ self.textField.font = font
+ self.font = font
+ }
+
+ func size(for text: String) -> NSSize {
+
+ if let cachedSize = cache[text] {
+ return cachedSize
+ }
+
+ textField.stringValue = text
+ var calculatedSize = textField.fittingSize
+ calculatedSize.height = ceil(calculatedSize.height)
+ calculatedSize.width = ceil(calculatedSize.width)
+
+ cache[text] = calculatedSize
+ return calculatedSize
+ }
+
+ static private var sizers = [NSFont: SingleLineTextFieldSizer]()
+
+ static func sizer(for font: NSFont) -> SingleLineTextFieldSizer {
+
+ if let cachedSizer = sizers[font] {
+ return cachedSizer
+ }
+
+ let newSizer = SingleLineTextFieldSizer(font: font)
+ sizers[font] = newSizer
+
+ return newSizer
+ }
+
+ static func size(for text: String, font: NSFont) -> NSSize {
+
+ return sizer(for: font).size(for: text)
+ }
+}
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index a53a5bfe2..ab6dcb509 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -107,13 +107,15 @@ private extension TimelineCellLayout {
static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ attributedString: NSAttributedString) -> NSRect {
- let renderer = RSSingleLineRenderer(attributedTitle: attributedString)
+ let font = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as! NSFont
+ let textFieldSize = SingleLineTextFieldSizer.size(for: attributedString.string, font: font)
+// let renderer = RSSingleLineRenderer(attributedTitle: attributedString)
var r = NSZeroRect
- r.size = renderer.size
+ r.size = textFieldSize
r.origin.y = NSMaxY(rectAbove) + topMargin
r.origin.x = textBoxRect.origin.x
- var width = renderer.size.width
+ var width = textFieldSize.width
width = min(width, textBoxRect.size.width)
width = max(width, 0.0)
r.size.width = width
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index a7971a06d..056fe1a7f 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -13,29 +13,18 @@ class TimelineTableCellView: NSTableCellView {
private let titleView = RSMultiLineView(frame: NSZeroRect)
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
- private let dateView = RSSingleLineView(frame: NSZeroRect)
- private let feedNameView = RSSingleLineView(frame: NSZeroRect)
+ private let dateView = TimelineTableCellView.singleLineTextField()
+ private let feedNameView = TimelineTableCellView.singleLineTextField()
+ private let avatarImageView = TimelineTableCellView.imageView(with: AppImages.genericFeedImage, scaling: .scaleProportionallyDown)
+ private let starView = TimelineTableCellView.imageView(with: AppImages.timelineStar, scaling: .scaleNone)
- private let avatarImageView: NSImageView = {
- let imageView = NSImageView(frame: NSRect.zero)
- imageView.imageScaling = .scaleProportionallyDown
- imageView.animates = false
- imageView.imageAlignment = .alignCenter
- imageView.image = AppImages.genericFeedImage
- return imageView
- }()
-
- private let starView: NSImageView = {
- let imageView = NSImageView(frame: NSRect.zero)
- imageView.imageScaling = .scaleNone
- imageView.animates = false
- imageView.imageAlignment = .alignCenter
- imageView.image = AppImages.timelineStar
- return imageView
+ private lazy var textFields = {
+ return [self.dateView, self.feedNameView]
}()
var cellAppearance: TimelineCellAppearance! {
didSet {
+ updateTextFields()
needsLayout = true
}
}
@@ -60,20 +49,18 @@ class TimelineTableCellView: NSTableCellView {
var isEmphasized = false {
didSet {
- dateView.emphasized = isEmphasized
- feedNameView.emphasized = isEmphasized
titleView.emphasized = isEmphasized
unreadIndicatorView.isEmphasized = isEmphasized
+ updateTextFieldColors()
needsDisplay = true
}
}
var isSelected = false {
didSet {
- dateView.selected = isSelected
- feedNameView.selected = isSelected
titleView.selected = isSelected
unreadIndicatorView.isSelected = isSelected
+ updateTextFieldColors()
needsDisplay = true
}
}
@@ -142,6 +129,48 @@ class TimelineTableCellView: NSTableCellView {
private extension TimelineTableCellView {
+ static func singleLineTextField() -> NSTextField {
+
+ let textField = NSTextField(labelWithString: "")
+ textField.usesSingleLineMode = true
+ textField.maximumNumberOfLines = 1
+ textField.isEditable = false
+ textField.lineBreakMode = .byTruncatingTail
+ return textField
+ }
+
+ static func imageView(with image: NSImage?, scaling: NSImageScaling) -> NSImageView {
+
+ let imageView = image != nil ? NSImageView(image: image!) : NSImageView(frame: NSRect.zero)
+ imageView.animates = false
+ imageView.imageAlignment = .alignCenter
+ imageView.imageScaling = scaling
+ return imageView
+ }
+
+ func updateTextFieldColors() {
+
+ if isEmphasized && isSelected {
+ textFields.forEach { $0.textColor = NSColor.white }
+ }
+ else {
+ feedNameView.textColor = cellAppearance.feedNameColor
+ dateView.textColor = cellAppearance.dateColor
+ }
+ }
+
+ func updateTextFieldFonts() {
+
+ feedNameView.font = cellAppearance.feedNameFont
+ dateView.font = cellAppearance.dateFont
+ }
+
+ func updateTextFields() {
+
+ updateTextFieldColors()
+ updateTextFieldFonts()
+ }
+
func addSubviewAtInit(_ view: NSView, hidden: Bool) {
addSubview(view)
@@ -184,7 +213,7 @@ private extension TimelineTableCellView {
func updateDateView() {
- dateView.attributedStringValue = cellData.attributedDateString
+ dateView.stringValue = cellData.attributedDateString.string
needsLayout = true
}
@@ -194,7 +223,7 @@ private extension TimelineTableCellView {
if feedNameView.isHidden {
feedNameView.isHidden = false
}
- feedNameView.attributedStringValue = cellData.attributedFeedName
+ feedNameView.stringValue = cellData.attributedFeedName.string
}
else {
if !feedNameView.isHidden {
diff --git a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
index 03ed5b125..996b79958 100644
--- a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
@@ -64,17 +64,17 @@ class TimelineTableRowView : NSTableRowView {
path.stroke()
}
- override func draw(_ dirtyRect: NSRect) {
-
- super.draw(dirtyRect)
-
- if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
- drawSeparator(in: dirtyRect)
- }
- }
+// override func draw(_ dirtyRect: NSRect) {
+//
+// super.draw(dirtyRect)
+//
+// if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
+// drawSeparator(in: dirtyRect)
+// }
+// }
func invalidateGridRect() {
- setNeedsDisplay(gridRect)
+// setNeedsDisplay(gridRect)
}
}
From 6d46b44e22ce4102a5ebbdc16e970bc5a4b8f2ab Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 16:00:26 -0800
Subject: [PATCH 02/52] Remove no-longer-used RSSingleLineView and
RSSingleLineRenderer.
---
Evergreen/AppDelegate.swift | 1 -
.../Timeline/Cell/TimelineCellLayout.swift | 1 -
.../Timeline/TimelineViewController.swift | 1 -
.../RSTextDrawing.xcodeproj/project.pbxproj | 16 --
.../RSTextDrawing/RSSingleLineRenderer.h | 24 ---
.../RSTextDrawing/RSSingleLineRenderer.m | 204 ------------------
.../RSTextDrawing/RSSingleLineView.h | 22 --
.../RSTextDrawing/RSSingleLineView.m | 158 --------------
.../RSTextDrawing/RSTextDrawing.h | 2 -
9 files changed, 429 deletions(-)
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m
diff --git a/Evergreen/AppDelegate.swift b/Evergreen/AppDelegate.swift
index 13e70cf76..57eddc4c3 100644
--- a/Evergreen/AppDelegate.swift
+++ b/Evergreen/AppDelegate.swift
@@ -165,7 +165,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
func applicationDidResignActive(_ notification: Notification) {
- RSSingleLineRenderer.emptyCache()
RSMultiLineRenderer.emptyCache()
TimelineCellData.emptyCache()
timelineEmptyCaches()
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index ab6dcb509..0a1a2946c 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -109,7 +109,6 @@ private extension TimelineCellLayout {
let font = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as! NSFont
let textFieldSize = SingleLineTextFieldSizer.size(for: attributedString.string, font: font)
-// let renderer = RSSingleLineRenderer(attributedTitle: attributedString)
var r = NSZeroRect
r.size = textFieldSize
r.origin.y = NSMaxY(rectAbove) + topMargin
diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
index 2d1ea4635..aa29932d5 100644
--- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
@@ -135,7 +135,6 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
private func fontSizeDidChange() {
TimelineCellData.emptyCache()
- RSSingleLineRenderer.emptyCache()
RSMultiLineRenderer.emptyCache()
cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize)
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
index 8f4524a59..9bd1d0a2f 100644
--- a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
+++ b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
@@ -12,10 +12,6 @@
8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */ = {isa = PBXBuildFile; fileRef = 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */; settings = {ATTRIBUTES = (Public, ); }; };
8439DA061C8937C800E5E4B4 /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */; };
8439DA0B1C8937C800E5E4B4 /* RSTextDrawingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */; };
- 846416401C8938210064C661 /* RSSingleLineRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 846416411C8938210064C661 /* RSSingleLineRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */; };
- 84B717761CF9629000FF029D /* RSSingleLineView.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B717741CF9629000FF029D /* RSSingleLineView.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 84B717771CF9629000FF029D /* RSSingleLineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B717751CF9629000FF029D /* RSSingleLineView.m */; };
84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B717791CF9665100FF029D /* RSMultiLineView.h */; settings = {ATTRIBUTES = (Public, ); }; };
84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B7177A1CF9665100FF029D /* RSMultiLineView.m */; };
84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 846416431C8938470064C661 /* RSMultiLineRenderer.m */; };
@@ -43,12 +39,8 @@
8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSTextDrawingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSTextDrawingTests.m; sourceTree = ""; };
8439DA0C1C8937C800E5E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSSingleLineRenderer.h; path = RSTextDrawing/RSSingleLineRenderer.h; sourceTree = ""; };
- 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSingleLineRenderer.m; path = RSTextDrawing/RSSingleLineRenderer.m; sourceTree = ""; };
846416421C8938470064C661 /* RSMultiLineRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineRenderer.h; path = RSTextDrawing/RSMultiLineRenderer.h; sourceTree = ""; };
846416431C8938470064C661 /* RSMultiLineRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineRenderer.m; path = RSTextDrawing/RSMultiLineRenderer.m; sourceTree = ""; };
- 84B717741CF9629000FF029D /* RSSingleLineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSSingleLineView.h; path = RSTextDrawing/RSSingleLineView.h; sourceTree = ""; };
- 84B717751CF9629000FF029D /* RSSingleLineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSingleLineView.m; path = RSTextDrawing/RSSingleLineView.m; sourceTree = ""; };
84B717791CF9665100FF029D /* RSMultiLineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineView.h; path = RSTextDrawing/RSMultiLineView.h; sourceTree = ""; };
84B7177A1CF9665100FF029D /* RSMultiLineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineView.m; path = RSTextDrawing/RSMultiLineView.m; sourceTree = ""; };
84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSTextRendererProtocol.h; path = RSTextDrawing/RSTextRendererProtocol.h; sourceTree = ""; };
@@ -84,13 +76,9 @@
isa = PBXGroup;
children = (
8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */,
- 84B717741CF9629000FF029D /* RSSingleLineView.h */,
- 84B717751CF9629000FF029D /* RSSingleLineView.m */,
84B717791CF9665100FF029D /* RSMultiLineView.h */,
84B7177A1CF9665100FF029D /* RSMultiLineView.m */,
84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */,
- 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */,
- 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */,
846416421C8938470064C661 /* RSMultiLineRenderer.h */,
846416431C8938470064C661 /* RSMultiLineRenderer.m */,
84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */,
@@ -141,12 +129,10 @@
buildActionMask = 2147483647;
files = (
8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */,
- 84B717761CF9629000FF029D /* RSSingleLineView.h in Headers */,
84B7177E1CF9834A00FF029D /* RSMultiLineRenderer.h in Headers */,
84BA010F1C8D20C60029943B /* RSTextRendererProtocol.h in Headers */,
84193AB31CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h in Headers */,
84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */,
- 846416401C8938210064C661 /* RSSingleLineRenderer.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -249,9 +235,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 84B717771CF9629000FF029D /* RSSingleLineView.m in Sources */,
84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */,
- 846416411C8938210064C661 /* RSSingleLineRenderer.m in Sources */,
84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */,
84193AB41CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m in Sources */,
);
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h
deleted file mode 100644
index 323d64d9a..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// SingleLineRenderer.h
-// RSTextDrawing
-//
-// Created by Brent Simmons on 3/3/16.
-// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
-//
-
-@import AppKit;
-#import
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface RSSingleLineRenderer : NSObject
-
-+ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title;
-
-@property (nonatomic, readonly) NSSize size;
-
-@property (nonatomic, strong) NSColor *backgroundColor; // Default is white.
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m
deleted file mode 100644
index b616982f0..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m
+++ /dev/null
@@ -1,204 +0,0 @@
-//
-// RSSingleLineRenderer.m
-// RSTextDrawing
-//
-// Created by Brent Simmons on 3/3/16.
-// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
-//
-
-#import "RSSingleLineRenderer.h"
-
-static NSMutableDictionary *rendererCache = nil;
-
-@interface RSSingleLineRenderer ()
-
-@property (nonatomic, readonly) NSAttributedString *title;
-@property (nonatomic) NSRect rect;
-@property (nonatomic, readonly) CTFramesetterRef framesetter;
-@property (nonatomic) CTFrameRef frameref;
-
-@end
-
-
-@implementation RSSingleLineRenderer
-
-@synthesize size = _size;
-
-#pragma mark - Class Methods
-
-+ (void)initialize {
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
-
- rendererCache = [NSMutableDictionary new];
- });
-}
-
-
-+ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title {
-
- RSSingleLineRenderer *cachedRenderer = rendererCache[title];
- if (cachedRenderer != nil) {
- return cachedRenderer;
- }
-
- RSSingleLineRenderer *renderer = [[RSSingleLineRenderer alloc] initWithAttributedTitle:title];
- rendererCache[title] = renderer;
- return renderer;
-}
-
-
-+ (void)emptyCache {
-
- rendererCache = [NSMutableDictionary new];
-}
-
-
-#pragma mark - Init
-
-- (instancetype)initWithAttributedTitle:(NSAttributedString *)title {
-
- self = [super init];
- if (self == nil) {
- return nil;
- }
-
- _title = title;
- _framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)title);
- _backgroundColor = NSColor.whiteColor;
-
- return self;
-}
-
-
-#pragma mark - Dealloc
-
-- (void)dealloc {
-
- if (_framesetter) {
- CFRelease(_framesetter);
- _framesetter = nil;
- }
-
- if (_frameref) {
- CFRelease(_frameref);
- _frameref = nil;
- }
-}
-
-
-#pragma mark - Accessors
-
-- (void)setRect:(NSRect)r {
-
- r.origin.y = floor(r.origin.y);
- r.origin.x = floor(r.origin.x);
- r.size.height = floor(r.size.height);
- if (r.size.height > self.size.height) {
- r.size.height = self.size.height;
- }
- r.size.width = floor(r.size.width);
- if (r.size.width > self.size.width) {
- r.size.width = self.size.width;
- }
-
- if (!NSEqualRects(r, _rect)) {
- _rect = r;
- [self releaseFrameref];
- }
-}
-
-
-- (void)releaseFrameref {
-
- if (_frameref) {
- CFRelease(_frameref);
- _frameref = nil;
- }
-}
-
-
-- (NSSize)size {
-
- if (self.title.string.length < 1) {
- return NSZeroSize;
- }
-
- if (NSEqualSizes(_size, NSZeroSize)) {
- _size = [self calculatedSize];
- }
- return _size;
-}
-
-#pragma mark - Measurements
-
-static const CGFloat kMaxWidth = 10000.0;
-static const CGFloat kMaxHeight = 10000.0;
-
-- (NSSize)calculatedSize {
-
- NSSize size = NSZeroSize;
-
- @autoreleasepool {
-
- CGRect r = CGRectMake(0.0f, 0.0f, kMaxWidth, kMaxHeight);
- CGPathRef path = CGPathCreateWithRect(r, NULL);
-
- CTFrameRef frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL);
-
- NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameref);
-
- if (lines.count > 0) {
-
- CTLineRef firstLine = (__bridge CTLineRef)lines[0];
- CGRect firstLineRect = CTLineGetBoundsWithOptions(firstLine, 0);
- CGFloat height = ceil(NSHeight(firstLineRect));
- CGFloat width = ceil(NSWidth(firstLineRect));
- size = NSMakeSize(width, height);
- }
-
- CFRelease(path);
- CFRelease(frameref);
- }
-
- return size;
-}
-
-
-#pragma mark - Drawing
-
-- (void)renderTextInRect:(CGRect)r {
-
- self.rect = r;
-
- CGContextRef context = [NSGraphicsContext currentContext].CGContext;
- CGContextSaveGState(context);
-
- CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
- CGContextFillRect(context, r);
-
- CGContextSetShouldSmoothFonts(context, true);
-
- CTFrameDraw(self.frameref, context);
-
- CGContextRestoreGState(context);
-}
-
-
-- (CTFrameRef)frameref {
-
- if (_frameref) {
- return _frameref;
- }
-
- CGPathRef path = CGPathCreateWithRect(self.rect, NULL);
-
- _frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL);
-
- CFRelease(path);
-
- return _frameref;
-}
-
-@end
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h
deleted file mode 100644
index 18b468fc3..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// RSSingleLineView.h
-// RSTextDrawing
-//
-// Created by Brent Simmons on 5/27/16.
-// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
-//
-
-@import AppKit;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface RSSingleLineView : NSView
-
-@property (nonatomic, strong) NSAttributedString *attributedStringValue;
-
-@property (nonatomic) BOOL selected;
-@property (nonatomic) BOOL emphasized;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m
deleted file mode 100644
index 7830563fe..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m
+++ /dev/null
@@ -1,158 +0,0 @@
-//
-// RSSingleLineView.m
-// RSTextDrawing
-//
-// Created by Brent Simmons on 5/27/16.
-// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
-//
-
-@import RSCore;
-#import "RSSingleLineView.h"
-#import "RSSingleLineRenderer.h"
-
-@interface RSSingleLineView ()
-
-@property (nonatomic) RSSingleLineRenderer *renderer;
-@property (nonatomic) NSSize intrinsicSize;
-@property (nonatomic) BOOL intrinsicSizeIsValid;
-@property (nonatomic) RSSingleLineRenderer *selectedRenderer;
-@property (nonatomic) NSAttributedString *selectedAttributedStringValue;
-
-@end
-
-static NSAttributedString *emptyAttributedString = nil;
-
-@implementation RSSingleLineView
-
-- (instancetype)initWithFrame:(NSRect)r {
-
- self = [super initWithFrame:r];
- if (!self) {
- return nil;
- }
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- emptyAttributedString = [[NSAttributedString alloc] initWithString:@""];
- });
-
- _renderer = [RSSingleLineRenderer rendererWithAttributedTitle:emptyAttributedString];
-
- return self;
-}
-
-
-- (void)setAttributedStringValue:(NSAttributedString *)attributedStringValue {
-
- _attributedStringValue = attributedStringValue;
- self.selectedAttributedStringValue = nil;
- self.selectedRenderer = nil;
-
- self.renderer = [RSSingleLineRenderer rendererWithAttributedTitle:attributedStringValue];
-}
-
-
-- (void)setRenderer:(RSSingleLineRenderer *)renderer {
-
- if (_renderer == renderer) {
- return;
- }
- _renderer = renderer;
- [self invalidateIntrinsicContentSize];
- self.needsDisplay = YES;
-}
-
-
-- (RSSingleLineRenderer *)selectedRenderer {
-
- if (_selectedRenderer) {
- return _selectedRenderer;
- }
-
- _selectedRenderer = [RSSingleLineRenderer rendererWithAttributedTitle:self.selectedAttributedStringValue];
- _selectedRenderer.backgroundColor = NSColor.alternateSelectedControlColor;
- return _selectedRenderer;
-}
-
-
-- (void)setSelected:(BOOL)selected {
-
- _selected = selected;
- self.needsDisplay = YES;
-}
-
-
-- (void)setEmphasized:(BOOL)emphasized {
-
- _emphasized = emphasized;
- self.needsDisplay = YES;
-}
-
-
-- (NSAttributedString *)selectedAttributedStringValue {
-
- if (!self.attributedStringValue) {
- return emptyAttributedString;
- }
-
- NSMutableAttributedString *s = [self.attributedStringValue mutableCopy];
- [s addAttribute:NSForegroundColorAttributeName value:NSColor.alternateSelectedControlTextColor range:NSMakeRange(0, s.string.length)];
- _selectedAttributedStringValue = s;
-
- return _selectedAttributedStringValue;
-}
-
-
-- (void)invalidateIntrinsicContentSize {
-
- self.intrinsicSizeIsValid = NO;
-}
-
-
-- (NSSize)intrinsicContentSize {
-
- if (!self.intrinsicSizeIsValid) {
- if (!self.attributedStringValue) {
- self.intrinsicSize = NSZeroSize;
- }
- else {
- self.intrinsicSize = ((RSSingleLineRenderer *)(self.renderer)).size;
- }
- self.intrinsicSizeIsValid = YES;
- }
-
- return self.intrinsicSize;
-}
-
-- (NSMenu *)menuForEvent:(NSEvent *)event {
-
- NSTableView *tableView = [self rs_enclosingTableView];
- if (tableView) {
- return [tableView menuForEvent:event];
- }
- return nil;
-}
-
-- (void)drawRect:(NSRect)r {
-
- if (self.selected) {
-
- if (self.emphasized) {
- [self.selectedRenderer renderTextInRect:self.bounds];
- }
- else {
- NSColor *savedBackgroundColor = self.renderer.backgroundColor;
- self.renderer.backgroundColor = NSColor.secondarySelectedControlColor;
- [self.renderer renderTextInRect:self.bounds];
- self.renderer.backgroundColor = savedBackgroundColor;
- }
- }
-
- else {
- [self.renderer renderTextInRect:self.bounds];
- }
-}
-
-
-@end
-
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h
index 3b32fae67..812d4cc6e 100644
--- a/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h
+++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h
@@ -8,10 +8,8 @@
@import AppKit;
-#import
#import
-#import
#import
#import
#import
From 127dd24016416b31a3e522ebbf8f43d5455e4585 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 20:28:00 -0800
Subject: [PATCH 03/52] Create and use MultiLineTextFieldSizer for sizing the
title/text field in the timeline.
---
Evergreen.xcodeproj/project.pbxproj | 4 +
.../Cell/MultilineTextFieldSizer.swift | 128 ++++++++++++++++++
.../Cell/SingleLineTextFieldSizer.swift | 8 ++
.../Cell/TimelineCellAppearance.swift | 1 +
.../Timeline/Cell/TimelineCellLayout.swift | 35 ++---
5 files changed, 160 insertions(+), 16 deletions(-)
create mode 100644 Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj
index 633bb9b75..084d7ac9d 100644
--- a/Evergreen.xcodeproj/project.pbxproj
+++ b/Evergreen.xcodeproj/project.pbxproj
@@ -141,6 +141,7 @@
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
84DAEE321F870B390058304B /* DockBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE311F870B390058304B /* DockBadge.swift */; };
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
+ 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; };
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; };
@@ -663,6 +664,7 @@
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = ""; };
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = ""; };
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = ""; };
+ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = ""; };
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDefaults.swift; path = Evergreen/AppDefaults.swift; sourceTree = ""; };
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = ""; };
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = ""; };
@@ -1011,6 +1013,7 @@
849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */,
849A97721ED9EC04007D329B /* TimelineCellLayout.swift */,
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */,
+ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */,
849A97711ED9EC04007D329B /* TimelineCellData.swift */,
849A97731ED9EC04007D329B /* TimelineStringUtilities.swift */,
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
@@ -1952,6 +1955,7 @@
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
849A97791ED9EC04007D329B /* TimelineStringUtilities.swift in Sources */,
+ 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */,
843A3B5620311E7700BF76EC /* FeedListOutlineView.swift in Sources */,
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
84AD1EAA2031617300BC20B7 /* FolderPasteboardWriter.swift in Sources */,
diff --git a/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
new file mode 100644
index 000000000..a4ab4689d
--- /dev/null
+++ b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
@@ -0,0 +1,128 @@
+//
+// MultilineTextFieldSizer.swift
+// Evergreen
+//
+// Created by Brent Simmons on 2/19/18.
+// Copyright © 2018 Ranchero Software. All rights reserved.
+//
+
+import AppKit
+
+// Get the height of an NSTextField given an NSAttributedString and a width.
+// Uses a cache. Avoids actually measuring text as much as possible.
+// Main thread only.
+
+typealias WidthHeightCache = [Int: Int] // width: height
+
+final class MultilineTextFieldSizer {
+
+ private let numberOfLines: Int
+ private let textField:NSTextField
+ private var cache = [NSAttributedString: WidthHeightCache]() // Each string has a cache.
+ private static var sizers = [Int: MultilineTextFieldSizer]()
+
+ private init(numberOfLines: Int) {
+
+ self.numberOfLines = numberOfLines
+ self.textField = MultilineTextFieldSizer.createTextField(numberOfLines)
+ }
+
+ static func size(for attributedString: NSAttributedString, numberOfLines: Int, width: Int) -> Int {
+
+ return sizer(numberOfLines: numberOfLines).height(for: attributedString, width: width)
+ }
+
+ static func emptyCache() {
+
+ sizers = [Int: MultilineTextFieldSizer]()
+ }
+}
+
+// MARK: - Private
+
+private extension MultilineTextFieldSizer {
+
+ static func sizer(numberOfLines: Int) -> MultilineTextFieldSizer {
+
+ if let cachedSizer = sizers[numberOfLines] {
+ return cachedSizer
+ }
+
+ let newSizer = MultilineTextFieldSizer(numberOfLines: numberOfLines)
+ sizers[numberOfLines] = newSizer
+ return newSizer
+ }
+
+ func height(for attributedString: NSAttributedString, width: Int) -> Int {
+
+ if cache[attributedString] == nil {
+ cache[attributedString] = WidthHeightCache()
+ }
+
+ if let height = cache[attributedString]![width] {
+ return height
+ }
+
+ if let height = heightConsideringNeighbors(cache[attributedString]!, width) {
+ return height
+ }
+
+ let height = calculateHeight(attributedString, width)
+ cache[attributedString]![width] = height
+
+ return height
+ }
+
+ static func createTextField(_ numberOfLines: Int) -> NSTextField {
+
+ let textField = NSTextField(wrappingLabelWithString: "")
+ textField.usesSingleLineMode = false
+ textField.maximumNumberOfLines = numberOfLines
+ textField.isEditable = false
+
+ return textField
+ }
+
+ func calculateHeight(_ attributedString: NSAttributedString, _ width: Int) -> Int {
+
+ textField.attributedStringValue = attributedString
+ textField.preferredMaxLayoutWidth = CGFloat(width)
+ let size = textField.fittingSize
+ return Int(ceil(size.height))
+ }
+
+// func widthHeightCache(for attributedString: NSAttributedString) -> WidthHeightCache {
+//
+// if let foundCache = cache[attributedString] {
+// return foundCache
+// }
+// let newCache = WidthHeightCache()
+// cache[attributedString] = newCache
+// return newCache
+// }
+
+ func heightConsideringNeighbors(_ heightCache: WidthHeightCache, _ width: Int) -> Int? {
+
+ // Given width, if the height at width - something and width + something is equal,
+ // then that height must be correct for the given width.
+
+ var smallNeighbor = (width: 0, height: 0)
+ var largeNeighbor = (width: 0, height: 0)
+
+ for (oneWidth, oneHeight) in heightCache {
+
+ if oneWidth < width && (oneWidth > smallNeighbor.width || smallNeighbor.width == 0) {
+ smallNeighbor = (oneWidth, oneHeight)
+ }
+ else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) {
+ largeNeighbor = (oneWidth, oneHeight)
+ }
+
+ if smallNeighbor.width != 0 && smallNeighbor.height == largeNeighbor.height {
+ return smallNeighbor.height
+ }
+ }
+
+ return nil
+ }
+}
diff --git a/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift b/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
index 99b9434c4..da8029130 100644
--- a/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift
@@ -10,6 +10,7 @@ import AppKit
// Get the size of an NSTextField configured with a specific font with a specific size.
// Uses a cache.
+// Main thready only.
final class SingleLineTextFieldSizer {
@@ -53,8 +54,15 @@ final class SingleLineTextFieldSizer {
return newSizer
}
+ // Use this call. It’s easiest.
+
static func size(for text: String, font: NSFont) -> NSSize {
return sizer(for: font).size(for: text)
}
+
+ static func emptyCache() {
+
+ sizers = [NSFont: SingleLineTextFieldSizer]()
+ }
}
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
index 01e4d8bee..ca40fb244 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
@@ -23,6 +23,7 @@ struct TimelineCellAppearance: Equatable {
let titleColor: NSColor
let titleFont: NSFont
let titleBottomMargin: CGFloat
+ let titleNumberOfLines = 2
let textColor: NSColor
let textFont: NSFont
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 0a1a2946c..1e6f410fa 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -40,14 +40,14 @@ struct TimelineCellLayout {
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, width)
- let (titleRect, titleLine1Rect) = TimelineCellLayout.rectsForTitle(textBoxRect, cellData)
+ let titleRect = TimelineCellLayout.rectForTitle(textBoxRect, cellData)
let dateRect = TimelineCellLayout.rectForDate(textBoxRect, titleRect, appearance, cellData)
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
- let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, titleLine1Rect)
- let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
textBoxRect.size.height = ceil([titleRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, textBoxRect, width)
+ let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, titleRect)
+ let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
let paddingBottom = appearance.cellPadding.bottom
@@ -77,18 +77,20 @@ private extension TimelineCellLayout {
return textBoxRect
}
- static func rectsForTitle(_ textBoxRect: NSRect, _ cellData: TimelineCellData) -> (NSRect, NSRect) {
+ static func rectForTitle(_ textBoxRect: NSRect, _ cellData: TimelineCellData) -> NSRect {
var r = textBoxRect
- let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
+ let height = MultilineTextFieldSizer.size(for: cellData.attributedTitle, numberOfLines: 2, width: Int(textBoxRect.width))
+// let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
+//
+// let measurements = renderer.measurements(forWidth: textBoxRect.width)
+ r.size.height = CGFloat(height)
- let measurements = renderer.measurements(forWidth: textBoxRect.width)
- r.size.height = CGFloat(measurements.height)
-
- var rline1 = r
- rline1.size.height = CGFloat(measurements.heightOfFirstLine)
-
- return (r, rline1)
+ return r
+// var rline1 = r
+// rline1.size.height = CGFloat(measurements.heightOfFirstLine)
+//
+// return (r, rline1)
}
static func rectForDate(_ textBoxRect: NSRect, _ titleRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
@@ -122,13 +124,14 @@ private extension TimelineCellLayout {
return r
}
- static func rectForUnreadIndicator(_ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
+ static func rectForUnreadIndicator(_ appearance: TimelineCellAppearance, _ titleRect: NSRect) -> NSRect {
var r = NSZeroRect
r.size = NSSize(width: appearance.unreadCircleDimension, height: appearance.unreadCircleDimension)
r.origin.x = appearance.cellPadding.left
- r = RSRectCenteredVerticallyInRect(r, titleLine1Rect)
- r.origin.y += 1
+ r.origin.y = titleRect.minY + 6
+// r = RSRectCenteredVerticallyInRect(r, titleRect)
+// r.origin.y += 1
return r
}
@@ -139,7 +142,7 @@ private extension TimelineCellLayout {
r.size.width = appearance.starDimension
r.size.height = appearance.starDimension
r.origin.x = floor(unreadIndicatorRect.origin.x - ((appearance.starDimension - appearance.unreadCircleDimension) / 2.0))
- r.origin.y = unreadIndicatorRect.origin.y - 3.0
+ r.origin.y = unreadIndicatorRect.origin.y - 4.0
return r
}
From 6342eaeb003bb7380b099eeeb15bafa6f15ddb15 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:23:58 -0800
Subject: [PATCH 04/52] Add mactories.net to the JSON special case list with
titles.
---
Frameworks/RSParser/Feeds/JSON/JSONFeedParser.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Frameworks/RSParser/Feeds/JSON/JSONFeedParser.swift b/Frameworks/RSParser/Feeds/JSON/JSONFeedParser.swift
index 166898af4..3f3bbf293 100644
--- a/Frameworks/RSParser/Feeds/JSON/JSONFeedParser.swift
+++ b/Frameworks/RSParser/Feeds/JSON/JSONFeedParser.swift
@@ -171,7 +171,7 @@ private extension JSONFeedParser {
// If we find more feeds like this, we’ll add them here. If these feeds get fixed, we’ll remove them.
let lowerFeedURL = feedURL.lowercased()
- let matchStrings = ["kottke.org", "pxlnv.com"]
+ let matchStrings = ["kottke.org", "pxlnv.com", "macstories.net"]
for matchString in matchStrings {
if lowerFeedURL.contains(matchString) {
return true
From 7ed83994d03ae3755d27a130043ea9a28d815575 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:24:33 -0800
Subject: [PATCH 05/52] =?UTF-8?q?Turn=20drawGrid=20back=20on.=20Make=20ava?=
=?UTF-8?q?tars=2064=20its=20instead=20of=2048.=20They=E2=80=99re=20more?=
=?UTF-8?q?=20legible=20at=20this=20size.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Evergreen/Resources/DB5.plist | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 52de2341e..888b6c830 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -67,7 +67,7 @@
gridColorAlpha
0.1
drawsGrid
-
+
header
backgroundColor
@@ -100,7 +100,7 @@
titleColor
222222
titleMarginBottom
- 2
+ 1
unreadCircleColor
#2db6ff
unreadCircleDimension
@@ -108,9 +108,9 @@
unreadCircleMarginRight
8
avatarHeight
- 48
+ 64
avatarWidth
- 48
+ 64
avatarMarginRight
20
avatarMarginLeft
From 6e875888c4a04b8d04bd8aa3aa0d080158002df6 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:25:11 -0800
Subject: [PATCH 06/52] Reenable the code that draws the grid in the timeline.
---
.../Timeline/TimelineTableRowView.swift | 18 +++++++++---------
.../Timeline/TimelineViewController.swift | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
index 996b79958..03ed5b125 100644
--- a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
@@ -64,17 +64,17 @@ class TimelineTableRowView : NSTableRowView {
path.stroke()
}
-// override func draw(_ dirtyRect: NSRect) {
-//
-// super.draw(dirtyRect)
-//
-// if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
-// drawSeparator(in: dirtyRect)
-// }
-// }
+ override func draw(_ dirtyRect: NSRect) {
+
+ super.draw(dirtyRect)
+
+ if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
+ drawSeparator(in: dirtyRect)
+ }
+ }
func invalidateGridRect() {
-// setNeedsDisplay(gridRect)
+ setNeedsDisplay(gridRect)
}
}
diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
index aa29932d5..687aad8ec 100644
--- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
@@ -515,7 +515,7 @@ extension TimelineViewController: NSTableViewDelegate {
func tableViewSelectionDidChange(_ notification: Notification) {
- // tableView.redrawGrid()
+ tableView.redrawGrid()
if selectedArticles.isEmpty {
postTimelineSelectionDidChangeNotification(nil)
From 28f001b3ed821da36587e1fd6b73fcc28f76322e Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:25:32 -0800
Subject: [PATCH 07/52] Make the summary text start right after the title
rather than on the next line.
---
Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
index 7e70e34ad..14df046e4 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
@@ -108,7 +108,7 @@ private func attributedTitleString(_ title: String, _ text: String, _ appearance
if !title.isEmpty && !text.isEmpty {
let titleMutable = NSMutableAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor: appearance.titleColor, NSAttributedStringKey.font: appearance.titleFont])
- let attributedText = NSAttributedString(string: "\n" + text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textColor, NSAttributedStringKey.font: appearance.textFont])
+ let attributedText = NSAttributedString(string: " " + text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textColor, NSAttributedStringKey.font: appearance.textFont])
titleMutable.append(attributedText)
return titleMutable
}
From 83f97cf1185e43349b4fbf99714ca81d26684c96 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:25:56 -0800
Subject: [PATCH 08/52] Add one pixel between rows so that the grid can draw.
---
Evergreen/Base.lproj/MainWindow.storyboard | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard
index 78bae30e7..a8077be03 100644
--- a/Evergreen/Base.lproj/MainWindow.storyboard
+++ b/Evergreen/Base.lproj/MainWindow.storyboard
@@ -535,7 +535,7 @@
-
+
@@ -544,6 +544,7 @@
+
@@ -565,7 +566,7 @@
-
+
From 12575901ec17594de5e376ce06a003dad9b30800 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:26:15 -0800
Subject: [PATCH 09/52] Use an NSTextField to draw the title in the timeline.
---
.../Timeline/Cell/TimelineTableCellView.swift | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 056fe1a7f..651871aee 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -11,7 +11,7 @@ import RSTextDrawing
class TimelineTableCellView: NSTableCellView {
- private let titleView = RSMultiLineView(frame: NSZeroRect)
+ private let titleView = TimelineTableCellView.multiLineTextField()
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
private let dateView = TimelineTableCellView.singleLineTextField()
private let feedNameView = TimelineTableCellView.singleLineTextField()
@@ -19,7 +19,7 @@ class TimelineTableCellView: NSTableCellView {
private let starView = TimelineTableCellView.imageView(with: AppImages.timelineStar, scaling: .scaleNone)
private lazy var textFields = {
- return [self.dateView, self.feedNameView]
+ return [self.dateView, self.feedNameView, self.titleView]
}()
var cellAppearance: TimelineCellAppearance! {
@@ -49,7 +49,7 @@ class TimelineTableCellView: NSTableCellView {
var isEmphasized = false {
didSet {
- titleView.emphasized = isEmphasized
+// titleView.emphasized = isEmphasized
unreadIndicatorView.isEmphasized = isEmphasized
updateTextFieldColors()
needsDisplay = true
@@ -58,7 +58,7 @@ class TimelineTableCellView: NSTableCellView {
var isSelected = false {
didSet {
- titleView.selected = isSelected
+// titleView.selected = isSelected
unreadIndicatorView.isSelected = isSelected
updateTextFieldColors()
needsDisplay = true
@@ -139,6 +139,17 @@ private extension TimelineTableCellView {
return textField
}
+ static func multiLineTextField() -> NSTextField {
+
+ let textField = NSTextField(wrappingLabelWithString: "")
+ textField.usesSingleLineMode = false
+ textField.maximumNumberOfLines = 2
+ textField.isEditable = false
+ textField.lineBreakMode = .byTruncatingTail
+ textField.cell?.truncatesLastVisibleLine = true
+ return textField
+ }
+
static func imageView(with image: NSImage?, scaling: NSImageScaling) -> NSImageView {
let imageView = image != nil ? NSImageView(image: image!) : NSImageView(frame: NSRect.zero)
@@ -156,6 +167,10 @@ private extension TimelineTableCellView {
else {
feedNameView.textColor = cellAppearance.feedNameColor
dateView.textColor = cellAppearance.dateColor
+
+ if let attributedTitle = cellData?.attributedTitle {
+ titleView.attributedStringValue = attributedTitle
+ }
}
}
From 389f04bcfbd3bae0b5520f5a5c2a6cc40400bec1 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 21:49:32 -0800
Subject: [PATCH 10/52] Make the selected text in the timeline white.
---
.../Timeline/Cell/TimelineTableCellView.swift | 20 +++++++++++++------
.../RSCore/RSCore.xcodeproj/project.pbxproj | 8 ++++++++
.../RSCore/AppKit/NSAttributedString+RSCore.h | 17 ++++++++++++++++
.../RSCore/AppKit/NSAttributedString+RSCore.m | 20 +++++++++++++++++++
Frameworks/RSCore/RSCore/RSCore.h | 1 +
5 files changed, 60 insertions(+), 6 deletions(-)
create mode 100644 Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.h
create mode 100644 Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.m
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 651871aee..d73cf0898 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import RSTextDrawing
+import RSCore
class TimelineTableCellView: NSTableCellView {
@@ -161,16 +161,14 @@ private extension TimelineTableCellView {
func updateTextFieldColors() {
+ updateTitleView()
+
if isEmphasized && isSelected {
textFields.forEach { $0.textColor = NSColor.white }
}
else {
feedNameView.textColor = cellAppearance.feedNameColor
dateView.textColor = cellAppearance.dateColor
-
- if let attributedTitle = cellData?.attributedTitle {
- titleView.attributedStringValue = attributedTitle
- }
}
}
@@ -222,7 +220,17 @@ private extension TimelineTableCellView {
func updateTitleView() {
- titleView.attributedStringValue = cellData.attributedTitle
+ if isEmphasized && isSelected {
+ if let attributedTitle = cellData?.attributedTitle {
+ titleView.attributedStringValue = attributedTitle.rs_attributedStringByMakingTextWhite()
+ }
+ }
+ else {
+ if let attributedTitle = cellData?.attributedTitle {
+ titleView.attributedStringValue = attributedTitle
+ }
+ }
+
needsLayout = true
}
diff --git a/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj b/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj
index e408ccf8d..47ab88ed2 100755
--- a/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj
+++ b/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj
@@ -161,6 +161,8 @@
84CFF56D1AC3D20A00CEA6C8 /* NSImage+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFF56B1AC3D20A00CEA6C8 /* NSImage+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
84CFF56E1AC3D20A00CEA6C8 /* NSImage+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFF56C1AC3D20A00CEA6C8 /* NSImage+RSCore.m */; };
84D5BA1E201E87E2009092BD /* URLPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1D201E87E2009092BD /* URLPasteboardWriter.swift */; };
+ 84E185C6203BEA7900F69BFA /* NSAttributedString+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84E185C4203BEA7900F69BFA /* NSAttributedString+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 84E185C7203BEA7900F69BFA /* NSAttributedString+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C5203BEA7900F69BFA /* NSAttributedString+RSCore.m */; };
84E34DA61F9FA1070077082F /* UndoableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E34DA51F9FA1070077082F /* UndoableCommand.swift */; };
84E8E0D9202EC39800562D8F /* NSMenu+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0D8202EC39800562D8F /* NSMenu+Extensions.swift */; };
84F20F831F16BA6200D8E682 /* PropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F821F16BA6200D8E682 /* PropertyList.swift */; };
@@ -285,6 +287,8 @@
84CFF56B1AC3D20A00CEA6C8 /* NSImage+RSCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage+RSCore.h"; sourceTree = ""; };
84CFF56C1AC3D20A00CEA6C8 /* NSImage+RSCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+RSCore.m"; sourceTree = ""; };
84D5BA1D201E87E2009092BD /* URLPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLPasteboardWriter.swift; path = AppKit/URLPasteboardWriter.swift; sourceTree = ""; };
+ 84E185C4203BEA7900F69BFA /* NSAttributedString+RSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+RSCore.h"; path = "AppKit/NSAttributedString+RSCore.h"; sourceTree = ""; };
+ 84E185C5203BEA7900F69BFA /* NSAttributedString+RSCore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+RSCore.m"; path = "AppKit/NSAttributedString+RSCore.m"; sourceTree = ""; };
84E34DA51F9FA1070077082F /* UndoableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UndoableCommand.swift; path = RSCore/UndoableCommand.swift; sourceTree = ""; };
84E8E0D8202EC39800562D8F /* NSMenu+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSMenu+Extensions.swift"; path = "AppKit/NSMenu+Extensions.swift"; sourceTree = ""; };
84F20F821F16BA6200D8E682 /* PropertyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyList.swift; sourceTree = ""; };
@@ -460,6 +464,8 @@
84CFF5511AC3CF4700CEA6C8 /* NSColor+RSCore.h */,
84C6329E200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.h */,
84C6329F200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.m */,
+ 84E185C4203BEA7900F69BFA /* NSAttributedString+RSCore.h */,
+ 84E185C5203BEA7900F69BFA /* NSAttributedString+RSCore.m */,
84C632A2200D356E007BEEAA /* SendToBlogEditorApp.h */,
84C632A3200D356E007BEEAA /* SendToBlogEditorApp.m */,
84CFF5521AC3CF4700CEA6C8 /* NSColor+RSCore.m */,
@@ -579,6 +585,7 @@
84CFF5271AC3C9A200CEA6C8 /* NSArray+RSCore.h in Headers */,
84CFF5531AC3CF4700CEA6C8 /* NSColor+RSCore.h in Headers */,
84CFF4FA1AC3C69700CEA6C8 /* RSCore.h in Headers */,
+ 84E185C6203BEA7900F69BFA /* NSAttributedString+RSCore.h in Headers */,
844F91D51D90D86100820C48 /* RSTransparentContainerView.h in Headers */,
84CFF53F1AC3CD0100CEA6C8 /* NSMutableSet+RSCore.h in Headers */,
84C632A0200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.h in Headers */,
@@ -791,6 +798,7 @@
84CFF53C1AC3CCCA00CEA6C8 /* NSMutableDictionary+RSCore.m in Sources */,
8414CBAC1C95F8F700333C12 /* RSGeometry.m in Sources */,
84134D201C59D5450063FD24 /* NSCalendar+RSCore.m in Sources */,
+ 84E185C7203BEA7900F69BFA /* NSAttributedString+RSCore.m in Sources */,
84CFF5651AC3D13C00CEA6C8 /* RSImageRenderer.m in Sources */,
849EE70D2039187D0082A1EA /* NSWindowController+RSCore.swift in Sources */,
84CFF5381AC3CBB200CEA6C8 /* NSMutableArray+RSCore.m in Sources */,
diff --git a/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.h b/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.h
new file mode 100644
index 000000000..e6202fc55
--- /dev/null
+++ b/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.h
@@ -0,0 +1,17 @@
+//
+// NSAttributedString.h
+// RSCore
+//
+// Created by Brent Simmons on 2/19/18.
+// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
+//
+
+@import AppKit;
+
+@interface NSAttributedString (RSCore)
+
+// Useful for table/outline views when a row is selected.
+
+- (NSAttributedString *)rs_attributedStringByMakingTextWhite;
+
+@end
diff --git a/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.m b/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.m
new file mode 100644
index 000000000..5d17f1b52
--- /dev/null
+++ b/Frameworks/RSCore/RSCore/AppKit/NSAttributedString+RSCore.m
@@ -0,0 +1,20 @@
+//
+// NSAttributedString.m
+// RSCore
+//
+// Created by Brent Simmons on 2/19/18.
+// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
+//
+
+#import "NSAttributedString+RSCore.h"
+
+@implementation NSAttributedString (RSCore)
+
+- (NSAttributedString *)rs_attributedStringByMakingTextWhite {
+
+ NSMutableAttributedString *mutableString = [self mutableCopy];
+ [mutableString addAttribute:NSForegroundColorAttributeName value:NSColor.whiteColor range:NSMakeRange(0, mutableString.string.length)];
+ return [mutableString copy];
+}
+
+@end
diff --git a/Frameworks/RSCore/RSCore/RSCore.h b/Frameworks/RSCore/RSCore/RSCore.h
index 6c832cf8e..658fe5ea4 100755
--- a/Frameworks/RSCore/RSCore/RSCore.h
+++ b/Frameworks/RSCore/RSCore/RSCore.h
@@ -55,6 +55,7 @@
#import
#import
+#import
#endif
From 1614416fe06b901ab29d2f9527c9587117a5b701 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 22:05:47 -0800
Subject: [PATCH 11/52] =?UTF-8?q?Don=E2=80=99t=20show=20summary=20text=20i?=
=?UTF-8?q?n=20the=20timeline=20if=20it=E2=80=99s=20just=20=E2=80=9CCommen?=
=?UTF-8?q?ts.=E2=80=9D=20(As=20in=20Hacker=20News.)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MainWindow/Timeline/Cell/TimelineStringUtilities.swift | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
index 2d71bb19f..6dd0acfdd 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineStringUtilities.swift
@@ -128,7 +128,9 @@ func timelineSummaryForArticle(_ article: Article) -> String {
var s = body.rs_string(byStrippingHTML: 300)
s = timelineNormalizedText(s)
-
+ if s == "Comments" { // Hacker News.
+ s = ""
+ }
summaryCache[body] = s
return s
}
From 08b9a56ab3298d66ef22ce46dfacddcb86cbced9 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 22:06:24 -0800
Subject: [PATCH 12/52] =?UTF-8?q?Don=E2=80=99t=20show=20text=20in=20the=20?=
=?UTF-8?q?timeline=20when=20there=E2=80=99s=20a=20title.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
index 14df046e4..228e6cff4 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
@@ -108,8 +108,8 @@ private func attributedTitleString(_ title: String, _ text: String, _ appearance
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)
+// let attributedText = NSAttributedString(string: " " + text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textColor, NSAttributedStringKey.font: appearance.textFont])
+// titleMutable.append(attributedText)
return titleMutable
}
From b39bcd9a776ca063979095be76ba53a6925d6d4b Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 22:06:36 -0800
Subject: [PATCH 13/52] Remove RSTextDrawing and all references to it.
---
Evergreen.xcodeproj/project.pbxproj | 60 ----
Evergreen/AppDelegate.swift | 2 -
.../Timeline/Cell/TimelineCellLayout.swift | 8 -
.../Timeline/TimelineTableRowView.swift | 2 +-
.../Timeline/TimelineViewController.swift | 2 -
.../RSTextDrawing.xcodeproj/project.pbxproj | 340 ------------------
.../contents.xcworkspacedata | 7 -
7 files changed, 1 insertion(+), 420 deletions(-)
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
delete mode 100644 Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj
index 084d7ac9d..c719c6624 100644
--- a/Evergreen.xcodeproj/project.pbxproj
+++ b/Evergreen.xcodeproj/project.pbxproj
@@ -123,8 +123,6 @@
84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FFD1ED3818D00F0B54B /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; };
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; };
- 84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; };
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */; };
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */; };
@@ -431,27 +429,6 @@
remoteGlobalIDString = 842A0BE01CFCB9BC00BF746C;
remoteInfo = RSTree;
};
- 84B070061ED3821900F0B54B /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 8439D9FB1C8937C800E5E4B4;
- remoteInfo = RSTextDrawing;
- };
- 84B070081ED3821900F0B54B /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 8439DA051C8937C800E5E4B4;
- remoteInfo = RSTextDrawingTests;
- };
- 84B0700C1ED3822600F0B54B /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = 8439D9FA1C8937C800E5E4B4;
- remoteInfo = RSTextDrawing;
- };
84BB4B671F1174D400858766 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84BB4B611F1174D400858766 /* Data.xcodeproj */;
@@ -487,7 +464,6 @@
849B897D1F0349D000578A8D /* RSParser.framework in Embed Frameworks */,
846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */,
84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */,
- 84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */,
84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */,
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */,
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */,
@@ -646,7 +622,6 @@
84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DB5.xcodeproj; path = Frameworks/DB5/DB5.xcodeproj; sourceTree = ""; };
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = ""; };
84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = ""; };
- 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = ""; };
84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SidebarViewController+ContextualMenus.swift"; sourceTree = ""; };
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTreeControllerDelegate.swift; sourceTree = ""; };
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFolder.swift; sourceTree = ""; };
@@ -744,7 +719,6 @@
84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */,
849B897C1F0349D000578A8D /* RSParser.framework in Frameworks */,
846E77411F6EF6A100A165E2 /* Database.framework in Frameworks */,
- 84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */,
84B06FE91ED3803A00F0B54B /* RSFeedFinder.framework in Frameworks */,
84B06FFD1ED3818D00F0B54B /* RSTree.framework in Frameworks */,
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */,
@@ -1148,7 +1122,6 @@
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */,
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */,
849B89681F0349C100578A8D /* RSParser.xcodeproj */,
- 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */,
84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */,
84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */,
);
@@ -1252,15 +1225,6 @@
name = Products;
sourceTree = "";
};
- 84B070021ED3821800F0B54B /* Products */ = {
- isa = PBXGroup;
- children = (
- 84B070071ED3821900F0B54B /* RSTextDrawing.framework */,
- 84B070091ED3821900F0B54B /* RSTextDrawingTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
84BB4B621F1174D400858766 /* Products */ = {
isa = PBXGroup;
children = (
@@ -1482,7 +1446,6 @@
84B06FD21ED37F7D00F0B54B /* PBXTargetDependency */,
84B06FEC1ED3803A00F0B54B /* PBXTargetDependency */,
84B070001ED3818D00F0B54B /* PBXTargetDependency */,
- 84B0700D1ED3822600F0B54B /* PBXTargetDependency */,
849B897F1F0349D000578A8D /* PBXTargetDependency */,
84BB4B7A1F11753300858766 /* PBXTargetDependency */,
846E77401F6EF67A00A165E2 /* PBXTargetDependency */,
@@ -1596,10 +1559,6 @@
ProductGroup = 849B89691F0349C100578A8D /* Products */;
ProjectRef = 849B89681F0349C100578A8D /* RSParser.xcodeproj */;
},
- {
- ProductGroup = 84B070021ED3821800F0B54B /* Products */;
- ProjectRef = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */;
- },
{
ProductGroup = 84B06FF51ED3818000F0B54B /* Products */;
ProjectRef = 84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */;
@@ -1768,20 +1727,6 @@
remoteRef = 84B06FFB1ED3818000F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
- 84B070071ED3821900F0B54B /* RSTextDrawing.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = RSTextDrawing.framework;
- remoteRef = 84B070061ED3821900F0B54B /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 84B070091ED3821900F0B54B /* RSTextDrawingTests.xctest */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.cfbundle;
- path = RSTextDrawingTests.xctest;
- remoteRef = 84B070081ED3821900F0B54B /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
84BB4B681F1174D400858766 /* Data.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
@@ -2104,11 +2049,6 @@
name = RSTree;
targetProxy = 84B06FFF1ED3818D00F0B54B /* PBXContainerItemProxy */;
};
- 84B0700D1ED3822600F0B54B /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = RSTextDrawing;
- targetProxy = 84B0700C1ED3822600F0B54B /* PBXContainerItemProxy */;
- };
84BB4B7A1F11753300858766 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = Data;
diff --git a/Evergreen/AppDelegate.swift b/Evergreen/AppDelegate.swift
index 57eddc4c3..25e298b3c 100644
--- a/Evergreen/AppDelegate.swift
+++ b/Evergreen/AppDelegate.swift
@@ -9,7 +9,6 @@
import AppKit
import DB5
import Data
-import RSTextDrawing
import RSTree
import RSWeb
import Account
@@ -165,7 +164,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
func applicationDidResignActive(_ notification: Notification) {
- RSMultiLineRenderer.emptyCache()
TimelineCellData.emptyCache()
timelineEmptyCaches()
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 1e6f410fa..a1a4f78b7 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -7,7 +7,6 @@
//
import AppKit
-import RSTextDrawing
import RSCore
struct TimelineCellLayout {
@@ -81,16 +80,9 @@ private extension TimelineCellLayout {
var r = textBoxRect
let height = MultilineTextFieldSizer.size(for: cellData.attributedTitle, numberOfLines: 2, width: Int(textBoxRect.width))
-// let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
-//
-// let measurements = renderer.measurements(forWidth: textBoxRect.width)
r.size.height = CGFloat(height)
return r
-// var rline1 = r
-// rline1.size.height = CGFloat(measurements.heightOfFirstLine)
-//
-// return (r, rline1)
}
static func rectForDate(_ textBoxRect: NSRect, _ titleRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
diff --git a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
index 03ed5b125..06f76089b 100644
--- a/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineTableRowView.swift
@@ -54,7 +54,7 @@ class TimelineTableRowView : NSTableRowView {
override func drawSeparator(in dirtyRect: NSRect) {
let path = NSBezierPath()
- let originX = floor(cellAppearance.boxLeftMargin)
+ let originX = floor(cellAppearance.boxLeftMargin) + 2.0
let destinationX = ceil(NSMaxX(bounds))
let y = floor(NSMaxY(bounds)) - 0.5
path.move(to: NSPoint(x: originX, y: y))
diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
index 687aad8ec..d445e0150 100644
--- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
@@ -8,7 +8,6 @@
import Foundation
import RSCore
-import RSTextDrawing
import Data
import Account
@@ -135,7 +134,6 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
private func fontSizeDidChange() {
TimelineCellData.emptyCache()
- RSMultiLineRenderer.emptyCache()
cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize)
cellAppearanceWithAvatar = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: true, fontSize: fontSize)
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
deleted file mode 100644
index 9bd1d0a2f..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,340 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 46;
- objects = {
-
-/* Begin PBXBuildFile section */
- 84193AB31CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h in Headers */ = {isa = PBXBuildFile; fileRef = 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 84193AB41CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m in Sources */ = {isa = PBXBuildFile; fileRef = 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */; };
- 8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */ = {isa = PBXBuildFile; fileRef = 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 8439DA061C8937C800E5E4B4 /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */; };
- 8439DA0B1C8937C800E5E4B4 /* RSTextDrawingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */; };
- 84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B717791CF9665100FF029D /* RSMultiLineView.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B7177A1CF9665100FF029D /* RSMultiLineView.m */; };
- 84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 846416431C8938470064C661 /* RSMultiLineRenderer.m */; };
- 84B7177E1CF9834A00FF029D /* RSMultiLineRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 846416421C8938470064C661 /* RSMultiLineRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 84BA010F1C8D20C60029943B /* RSTextRendererProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 84E697E41C8E6C01009C585A /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84E697E31C8E6C01009C585A /* RSCore.framework */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 8439DA071C8937C800E5E4B4 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 8439D9F21C8937C800E5E4B4 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 8439D9FA1C8937C800E5E4B4;
- remoteInfo = RSTextDrawing;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXFileReference section */
- 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineRendererMeasurements.h; path = RSTextDrawing/RSMultiLineRendererMeasurements.h; sourceTree = ""; };
- 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineRendererMeasurements.m; path = RSTextDrawing/RSMultiLineRendererMeasurements.m; sourceTree = ""; };
- 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSTextDrawing.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RSTextDrawing.h; path = RSTextDrawing/RSTextDrawing.h; sourceTree = ""; };
- 8439DA001C8937C800E5E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = RSTextDrawing/Info.plist; sourceTree = ""; };
- 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSTextDrawingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSTextDrawingTests.m; sourceTree = ""; };
- 8439DA0C1C8937C800E5E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 846416421C8938470064C661 /* RSMultiLineRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineRenderer.h; path = RSTextDrawing/RSMultiLineRenderer.h; sourceTree = ""; };
- 846416431C8938470064C661 /* RSMultiLineRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineRenderer.m; path = RSTextDrawing/RSMultiLineRenderer.m; sourceTree = ""; };
- 84B717791CF9665100FF029D /* RSMultiLineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineView.h; path = RSTextDrawing/RSMultiLineView.h; sourceTree = ""; };
- 84B7177A1CF9665100FF029D /* RSMultiLineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineView.m; path = RSTextDrawing/RSMultiLineView.m; sourceTree = ""; };
- 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSTextRendererProtocol.h; path = RSTextDrawing/RSTextRendererProtocol.h; sourceTree = ""; };
- 84E697E31C8E6C01009C585A /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = ../RSCore/build/Debug/RSCore.framework; sourceTree = ""; };
- D511EF1520242EB900712EC3 /* RSTextDrawing_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RSTextDrawing_project_release.xcconfig; sourceTree = ""; };
- D511EF1620242EB900712EC3 /* RSTextDrawingTests_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RSTextDrawingTests_target.xcconfig; sourceTree = ""; };
- D511EF1720242EB900712EC3 /* RSTextDrawing_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RSTextDrawing_project_debug.xcconfig; sourceTree = ""; };
- D511EF1820242EB900712EC3 /* RSTextDrawing_project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RSTextDrawing_project.xcconfig; sourceTree = ""; };
- D511EF1920242EB900712EC3 /* RSTextDrawing_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RSTextDrawing_target.xcconfig; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 8439D9F71C8937C800E5E4B4 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 84E697E41C8E6C01009C585A /* RSCore.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 8439DA021C8937C800E5E4B4 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8439DA061C8937C800E5E4B4 /* RSTextDrawing.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 8439D9F11C8937C800E5E4B4 = {
- isa = PBXGroup;
- children = (
- 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */,
- 84B717791CF9665100FF029D /* RSMultiLineView.h */,
- 84B7177A1CF9665100FF029D /* RSMultiLineView.m */,
- 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */,
- 846416421C8938470064C661 /* RSMultiLineRenderer.h */,
- 846416431C8938470064C661 /* RSMultiLineRenderer.m */,
- 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */,
- 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */,
- 8439DA001C8937C800E5E4B4 /* Info.plist */,
- 8439DA091C8937C800E5E4B4 /* RSTextDrawingTests */,
- 8439D9FC1C8937C800E5E4B4 /* Products */,
- 84E697E31C8E6C01009C585A /* RSCore.framework */,
- D511EF1420242EB900712EC3 /* xcconfig */,
- );
- sourceTree = "";
- };
- 8439D9FC1C8937C800E5E4B4 /* Products */ = {
- isa = PBXGroup;
- children = (
- 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */,
- 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 8439DA091C8937C800E5E4B4 /* RSTextDrawingTests */ = {
- isa = PBXGroup;
- children = (
- 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */,
- 8439DA0C1C8937C800E5E4B4 /* Info.plist */,
- );
- path = RSTextDrawingTests;
- sourceTree = "";
- };
- D511EF1420242EB900712EC3 /* xcconfig */ = {
- isa = PBXGroup;
- children = (
- D511EF1820242EB900712EC3 /* RSTextDrawing_project.xcconfig */,
- D511EF1720242EB900712EC3 /* RSTextDrawing_project_debug.xcconfig */,
- D511EF1520242EB900712EC3 /* RSTextDrawing_project_release.xcconfig */,
- D511EF1920242EB900712EC3 /* RSTextDrawing_target.xcconfig */,
- D511EF1620242EB900712EC3 /* RSTextDrawingTests_target.xcconfig */,
- );
- path = xcconfig;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXHeadersBuildPhase section */
- 8439D9F81C8937C800E5E4B4 /* Headers */ = {
- isa = PBXHeadersBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */,
- 84B7177E1CF9834A00FF029D /* RSMultiLineRenderer.h in Headers */,
- 84BA010F1C8D20C60029943B /* RSTextRendererProtocol.h in Headers */,
- 84193AB31CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h in Headers */,
- 84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXHeadersBuildPhase section */
-
-/* Begin PBXNativeTarget section */
- 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 8439DA0F1C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawing" */;
- buildPhases = (
- 8439D9F61C8937C800E5E4B4 /* Sources */,
- 8439D9F71C8937C800E5E4B4 /* Frameworks */,
- 8439D9F81C8937C800E5E4B4 /* Headers */,
- 8439D9F91C8937C800E5E4B4 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = RSTextDrawing;
- productName = RSTextDrawing;
- productReference = 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */;
- productType = "com.apple.product-type.framework";
- };
- 8439DA041C8937C800E5E4B4 /* RSTextDrawingTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 8439DA121C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawingTests" */;
- buildPhases = (
- 8439DA011C8937C800E5E4B4 /* Sources */,
- 8439DA021C8937C800E5E4B4 /* Frameworks */,
- 8439DA031C8937C800E5E4B4 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 8439DA081C8937C800E5E4B4 /* PBXTargetDependency */,
- );
- name = RSTextDrawingTests;
- productName = RSTextDrawingTests;
- productReference = 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 8439D9F21C8937C800E5E4B4 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastSwiftUpdateCheck = 0730;
- LastUpgradeCheck = 0930;
- ORGANIZATIONNAME = "Ranchero Software";
- TargetAttributes = {
- 8439D9FA1C8937C800E5E4B4 = {
- CreatedOnToolsVersion = 7.2.1;
- LastSwiftMigration = 0800;
- };
- 8439DA041C8937C800E5E4B4 = {
- CreatedOnToolsVersion = 7.2.1;
- LastSwiftMigration = 0800;
- };
- };
- };
- buildConfigurationList = 8439D9F51C8937C800E5E4B4 /* Build configuration list for PBXProject "RSTextDrawing" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- );
- mainGroup = 8439D9F11C8937C800E5E4B4;
- productRefGroup = 8439D9FC1C8937C800E5E4B4 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */,
- 8439DA041C8937C800E5E4B4 /* RSTextDrawingTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 8439D9F91C8937C800E5E4B4 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 8439DA031C8937C800E5E4B4 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 8439D9F61C8937C800E5E4B4 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */,
- 84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */,
- 84193AB41CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 8439DA011C8937C800E5E4B4 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8439DA0B1C8937C800E5E4B4 /* RSTextDrawingTests.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 8439DA081C8937C800E5E4B4 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */;
- targetProxy = 8439DA071C8937C800E5E4B4 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin XCBuildConfiguration section */
- 8439DA0D1C8937C800E5E4B4 /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1720242EB900712EC3 /* RSTextDrawing_project_debug.xcconfig */;
- buildSettings = {
- CODE_SIGN_IDENTITY = "-";
- };
- name = Debug;
- };
- 8439DA0E1C8937C800E5E4B4 /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1520242EB900712EC3 /* RSTextDrawing_project_release.xcconfig */;
- buildSettings = {
- CODE_SIGN_IDENTITY = "-";
- };
- name = Release;
- };
- 8439DA101C8937C800E5E4B4 /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1920242EB900712EC3 /* RSTextDrawing_target.xcconfig */;
- buildSettings = {
- };
- name = Debug;
- };
- 8439DA111C8937C800E5E4B4 /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1920242EB900712EC3 /* RSTextDrawing_target.xcconfig */;
- buildSettings = {
- };
- name = Release;
- };
- 8439DA131C8937C800E5E4B4 /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1620242EB900712EC3 /* RSTextDrawingTests_target.xcconfig */;
- buildSettings = {
- };
- name = Debug;
- };
- 8439DA141C8937C800E5E4B4 /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = D511EF1620242EB900712EC3 /* RSTextDrawingTests_target.xcconfig */;
- buildSettings = {
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 8439D9F51C8937C800E5E4B4 /* Build configuration list for PBXProject "RSTextDrawing" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 8439DA0D1C8937C800E5E4B4 /* Debug */,
- 8439DA0E1C8937C800E5E4B4 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 8439DA0F1C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawing" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 8439DA101C8937C800E5E4B4 /* Debug */,
- 8439DA111C8937C800E5E4B4 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 8439DA121C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawingTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 8439DA131C8937C800E5E4B4 /* Debug */,
- 8439DA141C8937C800E5E4B4 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 8439D9F21C8937C800E5E4B4 /* Project object */;
-}
diff --git a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index a11ac5bda..000000000
--- a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
From 0da5d25ec9a91585c76541408b4951869bb1c309 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 22:09:41 -0800
Subject: [PATCH 14/52] Replace reference to Twitter with reference to
Micro.blog.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 58dfd88c0..0c1da81b7 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,6 @@ It’s pretty early still, and we have strong opinions about how we want to do t
That said, we will seriously consider any pull requests we do get. Just note that we may not accept them, or we may accept them and do a bunch of revision.
-It’s probably a good idea to let us know first what you’d like to do. ([Ask on Twitter](https://twitter.com/evergreen_mac), or email brent@ranchero.com, or post something to the [bug tracker](https://github.com/brentsimmons/Evergreen/issues).)
+It’s probably a good idea to let us know first what you’d like to do. ([Ask on Micro.blog](https://micro.blog/brentsimmons), or email brent@ranchero.com, or post something to the [bug tracker](https://github.com/brentsimmons/Evergreen/issues).)
In the future — some time after 1.0 ships — we expect to want contributions. The plan is *not* to make this a one-person show forever.
From b52c82e8b6113b2385728356436ffa6180891dfc Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 19 Feb 2018 22:20:23 -0800
Subject: [PATCH 15/52] =?UTF-8?q?Make=20no-selection-view=E2=80=99s=20back?=
=?UTF-8?q?ground=20color=20white.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Evergreen/Base.lproj/MainWindow.storyboard | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard
index a8077be03..18ce03396 100644
--- a/Evergreen/Base.lproj/MainWindow.storyboard
+++ b/Evergreen/Base.lproj/MainWindow.storyboard
@@ -741,7 +741,7 @@
-
+
From c9df252f9a62522d2ff6f34341b6f00d1bf1137e Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Tue, 20 Feb 2018 13:06:35 -0800
Subject: [PATCH 16/52] Remove TimelineCellData attributed string properties
that are no longer needed.
---
.../Timeline/Cell/TimelineCellData.swift | 18 ------------------
.../Timeline/Cell/TimelineCellLayout.swift | 9 ++++-----
.../Timeline/Cell/TimelineTableCellView.swift | 4 ++--
3 files changed, 6 insertions(+), 25 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
index 228e6cff4..cfe2f33ab 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellData.swift
@@ -19,9 +19,7 @@ struct TimelineCellData {
let text: String
let attributedTitle: NSAttributedString //title + text
let dateString: String
- let attributedDateString: NSAttributedString
let feedName: String
- let attributedFeedName: NSAttributedString
let showFeedName: Bool
let avatar: NSImage? // feed icon, user avatar, or favicon
let showAvatar: Bool // Make space even when avatar is nil
@@ -44,13 +42,6 @@ struct TimelineCellData {
}
self.dateString = timelineDateString(article.logicalDatePublished)
- if let s = attributedDateCache[self.dateString] {
- self.attributedDateString = s
- }
- else {
- self.attributedDateString = NSAttributedString(string: self.dateString, attributes: [NSAttributedStringKey.foregroundColor: appearance.dateColor, NSAttributedStringKey.font: appearance.dateFont])
- attributedDateCache[self.dateString] = self.attributedDateString
- }
if let feedName = feedName {
self.feedName = timelineTruncatedFeedName(feedName)
@@ -58,13 +49,6 @@ struct TimelineCellData {
else {
self.feedName = ""
}
- if let s = attributedFeedNameCache[self.feedName] {
- self.attributedFeedName = s
- }
- else {
- self.attributedFeedName = NSAttributedString(string: self.feedName, attributes: [NSAttributedStringKey.foregroundColor: appearance.feedNameColor, NSAttributedStringKey.font: appearance.feedNameFont])
- attributedFeedNameCache[self.feedName] = self.attributedFeedName
- }
self.showFeedName = showFeedName
@@ -82,9 +66,7 @@ struct TimelineCellData {
self.attributedTitle = NSAttributedString(string: "")
self.text = ""
self.dateString = ""
- self.attributedDateString = NSAttributedString(string: "")
self.feedName = ""
- self.attributedFeedName = NSAttributedString(string: "")
self.showFeedName = false
self.showAvatar = false
self.avatar = nil
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index a1a4f78b7..a96c72d11 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -87,7 +87,7 @@ private extension TimelineCellLayout {
static func rectForDate(_ textBoxRect: NSRect, _ titleRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
- return rectOfLineBelow(textBoxRect, titleRect, appearance.titleBottomMargin, cellData.attributedDateString)
+ return rectOfLineBelow(textBoxRect, titleRect, appearance.titleBottomMargin, cellData.dateString, appearance.dateFont)
}
static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
@@ -96,13 +96,12 @@ private extension TimelineCellLayout {
return NSZeroRect
}
- return rectOfLineBelow(textBoxRect, dateRect, appearance.titleBottomMargin, cellData.attributedFeedName)
+ return rectOfLineBelow(textBoxRect, dateRect, appearance.titleBottomMargin, cellData.feedName, appearance.feedNameFont)
}
- static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ attributedString: NSAttributedString) -> NSRect {
+ static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ value: String, _ font: NSFont) -> NSRect {
- let font = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as! NSFont
- let textFieldSize = SingleLineTextFieldSizer.size(for: attributedString.string, font: font)
+ let textFieldSize = SingleLineTextFieldSizer.size(for: value, font: font)
var r = NSZeroRect
r.size = textFieldSize
r.origin.y = NSMaxY(rectAbove) + topMargin
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index d73cf0898..5a24be79f 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -236,7 +236,7 @@ private extension TimelineTableCellView {
func updateDateView() {
- dateView.stringValue = cellData.attributedDateString.string
+ dateView.stringValue = cellData.dateString
needsLayout = true
}
@@ -246,7 +246,7 @@ private extension TimelineTableCellView {
if feedNameView.isHidden {
feedNameView.isHidden = false
}
- feedNameView.stringValue = cellData.attributedFeedName.string
+ feedNameView.stringValue = cellData.feedName
}
else {
if !feedNameView.isHidden {
From ab6d232377f7f0b6a685ba94dca6db747e69868d Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Tue, 20 Feb 2018 22:32:14 -0800
Subject: [PATCH 17/52] Start work on multiline text measurement.
---
.../Cell/MultilineTextFieldSizer.swift | 124 +++++++++++++-----
.../Timeline/Cell/TimelineCellLayout.swift | 2 +-
.../Timeline/Cell/TimelineTableCellView.swift | 2 +
3 files changed, 96 insertions(+), 32 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
index a4ab4689d..85b5da751 100644
--- a/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift
@@ -8,33 +8,69 @@
import AppKit
-// Get the height of an NSTextField given an NSAttributedString and a width.
+// Get the height of an NSTextField given a string, font, and width.
// Uses a cache. Avoids actually measuring text as much as possible.
// Main thread only.
typealias WidthHeightCache = [Int: Int] // width: height
+private struct TextFieldSizerSpecifier: Equatable, Hashable {
+
+ let numberOfLines: Int
+ let font: NSFont
+ let hashValue: Int
+
+ init(numberOfLines: Int, font: NSFont) {
+ self.numberOfLines = numberOfLines
+ self.font = font
+ self.hashValue = font.hashValue ^ numberOfLines
+ }
+
+ static func ==(lhs : TextFieldSizerSpecifier, rhs: TextFieldSizerSpecifier) -> Bool {
+
+ return lhs.numberOfLines == rhs.numberOfLines && lhs.font == rhs.font
+ }
+}
+
+struct TextFieldSizeInfo {
+
+ let size: NSSize // Integral size (ceiled)
+ let numberOfLinesUsed: Int // A two-line text field may only use one line, for instance. This would equal 1, then.
+
+ init(size: NSSize, numberOfLinesUsed: Int) {
+ self.size = size
+ self.numberOfLinesUsed = numberOfLinesUsed
+ }
+}
+
final class MultilineTextFieldSizer {
private let numberOfLines: Int
+ private let font: NSFont
private let textField:NSTextField
- private var cache = [NSAttributedString: WidthHeightCache]() // Each string has a cache.
- private static var sizers = [Int: MultilineTextFieldSizer]()
+ private let singleLineHeightEstimate: Int
+ private let doubleLineHeightEstimate: Int
+ private var cache = [String: WidthHeightCache]() // Each string has a cache.
+ private static var sizers = [TextFieldSizerSpecifier: MultilineTextFieldSizer]()
- private init(numberOfLines: Int) {
+ private init(numberOfLines: Int, font: NSFont) {
self.numberOfLines = numberOfLines
- self.textField = MultilineTextFieldSizer.createTextField(numberOfLines)
+ self.font = font
+ self.textField = MultilineTextFieldSizer.createTextField(numberOfLines, font)
+
+ self.singleLineHeightEstimate = MultilineTextFieldSizer.calculateHeight("AqLjJ0/y", 200, self.textField)
+ self.doubleLineHeightEstimate = MultilineTextFieldSizer.calculateHeight("AqLjJ0/y\nAqLjJ0/y", 200, self.textField)
}
- static func size(for attributedString: NSAttributedString, numberOfLines: Int, width: Int) -> Int {
+ static func size(for string: String, font: NSFont, numberOfLines: Int, width: Int) -> TextFieldSizeInfo {
- return sizer(numberOfLines: numberOfLines).height(for: attributedString, width: width)
+ return sizer(numberOfLines: numberOfLines, font: font).sizeInfo(for: string, width: width)
}
static func emptyCache() {
- sizers = [Int: MultilineTextFieldSizer]()
+ sizers = [TextFieldSizerSpecifier: MultilineTextFieldSizer]()
}
}
@@ -42,75 +78,101 @@ final class MultilineTextFieldSizer {
private extension MultilineTextFieldSizer {
- static func sizer(numberOfLines: Int) -> MultilineTextFieldSizer {
+ static func sizer(numberOfLines: Int, font: NSFont) -> MultilineTextFieldSizer {
- if let cachedSizer = sizers[numberOfLines] {
+ let specifier = TextFieldSizerSpecifier(numberOfLines: numberOfLines, font: font)
+ if let cachedSizer = sizers[specifier] {
return cachedSizer
}
- let newSizer = MultilineTextFieldSizer(numberOfLines: numberOfLines)
- sizers[numberOfLines] = newSizer
+ let newSizer = MultilineTextFieldSizer(numberOfLines: numberOfLines, font: font)
+ sizers[specifier] = newSizer
return newSizer
}
- func height(for attributedString: NSAttributedString, width: Int) -> Int {
+ func sizeInfo(for string: String, width: Int) -> Int {
- if cache[attributedString] == nil {
- cache[attributedString] = WidthHeightCache()
+ if cache[string] == nil {
+ cache[string] = WidthHeightCache()
}
- if let height = cache[attributedString]![width] {
+ if let height = cache[string]![width] {
return height
}
- if let height = heightConsideringNeighbors(cache[attributedString]!, width) {
+ if let height = heightConsideringNeighbors(cache[string]!, width) {
return height
}
- let height = calculateHeight(attributedString, width)
- cache[attributedString]![width] = height
+ let height = calculateHeight(string, width)
+ cache[string]![width] = height
return height
}
- static func createTextField(_ numberOfLines: Int) -> NSTextField {
+ static func createTextField(_ numberOfLines: Int, _ font: NSFont) -> NSTextField {
let textField = NSTextField(wrappingLabelWithString: "")
textField.usesSingleLineMode = false
textField.maximumNumberOfLines = numberOfLines
textField.isEditable = false
+ textField.font = font
+ textField.allowsDefaultTighteningForTruncation = false
return textField
}
- func calculateHeight(_ attributedString: NSAttributedString, _ width: Int) -> Int {
+ func calculateHeight(_ string: String, _ width: Int) -> Int {
- textField.attributedStringValue = attributedString
+ return MultilineTextFieldSizer.calculateHeight(string, width, textField)
+ }
+
+ static func calculateHeight(_ string: String, _ width: Int, _ textField: NSTextField) -> Int {
+
+ textField.stringValue = string
textField.preferredMaxLayoutWidth = CGFloat(width)
let size = textField.fittingSize
return Int(ceil(size.height))
}
-// func widthHeightCache(for attributedString: NSAttributedString) -> WidthHeightCache {
-//
-// if let foundCache = cache[attributedString] {
-// return foundCache
-// }
-// let newCache = WidthHeightCache()
-// cache[attributedString] = newCache
-// return newCache
-// }
+ func heightIsProbablySingleLineHeight(_ height: Int) -> Bool {
+
+ return heightIsProbablyEqualToEstimate(height, singleLineHeightEstimate)
+ }
+
+ func heightIsProbablyDoubleLineHeight(_ height: Int) -> Bool {
+
+ return heightIsProbablyEqualToEstimate(height, doubleLineHeightEstimate)
+ }
+
+ func heightIsProbablyEqualToEstimate(_ height: Int, _ estimate: Int) -> Bool {
+
+ let slop = 4
+ let minimum = estimate - slop
+ let maximum = estimate + slop
+ return height >= minimum && height <= maximum
+ }
func heightConsideringNeighbors(_ heightCache: WidthHeightCache, _ width: Int) -> Int? {
// Given width, if the height at width - something and width + something is equal,
// then that height must be correct for the given width.
+ // Also:
+ // If a narrower neighbor’s height is single line height, then this wider width must also be single-line height.
+ // If a wider neighbor’s height is double line height, and numberOfLines == 2, then this narrower width must able be double-line height.
var smallNeighbor = (width: 0, height: 0)
var largeNeighbor = (width: 0, height: 0)
for (oneWidth, oneHeight) in heightCache {
+ if oneWidth < width && heightIsProbablySingleLineHeight(oneHeight) {
+ return oneHeight
+ }
+ if numberOfLines == 2 && oneWidth > width && heightIsProbablyDoubleLineHeight(oneHeight) {
+ return oneHeight
+ }
+
if oneWidth < width && (oneWidth > smallNeighbor.width || smallNeighbor.width == 0) {
smallNeighbor = (oneWidth, oneHeight)
}
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index a96c72d11..ab86a7b10 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -79,7 +79,7 @@ private extension TimelineCellLayout {
static func rectForTitle(_ textBoxRect: NSRect, _ cellData: TimelineCellData) -> NSRect {
var r = textBoxRect
- let height = MultilineTextFieldSizer.size(for: cellData.attributedTitle, numberOfLines: 2, width: Int(textBoxRect.width))
+ let height = MultilineTextFieldSizer.size(for: cellData.title, font: appearance.titleFont, numberOfLines: 2, width: Int(textBoxRect.width))
r.size.height = CGFloat(height)
return r
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 5a24be79f..4dae483b2 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -136,6 +136,7 @@ private extension TimelineTableCellView {
textField.maximumNumberOfLines = 1
textField.isEditable = false
textField.lineBreakMode = .byTruncatingTail
+ textField.allowsDefaultTighteningForTruncation = false
return textField
}
@@ -147,6 +148,7 @@ private extension TimelineTableCellView {
textField.isEditable = false
textField.lineBreakMode = .byTruncatingTail
textField.cell?.truncatesLastVisibleLine = true
+ textField.allowsDefaultTighteningForTruncation = false
return textField
}
From d3846a6a370af6870d3bb3d70c50d5165d46736c Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Wed, 21 Feb 2018 22:48:34 -0800
Subject: [PATCH 18/52] Continue progress toward variable row heights.
---
Evergreen/AppDelegate.swift | 1 -
.../Cell/MultilineTextFieldSizer.swift | 21 +++++-
.../Cell/TimelineCellAppearance.swift | 9 ++-
.../Timeline/Cell/TimelineCellData.swift | 42 ------------
.../Timeline/Cell/TimelineCellLayout.swift | 64 +++++++++++++++----
.../Cell/TimelineStringUtilities.swift | 3 +-
.../Timeline/Cell/TimelineTableCellView.swift | 16 ++---
.../Timeline/TimelineViewController.swift | 4 +-
Evergreen/Resources/DB5.plist | 2 +
9 files changed, 86 insertions(+), 76 deletions(-)
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
From c5742d0242c9ffd2d7567368e24cdd7231224aeb Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 22:15:35 -0800
Subject: [PATCH 19/52] Use separate text field for title, summary, and
text-only in the timeline.
---
.../Timeline/Cell/TimelineCellLayout.swift | 10 +-
.../Timeline/Cell/TimelineTableCellView.swift | 100 ++++++++++++++----
2 files changed, 86 insertions(+), 24 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 18392af84..904b29e7d 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -48,7 +48,15 @@ struct TimelineCellLayout {
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)
+
+ var lastTextRect = titleRect
+ if numberOfLinesForTitle == 0 {
+ lastTextRect = textRect
+ }
+ else if numberOfLinesForTitle == 1 {
+ lastTextRect = summaryRect
+ }
+ let dateRect = TimelineCellLayout.rectForDate(textBoxRect, lastTextRect, appearance, cellData)
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
textBoxRect.size.height = ceil([titleRect, summaryRect, textRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 9b6c5fe44..335e2301f 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -12,6 +12,8 @@ import RSCore
class TimelineTableCellView: NSTableCellView {
private let titleView = TimelineTableCellView.multiLineTextField()
+ private let summaryView = TimelineTableCellView.singleLineTextField()
+ private let textView = TimelineTableCellView.multiLineTextField()
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
private let dateView = TimelineTableCellView.singleLineTextField()
private let feedNameView = TimelineTableCellView.singleLineTextField()
@@ -19,7 +21,7 @@ class TimelineTableCellView: NSTableCellView {
private let starView = TimelineTableCellView.imageView(with: AppImages.timelineStar, scaling: .scaleNone)
private lazy var textFields = {
- return [self.dateView, self.feedNameView, self.titleView]
+ return [self.dateView, self.feedNameView, self.titleView, self.summaryView, self.textView]
}()
var cellAppearance: TimelineCellAppearance! {
@@ -101,9 +103,13 @@ class TimelineTableCellView: NSTableCellView {
override func resizeSubviews(withOldSize oldSize: NSSize) {
let layoutRects = updatedLayoutRects()
- titleView.rs_setFrameIfNotEqual(layoutRects.titleRect)
- unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
+
+ setFrame(for: titleView, rect: layoutRects.titleRect)
+ setFrame(for: summaryView, rect: layoutRects.summaryRect)
+ setFrame(for: textView, rect: layoutRects.textRect)
+
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
+ unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
@@ -161,6 +167,17 @@ private extension TimelineTableCellView {
return imageView
}
+ func setFrame(for textField: NSTextField, rect: NSRect) {
+
+ if Int(floor(rect.height)) == 0 || Int(floor(rect.width)) == 0 {
+ hideView(textField)
+ }
+ else {
+ showView(textField)
+ textField.rs_setFrameIfNotEqual(rect)
+ }
+ }
+
func updateTextFieldColors() {
updateTitleView()
@@ -172,6 +189,8 @@ private extension TimelineTableCellView {
feedNameView.textColor = cellAppearance.feedNameColor
dateView.textColor = cellAppearance.dateColor
titleView.textColor = cellAppearance.titleColor
+ summaryView.textColor = cellAppearance.textColor
+ textView.textColor = cellAppearance.textOnlyColor
}
}
@@ -180,6 +199,8 @@ private extension TimelineTableCellView {
feedNameView.font = cellAppearance.feedNameFont
dateView.font = cellAppearance.dateFont
titleView.font = cellAppearance.titleFont
+ summaryView.font = cellAppearance.textFont
+ textView.font = cellAppearance.textOnlyFont
}
func updateTextFields() {
@@ -198,11 +219,13 @@ private extension TimelineTableCellView {
func commonInit() {
addSubviewAtInit(titleView, hidden: false)
+ addSubviewAtInit(summaryView, hidden: true)
+ addSubviewAtInit(textView, hidden: true)
addSubviewAtInit(unreadIndicatorView, hidden: true)
addSubviewAtInit(dateView, hidden: false)
addSubviewAtInit(feedNameView, hidden: true)
- addSubviewAtInit(avatarImageView, hidden: false)
- addSubviewAtInit(starView, hidden: false)
+ addSubviewAtInit(avatarImageView, hidden: true)
+ addSubviewAtInit(starView, hidden: true)
}
func updatedLayoutRects() -> TimelineCellLayout {
@@ -224,53 +247,63 @@ private extension TimelineTableCellView {
func updateTitleView() {
- titleView.stringValue = cellData?.title ?? ""
- needsLayout = true
+ updateTextFieldText(titleView, cellData?.title)
+ }
+
+ func updateSummaryView() {
+
+ updateTextFieldText(summaryView, cellData?.text)
+ }
+
+ func updateTextView() {
+
+ updateTextFieldText(textView, cellData?.text)
}
func updateDateView() {
- dateView.stringValue = cellData.dateString
- needsLayout = true
+ updateTextFieldText(dateView, cellData.dateString)
+ }
+
+ func updateTextFieldText(_ textField: NSTextField, _ text: String?) {
+
+ let s = text ?? ""
+ if textField.stringValue != s {
+ textField.stringValue = s
+ needsLayout = true
+ }
}
func updateFeedNameView() {
if cellData.showFeedName {
- if feedNameView.isHidden {
- feedNameView.isHidden = false
- }
- feedNameView.stringValue = cellData.feedName
+ showView(feedNameView)
+ updateTextFieldText(feedNameView, cellData.feedName)
}
else {
- if !feedNameView.isHidden {
- feedNameView.isHidden = true
- }
+ hideView(feedNameView)
}
}
func updateUnreadIndicator() {
- let shouldHide = cellData.read || cellData.starred
- if unreadIndicatorView.isHidden != shouldHide {
- unreadIndicatorView.isHidden = shouldHide
- }
+ showOrHideView(unreadIndicatorView, cellData.read || cellData.starred)
}
func updateStarView() {
- starView.isHidden = !cellData.starred
+ showOrHideView(starView, !cellData.starred)
}
func updateAvatar() {
if !cellData.showAvatar {
avatarImageView.image = nil
- avatarImageView.isHidden = true
+ hideView(avatarImageView)
return
}
- avatarImageView.isHidden = false
+ showView(avatarImageView)
if let image = cellData.avatar {
if avatarImageView.image !== image {
@@ -285,9 +318,30 @@ private extension TimelineTableCellView {
avatarImageView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
}
+ func hideView(_ view: NSView) {
+
+ if !view.isHidden {
+ view.isHidden = true
+ }
+ }
+
+ func showView(_ view: NSView) {
+
+ if view.isHidden {
+ view.isHidden = false
+ }
+ }
+
+ func showOrHideView(_ view: NSView, _ shouldHide: Bool) {
+
+ shouldHide ? hideView(view) : showView(view)
+ }
+
func updateSubviews() {
updateTitleView()
+ updateSummaryView()
+ updateTextView()
updateDateView()
updateFeedNameView()
updateUnreadIndicator()
From 5d756d6a20220ae4f435ee5dabf8d111fefb4b58 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 22:20:59 -0800
Subject: [PATCH 20/52] Fix layout bug with 1-line title and 0-line summary.
---
Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 904b29e7d..f08c03bc8 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -54,7 +54,9 @@ struct TimelineCellLayout {
lastTextRect = textRect
}
else if numberOfLinesForTitle == 1 {
- lastTextRect = summaryRect
+ if summaryRect.height > 0.1 {
+ lastTextRect = summaryRect
+ }
}
let dateRect = TimelineCellLayout.rectForDate(textBoxRect, lastTextRect, appearance, cellData)
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
From ed948874640132715a1725270c279c5035ce50b9 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 22:21:48 -0800
Subject: [PATCH 21/52] Turn off drawing grid lines in the timeline.
---
Evergreen/Resources/DB5.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 9e50e9602..c3eb3d16d 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -67,7 +67,7 @@
gridColorAlpha
0.1
drawsGrid
-
+
header
backgroundColor
From 3ba7fa9e8df4d1e7080e8c2ab129e4f36569e37e Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 22:25:26 -0800
Subject: [PATCH 22/52] Reduce corner radius in timeline avatars.
---
Evergreen/Resources/DB5.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index c3eb3d16d..9372a1c1f 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -118,7 +118,7 @@
avatarAdjustmentTop
4
avatarCornerRadius
- 7
+ 4
starDimension
13
titleMaximumLines
From 4b766a0b0af58b4db71aefb1f8c7d9f3659491b0 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:00:28 -0800
Subject: [PATCH 23/52] Lower the corner radius on the upper-right avatar in
the detail view.
---
Evergreen/MainWindow/Detail/styleSheet.css | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index c7cb0e2a3..9b6aea583 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -30,7 +30,7 @@ a:hover {
color: rgba(0, 0, 0, 0.3);
}
#articleDateline img {
- border-radius: 7px;
+ border-radius: 4px;
}
.articleDate {
color: #2db6ff;
@@ -47,7 +47,7 @@ a:hover {
text-align: left;
}
#authorAvatar img {
- border-radius: 5px;
+ border-radius: 4px;
}
.rightAlign {
text-align: right;
@@ -70,13 +70,13 @@ a:hover {
height: 68px;
}
.avatar img {
- border-radius: 7px;
+ border-radius: 4px;
}
.headerContainer a:link, .headerContainer a:visited {
color: rgba(0, 0, 0, 0.3);
}
.feedIcon {
- border-radius: 5px;
+ border-radius: 4px;
}
h1 {
line-height: 1.15em;
From 94b71d146455a4b48b08477b4c8692d5e182c26a Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:02:30 -0800
Subject: [PATCH 24/52] Lighten the split view color.
---
Evergreen/MainWindow/MainWindowSplitView.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/MainWindowSplitView.swift b/Evergreen/MainWindow/MainWindowSplitView.swift
index b18fb7752..0af4bfe13 100644
--- a/Evergreen/MainWindow/MainWindowSplitView.swift
+++ b/Evergreen/MainWindow/MainWindowSplitView.swift
@@ -10,7 +10,7 @@ import AppKit
class MainWindowSplitView: NSSplitView {
- private let splitViewDividerColor = NSColor(calibratedWhite: 0.60, alpha: 1.0)
+ private let splitViewDividerColor = NSColor(calibratedWhite: 0.75, alpha: 1.0)
override var dividerColor: NSColor {
return splitViewDividerColor
From 443958423ee5ddc2cf496d213185b4a21d36e1a3 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:03:01 -0800
Subject: [PATCH 25/52] Tweak some timeline settings.
---
.../MainWindow/Timeline/Cell/TimelineCellAppearance.swift | 4 +++-
Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift | 2 +-
Evergreen/Resources/DB5.plist | 6 +++---
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
index a552efeee..d27ea9410 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift
@@ -18,6 +18,7 @@ struct TimelineCellAppearance: Equatable {
let dateColor: NSColor
let dateMarginLeft: CGFloat
+ let dateMarginBottom: CGFloat
let dateFont: NSFont
let titleColor: NSColor
@@ -61,8 +62,9 @@ struct TimelineCellAppearance: Equatable {
self.feedNameFont = NSFont.systemFont(ofSize: smallItemFontSize)
self.dateColor = theme.color(forKey: "MainWindow.Timeline.cell.dateColor")
- self.dateFont = NSFont.systemFont(ofSize: smallItemFontSize)
+ self.dateFont = NSFont.systemFont(ofSize: smallItemFontSize, weight: NSFont.Weight.bold)
self.dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft")
+ self.dateMarginBottom = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginBottom")
self.titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor")
self.titleFont = NSFont.systemFont(ofSize: largeItemFontSize, weight: NSFont.Weight.semibold)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index f08c03bc8..81b9b3bdc 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -148,7 +148,7 @@ private extension TimelineCellLayout {
return NSZeroRect
}
- return rectOfLineBelow(textBoxRect, dateRect, appearance.titleBottomMargin, cellData.feedName, appearance.feedNameFont)
+ return rectOfLineBelow(textBoxRect, dateRect, appearance.dateMarginBottom, cellData.feedName, appearance.feedNameFont)
}
static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ value: String, _ font: NSFont) -> NSRect {
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 9372a1c1f..6b7b51c17 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -80,7 +80,7 @@
paddingRight
20
paddingTop
- 16
+ 14
paddingBottom
16
feedNameColor
@@ -92,9 +92,9 @@
dateMarginLeft
10
dateMarginBottom
- 2
+ 1
textColor
- aaaaaa
+ 999999
textOnlyColor
222222
titleColor
From 9e743d0192bdfb1454fcacd86956ecbff4a27937 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:05:51 -0800
Subject: [PATCH 26/52] Add a fix pixels of white space inside the left side of
the sidebar.
---
Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift b/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift
index 2ed394dd7..81b37380e 100644
--- a/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift
+++ b/Evergreen/MainWindow/Sidebar/SidebarOutlineView.swift
@@ -21,6 +21,8 @@ class SidebarOutlineView : NSOutlineView {
// Don’t allow the pseudo-feeds at the top level to be indented.
var frame = super.frameOfCell(atColumn: column, row: row)
+ frame.origin.x += 4.0
+ frame.size.width -= 4.0
let node = item(atRow: row) as! Node
guard let parentNode = node.parent, parentNode.isRoot else {
From 5643a51d16f6882c6970283e00002b50d0cb5286 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:31:59 -0800
Subject: [PATCH 27/52] =?UTF-8?q?Skip=20showing=20favicons=20in=20the=20ti?=
=?UTF-8?q?meline=20when=20they=E2=80=99re=20too=20small=20to=20look=20goo?=
=?UTF-8?q?d.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Timeline/Cell/TimelineTableCellView.swift | 34 ++++++++++++-------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 335e2301f..4d0927a74 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -17,7 +17,13 @@ class TimelineTableCellView: NSTableCellView {
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
private let dateView = TimelineTableCellView.singleLineTextField()
private let feedNameView = TimelineTableCellView.singleLineTextField()
- private let avatarImageView = TimelineTableCellView.imageView(with: AppImages.genericFeedImage, scaling: .scaleProportionallyDown)
+
+ private lazy var avatarImageView: NSImageView = {
+ let imageView = TimelineTableCellView.imageView(with: AppImages.genericFeedImage, scaling: .scaleProportionallyDown)
+ imageView.wantsLayer = true
+ return imageView
+ }()
+
private let starView = TimelineTableCellView.imageView(with: AppImages.timelineStar, scaling: .scaleNone)
private lazy var textFields = {
@@ -27,6 +33,7 @@ class TimelineTableCellView: NSTableCellView {
var cellAppearance: TimelineCellAppearance! {
didSet {
updateTextFields()
+ avatarImageView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
needsLayout = true
}
}
@@ -297,25 +304,26 @@ private extension TimelineTableCellView {
func updateAvatar() {
- if !cellData.showAvatar {
- avatarImageView.image = nil
- hideView(avatarImageView)
+ // The avatar should be bigger than a favicon. They’re too small; they look weird.
+ guard let image = cellData.avatar, cellData.showAvatar, image.size.height >= 22.0, image.size.width >= 22.0 else {
+ makeAvatarEmpty()
return
}
showView(avatarImageView)
-
- if let image = cellData.avatar {
- if avatarImageView.image !== image {
- avatarImageView.image = image
- }
+ if avatarImageView.image !== image {
+ avatarImageView.image = image
+ needsLayout = true
}
- else {
+ }
+
+ func makeAvatarEmpty() {
+
+ if avatarImageView.image != nil {
avatarImageView.image = nil
+ needsLayout = true
}
-
- avatarImageView.wantsLayer = true
- avatarImageView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
+ hideView(avatarImageView)
}
func hideView(_ view: NSView) {
From e93fa704fc01b0963b91a58cb7c834e5250754eb Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 23 Feb 2018 23:46:27 -0800
Subject: [PATCH 28/52] Make sidebar white.
---
Evergreen/Base.lproj/MainWindow.storyboard | 2 +-
Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard
index 18ce03396..9ba34f424 100644
--- a/Evergreen/Base.lproj/MainWindow.storyboard
+++ b/Evergreen/Base.lproj/MainWindow.storyboard
@@ -279,7 +279,7 @@
-
+
diff --git a/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift b/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
index dbd4df147..6c85f50bc 100644
--- a/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
+++ b/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
@@ -57,8 +57,8 @@ final class SidebarStatusBarView: NSView {
return
}
- let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
- layer.backgroundColor = color.cgColor
+// let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
+ layer.backgroundColor = NSColor.white.cgColor
didConfigureLayer = true
}
From 04c2d576376fec0b72de4f47a4f9b30ee4656d0d Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 11:04:27 -0800
Subject: [PATCH 29/52] Turn drawGrid back on for now.
---
Evergreen/Resources/DB5.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 6b7b51c17..3d3619e09 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -67,7 +67,7 @@
gridColorAlpha
0.1
drawsGrid
-
+
header
backgroundColor
From 53ec85c6bbbd6b45f0d768dbf43bf422ea67529f Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 11:04:47 -0800
Subject: [PATCH 30/52] Fix status bar color.
---
.../Sidebar/SidebarStatusBarView.swift | 4 +--
.../Timeline/Cell/TimelineCellLayout.swift | 32 +++++++++++++++++++
.../Timeline/Cell/TimelineTableCellView.swift | 3 +-
3 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift b/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
index 6c85f50bc..dbd4df147 100644
--- a/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
+++ b/Evergreen/MainWindow/Sidebar/SidebarStatusBarView.swift
@@ -57,8 +57,8 @@ final class SidebarStatusBarView: NSView {
return
}
-// let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
- layer.backgroundColor = NSColor.white.cgColor
+ let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
+ layer.backgroundColor = color.cgColor
didConfigureLayer = true
}
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 81b9b3bdc..9dd24cd62 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -76,12 +76,44 @@ struct TimelineCellLayout {
let layout = TimelineCellLayout(width: width, cellData: cellData, appearance: appearance)
return layout.height
}
+
+ func adjustedForHeight(_ newHeight: CGFloat) -> TimelineCellLayout {
+
+ // When the cell is laying out subviews, the height of the cell is known,
+ // and that height may not match the ideal height calculated in TimelineCellLayout.
+ // In that case we may need to adjust the layout.
+
+ if abs(newHeight - height) < 0.01 {
+ return self
+ }
+
+ return TimelineCellLayout(otherLayout: self, height: newHeight)
+ }
}
// MARK: - Calculate Rects
private extension TimelineCellLayout {
+ private init(otherLayout: TimelineCellLayout, height: CGFloat) {
+
+ self.width = otherLayout.width
+ self.feedNameRect = otherLayout.feedNameRect
+ self.dateRect = otherLayout.dateRect
+ self.titleRect = otherLayout.titleRect
+ self.numberOfLinesForTitle = otherLayout.numberOfLinesForTitle
+ self.summaryRect = otherLayout.summaryRect
+ self.textRect = otherLayout.textRect
+ self.unreadIndicatorRect = otherLayout.unreadIndicatorRect
+ self.starRect = otherLayout.starRect
+ self.paddingBottom = otherLayout.paddingBottom
+
+ let bounds = NSRect(x: 0.0, y: 0.0, width: otherLayout.width, height: height)
+ self.avatarImageRect = RSRectCenteredVerticallyInRect(otherLayout.avatarImageRect, bounds)
+
+ self.height = height
+ }
+
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ width: CGFloat) -> NSRect {
// Returned height is a placeholder. Not needed when this is calculated.
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 4d0927a74..4516c0ad8 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -237,7 +237,8 @@ private extension TimelineTableCellView {
func updatedLayoutRects() -> TimelineCellLayout {
- return TimelineCellLayout(width: bounds.width, cellData: cellData, appearance: cellAppearance)
+ let layout = TimelineCellLayout(width: bounds.width, cellData: cellData, appearance: cellAppearance)
+ return layout.adjustedForHeight(bounds.height)
}
func updateAppearance() {
From 12ac5ee7fc56e742360412d9d7bba26fe09862b6 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 11:10:47 -0800
Subject: [PATCH 31/52] Match background color.
---
Evergreen/Base.lproj/MainWindow.storyboard | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard
index 9ba34f424..18ce03396 100644
--- a/Evergreen/Base.lproj/MainWindow.storyboard
+++ b/Evergreen/Base.lproj/MainWindow.storyboard
@@ -279,7 +279,7 @@
-
+
From 202d878cb598fdb544c6e0e790c66e063117c4d6 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 11:41:02 -0800
Subject: [PATCH 32/52] Adjust layout to match actual height of timeline cell.
Adjust layout to account whether or not an avatar image exists.
---
.../Timeline/Cell/TimelineCellLayout.swift | 69 +++++++------------
.../Timeline/Cell/TimelineTableCellView.swift | 3 +-
2 files changed, 27 insertions(+), 45 deletions(-)
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
index 9dd24cd62..edc6d522a 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift
@@ -24,7 +24,7 @@ struct TimelineCellLayout {
let avatarImageRect: NSRect
let 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) {
+ init(width: CGFloat, height: 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
@@ -38,12 +38,20 @@ struct TimelineCellLayout {
self.avatarImageRect = avatarImageRect
self.paddingBottom = paddingBottom
- self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
+ if height > 0.1 {
+ self.height = height
+ }
+ else {
+ self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
+ }
}
- init(width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) {
+ init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasAvatar: Bool) {
- var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, width)
+ // If height == 0.0, then height is calculated.
+
+ let showAvatar = hasAvatar && cellData.showAvatar
+ var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, showAvatar, width)
let (titleRect, numberOfLinesForTitle) = TimelineCellLayout.rectForTitle(textBoxRect, appearance, cellData)
let summaryRect = numberOfLinesForTitle > 0 ? TimelineCellLayout.rectForSummary(textBoxRect, titleRect, numberOfLinesForTitle, appearance, cellData) : NSRect.zero
@@ -62,64 +70,32 @@ 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, textBoxRect, width)
+ let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, showAvatar, textBoxRect, width, height)
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, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, paddingBottom: paddingBottom)
+ self.init(width: width, height: height, 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 {
- let layout = TimelineCellLayout(width: width, cellData: cellData, appearance: appearance)
+ let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasAvatar: true)
return layout.height
}
-
- func adjustedForHeight(_ newHeight: CGFloat) -> TimelineCellLayout {
-
- // When the cell is laying out subviews, the height of the cell is known,
- // and that height may not match the ideal height calculated in TimelineCellLayout.
- // In that case we may need to adjust the layout.
-
- if abs(newHeight - height) < 0.01 {
- return self
- }
-
- return TimelineCellLayout(otherLayout: self, height: newHeight)
- }
}
// MARK: - Calculate Rects
private extension TimelineCellLayout {
- private init(otherLayout: TimelineCellLayout, height: CGFloat) {
-
- self.width = otherLayout.width
- self.feedNameRect = otherLayout.feedNameRect
- self.dateRect = otherLayout.dateRect
- self.titleRect = otherLayout.titleRect
- self.numberOfLinesForTitle = otherLayout.numberOfLinesForTitle
- self.summaryRect = otherLayout.summaryRect
- self.textRect = otherLayout.textRect
- self.unreadIndicatorRect = otherLayout.unreadIndicatorRect
- self.starRect = otherLayout.starRect
- self.paddingBottom = otherLayout.paddingBottom
-
- let bounds = NSRect(x: 0.0, y: 0.0, width: otherLayout.width, height: height)
- self.avatarImageRect = RSRectCenteredVerticallyInRect(otherLayout.avatarImageRect, bounds)
-
- self.height = height
- }
-
- static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ width: CGFloat) -> NSRect {
+ static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showAvatar: Bool, _ width: CGFloat) -> NSRect {
// Returned height is a placeholder. Not needed when this is calculated.
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
- let textBoxMaxX = floor((width - appearance.cellPadding.right) - (cellData.showAvatar ? appearance.avatarSize.width + appearance.avatarMarginLeft : 0.0))
+ let textBoxMaxX = floor((width - appearance.cellPadding.right) - (showAvatar ? appearance.avatarSize.width + appearance.avatarMarginLeft : 0.0))
let textBoxWidth = floor(textBoxMaxX - textBoxOriginX)
let textBoxRect = NSRect(x: textBoxOriginX, y: appearance.cellPadding.top, width: textBoxWidth, height: 1000000)
@@ -221,15 +197,22 @@ private extension TimelineCellLayout {
return r
}
- static func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ textBoxRect: NSRect, _ width: CGFloat) -> NSRect {
+ static func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showAvatar: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
var r = NSRect.zero
- if !cellData.showAvatar {
+ if !showAvatar {
return r
}
r.size = appearance.avatarSize
r.origin.x = (width - appearance.cellPadding.right) - r.size.width
r = RSRectCenteredVerticallyInRect(r, textBoxRect)
+ if height > 0.1 {
+ let bounds = NSRect(x: 0.0, y: 0.0, width: width, height: height)
+ r = RSRectCenteredVerticallyInRect(r, bounds)
+ }
+ else {
+ r = RSRectCenteredVerticallyInRect(r, textBoxRect)
+ }
return r
}
diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
index 4516c0ad8..e5c5874a8 100644
--- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
+++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift
@@ -237,8 +237,7 @@ private extension TimelineTableCellView {
func updatedLayoutRects() -> TimelineCellLayout {
- let layout = TimelineCellLayout(width: bounds.width, cellData: cellData, appearance: cellAppearance)
- return layout.adjustedForHeight(bounds.height)
+ return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarImageView.image != nil)
}
func updateAppearance() {
From 18a5c6114e70e239bdf48d6bc9e3b4a66d837f54 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 11:56:07 -0800
Subject: [PATCH 33/52] Use a link color (in detail view) that goes with
toolbar icons blue color.
---
Evergreen/MainWindow/Detail/styleSheet.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index 9b6aea583..b32f65528 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -12,7 +12,7 @@ a {
text-decoration: none;
}
a, a:link, a:visited {
- color: #015cdc;
+ color: hsla(201, 100%, 45%, 1);
}
a:hover {
text-decoration: underline;
From b3e54a0ab2c2a6d5b0dd2545db9e9d928ddac036 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 12:47:07 -0800
Subject: [PATCH 34/52] Make some tweaks to article css.
---
Evergreen/MainWindow/Detail/styleSheet.css | 36 +++++++++-------------
1 file changed, 14 insertions(+), 22 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index b32f65528..c36c4b087 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -1,10 +1,10 @@
body {
color: #444;
background-color: white;
- margin-top: 42px;
+ margin-top: 32px;
margin-bottom: 100px;
- margin-left: 80px;
- margin-right: 80px;
+ margin-left: 96px;
+ margin-right: 96px;
font-family: -apple-system;
font-size: 18px;
}
@@ -19,26 +19,16 @@ a:hover {
}
.articleDateline {
color: rgba(0, 0, 0, 0.3);
- border-bottxom: 1px solid rgba(0, 0, 0, 0.1);
- border-txop: 1px solid rgba(0, 0, 0, 0.1);
+ border-bxottom: 1px solid rgba(0, 0, 0, 0.1);
+ border-toxp: 1px solid rgba(0, 0, 0, 0.1);
padding-bottom: 0px;
padding-top: 0px;
- margin-bottom: 25px;
+ margin-bottom: 32px;
font-style: italic;
}
-.articleDateline a:link, #articleDateline a:visited {
+.articleDateline, .articleDateline a:link, .articleDateline a:visited {
color: rgba(0, 0, 0, 0.3);
}
-#articleDateline img {
- border-radius: 4px;
-}
-.articleDate {
- color: #2db6ff;
- text-align: right;
- border-bottom: 1px solid rgba(0, 0, 0, 0.5);
- padding-bottom: 8px;
- margin-bottom: 1em;
-}
#articleDescription {
line-height: 1.5em;
}
@@ -57,9 +47,14 @@ a:hover {
}
.articleTitle {
border-top: 1px solid rgba(0, 0, 0, 0.1);
+ padding-top: 1em;
+ padding-bottom: 0;
+ margin-bottom: 0;
}
.headerContainer {
- border-botxtom: 1px solid rgba(0, 0, 0, 0.1);
+ color: rgba(0, 0, 0, 0.3);
+}
+.headerContainer a:link, .headerContainer a:visited {
color: rgba(0, 0, 0, 0.3);
}
.header {
@@ -72,15 +67,12 @@ a:hover {
.avatar img {
border-radius: 4px;
}
-.headerContainer a:link, .headerContainer a:visited {
- color: rgba(0, 0, 0, 0.3);
-}
.feedIcon {
border-radius: 4px;
}
h1 {
line-height: 1.15em;
- font-weight: medium;
+ font-weight: bold;
}
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, "Helvetica Neue"
From 16615ada32519368eb79819762af3ee97294cf9d Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 13:06:36 -0800
Subject: [PATCH 35/52] Turn off grid in timeline. I know I keep going back and
forth on this.
---
Evergreen/Resources/DB5.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist
index 3d3619e09..6b7b51c17 100644
--- a/Evergreen/Resources/DB5.plist
+++ b/Evergreen/Resources/DB5.plist
@@ -67,7 +67,7 @@
gridColorAlpha
0.1
drawsGrid
-
+
header
backgroundColor
From d331ca72b68f591995890be6cef151e55f793832 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 13:13:42 -0800
Subject: [PATCH 36/52] Revert the detail view back to the stronger blue link
color.
---
Evergreen/MainWindow/Detail/styleSheet.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index c36c4b087..84fb1e096 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -12,7 +12,7 @@ a {
text-decoration: none;
}
a, a:link, a:visited {
- color: hsla(201, 100%, 45%, 1);
+ color: hsla(215, 99%, 43%, 1);
}
a:hover {
text-decoration: underline;
From cfd862c30b597521d92abae283b32f7712a9a086 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 13:37:47 -0800
Subject: [PATCH 37/52] Hide feedblitz share buttons in detail view.
---
Evergreen/MainWindow/Detail/styleSheet.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index 84fb1e096..32fd5a2d6 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -121,6 +121,7 @@ img[src*="//ads."],
img[src*="doubleclick"],
img[src*="feedads"],
img[src*="feedburner"],
+img[src*="feedblitz"],
img[src*="share-buttons"] {
display: none !important;
}
From 7def2ae8d0e1694d7533e7bdbfc94ebf729644c0 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 24 Feb 2018 15:54:32 -0800
Subject: [PATCH 38/52] =?UTF-8?q?Fix=20bug=20where=20deleting=20from=20the?=
=?UTF-8?q?=20sidebar=20would=20not=20stick=20across=20runs=20of=20the=20a?=
=?UTF-8?q?pp=20sometimes=20=E2=80=94=C2=A0whatever=20you=20deleted=20coul?=
=?UTF-8?q?d=20come=20back.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Frameworks/Account/Account.swift | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift
index 76d32af9b..81c38f919 100644
--- a/Frameworks/Account/Account.swift
+++ b/Frameworks/Account/Account.swift
@@ -117,6 +117,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
pullObjectsFromDisk()
@@ -447,23 +448,34 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
- @objc func displayNameDidChange(_ note: Notification) {
+ @objc func childrenDidChange(_ note: Notification) {
- if let feed = note.object as? Feed, let feedAccount = feed.account, feedAccount === self {
+ guard let object = note.object else {
+ return
+ }
+ if let account = object as? Account, account === self {
dirty = true
}
- if let folder = note.object as? Folder, let folderAccount = folder.account, folderAccount === self {
+ if let folder = object as? Folder, folder.account === self {
+ dirty = true
+ }
+ }
+
+ @objc func displayNameDidChange(_ note: Notification) {
+
+ if let feed = note.object as? Feed, feed.account === self {
+ dirty = true
+ }
+ if let folder = note.object as? Folder, folder.account === self {
dirty = true
}
}
@objc func saveToDiskIfNeeded() {
- guard dirty else {
- return
+ if dirty {
+ saveToDisk()
}
- saveToDisk()
- dirty = false
}
// MARK: - Equatable
@@ -563,6 +575,7 @@ private extension Account {
catch let error as NSError {
NSApplication.shared.presentError(error)
}
+ dirty = false
}
}
From 6e6eefab27ea03bbb344196510c1f746179a4b5f Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:01:09 -0800
Subject: [PATCH 39/52] Make Node.childNodes non-optional.
---
Frameworks/RSTree/RSTree/Node.swift | 12 +++---------
Frameworks/RSTree/RSTree/TreeController.swift | 10 +++++-----
2 files changed, 8 insertions(+), 14 deletions(-)
diff --git a/Frameworks/RSTree/RSTree/Node.swift b/Frameworks/RSTree/RSTree/Node.swift
index 8a05ab274..f266ec6ae 100644
--- a/Frameworks/RSTree/RSTree/Node.swift
+++ b/Frameworks/RSTree/RSTree/Node.swift
@@ -16,7 +16,7 @@ public final class Node: Hashable {
public let representedObject: AnyObject
public var canHaveChildNodes = false
public var isGroupItem = false
- public var childNodes: [Node]?
+ public var childNodes = [Node]()
public let hashValue: Int
private static var incrementingID = 0
@@ -28,7 +28,7 @@ public final class Node: Hashable {
}
public var numberOfChildNodes: Int {
- return childNodes?.count ?? 0
+ return childNodes.count
}
public var indexPath: IndexPath {
@@ -87,9 +87,6 @@ public final class Node: Hashable {
public func childAtIndex(_ index: Int) -> Node? {
- guard let childNodes = childNodes else {
- return nil
- }
if index >= childNodes.count || index < 0 {
return nil
}
@@ -98,7 +95,7 @@ public final class Node: Hashable {
public func indexOfChild(_ node: Node) -> Int? {
- return childNodes?.index{ (oneChildNode) -> Bool in
+ return childNodes.index{ (oneChildNode) -> Bool in
oneChildNode === node
}
}
@@ -189,9 +186,6 @@ private extension Node {
func findNodeRepresentingObject(_ obj: AnyObject, recursively: Bool = false) -> Node? {
- guard let childNodes = childNodes else {
- return nil
- }
for childNode in childNodes {
if childNode.representedObject === obj {
return childNode
diff --git a/Frameworks/RSTree/RSTree/TreeController.swift b/Frameworks/RSTree/RSTree/TreeController.swift
index 435b9087e..f3a18836f 100644
--- a/Frameworks/RSTree/RSTree/TreeController.swift
+++ b/Frameworks/RSTree/RSTree/TreeController.swift
@@ -53,8 +53,8 @@ public final class TreeController {
return oneNode
}
- if recurse, oneNode.canHaveChildNodes, let childNodes = oneNode.childNodes {
- if let foundNode = nodeInArrayRepresentingObject(nodes: childNodes, representedObject: representedObject, recurse: recurse) {
+ if recurse, oneNode.canHaveChildNodes {
+ if let foundNode = nodeInArrayRepresentingObject(nodes: oneNode.childNodes, representedObject: representedObject, recurse: recurse) {
return foundNode
}
@@ -89,7 +89,7 @@ private extension TreeController {
func visitNode(_ node: Node, _ visitBlock: NodeVisitBlock) {
visitBlock(node)
- node.childNodes?.forEach{ (oneChildNode) in
+ node.childNodes.forEach{ (oneChildNode) in
visitNode(oneChildNode, visitBlock)
}
}
@@ -117,14 +117,14 @@ private extension TreeController {
var childNodesDidChange = false
- let childNodes = delegate?.treeController(treeController: self, childNodesFor: node)
+ let childNodes = delegate?.treeController(treeController: self, childNodesFor: node) ?? [Node]()
childNodesDidChange = !nodeArraysAreEqual(childNodes, node.childNodes)
if (childNodesDidChange) {
node.childNodes = childNodes
}
- childNodes?.forEach{ (oneChildNode) in
+ childNodes.forEach{ (oneChildNode) in
if rebuildChildNodes(node: oneChildNode) {
childNodesDidChange = true
}
From 49059fe78e7a00907ae363d474ff862938853436 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:01:40 -0800
Subject: [PATCH 40/52] Make the dateline in the detail view not italic.
---
Evergreen/MainWindow/Detail/styleSheet.css | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index 32fd5a2d6..68616710e 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -19,12 +19,10 @@ a:hover {
}
.articleDateline {
color: rgba(0, 0, 0, 0.3);
- border-bxottom: 1px solid rgba(0, 0, 0, 0.1);
- border-toxp: 1px solid rgba(0, 0, 0, 0.1);
padding-bottom: 0px;
padding-top: 0px;
- margin-bottom: 32px;
- font-style: italic;
+ margin-bottom: 26px;
+ font-weight: medium;
}
.articleDateline, .articleDateline a:link, .articleDateline a:visited {
color: rgba(0, 0, 0, 0.3);
From fc38a485d63a694e6f2b4efdececb22fb704c3da Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:02:16 -0800
Subject: [PATCH 41/52] Deal with non-optional Node.childNodes.
---
Evergreen/FeedList/FeedListViewController.swift | 2 +-
Evergreen/MainWindow/AddFeed/AddFeedWindowController.swift | 7 +++----
.../MainWindow/Sidebar/SidebarOutlineDataSource.swift | 2 +-
3 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/Evergreen/FeedList/FeedListViewController.swift b/Evergreen/FeedList/FeedListViewController.swift
index b925e01f5..9cc83a96b 100644
--- a/Evergreen/FeedList/FeedListViewController.swift
+++ b/Evergreen/FeedList/FeedListViewController.swift
@@ -91,7 +91,7 @@ extension FeedListViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
- return nodeForItem(item as AnyObject?).childNodes![index]
+ return nodeForItem(item as AnyObject?).childNodes[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
diff --git a/Evergreen/MainWindow/AddFeed/AddFeedWindowController.swift b/Evergreen/MainWindow/AddFeed/AddFeedWindowController.swift
index e741f0c18..9b838e785 100644
--- a/Evergreen/MainWindow/AddFeed/AddFeedWindowController.swift
+++ b/Evergreen/MainWindow/AddFeed/AddFeedWindowController.swift
@@ -147,9 +147,8 @@ private extension AddFeedWindowController {
menuItem.representedObject = folderTreeController.rootNode.representedObject
menu.addItem(menuItem)
- if let childNodes = folderTreeController.rootNode.childNodes {
- addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
- }
+ let childNodes = folderTreeController.rootNode.childNodes
+ addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
return menu
}
@@ -166,7 +165,7 @@ private extension AddFeedWindowController {
menu.addItem(menuItem)
if oneNode.numberOfChildNodes > 0 {
- addFolderItemsToMenuWithNodes(menu: menu, nodes: oneNode.childNodes!, indentationLevel: indentationLevel + 1)
+ addFolderItemsToMenuWithNodes(menu: menu, nodes: oneNode.childNodes, indentationLevel: indentationLevel + 1)
}
}
}
diff --git a/Evergreen/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Evergreen/MainWindow/Sidebar/SidebarOutlineDataSource.swift
index 5b8b30e88..844eba921 100644
--- a/Evergreen/MainWindow/Sidebar/SidebarOutlineDataSource.swift
+++ b/Evergreen/MainWindow/Sidebar/SidebarOutlineDataSource.swift
@@ -28,7 +28,7 @@ import RSCore
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
- return nodeForItem(item as AnyObject?).childNodes![index]
+ return nodeForItem(item as AnyObject?).childNodes[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
From 0debd033de4197e61b98f21850192af74e672361 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:02:55 -0800
Subject: [PATCH 42/52] Rebuild the tree controller when
deleting/undoing-deleting from the sidebar. Fix #292.
---
Commands/DeleteFromSidebarCommand.swift | 6 +++++-
Evergreen/MainWindow/Sidebar/SidebarViewController.swift | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/Commands/DeleteFromSidebarCommand.swift b/Commands/DeleteFromSidebarCommand.swift
index 5652bf672..a93bed08c 100644
--- a/Commands/DeleteFromSidebarCommand.swift
+++ b/Commands/DeleteFromSidebarCommand.swift
@@ -14,6 +14,7 @@ import Data
final class DeleteFromSidebarCommand: UndoableCommand {
+ let treeController: TreeController
let undoManager: UndoManager
let undoActionName: String
var redoActionName: String {
@@ -22,7 +23,7 @@ final class DeleteFromSidebarCommand: UndoableCommand {
private let itemSpecifiers: [SidebarItemSpecifier]
- init?(nodesToDelete: [Node], undoManager: UndoManager) {
+ init?(nodesToDelete: [Node], treeController: TreeController, undoManager: UndoManager) {
guard DeleteFromSidebarCommand.canDelete(nodesToDelete) else {
return nil
@@ -31,6 +32,7 @@ final class DeleteFromSidebarCommand: UndoableCommand {
return nil
}
+ self.treeController = treeController
self.undoActionName = actionName
self.undoManager = undoManager
@@ -45,6 +47,7 @@ final class DeleteFromSidebarCommand: UndoableCommand {
BatchUpdate.shared.perform {
itemSpecifiers.forEach { $0.delete() }
+ treeController.rebuild()
}
registerUndo()
}
@@ -53,6 +56,7 @@ final class DeleteFromSidebarCommand: UndoableCommand {
BatchUpdate.shared.perform {
itemSpecifiers.forEach { $0.restore() }
+ treeController.rebuild()
}
registerRedo()
}
diff --git a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift
index 2a035aab7..051d828f1 100644
--- a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift
+++ b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift
@@ -129,7 +129,7 @@ import RSCore
let nodesToDelete = treeController.normalizedSelectedNodes(selectedNodes)
- guard let undoManager = undoManager, let deleteCommand = DeleteFromSidebarCommand(nodesToDelete: nodesToDelete, undoManager: undoManager) else {
+ guard let undoManager = undoManager, let deleteCommand = DeleteFromSidebarCommand(nodesToDelete: nodesToDelete, treeController: treeController, undoManager: undoManager) else {
return
}
From bb160bf7c3a26ec448b0412b6b47e96c6f173aca Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:06:59 -0800
Subject: [PATCH 43/52] Bump version.
---
Evergreen/Info.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Info.plist b/Evergreen/Info.plist
index 511f4486c..0a1b72e5f 100644
--- a/Evergreen/Info.plist
+++ b/Evergreen/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0d40
+ 1.0d41
CFBundleVersion
522
LSMinimumSystemVersion
From b6d29d9b147e60fbf2280f08741a0cf72e7438ee Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:21:13 -0800
Subject: [PATCH 44/52] Update appcast.
---
Appcasts/evergreen-beta.xml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/Appcasts/evergreen-beta.xml b/Appcasts/evergreen-beta.xml
index 6ef9c5233..3b6ccd8f7 100755
--- a/Appcasts/evergreen-beta.xml
+++ b/Appcasts/evergreen-beta.xml
@@ -6,6 +6,26 @@
Most recent Evergreen changes with links to updates.
en
+ -
+ Evergreen 1.0d41
+ Timeline
+
Text is now ellipsized.
+ Avatars and feed icons are now larger — 64pts instead of 48pts. This is the right size.
+ Improved the layout, especially when there’s little text and when there’s no avatar or feed icon and one is hoped-for.
+ Don’t show favicons in place of avatars and feed icons (when neither can be found) — they’re too small and don’t look good.
+
+ Sidebar
+ Fixed a bug where deleting a thing wouldn’t stick — it could come back on the next run.
+ Fixed a bug where deleting a thing, and then deleting another thing, would result in the wrong thing being deleted.
+
+ ]]>
+ Sun, 25 Feb 2018 22:10:00 -0800
+
+ 10.13
+
+
-
Evergreen 1.0d40
Date: Sun, 25 Feb 2018 22:46:25 -0800
Subject: [PATCH 45/52] Force favicons to appear at 16x16 in the detail view.
Allow favicons to appear larger, when they are in fact larger.
---
Evergreen/MainWindow/Detail/ArticleRenderer.swift | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/ArticleRenderer.swift b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
index ca73dde0d..ca82038c0 100644
--- a/Evergreen/MainWindow/Detail/ArticleRenderer.swift
+++ b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
@@ -222,7 +222,7 @@ class ArticleRenderer {
func html(dimension: Int) -> String {
- let imageTag = "
"
if let url = url {
return linkWithText(imageTag, url)
}
@@ -238,7 +238,9 @@ class ArticleRenderer {
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
if let s = base64String(forImage: favicon) {
- let imgTag = "
"
+ var dimension = min(favicon.size.height, 48) // Assuming square images.
+ dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
+ let imgTag = "
"
ArticleRenderer.faviconImgTagCache[feed] = imgTag
return imgTag
}
From d08da99a61307067993038de6626c70afddb300a Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 25 Feb 2018 22:58:08 -0800
Subject: [PATCH 46/52] Draw large favicons, and use rounded corners, in the
detail view when possible.
---
Evergreen/MainWindow/Detail/ArticleRenderer.swift | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/ArticleRenderer.swift b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
index ca82038c0..6f05b0cb5 100644
--- a/Evergreen/MainWindow/Detail/ArticleRenderer.swift
+++ b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
@@ -238,9 +238,20 @@ class ArticleRenderer {
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
if let s = base64String(forImage: favicon) {
- var dimension = min(favicon.size.height, 48) // Assuming square images.
+ var dimension = min(favicon.size.height, CGFloat(avatarDimension)) // Assuming square images.
dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
- let imgTag = "
"
+ if dimension >= CGFloat(avatarDimension) * 0.8 { //Close enough to scale up.
+ dimension = CGFloat(avatarDimension)
+ }
+
+ let imgTag: String
+ if dimension >= CGFloat(avatarDimension) {
+ // Use rounded corners.
+ imgTag = "
"
+ }
+ else {
+ imgTag = "
"
+ }
ArticleRenderer.faviconImgTagCache[feed] = imgTag
return imgTag
}
From bfc57d6e7a6f56de796509dc0de73ca6fefa5fbd Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 26 Feb 2018 13:10:23 -0800
Subject: [PATCH 47/52] Reduce detail view margins just a little.
---
Evergreen/MainWindow/Detail/styleSheet.css | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index 68616710e..9d9599227 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -3,8 +3,8 @@ body {
background-color: white;
margin-top: 32px;
margin-bottom: 100px;
- margin-left: 96px;
- margin-right: 96px;
+ margin-left: 64px;
+ margin-right: 64px;
font-family: -apple-system;
font-size: 18px;
}
From 2281c4a3d8db678eca6a19231e6e2bc9149f3c39 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 26 Feb 2018 22:31:07 -0800
Subject: [PATCH 48/52] Save and restore split view state. Fix #134.
---
Evergreen/AppDefaults.swift | 10 ++++
.../MainWindow/MainWindowController.swift | 47 +++++++++++++++++--
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/Evergreen/AppDefaults.swift b/Evergreen/AppDefaults.swift
index 6841b0b61..ae137aca4 100644
--- a/Evergreen/AppDefaults.swift
+++ b/Evergreen/AppDefaults.swift
@@ -27,6 +27,7 @@ final class AppDefaults {
static let timelineSortDirection = "timelineSortDirection"
static let detailFontSize = "detailFontSize"
static let openInBrowserInBackground = "openInBrowserInBackground"
+ static let mainWindowWidths = "mainWindowWidths"
// Hidden prefs
static let showTitleOnMainWindow = "KafasisTitleMode"
@@ -86,6 +87,15 @@ final class AppDefaults {
}
}
+ var mainWindowWidths: [Int]? {
+ get {
+ return UserDefaults.standard.object(forKey: Key.mainWindowWidths) as? [Int]
+ }
+ set {
+ UserDefaults.standard.set(newValue, forKey: Key.mainWindowWidths)
+ }
+ }
+
private init() {
AppDefaults.registerDefaults()
diff --git a/Evergreen/MainWindow/MainWindowController.swift b/Evergreen/MainWindow/MainWindowController.swift
index bc9617c6e..46c20420f 100644
--- a/Evergreen/MainWindow/MainWindowController.swift
+++ b/Evergreen/MainWindow/MainWindowController.swift
@@ -31,6 +31,8 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return window?.toolbar?.existingItem(withIdentifier: .Share)
}
+ private static var detailViewMinimumThickness = 384
+
// MARK: - NSWindowController
override func windowDidLoad() {
@@ -53,8 +55,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
}
}
- detailSplitViewItem?.minimumThickness = 384
-
+ detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness)
+ restoreSplitViewState()
+
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: NSApplication.willTerminateNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidBegin, object: nil)
@@ -72,11 +75,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
func saveState() {
- // TODO: save width of split view and anything else that should be saved.
-
-
+ saveSplitViewState()
}
+
func selectedObjectsInSidebar() -> [AnyObject]? {
return sidebarViewController?.selectedObjects
@@ -86,6 +88,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
@objc func applicationWillTerminate(_ note: Notification) {
+ saveState()
window?.saveFrame(usingName: windowAutosaveName)
}
@@ -483,5 +486,39 @@ private extension MainWindowController {
window?.title = "\(appDelegate.appName!) (\(unreadCount))"
}
}
+
+ func saveSplitViewState() {
+
+ // TODO: Update this for multiple windows.
+
+ guard let splitView = splitViewController?.splitView else {
+ return
+ }
+
+ let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
+ AppDefaults.shared.mainWindowWidths = widths
+ }
+
+ func restoreSplitViewState() {
+
+ // TODO: Update this for multiple windows.
+
+ guard let splitView = splitViewController?.splitView, let widths = AppDefaults.shared.mainWindowWidths, widths.count == 3, let window = window else {
+ return
+ }
+
+ let windowWidth = Int(floor(window.frame.width))
+ let dividerThickness: Int = Int(splitView.dividerThickness)
+ let sidebarWidth: Int = widths[0]
+ let timelineWidth: Int = widths[1]
+
+ // Make sure the detail view has its mimimum thickness, at least.
+ if windowWidth < sidebarWidth + dividerThickness + timelineWidth + dividerThickness + MainWindowController.detailViewMinimumThickness {
+ return
+ }
+
+ splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0)
+ splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1)
+ }
}
From 2066f598f186e9adfebbea4096d14fbfbd0172eb Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 26 Feb 2018 22:32:01 -0800
Subject: [PATCH 49/52] Bump version.
---
Evergreen/Info.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Evergreen/Info.plist b/Evergreen/Info.plist
index 0a1b72e5f..2813c9da5 100644
--- a/Evergreen/Info.plist
+++ b/Evergreen/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0d41
+ 1.0d42
CFBundleVersion
522
LSMinimumSystemVersion
From 9935df1a0df50b6a2750cca767697783f5d84e55 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 26 Feb 2018 22:41:23 -0800
Subject: [PATCH 50/52] Update appcast.
---
Appcasts/evergreen-beta.xml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Appcasts/evergreen-beta.xml b/Appcasts/evergreen-beta.xml
index 3b6ccd8f7..bd90e1f10 100755
--- a/Appcasts/evergreen-beta.xml
+++ b/Appcasts/evergreen-beta.xml
@@ -6,6 +6,18 @@
Most recent Evergreen changes with links to updates.
en
+ -
+ Evergreen 1.0d42
+ The app will now remember and restore the positions of split view dividers between runs. Finally.
+ When a favicon is large, allow it to be drawn large in the detail view, instead of forcing it to be 16 x 16.
+ ]]>
+ Mon, 26 Feb 2018 22:30:00 -0800
+
+ 10.13
+
+
-
Evergreen 1.0d41
Date: Wed, 28 Feb 2018 13:32:29 -0800
Subject: [PATCH 51/52] Start working on finalizing detail view design.
---
Evergreen/MainWindow/Detail/styleSheet.css | 24 +++++++++++++++-------
Evergreen/MainWindow/Detail/template.html | 3 ++-
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/styleSheet.css b/Evergreen/MainWindow/Detail/styleSheet.css
index 9d9599227..0390a7a40 100644
--- a/Evergreen/MainWindow/Detail/styleSheet.css
+++ b/Evergreen/MainWindow/Detail/styleSheet.css
@@ -1,10 +1,10 @@
body {
color: #444;
background-color: white;
- margin-top: 32px;
+ margin-top: 0px;
margin-bottom: 100px;
- margin-left: 64px;
- margin-right: 64px;
+ margin-left: 0px;
+ margin-right: 0px;
font-family: -apple-system;
font-size: 18px;
}
@@ -17,6 +17,10 @@ a, a:link, a:visited {
a:hover {
text-decoration: underline;
}
+.content {
+ margin-left: 64px;
+ margin-right: 64px;
+}
.articleDateline {
color: rgba(0, 0, 0, 0.3);
padding-bottom: 0px;
@@ -50,13 +54,19 @@ a:hover {
margin-bottom: 0;
}
.headerContainer {
- color: rgba(0, 0, 0, 0.3);
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background-color: rgba(66, 66, 73, 1.0);
}
-.headerContainer a:link, .headerContainer a:visited {
- color: rgba(0, 0, 0, 0.3);
+/*.headerContainer a:link, .headerContainer a:visited {*/
+/* color: rgba(1, 1, 1, 0.3);*/
+/*}*/
+.header, .header a:link, .header a:visited {
+ color: rgba(255, 255, 255, 0.75);
}
.header {
- color: rgba(0, 0, 0, 0.3);
+ padding-left: 64px;
+ padding-right: 64px;
}
.headerTable {
width: 100%;
diff --git a/Evergreen/MainWindow/Detail/template.html b/Evergreen/MainWindow/Detail/template.html
index 0a04b9615..bc8954b15 100644
--- a/Evergreen/MainWindow/Detail/template.html
+++ b/Evergreen/MainWindow/Detail/template.html
@@ -8,7 +8,8 @@
+
[[newsitem_title]]
[[date_medium]]
[[newsitem_description]]
-
+
From 8e03ac39b874d026406676c4ca672ae84eb26585 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Wed, 28 Feb 2018 22:38:29 -0800
Subject: [PATCH 52/52] Clean up the css and template for the detail view.
---
.../MainWindow/Detail/ArticleRenderer.swift | 8 +-
Evergreen/MainWindow/Detail/styleSheet.css | 85 ++++++++-----------
Evergreen/MainWindow/Detail/template.html | 11 +--
3 files changed, 41 insertions(+), 63 deletions(-)
diff --git a/Evergreen/MainWindow/Detail/ArticleRenderer.swift b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
index 6f05b0cb5..58d846622 100644
--- a/Evergreen/MainWindow/Detail/ArticleRenderer.swift
+++ b/Evergreen/MainWindow/Detail/ArticleRenderer.swift
@@ -163,12 +163,10 @@ class ArticleRenderer {
var d = [String: String]()
let title = titleOrTitleLink()
- d["newsitem_title"] = title
- d["article_title"] = title
+ d["title"] = title
let body = article.body == nil ? "" : article.body
- d["article_description"] = body
- d["newsitem_description"] = body
+ d["body"] = body
d["avatars"] = ""
var didAddAvatar = false
@@ -462,7 +460,7 @@ class ArticleRenderer {
s += "\n\n