From c8920ca5a676d91592239d0e782e0345b79b663d Mon Sep 17 00:00:00 2001 From: Jim Correia Date: Mon, 2 Sep 2019 09:13:21 -0700 Subject: [PATCH] Added optional separators in the timeline view. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timeline now optionally includes "Mail style" separators (behind a hidden default — "CorreiaSeparators" — which defaults to NO). --- Mac/AppDefaults.swift | 18 +++++++ .../Timeline/Cell/TimelineCellLayout.swift | 11 +++- .../Timeline/Cell/TimelineTableCellView.swift | 52 +++++++++++++++++++ .../Timeline/TimelineViewController.swift | 18 ++++++- .../Contents.json | 31 +++++++++++ 5 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/timelineSeparatorColor.colorset/Contents.json diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 390d55ab6..f4180440b 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -32,6 +32,7 @@ struct AppDefaults { static let exportOPMLAccountID = "exportOPMLAccountID" // Hidden prefs + static let timelineShowsSeparators = "CorreiaSeparators" static let showTitleOnMainWindow = "KafasisTitleMode" static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" } @@ -135,6 +136,10 @@ struct AppDefaults { setSortDirection(for: Key.timelineSortDirection, newValue) } } + + static var timelineShowsSeparators: Bool { + return bool(for: Key.timelineShowsSeparators) + } static var mainWindowWidths: [Int]? { get { @@ -268,4 +273,17 @@ private extension AppDefaults { } } +// MARK: - + +extension UserDefaults { + /// This property exists so that it can conveniently be observed via KVO + @objc var CorreiaSeparators: Bool { + get { + return bool(forKey: AppDefaults.Key.timelineShowsSeparators) + } + set { + set(newValue, forKey: AppDefaults.Key.timelineShowsSeparators) + } + } +} diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift index fcefc3420..6e6430db2 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift @@ -22,9 +22,10 @@ struct TimelineCellLayout { let unreadIndicatorRect: NSRect let starRect: NSRect let avatarImageRect: NSRect + let separatorRect: NSRect let paddingBottom: CGFloat - init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) { + init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) { self.width = width self.feedNameRect = feedNameRect @@ -36,6 +37,7 @@ struct TimelineCellLayout { self.unreadIndicatorRect = unreadIndicatorRect self.starRect = starRect self.avatarImageRect = avatarImageRect + self.separatorRect = separatorRect self.paddingBottom = paddingBottom if height > 0.1 { @@ -73,10 +75,11 @@ struct TimelineCellLayout { let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, showAvatar, textBoxRect, width, height) let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, textBoxRect) let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect) + let separatorRect = TimelineCellLayout.rectForSeparator(cellData, appearance, showAvatar ? avatarImageRect : titleRect, width, height) let paddingBottom = appearance.cellPadding.bottom - self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, 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, separatorRect: separatorRect, paddingBottom: paddingBottom) } static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat { @@ -210,6 +213,10 @@ private extension TimelineCellLayout { return r } + + static func rectForSeparator(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ alignmentRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect { + return NSRect(x: alignmentRect.minX, y: height - 1, width: width - alignmentRect.minX, height: 1) + } } private extension Array where Element == NSRect { diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 01f96f512..680b5e2db 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -27,11 +27,18 @@ class TimelineTableCellView: NSTableCellView { }() private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone) + private let separatorView = TimelineTableCellView.separatorView() private lazy var textFields = { return [self.dateView, self.feedNameView, self.titleView, self.summaryView, self.textView] }() + private var showsSeparator: Bool = AppDefaults.timelineShowsSeparators { + didSet { + separatorView.isHidden = !showsSeparator + } + } + var cellAppearance: TimelineCellAppearance! { didSet { if cellAppearance != oldValue { @@ -77,6 +84,15 @@ class TimelineTableCellView: NSTableCellView { convenience init() { self.init(frame: NSRect.zero) } + + override func prepareForReuse() { + super.prepareForReuse() + separatorView.isHidden = !showsSeparator + } + + func timelineShowsSeparatorsDefaultDidChange() { + showsSeparator = AppDefaults.timelineShowsSeparators + } override func setFrameSize(_ newSize: NSSize) { @@ -111,6 +127,7 @@ class TimelineTableCellView: NSTableCellView { feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect) avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect) starView.rs_setFrameIfNotEqual(layoutRects.starRect) + separatorView.rs_setFrameIfNotEqual(layoutRects.separatorRect) } } @@ -148,6 +165,11 @@ private extension TimelineTableCellView { imageView.imageScaling = scaling return imageView } + + static func separatorView() -> NSView { + + return TimelineSeparatorView(frame: .zero) + } func setFrame(for textField: NSTextField, rect: NSRect) { @@ -193,6 +215,7 @@ private extension TimelineTableCellView { addSubviewAtInit(feedNameView, hidden: true) addSubviewAtInit(avatarImageView, hidden: true) addSubviewAtInit(starView, hidden: true) + addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators) makeTextFieldColorsNormal() } @@ -296,3 +319,32 @@ private extension TimelineTableCellView { updateAvatar() } } + +// MARK: - + +private class TimelineSeparatorView: NSView { + private static let backgroundColor = NSColor(named: "timelineSeparatorColor")! + + override init(frame: NSRect) { + super.init(frame: frame) + self.wantsLayer = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidChangeEffectiveAppearance() { + super.viewDidChangeEffectiveAppearance() + needsDisplay = true + } + + override var wantsUpdateLayer: Bool { + return true + } + + override func updateLayer() { + super.updateLayer() + layer?.backgroundColor = TimelineSeparatorView.backgroundColor.cgColor + } +} diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 58ff207be..ea761cafc 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -141,12 +141,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } private let keyboardDelegate = TimelineKeyboardDelegate() + private var timelineShowsSeparatorsObserver: NSKeyValueObservation? convenience init(delegate: TimelineDelegate) { self.init(nibName: "TimelineTableView", bundle: nil) self.delegate = delegate + self.startObservingUserDefaults() } - + override func viewDidLoad() { cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize) @@ -846,6 +848,19 @@ extension TimelineViewController: NSTableViewDelegate { private extension TimelineViewController { + func startObservingUserDefaults() { + + assert(timelineShowsSeparatorsObserver == nil) + timelineShowsSeparatorsObserver = UserDefaults.standard.observe(\UserDefaults.CorreiaSeparators) { [weak self] (_, _) in + guard let self = self, self.isViewLoaded else { return } + self.tableView.enumerateAvailableRowViews { (rowView, index) in + if let cellView = rowView.view(atColumn: 0) as? TimelineTableCellView { + cellView.timelineShowsSeparatorsDefaultDidChange() + } + } + } + } + @objc func reloadAvailableCells() { if let indexesToReload = tableView.indexesOfAvailableRows() { @@ -1114,4 +1129,3 @@ private extension TimelineViewController { return false } } - diff --git a/Mac/Resources/Assets.xcassets/timelineSeparatorColor.colorset/Contents.json b/Mac/Resources/Assets.xcassets/timelineSeparatorColor.colorset/Contents.json new file mode 100644 index 000000000..fc4ff19b7 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/timelineSeparatorColor.colorset/Contents.json @@ -0,0 +1,31 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "white" : "0.900", + "alpha" : "1.000" + } + } + }, + { + "idiom" : "universal", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "osx", + "reference" : "gridColor" + } + } + ] +} \ No newline at end of file