Switch to using NSTextField for timeline date and feed name views.

This commit is contained in:
Brent Simmons 2018-02-19 15:56:15 -08:00
parent 01b5292e3d
commit f11604df48
6 changed files with 135 additions and 38 deletions

View File

@ -140,6 +140,7 @@
84D5BA20201E8FB6009092BD /* SidebarGearMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */; }; 84D5BA20201E8FB6009092BD /* SidebarGearMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */; };
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; }; 84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
84DAEE321F870B390058304B /* DockBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE311F870B390058304B /* DockBadge.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 */; }; 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.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 */; }; 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 = "<group>"; }; 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarGearMenuDelegate.swift; sourceTree = "<group>"; };
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; }; 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; }; 84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = "<group>"; };
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDefaults.swift; path = Evergreen/AppDefaults.swift; sourceTree = "<group>"; }; 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDefaults.swift; path = Evergreen/AppDefaults.swift; sourceTree = "<group>"; };
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = "<group>"; }; 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = "<group>"; };
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = "<group>"; }; 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = "<group>"; };
@ -1008,6 +1010,7 @@
849A97741ED9EC04007D329B /* TimelineTableCellView.swift */, 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */,
849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */, 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */,
849A97721ED9EC04007D329B /* TimelineCellLayout.swift */, 849A97721ED9EC04007D329B /* TimelineCellLayout.swift */,
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */,
849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */,
849A97731ED9EC04007D329B /* TimelineStringUtilities.swift */, 849A97731ED9EC04007D329B /* TimelineStringUtilities.swift */,
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
@ -2001,6 +2004,7 @@
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */, 849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */, D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */,
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */, 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */,
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */,
8414AD251FCF5A1E00955102 /* TimelineHeaderView.swift in Sources */, 8414AD251FCF5A1E00955102 /* TimelineHeaderView.swift in Sources */,
849EE71F20391DF20082A1EA /* MainWindowToolbarDelegate.swift in Sources */, 849EE71F20391DF20082A1EA /* MainWindowToolbarDelegate.swift in Sources */,
849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */, 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */,

View File

@ -28,8 +28,10 @@ struct SidebarCellLayout {
} }
self.faviconRect = rFavicon self.faviconRect = rFavicon
textField.sizeToFit() // textField.sizeToFit()
let textFieldSize = textField.frame.size // 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) var rTextField = NSRect(x: 0.0, y: 0.0, width: textFieldSize.width, height: textFieldSize.height)
if shouldShowImage { if shouldShowImage {
rTextField.origin.x = NSMaxX(rFavicon) + appearance.imageMarginRight rTextField.origin.x = NSMaxX(rFavicon) + appearance.imageMarginRight

View File

@ -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)
}
}

View File

@ -107,13 +107,15 @@ private extension TimelineCellLayout {
static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ attributedString: NSAttributedString) -> NSRect { 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 var r = NSZeroRect
r.size = renderer.size r.size = textFieldSize
r.origin.y = NSMaxY(rectAbove) + topMargin r.origin.y = NSMaxY(rectAbove) + topMargin
r.origin.x = textBoxRect.origin.x r.origin.x = textBoxRect.origin.x
var width = renderer.size.width var width = textFieldSize.width
width = min(width, textBoxRect.size.width) width = min(width, textBoxRect.size.width)
width = max(width, 0.0) width = max(width, 0.0)
r.size.width = width r.size.width = width

View File

@ -13,29 +13,18 @@ class TimelineTableCellView: NSTableCellView {
private let titleView = RSMultiLineView(frame: NSZeroRect) private let titleView = RSMultiLineView(frame: NSZeroRect)
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect) private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
private let dateView = RSSingleLineView(frame: NSZeroRect) private let dateView = TimelineTableCellView.singleLineTextField()
private let feedNameView = RSSingleLineView(frame: NSZeroRect) 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 = { private lazy var textFields = {
let imageView = NSImageView(frame: NSRect.zero) return [self.dateView, self.feedNameView]
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
}() }()
var cellAppearance: TimelineCellAppearance! { var cellAppearance: TimelineCellAppearance! {
didSet { didSet {
updateTextFields()
needsLayout = true needsLayout = true
} }
} }
@ -60,20 +49,18 @@ class TimelineTableCellView: NSTableCellView {
var isEmphasized = false { var isEmphasized = false {
didSet { didSet {
dateView.emphasized = isEmphasized
feedNameView.emphasized = isEmphasized
titleView.emphasized = isEmphasized titleView.emphasized = isEmphasized
unreadIndicatorView.isEmphasized = isEmphasized unreadIndicatorView.isEmphasized = isEmphasized
updateTextFieldColors()
needsDisplay = true needsDisplay = true
} }
} }
var isSelected = false { var isSelected = false {
didSet { didSet {
dateView.selected = isSelected
feedNameView.selected = isSelected
titleView.selected = isSelected titleView.selected = isSelected
unreadIndicatorView.isSelected = isSelected unreadIndicatorView.isSelected = isSelected
updateTextFieldColors()
needsDisplay = true needsDisplay = true
} }
} }
@ -142,6 +129,48 @@ class TimelineTableCellView: NSTableCellView {
private extension TimelineTableCellView { 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) { func addSubviewAtInit(_ view: NSView, hidden: Bool) {
addSubview(view) addSubview(view)
@ -184,7 +213,7 @@ private extension TimelineTableCellView {
func updateDateView() { func updateDateView() {
dateView.attributedStringValue = cellData.attributedDateString dateView.stringValue = cellData.attributedDateString.string
needsLayout = true needsLayout = true
} }
@ -194,7 +223,7 @@ private extension TimelineTableCellView {
if feedNameView.isHidden { if feedNameView.isHidden {
feedNameView.isHidden = false feedNameView.isHidden = false
} }
feedNameView.attributedStringValue = cellData.attributedFeedName feedNameView.stringValue = cellData.attributedFeedName.string
} }
else { else {
if !feedNameView.isHidden { if !feedNameView.isHidden {

View File

@ -64,17 +64,17 @@ class TimelineTableRowView : NSTableRowView {
path.stroke() path.stroke()
} }
override func draw(_ dirtyRect: NSRect) { // override func draw(_ dirtyRect: NSRect) {
//
super.draw(dirtyRect) // super.draw(dirtyRect)
//
if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected { // if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
drawSeparator(in: dirtyRect) // drawSeparator(in: dirtyRect)
} // }
} // }
func invalidateGridRect() { func invalidateGridRect() {
setNeedsDisplay(gridRect) // setNeedsDisplay(gridRect)
} }
} }