2017-05-27 19:43:27 +02:00
|
|
|
//
|
|
|
|
// TimelineCellLayout.swift
|
|
|
|
// Evergreen
|
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 2/6/16.
|
|
|
|
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
import RSTextDrawing
|
|
|
|
import RSCore
|
|
|
|
|
|
|
|
struct TimelineCellLayout {
|
|
|
|
|
|
|
|
let width: CGFloat
|
|
|
|
let height: CGFloat
|
|
|
|
let faviconRect: NSRect
|
|
|
|
let feedNameRect: NSRect
|
|
|
|
let dateRect: NSRect
|
|
|
|
let titleRect: NSRect
|
|
|
|
let unreadIndicatorRect: NSRect
|
2017-11-27 22:16:08 +01:00
|
|
|
let avatarImageRect: NSRect
|
2017-05-27 19:43:27 +02:00
|
|
|
let paddingBottom: CGFloat
|
|
|
|
|
2017-11-27 22:16:08 +01:00
|
|
|
init(width: CGFloat, faviconRect: NSRect, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, unreadIndicatorRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
self.width = width
|
|
|
|
self.faviconRect = faviconRect
|
|
|
|
self.feedNameRect = feedNameRect
|
|
|
|
self.dateRect = dateRect
|
|
|
|
self.titleRect = titleRect
|
|
|
|
self.unreadIndicatorRect = unreadIndicatorRect
|
2017-11-27 22:16:08 +01:00
|
|
|
self.avatarImageRect = avatarImageRect
|
2017-05-27 19:43:27 +02:00
|
|
|
self.paddingBottom = paddingBottom
|
|
|
|
|
2017-12-31 21:08:25 +01:00
|
|
|
var height = max(0, faviconRect.maxY)
|
|
|
|
height = max(height, feedNameRect.maxY)
|
|
|
|
height = max(height, dateRect.maxY)
|
|
|
|
height = max(height, titleRect.maxY)
|
|
|
|
height = max(height, unreadIndicatorRect.maxY)
|
|
|
|
height = max(height, avatarImageRect.maxY)
|
|
|
|
height += paddingBottom
|
2017-05-27 19:43:27 +02:00
|
|
|
self.height = height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func rectForDate(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance, _ titleRect: NSRect) -> NSRect {
|
|
|
|
|
|
|
|
let renderer = RSSingleLineRenderer(attributedTitle: cellData.attributedDateString)
|
|
|
|
var r = NSZeroRect
|
|
|
|
r.size = renderer.size
|
|
|
|
|
|
|
|
r.origin.y = NSMaxY(titleRect) + appearance.titleBottomMargin
|
|
|
|
r.origin.x = appearance.boxLeftMargin
|
|
|
|
|
2017-12-03 07:18:43 +01:00
|
|
|
r.size.width = min(width - (r.origin.x + appearance.cellPadding.right), r.size.width)
|
|
|
|
r.size.width = max(r.size.width, 0.0)
|
2017-11-29 06:39:09 +01:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2017-12-30 21:45:10 +01:00
|
|
|
private func rectForFeedName(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance, _ dateRect: NSRect) -> NSRect {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
if !cellData.showFeedName {
|
|
|
|
return NSZeroRect
|
|
|
|
}
|
|
|
|
|
|
|
|
let renderer = RSSingleLineRenderer(attributedTitle: cellData.attributedFeedName)
|
|
|
|
var r = NSZeroRect
|
|
|
|
r.size = renderer.size
|
2017-12-30 21:45:10 +01:00
|
|
|
r.origin.y = NSMaxY(dateRect) + appearance.titleBottomMargin
|
2017-05-27 19:43:27 +02:00
|
|
|
r.origin.x = appearance.boxLeftMargin
|
|
|
|
|
2017-12-31 21:08:25 +01:00
|
|
|
r.size.width = max(0, width - (r.origin.x + appearance.cellPadding.right))
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2017-12-30 21:45:10 +01:00
|
|
|
private func rectForFavicon(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ feedNameRect: NSRect, _ unreadIndicatorRect: NSRect) -> NSRect {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
guard let _ = cellData.favicon, cellData.showFeedName else {
|
|
|
|
return NSZeroRect
|
|
|
|
}
|
|
|
|
|
|
|
|
var r = NSZeroRect
|
|
|
|
r.size = appearance.faviconSize
|
2017-12-30 21:45:10 +01:00
|
|
|
r.origin.y = feedNameRect.origin.y
|
2017-05-27 19:43:27 +02:00
|
|
|
|
2017-12-30 21:45:10 +01:00
|
|
|
r = RSRectCenteredHorizontallyInRect(r, unreadIndicatorRect)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
private func rectsForTitle(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance) -> (NSRect, NSRect) {
|
|
|
|
|
|
|
|
var r = NSZeroRect
|
|
|
|
r.origin.x = appearance.boxLeftMargin
|
|
|
|
r.origin.y = appearance.cellPadding.top
|
|
|
|
|
|
|
|
let textWidth = width - (r.origin.x + appearance.cellPadding.right)
|
|
|
|
let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
|
|
|
|
|
|
|
|
let measurements = renderer.measurements(forWidth: textWidth)
|
|
|
|
r.size = NSSize(width: textWidth, height: CGFloat(measurements.height))
|
2017-11-29 06:39:09 +01:00
|
|
|
r.size.width = max(r.size.width, 0.0)
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
var rline1 = r
|
|
|
|
rline1.size.height = CGFloat(measurements.heightOfFirstLine)
|
|
|
|
|
|
|
|
return (r, rline1)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func rectForUnreadIndicator(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
|
|
|
|
|
|
|
|
var r = NSZeroRect
|
|
|
|
r.size = NSSize(width: appearance.unreadCircleDimension, height: appearance.unreadCircleDimension)
|
|
|
|
r.origin.x = appearance.cellPadding.left
|
|
|
|
r = RSRectCenteredVerticallyInRect(r, titleLine1Rect)
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2017-11-29 06:39:09 +01:00
|
|
|
private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
|
|
|
|
|
|
|
|
var r = NSRect.zero
|
|
|
|
r.size = appearance.avatarSize
|
|
|
|
r.origin.x = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
|
|
|
|
r.origin.y = titleLine1Rect.minY + appearance.avatarAdjustmentTop
|
2017-11-27 22:16:08 +01:00
|
|
|
|
2017-11-29 06:39:09 +01:00
|
|
|
return r
|
2017-11-27 22:16:08 +01:00
|
|
|
}
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
func timelineCellLayout(_ width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> TimelineCellLayout {
|
|
|
|
|
|
|
|
let (titleRect, titleLine1Rect) = rectsForTitle(cellData, width, appearance)
|
|
|
|
let dateRect = rectForDate(cellData, width, appearance, titleRect)
|
2017-12-30 21:45:10 +01:00
|
|
|
let feedNameRect = rectForFeedName(cellData, width, appearance, dateRect)
|
2017-05-27 19:43:27 +02:00
|
|
|
let unreadIndicatorRect = rectForUnreadIndicator(cellData, appearance, titleLine1Rect)
|
2017-12-30 21:45:10 +01:00
|
|
|
let faviconRect = rectForFavicon(cellData, appearance, feedNameRect, unreadIndicatorRect)
|
2017-11-29 06:39:09 +01:00
|
|
|
let avatarImageRect = rectForAvatar(cellData, appearance, titleLine1Rect)
|
2017-11-27 22:16:08 +01:00
|
|
|
|
|
|
|
return TimelineCellLayout(width: width, faviconRect: faviconRect, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, avatarImageRect: avatarImageRect, paddingBottom: appearance.cellPadding.bottom)
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func timelineCellHeight(_ width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
|
|
|
|
|
|
|
let layout = timelineCellLayout(width, cellData: cellData, appearance: appearance)
|
|
|
|
return layout.height
|
|
|
|
}
|