Compare commits
1
Commits
dev
...
mouse-hover
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bdcd12c6 |
No files matched your search
+33
-10
@@ -3,6 +3,7 @@ import Cocoa
|
||||
class Windows {
|
||||
static var list = [Window]()
|
||||
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
|
||||
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),
|
||||
app.focusedWindow == nil,
|
||||
let lastFocusedWindowIndex = getLastFocusedWindowIndex() {
|
||||
updateFocusedWindowIndex(lastFocusedWindowIndex)
|
||||
} else {
|
||||
updateFocusedWindowIndex(0)
|
||||
cycleFocusedWindowIndex(1)
|
||||
if focusedWindowIndex == 0 {
|
||||
updateFocusedWindowIndex(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,21 +99,34 @@ class Windows {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func updateFocusedWindowIndex(_ newIndex: Int) {
|
||||
ThumbnailsView.recycledViews[focusedWindowIndex].highlight(false)
|
||||
focusedWindowIndex = newIndex
|
||||
let focusedView = ThumbnailsView.recycledViews[focusedWindowIndex]
|
||||
focusedView.highlight(true)
|
||||
static func updateFocusedWindowIndex(_ newIndex: Int, _ fromMouse: Bool = false) {
|
||||
if (fromMouse && newIndex == hoveredWindowIndex) || (!fromMouse && newIndex == focusedWindowIndex) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
voiceOverFocusedWindow()
|
||||
voiceOverWindow(index)
|
||||
}
|
||||
|
||||
static func voiceOverFocusedWindow() {
|
||||
static func voiceOverWindow(_ windowIndex: Int = focusedWindowIndex) {
|
||||
guard App.app.appIsBeingUsed && App.app.thumbnailsPanel.isKeyWindow else { return }
|
||||
// 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
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
|
||||
let window = ThumbnailsView.recycledViews[focusedWindowIndex]
|
||||
let window = ThumbnailsView.recycledViews[windowIndex]
|
||||
if window.window_ != nil && window.window != nil {
|
||||
App.app.thumbnailsPanel.makeFirstResponder(window)
|
||||
}
|
||||
|
||||
+3
-2
@@ -238,7 +238,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
thumbnailsPanel.display()
|
||||
guard appIsBeingUsed else { return }
|
||||
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) -> ()? {
|
||||
@@ -252,6 +252,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
|
||||
func showUiOrCycleSelection(_ shortcutIndex: Int) {
|
||||
debugPrint("showUiOrCycleSelection")
|
||||
debugPrint("------", CGSCopyActiveMenuBarDisplayIdentifier(cgsMainConnectionId))
|
||||
if isFirstSummon {
|
||||
debugPrint("showUiOrCycleSelection: isFirstSummon")
|
||||
isFirstSummon = false
|
||||
@@ -272,7 +273,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
Windows.refreshWhichWindowsToShowTheUser(screen)
|
||||
Windows.reorderList()
|
||||
if (!Windows.list.contains { $0.shouldShowTheUser }) { hideUi(); return }
|
||||
Windows.setInitialFocusedWindowIndex()
|
||||
Windows.setInitialFocusedAndHoveredWindowIndex()
|
||||
delayedDisplayScheduled += 1
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay) { () -> () in
|
||||
if self.delayedDisplayScheduled == 1 {
|
||||
|
||||
@@ -52,7 +52,7 @@ class CustomRecorderControl: RecorderControl, RecorderControlDelegate {
|
||||
let isArrowKeys = ["←", "→", "↑", "↓"].contains(shortcutAlreadyAssigned.id)
|
||||
let existing = ControlsTab.shortcutControls[shortcutAlreadyAssigned.id]
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .critical
|
||||
alert.alertStyle = .warning
|
||||
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}"))
|
||||
if !id.starts(with: "holdShortcut") {
|
||||
|
||||
@@ -20,10 +20,11 @@ class ThumbnailView: NSStackView {
|
||||
var mouseUpCallback: (() -> Void)!
|
||||
var mouseMovedCallback: (() -> Void)!
|
||||
var dragAndDropTimer: Timer?
|
||||
var isHighlighted = false
|
||||
var indexInRecycledViews: Int!
|
||||
var shouldShowWindowControls = false
|
||||
var isShowingWindowControls = false
|
||||
var windowlessIcon = FontIcon(.newWindow, NSLocalizedString("App is running but has no open window", comment: ""))
|
||||
var frameInset = CGFloat(0)
|
||||
|
||||
// for VoiceOver cursor
|
||||
override var canBecomeKeyView: Bool { true }
|
||||
@@ -42,7 +43,7 @@ class ThumbnailView: NSStackView {
|
||||
layer!.backgroundColor = .clear
|
||||
layer!.borderColor = .clear
|
||||
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)
|
||||
orientation = .vertical
|
||||
let shadow = ThumbnailView.makeShadow(.gray)
|
||||
@@ -95,22 +96,18 @@ class ThumbnailView: NSStackView {
|
||||
}
|
||||
}
|
||||
|
||||
func highlight(_ highlight: Bool) {
|
||||
if isHighlighted != highlight {
|
||||
isHighlighted = highlight
|
||||
if frame != NSRect.zero {
|
||||
highlightOrNot()
|
||||
}
|
||||
func drawHighlight(_ i: Int) {
|
||||
let isFocused = indexInRecycledViews == Windows.focusedWindowIndex
|
||||
let isHovered = indexInRecycledViews == Windows.hoveredWindowIndex
|
||||
layer!.backgroundColor = isFocused ? Preferences.highlightBackgroundColor.cgColor : .clear
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
let edgeInsets_: CGFloat = Preferences.intraCellPadding * (isFocused ? 2 : 1)
|
||||
edgeInsets.top = edgeInsets_
|
||||
edgeInsets.right = edgeInsets_
|
||||
edgeInsets.bottom = edgeInsets_
|
||||
@@ -186,7 +183,7 @@ class ThumbnailView: NSStackView {
|
||||
windowlessIcon.needsDisplay = true
|
||||
}
|
||||
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 }
|
||||
showOrHideWindowControls(false)
|
||||
// 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() {
|
||||
showOrHideWindowControls(true)
|
||||
if Preferences.mouseHoverEnabled && !isHighlighted {
|
||||
if Preferences.mouseHoverEnabled {
|
||||
mouseMovedCallback()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,14 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
(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
|
||||
/// see https://stackoverflow.com/a/29386935/2249756
|
||||
func updateRoundedCorners(_ cornerRadius: CGFloat) {
|
||||
@@ -179,13 +187,7 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
}
|
||||
|
||||
private func highlightStartView() {
|
||||
_ = Windows.list.enumerated().contains { (index, _) in
|
||||
let view = ThumbnailsView.recycledViews[index]
|
||||
if view.isHighlighted {
|
||||
view.highlightOrNot()
|
||||
}
|
||||
return view.isHighlighted
|
||||
}
|
||||
ThumbnailsView.highlight(Windows.focusedWindowIndex)
|
||||
}
|
||||
|
||||
private func shiftRow(_ maxX: CGFloat, _ rowWidth: CGFloat, _ rowStartIndex: Int, _ index: Int) {
|
||||
@@ -251,11 +253,15 @@ class ScrollView: NSScrollView {
|
||||
}
|
||||
|
||||
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
|
||||
// shift can be part of shortcuts so we force shift scrolls to be vertical
|
||||
/// 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
|
||||
override func scrollWheel(with event: NSEvent) {
|
||||
if event.modifierFlags.contains(.shift) && event.scrollingDeltaY == 0 {
|
||||
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() {
|
||||
NotificationCenter.default.addObserver(forName: NSScroller.preferredScrollerStyleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
|
||||
self?.scrollerStyle = .overlay
|
||||
|
||||
Reference in new issue
Block a user