From da16a0b8f9ef02855e38eef5538eaaf49c8c5861 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Wed, 26 Aug 2020 17:50:58 +0900 Subject: [PATCH] feat: display quickly even with many open windows (closes #171) BREAKING CHANGE: the window thumbnails are now updated *after* the UI is shown. AltTab will first display its window, with the first 3 thumbnails up-to-date, then asynchronously update the rest of the thumbnails one-by-one. This improves the experience of users with lots of windows open. --- src/api-wrappers/HelperExtensions.swift | 2 +- src/logic/Window.swift | 1 + src/logic/Windows.swift | 28 ++++++++++++++++++++++++- src/ui/App.swift | 8 ++++--- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/api-wrappers/HelperExtensions.swift b/src/api-wrappers/HelperExtensions.swift index 6704256b..91bb9f0c 100644 --- a/src/api-wrappers/HelperExtensions.swift +++ b/src/api-wrappers/HelperExtensions.swift @@ -41,7 +41,7 @@ extension NSView { } } -extension Array { +extension Collection { // forEach with each iteration run concurrently on the global queue func forEachAsync(fn: @escaping (Element) -> Void) { let group = DispatchGroup() diff --git a/src/logic/Window.swift b/src/logic/Window.swift index 1b9e4f69..cf9ffbdc 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -42,6 +42,7 @@ class Window { self.position = position self.title = bestEffortTitle(axTitle) self.isTabbed = false + refreshThumbnail() debugPrint("Adding window", cgWindowId, title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") observeEvents() } diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index 9651d6aa..d083d71c 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -5,6 +5,8 @@ class Windows { static var list = [Window]() static var previousFocusedWindowIndex = Int(0) static var focusedWindowIndex = Int(0) + // the first few thumbnails are the most commonly looked at; we pay special attention to them + static let criticalFirstThumbnails = 3 static func updateFocusedWindowIndex(_ newIndex: Int) { previousFocusedWindowIndex = focusedWindowIndex @@ -84,11 +86,35 @@ class Windows { Windows.list = sortedTuples.map { $0.1 } } - static func refreshAllThumbnails() { + static func refreshFirstFewThumbnailsSync() { list.filter { $0.shouldShowTheUser } + .prefix(criticalFirstThumbnails) .forEachAsync { window in window.refreshThumbnail() } } + static func refreshThumbnailsAsync(_ screen: NSScreen, _ currentIndex: Int = criticalFirstThumbnails) { + guard App.app.appIsBeingUsed else { return } + BackgroundWork.mainQueueConcurrentWorkQueue.async { + if currentIndex < list.count { + let window = list[currentIndex] + if window.shouldShowTheUser { + window.refreshThumbnail() + DispatchQueue.main.async { + let view = ThumbnailsView.recycledViews[currentIndex] + if view.thumbnail.image != window.thumbnail { + view.thumbnail.image = window.thumbnail + let (thumbnailWidth, thumbnailHeight) = ThumbnailView.thumbnailSize(window.thumbnail, screen) + let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) + view.thumbnail.image?.size = thumbnailSize + view.thumbnail.frame.size = thumbnailSize + } + } + } + refreshThumbnailsAsync(screen, currentIndex + 1) + } + } + } + static func refreshWhichWindowsToShowTheUser(_ screen: NSScreen) { Windows.list.forEach { (window: Window) in refreshIfWindowShouldBeShownToTheUser(window, screen) diff --git a/src/ui/App.swift b/src/ui/App.swift index aec0fd23..16d75926 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -228,21 +228,23 @@ class App: AppCenterApplication, NSApplicationDelegate { Windows.updateFocusedWindowIndex(0) Windows.cycleFocusedWindowIndex(1) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay) { () -> () in - self.rebuildUi() + self.rebuildUi(screen) } } else { cycleSelection(.leading) } } - func rebuildUi() { + func rebuildUi(_ screen: NSScreen = Screen.preferred()) { guard appIsBeingUsed else { return } - Windows.refreshAllThumbnails() + Windows.refreshFirstFewThumbnailsSync() guard appIsBeingUsed else { return } Applications.refreshBadges() guard appIsBeingUsed else { return } refreshOpenUi() guard appIsBeingUsed else { return } thumbnailsPanel.show() + guard appIsBeingUsed else { return } + Windows.refreshThumbnailsAsync(screen) } }