Compare commits

...
1 Commits
Author SHA1 Message Date
Louis Pontoise b8bdcd12c6 wip: mouse hover 2023-02-07 19:22:09 +01:00
5 changed files with 70 additions and 43 deletions

No files matched your search

+33 -10
View File
@@ -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
View File
@@ -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") {
+16 -19
View File
@@ -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()
}
}
+17 -11
View File
@@ -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