diff --git a/alt-tab-macos/logic/Preferences.swift b/alt-tab-macos/logic/Preferences.swift index ed02f5a8..01572cb0 100644 --- a/alt-tab-macos/logic/Preferences.swift +++ b/alt-tab-macos/logic/Preferences.swift @@ -52,8 +52,8 @@ class Preferences { MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command)) ]) static var showOnScreenMacro = MacroPreferenceHelper([ - MacroPreference("with keyboard focus", ShowOnScreenPreference.MAIN), - MacroPreference("with mouse pointer", ShowOnScreenPreference.MOUSE), + MacroPreference("Main screen", ShowOnScreenPreference.MAIN), + MacroPreference("Screen including mouse", ShowOnScreenPreference.MOUSE), ]) private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json") diff --git a/alt-tab-macos/logic/Screen.swift b/alt-tab-macos/logic/Screen.swift index 26b62f11..f248b863 100644 --- a/alt-tab-macos/logic/Screen.swift +++ b/alt-tab-macos/logic/Screen.swift @@ -2,55 +2,44 @@ import Foundation import Cocoa class Screen { - // currently not in use (but kept for reference & future use), earlier registered by Application.applicationDidFinishLaunching() - static func listenToChanges() { - NotificationCenter.default.addObserver( - forName: NSApplication.didChangeScreenParametersNotification, - object: NSApplication.shared, - queue: OperationQueue.main - ) { notification -> Void in - // do something - } - } - - static func calcThumbnailMaxSize(_ screen: NSScreen) -> NSSize { - let width: CGFloat = (screen.frame.size.width * Preferences.maxScreenUsage! - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow! - Preferences.interItemPadding - let height: CGFloat = width * (screen.frame.height / screen.frame.width) - return NSSize(width: width, height: height) - } - - static func calcFrameMaxSize(_ screen: NSScreen) -> NSSize { - return NSSize(width: screen.frame.width * Preferences.maxScreenUsage!, height: screen.frame.height * Preferences.maxScreenUsage!) - } - - // TODO: currently unknown and unhandled error use-case possible: NSScreen.main being nil - static func getPreferredScreen() -> NSScreen { + static func preferredScreen() -> NSScreen { switch Preferences.showOnScreen! { case .MOUSE: - return getScreenWithMouse() ?? NSScreen.main!; // .main as fall-back + return screenWithMouse() ?? NSScreen.main!; // .main as fall-back case .MAIN: return NSScreen.main!; } } - static func getScreenWithMouse() -> NSScreen? { + private static func screenWithMouse() -> NSScreen? { return NSScreen.screens.first { NSMouseInRect(NSEvent.mouseLocation, $0.frame, false) } } - /* - usage notes: - - useAppleVerticalOffset: applies a vertical offset (higher positioning) attempting to approximate the NSView.center() results but only if we are not dealing with screen filling frames (height) - */ - static func showCenteredFrontPanel(_ panel: NSPanel, _ screen: NSScreen, _ useAppleVerticalOffset: Bool = true) { - var verticalOffset: CGFloat = 0 + static func thumbnailMaxSize(_ screen: NSScreen) -> NSSize { + let frame = screen.visibleFrame + let width = (frame.width * Preferences.maxScreenUsage! - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow! - Preferences.interItemPadding + let height = width * (frame.height / frame.width) + return NSSize(width: width, height: height) + } - if useAppleVerticalOffset && panel.frame.height < screen.visibleFrame.height / 1.5 { - verticalOffset = CGFloat(screen.visibleFrame.height * 0.175) - } + static func thumbnailPanelMaxSize(_ screen: NSScreen) -> NSSize { + let frame = screen.visibleFrame + return NSSize(width: frame.width * Preferences.maxScreenUsage!, height: frame.height * Preferences.maxScreenUsage!) + } - let centerPosition = NSPoint(x: screen.visibleFrame.midX - panel.frame.width / 2, y: screen.visibleFrame.midY - panel.frame.height / 2 + verticalOffset) - panel.setFrameOrigin(centerPosition) + static func showPanel(_ panel: NSPanel, _ screen: NSScreen, _ alignment: VerticalAlignment) { + let screenFrame = screen.visibleFrame + let panelFrame = panel.frame + let x = screenFrame.minX + max(screenFrame.width - panelFrame.width, 0) * 0.5 + let y = screenFrame.minY + max(screenFrame.height - panelFrame.height, 0) * alignment.rawValue + panel.setFrameOrigin(NSPoint(x: x, y: y)) panel.makeKeyAndOrderFront(nil) Application.shared.arrangeInFront(nil) } } + +enum VerticalAlignment: CGFloat { + case centered = 0.5 + // vertically centered but with an upward offset, similar to a book title; mimics NSView.center() + case appleCentered = 0.75 +} diff --git a/alt-tab-macos/logic/WindowManager.swift b/alt-tab-macos/logic/WindowManager.swift index 17ac52fc..39b99bf1 100644 --- a/alt-tab-macos/logic/WindowManager.swift +++ b/alt-tab-macos/logic/WindowManager.swift @@ -35,7 +35,7 @@ class OpenWindow { func computeDownscaledSize(_ image: NSImage, _ screen: NSScreen) -> (Int, Int) { let imageRatio = image.size.width / image.size.height - let thumbnailMaxSize = Screen.calcThumbnailMaxSize(screen) + let thumbnailMaxSize = Screen.thumbnailMaxSize(screen) let thumbnailWidth = Int(floor(thumbnailMaxSize.height * imageRatio)) if thumbnailWidth <= Int(thumbnailMaxSize.width) { return (thumbnailWidth, Int(thumbnailMaxSize.height)) diff --git a/alt-tab-macos/ui/Application.swift b/alt-tab-macos/ui/Application.swift index 3985c942..0c831529 100644 --- a/alt-tab-macos/ui/Application.swift +++ b/alt-tab-macos/ui/Application.swift @@ -28,7 +28,7 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { SystemPermissions.ensureAccessibilityCheckboxIsChecked() Preferences.loadFromDiskAndUpdateValues() statusItem = StatusItem.make(self) - thumbnailsPanel = ThumbnailsPanel(self, Screen.getPreferredScreen()) + thumbnailsPanel = ThumbnailsPanel(self) preferencesPanel = PreferencesPanel() Keyboard.listenToGlobalEvents(self) } @@ -58,8 +58,8 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { } } - @objc func showCenteredPreferencesPanel() { - Screen.showCenteredFrontPanel(preferencesPanel!, Screen.getPreferredScreen()) + @objc func showPreferencesPanel() { + Screen.showPanel(preferencesPanel!, Screen.preferredScreen(), .appleCentered) } func computeOpenWindows() { @@ -101,12 +101,12 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { return } selectedOpenWindow = cellWithStep(step) - let currentScreen = Screen.getPreferredScreen() // we want all computations and renderings to use the same screen for this summon (in case mouse movements switching screens etc.) var workItem: DispatchWorkItem! workItem = DispatchWorkItem { + let currentScreen = Screen.preferredScreen() // fix screen between steps since it could change (e.g. mouse moved to another screen) if !workItem.isCancelled { self.thumbnailsPanel!.computeThumbnails(currentScreen) } if !workItem.isCancelled { self.thumbnailsPanel!.highlightCellAt(step) } - if !workItem.isCancelled { Screen.showCenteredFrontPanel(self.thumbnailsPanel!, currentScreen, true) } + if !workItem.isCancelled { Screen.showPanel(self.thumbnailsPanel!, currentScreen, .appleCentered) } } workItems.append(workItem) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay!, execute: workItem) diff --git a/alt-tab-macos/ui/Cell.swift b/alt-tab-macos/ui/Cell.swift index cfea81ef..754e656b 100644 --- a/alt-tab-macos/ui/Cell.swift +++ b/alt-tab-macos/ui/Cell.swift @@ -35,8 +35,6 @@ class Cell: NSCollectionViewItem { } } - - func updateWithNewContent(_ element: OpenWindow, _ mouseDownCallback: @escaping MouseDownCallback, _ mouseMovedCallback: @escaping MouseMovedCallback, _ screen: NSScreen) { openWindow = element thumbnail.image = element.thumbnail diff --git a/alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift b/alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift index ab583490..a47f2d8c 100644 --- a/alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift +++ b/alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift @@ -1,16 +1,7 @@ import Cocoa class CollectionViewCenterFlowLayout: NSCollectionViewFlowLayout { - let currentScreen: NSScreen - - init(_ screen: NSScreen) { - currentScreen = screen - super.init() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + var currentScreen: NSScreen? override func layoutAttributesForElements(in rect: CGRect) -> [NSCollectionViewLayoutAttributes] { let attributes = super.layoutAttributesForElements(in: rect) @@ -25,7 +16,7 @@ class CollectionViewCenterFlowLayout: NSCollectionViewFlowLayout { var widestRow = CGFloat(0) var totalHeight = CGFloat(0) attributes.enumerated().forEach { - let isNewRow = abs($1.frame.origin.y - currentRowY) > Screen.calcThumbnailMaxSize(currentScreen).height + let isNewRow = abs($1.frame.origin.y - currentRowY) > Screen.thumbnailMaxSize(currentScreen!).height if isNewRow { computeOriginXForAllItems(currentRowWidth - minimumInteritemSpacing, previousRowMaxY, currentRow) currentRow.removeAll() diff --git a/alt-tab-macos/ui/PreferencesPanel.swift b/alt-tab-macos/ui/PreferencesPanel.swift index 59cb8a0c..0e27da28 100644 --- a/alt-tab-macos/ui/PreferencesPanel.swift +++ b/alt-tab-macos/ui/PreferencesPanel.swift @@ -33,7 +33,7 @@ class PreferencesPanel: NSPanel, NSTextViewDelegate { makeLabelWithInput(\PreferencesPanel.iconSize, "Apps icon size (px)", "iconSize"), makeLabelWithInput(\PreferencesPanel.fontHeight, "Font size (px)", "fontHeight"), makeLabelWithInput(\PreferencesPanel.windowDisplayDelay, "Window apparition delay (ms)", "windowDisplayDelay"), - makeLabelWithDropdown(\PreferencesPanel.showOnScreen, "Show on Screen", "showOnScreen", Preferences.showOnScreenMacro.labels) + makeLabelWithDropdown(\PreferencesPanel.showOnScreen, "Show on", "showOnScreen", Preferences.showOnScreenMacro.labels) ] } diff --git a/alt-tab-macos/ui/StatusItem.swift b/alt-tab-macos/ui/StatusItem.swift index abd8d70a..85299c5d 100644 --- a/alt-tab-macos/ui/StatusItem.swift +++ b/alt-tab-macos/ui/StatusItem.swift @@ -11,7 +11,7 @@ class StatusItem { keyEquivalent: "") item.menu!.addItem( withTitle: "Preferences…", - action: #selector(application.showCenteredPreferencesPanel), + action: #selector(application.showPreferencesPanel), keyEquivalent: ",") item.menu!.addItem( withTitle: "Quit \(ProcessInfo.processInfo.processName)", diff --git a/alt-tab-macos/ui/ThumbnailsPanel.swift b/alt-tab-macos/ui/ThumbnailsPanel.swift index a318148f..f04ca4c6 100644 --- a/alt-tab-macos/ui/ThumbnailsPanel.swift +++ b/alt-tab-macos/ui/ThumbnailsPanel.swift @@ -11,10 +11,9 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) } - convenience init(_ application: Application, _ currentScreen: NSScreen) { + convenience init(_ application: Application) { self.init() self.application = application - self.currentScreen = currentScreen isFloatingPanel = true animationBehavior = .none hidesOnDeactivate = false @@ -51,8 +50,8 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele } func makeLayout() -> CollectionViewCenterFlowLayout { - let layout = CollectionViewCenterFlowLayout(currentScreen!) - layout.estimatedItemSize = Screen.calcThumbnailMaxSize(currentScreen!) + let layout = CollectionViewCenterFlowLayout() + layout.estimatedItemSize = NSSize(width: 200, height: 200) layout.minimumInteritemSpacing = 5 layout.minimumLineSpacing = 5 return layout @@ -95,8 +94,9 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele func computeThumbnails(_ currentScreen: NSScreen) { self.currentScreen = currentScreen - collectionView_!.setFrameSize(Screen.calcFrameMaxSize(currentScreen)) - collectionView_!.collectionViewLayout = makeLayout() + (collectionView_.collectionViewLayout as! CollectionViewCenterFlowLayout).currentScreen = currentScreen + collectionView_!.setFrameSize(Screen.thumbnailPanelMaxSize(currentScreen)) + collectionView_!.collectionViewLayout!.invalidateLayout() collectionView_!.reloadData() collectionView_!.layoutSubtreeIfNeeded() setContentSize(NSSize(width: collectionView_!.frame.size.width + Preferences.windowPadding * 2, height: collectionView_!.frame.size.height + Preferences.windowPadding * 2))