Skip to content

Commit

Permalink
feat: windows on mouse screen, implements #28
Browse files Browse the repository at this point in the history
squash of pull-request corrections and improvements

refactor: better mimic apple centering

Also use enum instead of bool to self-document the interface

docs: clarify the show-on-screen preference wording

Discussion on this SO (https://stackoverflow.com/a/864432/2249756) seem to indicate that .main is not the screen with the menubar, however in my tests, it is always that window. I think macOS changed the behavior of .main without updating the documentation

refactor: shorter method names as swift promotes + removed debug code

fix: all methods in screen should use visibleFrame not frame

refactor: (hopefully) made code intent clearer

fix: panel and collection layout now use dynamic screen variable

fix: fix vertical origin from 4c44100
  • Loading branch information
Louis Pontoise authored and lwouis committed Nov 1, 2019
1 parent 6c93047 commit b841ec7
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 65 deletions.
4 changes: 2 additions & 2 deletions alt-tab-macos/logic/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class Preferences {
MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command))
])
static var showOnScreenMacro = MacroPreferenceHelper<ShowOnScreenPreference>([
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")
Expand Down
61 changes: 25 additions & 36 deletions alt-tab-macos/logic/Screen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion alt-tab-macos/logic/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
10 changes: 5 additions & 5 deletions alt-tab-macos/ui/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions alt-tab-macos/ui/Cell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 2 additions & 11 deletions alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion alt-tab-macos/ui/PreferencesPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]
}

Expand Down
2 changes: 1 addition & 1 deletion alt-tab-macos/ui/StatusItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
12 changes: 6 additions & 6 deletions alt-tab-macos/ui/ThumbnailsPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit b841ec7

Please sign in to comment.