diff --git a/alt-tab-macos/logic/Preferences.swift b/alt-tab-macos/logic/Preferences.swift index f5a581c04..876bb14e9 100644 --- a/alt-tab-macos/logic/Preferences.swift +++ b/alt-tab-macos/logic/Preferences.swift @@ -2,9 +2,14 @@ import Foundation import Cocoa import Carbon.HIToolbox.Events +enum ShowOnScreenPreference { + case MAIN + case MOUSE +} + class Preferences { static var defaults: [String: String] = [ - "version": "2", // bump this anytime the dictionary is changed + "version": "3", // bump this anytime the dictionary is changed "maxScreenUsage": "80", "maxThumbnailsPerRow": "4", "iconSize": "32", @@ -12,7 +17,8 @@ class Preferences { "tabKeyCode": String(kVK_Tab), "metaKey": metaKeyMacro.macros[0].label, "windowDisplayDelay": "0", - "theme": themeMacro.macros[0].label + "theme": themeMacro.macros[0].label, + "showOnScreen": showOnScreenMacro.macros[0].label ] static var rawValues = [String: String]() static var thumbnailMaxWidth: CGFloat = 200 @@ -37,6 +43,7 @@ class Preferences { static var windowDisplayDelay: DispatchTimeInterval? static var windowCornerRadius: CGFloat? static var font: NSFont? + static var showOnScreen: ShowOnScreenPreference? static var themeMacro = MacroPreferenceHelper<(CGFloat, CGFloat, CGFloat, NSColor, NSColor)>([ MacroPreference(" macOS", (0, 5, 20, .clear, NSColor(red: 0, green: 0, blue: 0, alpha: 0.3))), MacroPreference("❖ Windows 10", (2, 0, 0, .white, .clear)) @@ -46,6 +53,10 @@ class Preferences { MacroPreference("⌃ control", ([kVK_Control, kVK_RightControl], .control)), MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command)) ]) + static var showOnScreenMacro = MacroPreferenceHelper<(ShowOnScreenPreference)>([ + MacroPreference("with keyboard focus", (ShowOnScreenPreference.MAIN)), + MacroPreference("with mouse pointer", (ShowOnScreenPreference.MOUSE)), + ]) private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json") private static let userFile = fileFromPreferencesFolder("alt-tab-macos.json") @@ -89,6 +100,9 @@ class Preferences { highlightBackgroundColor = p.preferences.4 case "windowDisplayDelay": windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow()) + case "showOnScreen": + let p = try showOnScreenMacro.labelToMacro[value].orThrow() + showOnScreen = p.preferences default: throw "Tried to update an unknown preference: '\(valueName)' = '\(value)'" } diff --git a/alt-tab-macos/logic/Screen.swift b/alt-tab-macos/logic/Screen.swift index e20a8cf64..3d5eddc08 100644 --- a/alt-tab-macos/logic/Screen.swift +++ b/alt-tab-macos/logic/Screen.swift @@ -14,9 +14,23 @@ class Screen { static func updateThumbnailMaxSize() -> Void { if Preferences.thumbnailMaxWidth == 200 && Preferences.thumbnailMaxHeight == 200 { - let main = NSScreen.main!.frame + let screenFrame = getPreferredScreen().frame Preferences.thumbnailMaxWidth = (NSScreen.main!.frame.size.width * Preferences.maxScreenUsage! - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow! - Preferences.interItemPadding - Preferences.thumbnailMaxHeight = Preferences.thumbnailMaxWidth * (main.height / main.width) + Preferences.thumbnailMaxHeight = Preferences.thumbnailMaxWidth * (screenFrame.height / screenFrame.width) } } + + static func getPreferredScreen() -> NSScreen { + switch Preferences.showOnScreen! { + case .MOUSE: + return getScreenWithMouse() ?? NSScreen.main!; // .main as fail-safe + default: // no case for .MAIN since its currently the same as default case + return NSScreen.main!; + } + } + + // https://stackoverflow.com/a/49624487/4767781 + static func getScreenWithMouse() -> NSScreen? { + return NSScreen.screens.first { NSMouseInRect(NSEvent.mouseLocation, $0.frame, false) } + } } diff --git a/alt-tab-macos/ui/Application.swift b/alt-tab-macos/ui/Application.swift index b14c868b8..3bb3bc51a 100644 --- a/alt-tab-macos/ui/Application.swift +++ b/alt-tab-macos/ui/Application.swift @@ -61,11 +61,13 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { } @objc func showCenteredPreferencesPanel() { - showCenteredPanel(preferencesPanel!) + showCenteredFrontPanel(preferencesPanel!) } - func showCenteredPanel(_ panel: NSPanel) { - panel.center() + func showCenteredFrontPanel(_ panel: NSPanel, _ screenOverride: NSScreen? = nil) { + let preferredScreen = screenOverride ?? Screen.getPreferredScreen() // use optional provided screen or determine it just in time + let centerPosition = NSPoint(x: preferredScreen.visibleFrame.midX - panel.frame.width / 2, y: preferredScreen.visibleFrame.midY - panel.frame.height / 2) + panel.setFrameOrigin(centerPosition) panel.makeKeyAndOrderFront(nil) Application.shared.arrangeInFront(nil) } @@ -109,11 +111,12 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { return } selectedOpenWindow = cellWithStep(step) + let preferredScreen = Screen.getPreferredScreen() // we want the thumbnail computation and panel display use the same screen (in case mouse movements switching screens etc.) var workItem: DispatchWorkItem! workItem = DispatchWorkItem { - if !workItem.isCancelled { self.thumbnailsPanel!.computeThumbnails() } + if !workItem.isCancelled { self.thumbnailsPanel!.computeThumbnails(preferredScreen) } if !workItem.isCancelled { self.thumbnailsPanel!.highlightCellAt(step) } - if !workItem.isCancelled { self.showCenteredPanel(self.thumbnailsPanel!) } + if !workItem.isCancelled { self.showCenteredFrontPanel(self.thumbnailsPanel!, preferredScreen) } } workItems.append(workItem) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay!, execute: workItem) diff --git a/alt-tab-macos/ui/PreferencesPanel.swift b/alt-tab-macos/ui/PreferencesPanel.swift index 761fada4e..c333b38dd 100644 --- a/alt-tab-macos/ui/PreferencesPanel.swift +++ b/alt-tab-macos/ui/PreferencesPanel.swift @@ -15,6 +15,7 @@ class PreferencesPanel: NSPanel, NSTextViewDelegate { var windowDisplayDelay: NSTextView? var metaKey: NSPopUpButton? var theme: NSPopUpButton? + var showOnScreen: NSPopUpButton? var inputsMap = [NSTextView: String]() override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) { @@ -34,6 +35,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) ] } @@ -41,7 +43,7 @@ class PreferencesPanel: NSPanel, NSTextViewDelegate { let gridView = NSGridView(views: rows) gridView.setContentHuggingPriority(.defaultLow, for: .horizontal) gridView.setContentHuggingPriority(.defaultLow, for: .vertical) - gridView.widthAnchor.constraint(greaterThanOrEqualToConstant: 360).isActive = true + gridView.widthAnchor.constraint(greaterThanOrEqualToConstant: 380).isActive = true gridView.addRow(with: [warningLabel, NSGridCell.emptyContentView]) gridView.mergeCells(inHorizontalRange: NSRange(location: 0, length: 2), verticalRange: NSRange(location: gridView.numberOfRows - 1, length: 1)) return gridView @@ -61,7 +63,7 @@ class PreferencesPanel: NSPanel, NSTextViewDelegate { input.delegate = self input.font = Preferences.font input.string = Preferences.rawValues[rawName]! - input.widthAnchor.constraint(equalToConstant: 32).isActive = true + input.widthAnchor.constraint(equalToConstant: 40).isActive = true self[keyPath: keyPath] = input inputsMap[input] = rawName return [label, input] @@ -86,6 +88,8 @@ class PreferencesPanel: NSPanel, NSTextViewDelegate { try! Preferences.updateAndValidateFromString("theme", popUpButton.titleOfSelectedItem!) case metaKey: try! Preferences.updateAndValidateFromString("metaKey", popUpButton.titleOfSelectedItem!) + case showOnScreen: + try! Preferences.updateAndValidateFromString("showOnScreen", popUpButton.titleOfSelectedItem!) default: throw "Tried to update an unknown popUpButton: '\(popUpButton)' = '\(popUpButton.titleOfSelectedItem!)'" } diff --git a/alt-tab-macos/ui/ThumbnailsPanel.swift b/alt-tab-macos/ui/ThumbnailsPanel.swift index 1bcd1f7b4..15612d14a 100644 --- a/alt-tab-macos/ui/ThumbnailsPanel.swift +++ b/alt-tab-macos/ui/ThumbnailsPanel.swift @@ -91,8 +91,9 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele } } - func computeThumbnails() { - let maxSize = NSSize(width: NSScreen.main!.frame.width * Preferences.maxScreenUsage!, height: NSScreen.main!.frame.height * Preferences.maxScreenUsage!) + func computeThumbnails(_ screenOverride: NSScreen? = nil) { + let preferredScreen = screenOverride ?? Screen.getPreferredScreen() // use optional provided screen or determine it just in time + let maxSize = NSSize(width: preferredScreen.frame.width * Preferences.maxScreenUsage!, height: preferredScreen.frame.height * Preferences.maxScreenUsage!) collectionView_!.setFrameSize(maxSize) collectionView_!.collectionViewLayout!.invalidateLayout() collectionView_!.reloadData()