From f11604df4843f04784efb2119fc2fec761da89fe Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 19 Feb 2018 15:56:15 -0800 Subject: [PATCH] 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) } }