2017-05-27 19:43:27 +02:00
|
|
|
//
|
|
|
|
// TimelineTableCellView.swift
|
2018-08-29 07:18:24 +02:00
|
|
|
// NetNewsWire
|
2017-05-27 19:43:27 +02:00
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 8/31/15.
|
|
|
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2018-02-20 06:49:32 +01:00
|
|
|
import RSCore
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
class TimelineTableCellView: NSTableCellView {
|
|
|
|
|
2018-02-20 06:26:15 +01:00
|
|
|
private let titleView = TimelineTableCellView.multiLineTextField()
|
2018-02-24 07:15:35 +01:00
|
|
|
private let summaryView = TimelineTableCellView.singleLineTextField()
|
|
|
|
private let textView = TimelineTableCellView.multiLineTextField()
|
2018-02-18 07:37:33 +01:00
|
|
|
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
|
2018-02-20 00:56:15 +01:00
|
|
|
private let dateView = TimelineTableCellView.singleLineTextField()
|
|
|
|
private let feedNameView = TimelineTableCellView.singleLineTextField()
|
2018-02-24 08:31:59 +01:00
|
|
|
|
2019-09-17 05:01:30 +02:00
|
|
|
private lazy var avatarView = TimelineAvatarView()
|
2018-02-24 08:31:59 +01:00
|
|
|
|
2019-05-21 12:42:40 +02:00
|
|
|
private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone)
|
2019-09-02 18:13:21 +02:00
|
|
|
private let separatorView = TimelineTableCellView.separatorView()
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2018-02-20 00:56:15 +01:00
|
|
|
private lazy var textFields = {
|
2018-02-24 07:15:35 +01:00
|
|
|
return [self.dateView, self.feedNameView, self.titleView, self.summaryView, self.textView]
|
2018-02-18 07:23:36 +01:00
|
|
|
}()
|
2017-12-30 21:45:10 +01:00
|
|
|
|
2019-09-02 18:13:21 +02:00
|
|
|
private var showsSeparator: Bool = AppDefaults.timelineShowsSeparators {
|
|
|
|
didSet {
|
|
|
|
separatorView.isHidden = !showsSeparator
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-31 21:10:30 +01:00
|
|
|
var cellAppearance: TimelineCellAppearance! {
|
|
|
|
didSet {
|
2019-02-08 07:01:31 +01:00
|
|
|
if cellAppearance != oldValue {
|
|
|
|
updateTextFieldFonts()
|
2019-09-17 05:01:30 +02:00
|
|
|
avatarView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
|
2019-02-08 07:01:31 +01:00
|
|
|
needsLayout = true
|
|
|
|
}
|
2017-12-31 21:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
var cellData: TimelineCellData! {
|
|
|
|
didSet {
|
|
|
|
updateSubviews()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-21 23:08:16 +02:00
|
|
|
var isEmphasized: Bool = false {
|
|
|
|
didSet {
|
|
|
|
unreadIndicatorView.isEmphasized = isEmphasized
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var isSelected: Bool = false {
|
|
|
|
didSet {
|
|
|
|
unreadIndicatorView.isSelected = isSelected
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
override var isFlipped: Bool {
|
|
|
|
return true
|
|
|
|
}
|
2017-11-12 06:53:43 +01:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
override init(frame frameRect: NSRect) {
|
|
|
|
super.init(frame: frameRect)
|
|
|
|
commonInit()
|
|
|
|
}
|
|
|
|
|
2019-02-14 06:33:50 +01:00
|
|
|
required init?(coder: NSCoder) {
|
2017-05-27 19:43:27 +02:00
|
|
|
super.init(coder: coder)
|
|
|
|
commonInit()
|
|
|
|
}
|
2019-02-14 06:33:50 +01:00
|
|
|
|
|
|
|
convenience init() {
|
|
|
|
self.init(frame: NSRect.zero)
|
|
|
|
}
|
2019-09-02 18:13:21 +02:00
|
|
|
|
|
|
|
override func prepareForReuse() {
|
|
|
|
super.prepareForReuse()
|
|
|
|
separatorView.isHidden = !showsSeparator
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineShowsSeparatorsDefaultDidChange() {
|
|
|
|
showsSeparator = AppDefaults.timelineShowsSeparators
|
|
|
|
}
|
2019-02-14 06:33:50 +01:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
override func setFrameSize(_ newSize: NSSize) {
|
|
|
|
|
|
|
|
if newSize == self.frame.size {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
super.setFrameSize(newSize)
|
|
|
|
needsLayout = true
|
|
|
|
}
|
|
|
|
|
|
|
|
override func viewDidMoveToSuperview() {
|
|
|
|
|
|
|
|
updateSubviews()
|
|
|
|
}
|
|
|
|
|
|
|
|
override func layout() {
|
|
|
|
|
|
|
|
resizeSubviews(withOldSize: NSZeroSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
override func resizeSubviews(withOldSize oldSize: NSSize) {
|
|
|
|
|
|
|
|
let layoutRects = updatedLayoutRects()
|
2018-02-24 07:15:35 +01:00
|
|
|
|
|
|
|
setFrame(for: titleView, rect: layoutRects.titleRect)
|
|
|
|
setFrame(for: summaryView, rect: layoutRects.summaryRect)
|
|
|
|
setFrame(for: textView, rect: layoutRects.textRect)
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
|
2018-02-24 07:15:35 +01:00
|
|
|
unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
|
2017-05-27 19:43:27 +02:00
|
|
|
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
|
2019-09-17 05:01:30 +02:00
|
|
|
avatarView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
|
2018-02-18 07:23:36 +01:00
|
|
|
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
|
2019-09-02 18:13:21 +02:00
|
|
|
separatorView.rs_setFrameIfNotEqual(layoutRects.separatorRect)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
2018-02-18 07:37:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Private
|
|
|
|
|
|
|
|
private extension TimelineTableCellView {
|
|
|
|
|
2018-02-20 00:56:15 +01:00
|
|
|
static func singleLineTextField() -> NSTextField {
|
|
|
|
|
|
|
|
let textField = NSTextField(labelWithString: "")
|
|
|
|
textField.usesSingleLineMode = true
|
|
|
|
textField.maximumNumberOfLines = 1
|
|
|
|
textField.isEditable = false
|
|
|
|
textField.lineBreakMode = .byTruncatingTail
|
2018-02-21 07:32:14 +01:00
|
|
|
textField.allowsDefaultTighteningForTruncation = false
|
2018-02-20 00:56:15 +01:00
|
|
|
return textField
|
|
|
|
}
|
|
|
|
|
2018-02-20 06:26:15 +01:00
|
|
|
static func multiLineTextField() -> NSTextField {
|
|
|
|
|
|
|
|
let textField = NSTextField(wrappingLabelWithString: "")
|
|
|
|
textField.usesSingleLineMode = false
|
|
|
|
textField.maximumNumberOfLines = 2
|
|
|
|
textField.isEditable = false
|
|
|
|
textField.cell?.truncatesLastVisibleLine = true
|
2018-02-21 07:32:14 +01:00
|
|
|
textField.allowsDefaultTighteningForTruncation = false
|
2018-02-20 06:26:15 +01:00
|
|
|
return textField
|
|
|
|
}
|
|
|
|
|
2018-02-20 00:56:15 +01:00
|
|
|
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
|
|
|
|
}
|
2019-09-02 18:13:21 +02:00
|
|
|
|
|
|
|
static func separatorView() -> NSView {
|
|
|
|
|
|
|
|
return TimelineSeparatorView(frame: .zero)
|
|
|
|
}
|
2018-02-20 00:56:15 +01:00
|
|
|
|
2018-02-24 07:15:35 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-05 05:53:19 +02:00
|
|
|
func makeTextFieldColorsNormal() {
|
2018-07-18 22:34:44 +02:00
|
|
|
titleView.textColor = NSColor.labelColor
|
|
|
|
feedNameView.textColor = NSColor.secondaryLabelColor
|
|
|
|
dateView.textColor = NSColor.secondaryLabelColor
|
|
|
|
summaryView.textColor = NSColor.secondaryLabelColor
|
|
|
|
textView.textColor = NSColor.labelColor
|
2018-02-20 00:56:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func updateTextFieldFonts() {
|
|
|
|
|
|
|
|
feedNameView.font = cellAppearance.feedNameFont
|
|
|
|
dateView.font = cellAppearance.dateFont
|
2018-02-22 07:48:34 +01:00
|
|
|
titleView.font = cellAppearance.titleFont
|
2018-02-24 07:15:35 +01:00
|
|
|
summaryView.font = cellAppearance.textFont
|
|
|
|
textView.font = cellAppearance.textOnlyFont
|
2018-02-20 00:56:15 +01:00
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func addSubviewAtInit(_ view: NSView, hidden: Bool) {
|
2017-11-12 06:53:43 +01:00
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
addSubview(view)
|
|
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
view.isHidden = hidden
|
|
|
|
}
|
|
|
|
|
|
|
|
func commonInit() {
|
|
|
|
addSubviewAtInit(titleView, hidden: false)
|
2018-02-24 07:15:35 +01:00
|
|
|
addSubviewAtInit(summaryView, hidden: true)
|
|
|
|
addSubviewAtInit(textView, hidden: true)
|
2018-02-18 07:37:33 +01:00
|
|
|
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
|
|
|
addSubviewAtInit(dateView, hidden: false)
|
|
|
|
addSubviewAtInit(feedNameView, hidden: true)
|
2019-09-17 05:01:30 +02:00
|
|
|
addSubviewAtInit(avatarView, hidden: true)
|
2018-02-24 07:15:35 +01:00
|
|
|
addSubviewAtInit(starView, hidden: true)
|
2019-09-02 18:13:21 +02:00
|
|
|
addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators)
|
2019-02-08 07:01:31 +01:00
|
|
|
|
|
|
|
makeTextFieldColorsNormal()
|
2018-02-18 07:37:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func updatedLayoutRects() -> TimelineCellLayout {
|
|
|
|
|
2019-09-17 05:01:30 +02:00
|
|
|
return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarView.image != nil)
|
2018-02-18 07:37:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func updateTitleView() {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2018-02-24 07:15:35 +01:00
|
|
|
updateTextFieldText(titleView, cellData?.title)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateSummaryView() {
|
|
|
|
|
|
|
|
updateTextFieldText(summaryView, cellData?.text)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTextView() {
|
|
|
|
|
|
|
|
updateTextFieldText(textView, cellData?.text)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
2018-02-18 07:37:33 +01:00
|
|
|
|
|
|
|
func updateDateView() {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2018-02-24 07:15:35 +01:00
|
|
|
updateTextFieldText(dateView, cellData.dateString)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTextFieldText(_ textField: NSTextField, _ text: String?) {
|
|
|
|
let s = text ?? ""
|
|
|
|
if textField.stringValue != s {
|
|
|
|
textField.stringValue = s
|
|
|
|
needsLayout = true
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func updateFeedNameView() {
|
2017-05-27 19:43:27 +02:00
|
|
|
if cellData.showFeedName {
|
2018-02-24 07:15:35 +01:00
|
|
|
showView(feedNameView)
|
|
|
|
updateTextFieldText(feedNameView, cellData.feedName)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
else {
|
2018-02-24 07:15:35 +01:00
|
|
|
hideView(feedNameView)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func updateUnreadIndicator() {
|
2018-02-24 07:15:35 +01:00
|
|
|
showOrHideView(unreadIndicatorView, cellData.read || cellData.starred)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func updateStarView() {
|
2018-02-24 07:15:35 +01:00
|
|
|
showOrHideView(starView, !cellData.starred)
|
2018-02-18 07:23:36 +01:00
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func updateAvatar() {
|
2019-05-18 21:21:35 +02:00
|
|
|
guard let image = cellData.avatar, cellData.showAvatar else {
|
2018-02-24 08:31:59 +01:00
|
|
|
makeAvatarEmpty()
|
2018-01-05 06:20:09 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-17 05:01:30 +02:00
|
|
|
showView(avatarView)
|
|
|
|
if avatarView.image !== image {
|
|
|
|
avatarView.image = image
|
2018-02-24 08:31:59 +01:00
|
|
|
needsLayout = true
|
2017-12-31 21:10:30 +01:00
|
|
|
}
|
2018-02-24 08:31:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func makeAvatarEmpty() {
|
2019-09-17 05:01:30 +02:00
|
|
|
if avatarView.image != nil {
|
|
|
|
avatarView.image = nil
|
2018-02-24 08:31:59 +01:00
|
|
|
needsLayout = true
|
2017-12-31 21:10:30 +01:00
|
|
|
}
|
2019-09-17 05:01:30 +02:00
|
|
|
hideView(avatarView)
|
2017-11-29 06:39:09 +01:00
|
|
|
}
|
|
|
|
|
2018-02-24 07:15:35 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-02-18 07:37:33 +01:00
|
|
|
func updateSubviews() {
|
2017-05-27 19:43:27 +02:00
|
|
|
updateTitleView()
|
2018-02-24 07:15:35 +01:00
|
|
|
updateSummaryView()
|
|
|
|
updateTextView()
|
2017-05-27 19:43:27 +02:00
|
|
|
updateDateView()
|
|
|
|
updateFeedNameView()
|
|
|
|
updateUnreadIndicator()
|
2018-02-18 07:23:36 +01:00
|
|
|
updateStarView()
|
2017-11-29 06:39:09 +01:00
|
|
|
updateAvatar()
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
}
|
2019-09-02 18:13:21 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|