feat: improve responsiveness of showing the switcher

This commit is contained in:
lwouis committed 2025-11-09 09:40:06 +01:00
1 parent fab9d7c0ab
commit 689adc0a30
4 files changed
+40 -17

No files matched your search

+3 -3
View File
@@ -314,7 +314,7 @@ class Windows {
}
// dispatch screenshot requests off the main-thread, then wait for completion
static func refreshThumbnails(_ windows: [Window], _ source: RefreshCausedBy) {
static func refreshThumbnailsAsync(_ windows: [Window], _ source: RefreshCausedBy) {
var eligibleWindows = [Window]()
for window in windows {
if !window.isWindowlessApp, let cgWindowId = window.cgWindowId, cgWindowId != CGWindowID(bitPattern: -1) {
@@ -322,10 +322,10 @@ class Windows {
}
}
if eligibleWindows.isEmpty { return }
screenshotEligibleWindowsAndRefreshUi(eligibleWindows, source)
screenshotEligibleWindowsAndUpdateUi(eligibleWindows, source)
}
private static func screenshotEligibleWindowsAndRefreshUi(_ eligibleWindows: [Window], _ source: RefreshCausedBy) {
private static func screenshotEligibleWindowsAndUpdateUi(_ eligibleWindows: [Window], _ source: RefreshCausedBy) {
for window in eligibleWindows {
BackgroundWork.screenshotsQueue.addOperation { [weak window] in
if source == .refreshOnlyThumbnailsAfterShowUi && !App.app.appIsBeingUsed { return }
+3 -10
View File
@@ -206,7 +206,7 @@ class App: AppCenterApplication {
if !windowsToScreenshot.isEmpty && SystemPermissions.screenRecordingPermission == .granted
&& !Preferences.onlyShowApplications()
&& (!Appearance.hideThumbnails || Preferences.previewFocusedWindow) {
Windows.refreshThumbnails(windowsToScreenshot, source)
Windows.refreshThumbnailsAsync(windowsToScreenshot, source)
if source == .refreshOnlyThumbnailsAfterShowUi { return }
}
guard appIsBeingUsed else { return }
@@ -216,12 +216,7 @@ class App: AppCenterApplication {
guard appIsBeingUsed else { return }
Windows.updateFocusedWindowIndex()
guard appIsBeingUsed else { return }
thumbnailsPanel.thumbnailsView.updateItemsAndLayout()
guard appIsBeingUsed else { return }
thumbnailsPanel.setContentSize(thumbnailsPanel.thumbnailsView.contentView.frame.size)
thumbnailsPanel.display()
guard appIsBeingUsed else { return }
NSScreen.preferred.repositionPanel(thumbnailsPanel)
thumbnailsPanel.updateContents()
guard appIsBeingUsed else { return }
Windows.voiceOverWindow() // at this point ThumbnailViews are assigned to the window, and ready
guard appIsBeingUsed else { return }
@@ -267,12 +262,10 @@ class App: AppCenterApplication {
guard appIsBeingUsed else { return }
Appearance.update()
guard appIsBeingUsed else { return }
thumbnailsPanel.makeKeyAndOrderFront(nil) // workaround: without this, switching between 2 screens make thumbnailPanel invisible
KeyRepeatTimer.toggleRepeatingKeyNextWindow()
guard appIsBeingUsed else { return }
refreshOpenUi([], .showUi)
guard appIsBeingUsed else { return }
thumbnailsPanel.show()
KeyRepeatTimer.toggleRepeatingKeyNextWindow()
refreshOpenUi(Windows.list, .refreshOnlyThumbnailsAfterShowUi)
}
+23 -4
View File
@@ -13,27 +13,46 @@ class LightImageView: NSView {
super.init(frame: frameRect)
translatesAutoresizingMaskIntoConstraints = false
wantsLayer = true
// configure the layer for efficient GPU-scaling
layer!.contentsGravity = .resize
layer!.magnificationFilter = .trilinear
layer!.minificationFilter = .trilinear
layer!.minificationFilterBias = 0.0
layer!.shouldRasterize = false
// disable implicit animations
layer!.actions = [
"contents": NSNull(),
"bounds": NSNull(),
"position": NSNull(),
"contentsScale": NSNull()
]
}
func updateWithResizedCopy(_ image: CGImage?, _ size: NSSize) {
let scaleFactor = NSScreen.preferred.backingScaleFactor
if let image {
CATransaction.begin()
// disable implicit fade-in animation from CALayer
CATransaction.setDisableActions(true)
// alternatively, we could set layer!.contentsGravity to .center, and use the lines bellow to resize ourselves
// let scaledSize = NSSize(width: size.width * scaleFactor, height: size.height * scaleFactor)
// layer!.contents = image.resizedCopyWithCoreGraphics(scaledSize, fixBitmapInfo)
// it would produce subjectively better quality, but the resizing would be done on the CPU so poor performance
layer!.contents = image
layer!.contentsScale = scaleFactor
CATransaction.commit()
}
frame.size = size
}
/// this schedules the image update for the next cycle
/// The Panel will show with previous pictures, then they each will get updated quickly. It's a trade-off of getting AltTab interactive faster at the price of wrong data being shown
/// I feel the UX is better without it. I may reconsider
func updateWithResizedCopyAsync(_ image: CGImage?, _ size: NSSize) {
layer!.contentsScale = NSScreen.preferred.backingScaleFactor
frame.size = size
if let image {
// set image on another cycle so it doesn't block initial rendering
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.layer!.contents = image
}
}
}
}
+11
View File
@@ -30,6 +30,17 @@ class ThumbnailsPanel: NSPanel {
appearance = NSAppearance(named: Appearance.currentTheme == .dark ? .vibrantDark : .vibrantLight)
}
func updateContents() {
CATransaction.begin()
defer { CATransaction.commit() }
CATransaction.setDisableActions(true)
thumbnailsView.updateItemsAndLayout()
guard App.app.appIsBeingUsed else { return }
setContentSize(thumbnailsView.contentView.frame.size)
guard App.app.appIsBeingUsed else { return }
NSScreen.preferred.repositionPanel(self)
}
override func orderOut(_ sender: Any?) {
if Preferences.fadeOutAnimation {
NSAnimationContext.runAnimationGroup(