perf: cache expensive layout values
This commit is contained in:
1 parent
1f3a653faa
commit
904bd17c53
3 files changed
+36
-18
No files matched your search
@@ -2,6 +2,7 @@ import Cocoa
|
||||
|
||||
class ThumbnailTitleView: NSTextField {
|
||||
private var currentWidth: CGFloat = -1
|
||||
private var widthConstraint: NSLayoutConstraint?
|
||||
|
||||
convenience init(font: NSFont) {
|
||||
self.init(labelWithString: "")
|
||||
@@ -21,15 +22,13 @@ class ThumbnailTitleView: NSTextField {
|
||||
guard currentWidth != width else { return }
|
||||
currentWidth = width
|
||||
frame.size.width = width
|
||||
// TODO: NSTextField does some internal magic, and ends up with constraints.
|
||||
// we can't use addOrUpdateConstraint for some reason, otherwise it will only actually apply after the UI is shown twice
|
||||
// i tried everything and ended up removing all constraints then adding a fresh one. This seems to work
|
||||
let toRemove = constraints.filter {
|
||||
($0.firstItem as? NSView) === self && $0.firstAttribute == .width ||
|
||||
($0.secondItem as? NSView) === self && $0.secondAttribute == .width
|
||||
if let widthConstraint {
|
||||
widthConstraint.constant = width
|
||||
} else {
|
||||
let constraint = widthAnchor.constraint(equalToConstant: width)
|
||||
constraint.isActive = true
|
||||
widthConstraint = constraint
|
||||
}
|
||||
toRemove.forEach { removeConstraint($0) }
|
||||
widthAnchor.constraint(equalToConstant: width).isActive = true
|
||||
}
|
||||
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
|
||||
@@ -4,6 +4,7 @@ class ThumbnailView: FlippedView {
|
||||
static let noOpenWindowToolTip = NSLocalizedString("App is running but has no open window", comment: "")
|
||||
// when calculating the width of a nstextfield, somehow we need to add this suffix to get the correct width
|
||||
static let extraTextForPadding = "lmnopqrstuvw"
|
||||
|
||||
var window_: Window?
|
||||
var thumbnail = LightImageView(withTransparencyChecks: true)
|
||||
var appIcon = LightImageView()
|
||||
@@ -310,7 +311,7 @@ class ThumbnailView: FlippedView {
|
||||
}
|
||||
}
|
||||
let xPosition = -leftOffset
|
||||
let height = label.fittingSize.height
|
||||
let height = ThumbnailsView.layoutCache.labelHeight
|
||||
let yPosition = hStackView.frame.origin.y + hStackView.frame.height + Appearance.intraCellPadding * 2
|
||||
label.frame = NSRect(x: xPosition, y: yPosition, width: effectiveLabelWidth, height: height)
|
||||
label.setWidth(effectiveLabelWidth)
|
||||
@@ -379,7 +380,7 @@ class ThumbnailView: FlippedView {
|
||||
assignIfDifferent(&hStackView.frame.size, NSSize(width: appIcon.frame.width, height: appIcon.frame.height))
|
||||
} else {
|
||||
assignIfDifferent(&vStackView.frame.size, NSSize(width: frame.width, height: frame.height))
|
||||
assignIfDifferent(&hStackView.frame.size, NSSize(width: frame.width - Appearance.edgeInsetsSize * 2, height: max(appIcon.frame.height, label.fittingSize.height)))
|
||||
assignIfDifferent(&hStackView.frame.size, NSSize(width: frame.width - Appearance.edgeInsetsSize * 2, height: max(appIcon.frame.height, ThumbnailsView.layoutCache.labelHeight)))
|
||||
let labelWidth = hStackView.frame.width - appIcon.frame.width - Appearance.appIconLabelSpacing - indicatorsSpace()
|
||||
label.setWidth(labelWidth)
|
||||
}
|
||||
@@ -391,12 +392,12 @@ class ThumbnailView: FlippedView {
|
||||
assignIfDifferent(&appIcon.frame.origin.x, App.shared.userInterfaceLayoutDirection == .leftToRight
|
||||
? 0
|
||||
: hStackView.frame.width - appIcon.frame.width)
|
||||
let iconWidth = windowIndicatorIcons.first!.fittingSize.width
|
||||
let iconWidth = ThumbnailsView.layoutCache.iconWidth
|
||||
var indicatorSpace = CGFloat(0)
|
||||
for icon in windowIndicatorIcons {
|
||||
if !icon.isHidden {
|
||||
indicatorSpace += iconWidth
|
||||
icon.centerFrameInParent(y: true)
|
||||
assignIfDifferent(&icon.frame.origin.y, ((hStackView.frame.height - ThumbnailsView.layoutCache.iconHeight) / 2).rounded())
|
||||
assignIfDifferent(&icon.frame.origin.x, App.shared.userInterfaceLayoutDirection == .leftToRight
|
||||
? hStackView.frame.width - indicatorSpace
|
||||
: indicatorSpace - iconWidth)
|
||||
@@ -406,7 +407,7 @@ class ThumbnailView: FlippedView {
|
||||
assignIfDifferent(&label.frame.origin.x, App.shared.userInterfaceLayoutDirection == .leftToRight
|
||||
? appIcon.frame.maxX + Appearance.appIconLabelSpacing
|
||||
: hStackView.frame.width - appIcon.frame.width - Appearance.appIconLabelSpacing - labelWidth)
|
||||
label.centerFrameInParent(y: true)
|
||||
assignIfDifferent(&label.frame.origin.y, ((hStackView.frame.height - ThumbnailsView.layoutCache.labelHeight) / 2).rounded())
|
||||
}
|
||||
if Preferences.appearanceStyle == .thumbnails {
|
||||
assignIfDifferent(&thumbnail.frame.origin, NSPoint(x: Appearance.edgeInsetsSize, y: hStackView.frame.maxY + Appearance.intraCellPadding))
|
||||
@@ -438,8 +439,8 @@ class ThumbnailView: FlippedView {
|
||||
// we set dockLabelIcon origin, without checking if .isHidden
|
||||
// This is because its updated async. We need it positioned correctly always
|
||||
let (offsetX, offsetY) = dockLabelOffset()
|
||||
assignIfDifferent(&dockLabelIcon.frame.origin.x, appIcon.frame.maxX - (dockLabelIcon.fittingSize.width * offsetX).rounded())
|
||||
assignIfDifferent(&dockLabelIcon.frame.origin.y, appIcon.frame.maxY - (dockLabelIcon.fittingSize.height * offsetY).rounded())
|
||||
assignIfDifferent(&dockLabelIcon.frame.origin.x, appIcon.frame.maxX - (ThumbnailsView.layoutCache.dockLabelSize.width * offsetX).rounded())
|
||||
assignIfDifferent(&dockLabelIcon.frame.origin.y, appIcon.frame.maxY - (ThumbnailsView.layoutCache.dockLabelSize.height * offsetY).rounded())
|
||||
}
|
||||
|
||||
/// positioning the dock label is messy because it's an NSTextField so it's visual size doesn't match what we can through APIs
|
||||
@@ -469,8 +470,7 @@ class ThumbnailView: FlippedView {
|
||||
}
|
||||
|
||||
private func indicatorsSpace() -> CGFloat {
|
||||
let iconWidth = windowIndicatorIcons.first!.fittingSize.width
|
||||
return CGFloat(windowIndicatorIcons.filter { !$0.isHidden }.count) * iconWidth
|
||||
return CGFloat(windowIndicatorIcons.filter { !$0.isHidden }.count) * ThumbnailsView.layoutCache.iconWidth
|
||||
}
|
||||
|
||||
private func getAppOrAndWindowTitle() -> String {
|
||||
|
||||
@@ -8,11 +8,22 @@ class ThumbnailsView {
|
||||
static var recycledViews = [ThumbnailView]()
|
||||
static var thumbnailsWidth = CGFloat(0.0)
|
||||
static var thumbnailsHeight = CGFloat(0.0)
|
||||
static var layoutCache = LayoutCache()
|
||||
|
||||
init() {
|
||||
updateBackgroundView()
|
||||
// TODO: think about this optimization more
|
||||
(1...20).forEach { _ in ThumbnailsView.recycledViews.append(ThumbnailView()) }
|
||||
Self.updateCachedSizes()
|
||||
}
|
||||
|
||||
static func updateCachedSizes() {
|
||||
guard let firstView = ThumbnailsView.recycledViews.first else { return }
|
||||
layoutCache.labelHeight = firstView.label.fittingSize.height
|
||||
let iconFittingSize = firstView.windowIndicatorIcons.first!.fittingSize
|
||||
layoutCache.iconWidth = iconFittingSize.width
|
||||
layoutCache.iconHeight = iconFittingSize.height
|
||||
layoutCache.dockLabelSize = firstView.dockLabelIcon.fittingSize
|
||||
}
|
||||
|
||||
func updateBackgroundView() {
|
||||
@@ -39,6 +50,7 @@ class ThumbnailsView {
|
||||
for i in 0..<ThumbnailsView.recycledViews.count {
|
||||
ThumbnailsView.recycledViews[i] = ThumbnailView()
|
||||
}
|
||||
Self.updateCachedSizes()
|
||||
}
|
||||
|
||||
static func highlight(_ indexInRecycledViews: Int) {
|
||||
@@ -116,7 +128,7 @@ class ThumbnailsView {
|
||||
}
|
||||
|
||||
private func layoutThumbnailViews(_ widthMax: CGFloat) -> (CGFloat, CGFloat, CGFloat, [Int])? {
|
||||
let labelHeight = ThumbnailsView.recycledViews.first!.label.fittingSize.height
|
||||
let labelHeight = Self.layoutCache.labelHeight
|
||||
let height = ThumbnailView.height(labelHeight)
|
||||
let isLeftToRight = App.shared.userInterfaceLayoutDirection == .leftToRight
|
||||
let startingX = isLeftToRight ? Appearance.interCellPadding : widthMax - Appearance.interCellPadding
|
||||
@@ -247,6 +259,13 @@ class ThumbnailsView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutCache {
|
||||
var labelHeight = CGFloat(0)
|
||||
var iconWidth = CGFloat(0)
|
||||
var iconHeight = CGFloat(0)
|
||||
var dockLabelSize = NSSize.zero
|
||||
}
|
||||
}
|
||||
|
||||
class ScrollView: NSScrollView {
|
||||
|
||||
Reference in new issue
Block a user