Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bdcd12c6 |
No files matched your search
+33
-10
@@ -3,6 +3,7 @@ import Cocoa
|
|||||||
class Windows {
|
class Windows {
|
||||||
static var list = [Window]()
|
static var list = [Window]()
|
||||||
static var focusedWindowIndex = Int(0)
|
static var focusedWindowIndex = Int(0)
|
||||||
|
static var hoveredWindowIndex: Int?
|
||||||
// the first few thumbnails are the most commonly looked at; we pay special attention to them
|
// the first few thumbnails are the most commonly looked at; we pay special attention to them
|
||||||
static let criticalFirstThumbnails = 3
|
static let criticalFirstThumbnails = 3
|
||||||
|
|
||||||
@@ -24,14 +25,23 @@ class Windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setInitialFocusedWindowIndex() {
|
static func setInitialFocusedAndHoveredWindowIndex() {
|
||||||
|
let oldIndex = focusedWindowIndex
|
||||||
|
focusedWindowIndex = 0
|
||||||
|
ThumbnailsView.highlight(oldIndex)
|
||||||
|
if let oldIndex = hoveredWindowIndex {
|
||||||
|
hoveredWindowIndex = nil
|
||||||
|
ThumbnailsView.highlight(oldIndex)
|
||||||
|
}
|
||||||
if let app = Applications.find(NSWorkspace.shared.frontmostApplication?.processIdentifier),
|
if let app = Applications.find(NSWorkspace.shared.frontmostApplication?.processIdentifier),
|
||||||
app.focusedWindow == nil,
|
app.focusedWindow == nil,
|
||||||
let lastFocusedWindowIndex = getLastFocusedWindowIndex() {
|
let lastFocusedWindowIndex = getLastFocusedWindowIndex() {
|
||||||
updateFocusedWindowIndex(lastFocusedWindowIndex)
|
updateFocusedWindowIndex(lastFocusedWindowIndex)
|
||||||
} else {
|
} else {
|
||||||
updateFocusedWindowIndex(0)
|
|
||||||
cycleFocusedWindowIndex(1)
|
cycleFocusedWindowIndex(1)
|
||||||
|
if focusedWindowIndex == 0 {
|
||||||
|
updateFocusedWindowIndex(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,21 +99,34 @@ class Windows {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func updateFocusedWindowIndex(_ newIndex: Int) {
|
static func updateFocusedWindowIndex(_ newIndex: Int, _ fromMouse: Bool = false) {
|
||||||
ThumbnailsView.recycledViews[focusedWindowIndex].highlight(false)
|
if (fromMouse && newIndex == hoveredWindowIndex) || (!fromMouse && newIndex == focusedWindowIndex) {
|
||||||
focusedWindowIndex = newIndex
|
return
|
||||||
let focusedView = ThumbnailsView.recycledViews[focusedWindowIndex]
|
}
|
||||||
focusedView.highlight(true)
|
if fromMouse {
|
||||||
|
let oldIndex = hoveredWindowIndex
|
||||||
|
hoveredWindowIndex = newIndex
|
||||||
|
if let oldIndex = oldIndex {
|
||||||
|
ThumbnailsView.highlight(oldIndex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let oldIndex = focusedWindowIndex
|
||||||
|
focusedWindowIndex = newIndex
|
||||||
|
ThumbnailsView.highlight(oldIndex)
|
||||||
|
}
|
||||||
|
let index = fromMouse ? hoveredWindowIndex! : focusedWindowIndex
|
||||||
|
ThumbnailsView.highlight(index)
|
||||||
|
let focusedView = ThumbnailsView.recycledViews[index]
|
||||||
App.app.thumbnailsPanel.thumbnailsView.scrollView.contentView.scrollToVisible(focusedView.frame)
|
App.app.thumbnailsPanel.thumbnailsView.scrollView.contentView.scrollToVisible(focusedView.frame)
|
||||||
voiceOverFocusedWindow()
|
voiceOverWindow(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func voiceOverFocusedWindow() {
|
static func voiceOverWindow(_ windowIndex: Int = focusedWindowIndex) {
|
||||||
guard App.app.appIsBeingUsed && App.app.thumbnailsPanel.isKeyWindow else { return }
|
guard App.app.appIsBeingUsed && App.app.thumbnailsPanel.isKeyWindow else { return }
|
||||||
// it seems that sometimes makeFirstResponder is called before the view is visible
|
// it seems that sometimes makeFirstResponder is called before the view is visible
|
||||||
// and it creates a delay in showing the main window; calling it with some delay seems to work around this
|
// and it creates a delay in showing the main window; calling it with some delay seems to work around this
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
|
||||||
let window = ThumbnailsView.recycledViews[focusedWindowIndex]
|
let window = ThumbnailsView.recycledViews[windowIndex]
|
||||||
if window.window_ != nil && window.window != nil {
|
if window.window_ != nil && window.window != nil {
|
||||||
App.app.thumbnailsPanel.makeFirstResponder(window)
|
App.app.thumbnailsPanel.makeFirstResponder(window)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -238,7 +238,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
|||||||
thumbnailsPanel.display()
|
thumbnailsPanel.display()
|
||||||
guard appIsBeingUsed else { return }
|
guard appIsBeingUsed else { return }
|
||||||
currentScreen.repositionPanel(thumbnailsPanel, .appleCentered)
|
currentScreen.repositionPanel(thumbnailsPanel, .appleCentered)
|
||||||
Windows.voiceOverFocusedWindow() // at this point ThumbnailViews are assigned to the window, and ready
|
Windows.voiceOverWindow() // at this point ThumbnailViews are assigned to the window, and ready
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshSpecificWindows(_ windowsToUpdate: [Window]?, _ currentScreen: NSScreen) -> ()? {
|
private func refreshSpecificWindows(_ windowsToUpdate: [Window]?, _ currentScreen: NSScreen) -> ()? {
|
||||||
@@ -252,6 +252,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
|||||||
|
|
||||||
func showUiOrCycleSelection(_ shortcutIndex: Int) {
|
func showUiOrCycleSelection(_ shortcutIndex: Int) {
|
||||||
debugPrint("showUiOrCycleSelection")
|
debugPrint("showUiOrCycleSelection")
|
||||||
|
debugPrint("------", CGSCopyActiveMenuBarDisplayIdentifier(cgsMainConnectionId))
|
||||||
if isFirstSummon {
|
if isFirstSummon {
|
||||||
debugPrint("showUiOrCycleSelection: isFirstSummon")
|
debugPrint("showUiOrCycleSelection: isFirstSummon")
|
||||||
isFirstSummon = false
|
isFirstSummon = false
|
||||||
@@ -272,7 +273,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
|||||||
Windows.refreshWhichWindowsToShowTheUser(screen)
|
Windows.refreshWhichWindowsToShowTheUser(screen)
|
||||||
Windows.reorderList()
|
Windows.reorderList()
|
||||||
if (!Windows.list.contains { $0.shouldShowTheUser }) { hideUi(); return }
|
if (!Windows.list.contains { $0.shouldShowTheUser }) { hideUi(); return }
|
||||||
Windows.setInitialFocusedWindowIndex()
|
Windows.setInitialFocusedAndHoveredWindowIndex()
|
||||||
delayedDisplayScheduled += 1
|
delayedDisplayScheduled += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay) { () -> () in
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay) { () -> () in
|
||||||
if self.delayedDisplayScheduled == 1 {
|
if self.delayedDisplayScheduled == 1 {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class CustomRecorderControl: RecorderControl, RecorderControlDelegate {
|
|||||||
let isArrowKeys = ["←", "→", "↑", "↓"].contains(shortcutAlreadyAssigned.id)
|
let isArrowKeys = ["←", "→", "↑", "↓"].contains(shortcutAlreadyAssigned.id)
|
||||||
let existing = ControlsTab.shortcutControls[shortcutAlreadyAssigned.id]
|
let existing = ControlsTab.shortcutControls[shortcutAlreadyAssigned.id]
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.alertStyle = .critical
|
alert.alertStyle = .warning
|
||||||
alert.messageText = NSLocalizedString("Conflicting shortcut", comment: "")
|
alert.messageText = NSLocalizedString("Conflicting shortcut", comment: "")
|
||||||
alert.informativeText = String(format: NSLocalizedString("Shortcut already assigned to another action: %@", comment: ""), (isArrowKeys ? "Arrow keys" : existing!.1).replacingOccurrences(of: " ", with: "\u{00A0}"))
|
alert.informativeText = String(format: NSLocalizedString("Shortcut already assigned to another action: %@", comment: ""), (isArrowKeys ? "Arrow keys" : existing!.1).replacingOccurrences(of: " ", with: "\u{00A0}"))
|
||||||
if !id.starts(with: "holdShortcut") {
|
if !id.starts(with: "holdShortcut") {
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ class ThumbnailView: NSStackView {
|
|||||||
var mouseUpCallback: (() -> Void)!
|
var mouseUpCallback: (() -> Void)!
|
||||||
var mouseMovedCallback: (() -> Void)!
|
var mouseMovedCallback: (() -> Void)!
|
||||||
var dragAndDropTimer: Timer?
|
var dragAndDropTimer: Timer?
|
||||||
var isHighlighted = false
|
var indexInRecycledViews: Int!
|
||||||
var shouldShowWindowControls = false
|
var shouldShowWindowControls = false
|
||||||
var isShowingWindowControls = false
|
var isShowingWindowControls = false
|
||||||
var windowlessIcon = FontIcon(.newWindow, NSLocalizedString("App is running but has no open window", comment: ""))
|
var windowlessIcon = FontIcon(.newWindow, NSLocalizedString("App is running but has no open window", comment: ""))
|
||||||
|
var frameInset = CGFloat(0)
|
||||||
|
|
||||||
// for VoiceOver cursor
|
// for VoiceOver cursor
|
||||||
override var canBecomeKeyView: Bool { true }
|
override var canBecomeKeyView: Bool { true }
|
||||||
@@ -42,7 +43,7 @@ class ThumbnailView: NSStackView {
|
|||||||
layer!.backgroundColor = .clear
|
layer!.backgroundColor = .clear
|
||||||
layer!.borderColor = .clear
|
layer!.borderColor = .clear
|
||||||
layer!.cornerRadius = Preferences.cellCornerRadius
|
layer!.cornerRadius = Preferences.cellCornerRadius
|
||||||
layer!.borderWidth = Preferences.cellBorderWidth
|
layer!.borderWidth = CGFloat(2)
|
||||||
edgeInsets = NSEdgeInsets(top: Preferences.intraCellPadding, left: Preferences.intraCellPadding, bottom: Preferences.intraCellPadding, right: Preferences.intraCellPadding)
|
edgeInsets = NSEdgeInsets(top: Preferences.intraCellPadding, left: Preferences.intraCellPadding, bottom: Preferences.intraCellPadding, right: Preferences.intraCellPadding)
|
||||||
orientation = .vertical
|
orientation = .vertical
|
||||||
let shadow = ThumbnailView.makeShadow(.gray)
|
let shadow = ThumbnailView.makeShadow(.gray)
|
||||||
@@ -95,22 +96,18 @@ class ThumbnailView: NSStackView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlight(_ highlight: Bool) {
|
func drawHighlight(_ i: Int) {
|
||||||
if isHighlighted != highlight {
|
let isFocused = indexInRecycledViews == Windows.focusedWindowIndex
|
||||||
isHighlighted = highlight
|
let isHovered = indexInRecycledViews == Windows.hoveredWindowIndex
|
||||||
if frame != NSRect.zero {
|
layer!.backgroundColor = isFocused ? Preferences.highlightBackgroundColor.cgColor : .clear
|
||||||
highlightOrNot()
|
layer!.borderColor = isHovered ? NSColor.controlColor.cgColor : .clear
|
||||||
}
|
let newFrameInset = (isFocused) ? -Preferences.intraCellPadding : Preferences.intraCellPadding
|
||||||
|
if newFrameInset != frameInset {
|
||||||
|
frameInset = newFrameInset
|
||||||
|
frame = frame.insetBy(dx: frameInset, dy: frameInset)
|
||||||
|
debugPrint("-----", i, frameInset, frame.size.height)
|
||||||
}
|
}
|
||||||
showOrHideWindowControls(false)
|
let edgeInsets_: CGFloat = Preferences.intraCellPadding * (isFocused ? 2 : 1)
|
||||||
}
|
|
||||||
|
|
||||||
func highlightOrNot() {
|
|
||||||
layer!.backgroundColor = isHighlighted ? Preferences.highlightBackgroundColor.cgColor : .clear
|
|
||||||
layer!.borderColor = isHighlighted ? Preferences.highlightBorderColor.cgColor : .clear
|
|
||||||
let frameInset: CGFloat = Preferences.intraCellPadding * (isHighlighted ? -1 : 1)
|
|
||||||
frame = frame.insetBy(dx: frameInset, dy: frameInset)
|
|
||||||
let edgeInsets_: CGFloat = Preferences.intraCellPadding * (isHighlighted ? 2 : 1)
|
|
||||||
edgeInsets.top = edgeInsets_
|
edgeInsets.top = edgeInsets_
|
||||||
edgeInsets.right = edgeInsets_
|
edgeInsets.right = edgeInsets_
|
||||||
edgeInsets.bottom = edgeInsets_
|
edgeInsets.bottom = edgeInsets_
|
||||||
@@ -186,7 +183,7 @@ class ThumbnailView: NSStackView {
|
|||||||
windowlessIcon.needsDisplay = true
|
windowlessIcon.needsDisplay = true
|
||||||
}
|
}
|
||||||
self.mouseUpCallback = { () -> Void in App.app.focusSelectedWindow(element) }
|
self.mouseUpCallback = { () -> Void in App.app.focusSelectedWindow(element) }
|
||||||
self.mouseMovedCallback = { () -> Void in Windows.updateFocusedWindowIndex(index) }
|
self.mouseMovedCallback = { () -> Void in Windows.updateFocusedWindowIndex(index, true) }
|
||||||
[quitIcon, closeIcon, minimizeIcon, maximizeIcon].forEach { $0.window_ = element }
|
[quitIcon, closeIcon, minimizeIcon, maximizeIcon].forEach { $0.window_ = element }
|
||||||
showOrHideWindowControls(false)
|
showOrHideWindowControls(false)
|
||||||
// force a display to avoid flickering; see https://github.com/lwouis/alt-tab-macos/issues/197
|
// force a display to avoid flickering; see https://github.com/lwouis/alt-tab-macos/issues/197
|
||||||
@@ -261,7 +258,7 @@ class ThumbnailView: NSStackView {
|
|||||||
|
|
||||||
func mouseMoved() {
|
func mouseMoved() {
|
||||||
showOrHideWindowControls(true)
|
showOrHideWindowControls(true)
|
||||||
if Preferences.mouseHoverEnabled && !isHighlighted {
|
if Preferences.mouseHoverEnabled {
|
||||||
mouseMovedCallback()
|
mouseMovedCallback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ class ThumbnailsView: NSVisualEffectView {
|
|||||||
(1...100).forEach { _ in ThumbnailsView.recycledViews.append(ThumbnailView()) }
|
(1...100).forEach { _ in ThumbnailsView.recycledViews.append(ThumbnailView()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func highlight(_ indexInRecycledViews: Int) {
|
||||||
|
let v = recycledViews[indexInRecycledViews]
|
||||||
|
v.indexInRecycledViews = indexInRecycledViews
|
||||||
|
if v.frame != NSRect.zero {
|
||||||
|
v.drawHighlight(indexInRecycledViews)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// using layer!.cornerRadius works but the corners are aliased; this custom approach gives smooth rounded corners
|
/// using layer!.cornerRadius works but the corners are aliased; this custom approach gives smooth rounded corners
|
||||||
/// see https://stackoverflow.com/a/29386935/2249756
|
/// see https://stackoverflow.com/a/29386935/2249756
|
||||||
func updateRoundedCorners(_ cornerRadius: CGFloat) {
|
func updateRoundedCorners(_ cornerRadius: CGFloat) {
|
||||||
@@ -179,13 +187,7 @@ class ThumbnailsView: NSVisualEffectView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func highlightStartView() {
|
private func highlightStartView() {
|
||||||
_ = Windows.list.enumerated().contains { (index, _) in
|
ThumbnailsView.highlight(Windows.focusedWindowIndex)
|
||||||
let view = ThumbnailsView.recycledViews[index]
|
|
||||||
if view.isHighlighted {
|
|
||||||
view.highlightOrNot()
|
|
||||||
}
|
|
||||||
return view.isHighlighted
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shiftRow(_ maxX: CGFloat, _ rowWidth: CGFloat, _ rowStartIndex: Int, _ index: Int) {
|
private func shiftRow(_ maxX: CGFloat, _ rowWidth: CGFloat, _ rowStartIndex: Int, _ index: Int) {
|
||||||
@@ -251,11 +253,15 @@ class ScrollView: NSScrollView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func mouseExited(with event: NSEvent) {
|
override func mouseExited(with event: NSEvent) {
|
||||||
previousTarget?.showOrHideWindowControls(false)
|
if let oldIndex = Windows.hoveredWindowIndex {
|
||||||
|
Windows.hoveredWindowIndex = nil
|
||||||
|
ThumbnailsView.highlight(oldIndex)
|
||||||
|
ThumbnailsView.recycledViews[oldIndex].showOrHideWindowControls(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// holding shift and using the scrolling wheel will generate a horizontal movement
|
/// holding shift and using the scrolling wheel will generate a horizontal movement
|
||||||
// shift can be part of shortcuts so we force shift scrolls to be vertical
|
/// shift can be part of shortcuts so we force shift scrolls to be vertical
|
||||||
override func scrollWheel(with event: NSEvent) {
|
override func scrollWheel(with event: NSEvent) {
|
||||||
if event.modifierFlags.contains(.shift) && event.scrollingDeltaY == 0 {
|
if event.modifierFlags.contains(.shift) && event.scrollingDeltaY == 0 {
|
||||||
let cgEvent = event.cgEvent!
|
let cgEvent = event.cgEvent!
|
||||||
@@ -267,7 +273,7 @@ class ScrollView: NSScrollView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// force overlay style after a change in System Preference > General > Show scroll bars
|
/// force overlay style after a change in System Preference > General > Show scroll bars
|
||||||
private func forceOverlayStyle() {
|
private func forceOverlayStyle() {
|
||||||
NotificationCenter.default.addObserver(forName: NSScroller.preferredScrollerStyleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
|
NotificationCenter.default.addObserver(forName: NSScroller.preferredScrollerStyleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
|
||||||
self?.scrollerStyle = .overlay
|
self?.scrollerStyle = .overlay
|
||||||
|
|||||||
Reference in new issue
Block a user