feat: navigate with up/down arrow keys (closes #270)
This commit is contained in:
1 parent
d64a6a520a
commit
cc61ed4607
4 files changed
+65
-16
No files matched your search
@@ -14,6 +14,7 @@ class Window {
|
||||
var axUiElement: AXUIElement
|
||||
var application: Application
|
||||
var axObserver: AXObserver?
|
||||
var row: Int?
|
||||
|
||||
static let notifications = [
|
||||
kAXUIElementDestroyedNotification,
|
||||
|
||||
+11
-7
@@ -134,11 +134,15 @@ class App: NSApplication, NSApplicationDelegate {
|
||||
func showUi() {
|
||||
appIsBeingUsed = true
|
||||
appIsBeingUsed = true
|
||||
DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection(0) }
|
||||
DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection(.neutral) }
|
||||
}
|
||||
|
||||
func cycleSelection(_ step: Int) {
|
||||
Windows.cycleFocusedWindowIndex(step)
|
||||
func cycleSelection(_ direction: Direction) {
|
||||
if direction == .left || direction == .right || direction == .neutral {
|
||||
Windows.cycleFocusedWindowIndex(direction.step())
|
||||
} else {
|
||||
thumbnailsPanel.thumbnailsView.navigateUpOrDown(direction)
|
||||
}
|
||||
}
|
||||
|
||||
func focusSelectedWindow(_ window: Window?) {
|
||||
@@ -183,8 +187,8 @@ class App: NSApplication, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func showUiOrCycleSelection(_ step: Int) {
|
||||
debugPrint("showUiOrCycleSelection", step)
|
||||
func showUiOrCycleSelection(_ direction: Direction) {
|
||||
debugPrint("showUiOrCycleSelection", direction)
|
||||
if isFirstSummon {
|
||||
debugPrint("showUiOrCycleSelection: isFirstSummon")
|
||||
isFirstSummon = false
|
||||
@@ -197,12 +201,12 @@ class App: NSApplication, NSApplicationDelegate {
|
||||
Windows.refreshWhichWindowsToShowTheUser(screen)
|
||||
if Windows.list.first(where: { $0.shouldShowTheUser }) == nil { hideUi(); return }
|
||||
Windows.updateFocusedWindowIndex(0)
|
||||
Windows.cycleFocusedWindowIndex(step)
|
||||
Windows.cycleFocusedWindowIndex(direction.step())
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay) { () -> () in
|
||||
self.rebuildUi()
|
||||
}
|
||||
} else {
|
||||
cycleSelection(step)
|
||||
cycleSelection(direction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import Cocoa
|
||||
class ThumbnailsView: NSVisualEffectView {
|
||||
let scrollView = ScrollView()
|
||||
static var recycledViews = [ThumbnailView]()
|
||||
var rows = [[ThumbnailView]]()
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
@@ -15,6 +16,26 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
(1...100).forEach { _ in ThumbnailsView.recycledViews.append(ThumbnailView()) }
|
||||
}
|
||||
|
||||
func nextRow(_ direction: Direction) -> [ThumbnailView] {
|
||||
let step = direction == .down ? 1 : -1
|
||||
let indexAfterStep = (Windows.focusedWindow()!.row! + step) % rows.count
|
||||
let targetRowIndex = indexAfterStep < 0 ? rows.count + indexAfterStep : indexAfterStep
|
||||
return rows[targetRowIndex]
|
||||
}
|
||||
|
||||
func navigateUpOrDown(_ direction: Direction) {
|
||||
let focusedViewFrame = ThumbnailsView.recycledViews[Windows.focusedWindowIndex].frame
|
||||
let originCenter = NSMidX(focusedViewFrame)
|
||||
let targetRow = nextRow(direction)
|
||||
let leftSide = originCenter < NSMidX(frame)
|
||||
let iterable = leftSide ? targetRow : targetRow.reversed()
|
||||
let targetView = iterable.first(where: {
|
||||
leftSide ? NSMaxX($0.frame) > originCenter : NSMinX($0.frame) < originCenter
|
||||
}) ?? iterable.last!
|
||||
let targetIndex = ThumbnailsView.recycledViews.firstIndex(of: targetView)!
|
||||
Windows.updateFocusedWindowIndex(targetIndex)
|
||||
}
|
||||
|
||||
func updateItems(_ screen: NSScreen) {
|
||||
let widthMax = ThumbnailsPanel.widthMax(screen).rounded()
|
||||
let heightMax = ThumbnailsPanel.heightMax(screen).rounded()
|
||||
@@ -24,6 +45,8 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
var maxX = CGFloat(0)
|
||||
var maxY = height
|
||||
var newViews = [ThumbnailView]()
|
||||
rows.removeAll()
|
||||
rows.append([ThumbnailView]())
|
||||
for (index, window) in Windows.list.enumerated() {
|
||||
guard App.app.appIsBeingUsed else { return }
|
||||
guard window.shouldShowTheUser else { continue }
|
||||
@@ -37,12 +60,15 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
currentX = CGFloat(0)
|
||||
currentY = (currentY + Preferences.interCellPadding + height).rounded(.down)
|
||||
maxY = max(currentY + height, maxY)
|
||||
rows.append([ThumbnailView]())
|
||||
} else {
|
||||
maxX = max(currentX + width, maxX)
|
||||
}
|
||||
view.frame.origin = CGPoint(x: currentX, y: currentY)
|
||||
currentX = (currentX + Preferences.interCellPadding + width).rounded(.down)
|
||||
newViews.append(view)
|
||||
rows[rows.count - 1].append(view)
|
||||
window.row = rows.count - 1
|
||||
}
|
||||
scrollView.documentView!.subviews = newViews
|
||||
frame.size = NSSize(width: min(maxX, widthMax) + Preferences.windowPadding * 2, height: min(maxY, heightMax) + Preferences.windowPadding * 2)
|
||||
@@ -126,3 +152,20 @@ class ScrollView: NSScrollView {
|
||||
class FlippedView: NSView {
|
||||
override var isFlipped: Bool { true }
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
case neutral
|
||||
case left
|
||||
case right
|
||||
case up
|
||||
case down
|
||||
|
||||
func step() -> Int {
|
||||
if self == .left {
|
||||
return -1
|
||||
} else if self == .right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ class GeneralTab: NSViewController, PreferencePane {
|
||||
static var shortcutsDependentOnHoldShortcut = [NSControl]()
|
||||
static var shortcutsActionsBlocks = [
|
||||
"holdShortcut": { App.app.focusTarget() },
|
||||
"nextWindowShortcut": { App.app.showUiOrCycleSelection(1) },
|
||||
"previousWindowShortcut": { App.app.showUiOrCycleSelection(-1) },
|
||||
"→": { App.app.cycleSelection(1) },
|
||||
"←": { App.app.cycleSelection(-1) },
|
||||
"nextWindowShortcut": { App.app.showUiOrCycleSelection(.right) },
|
||||
"previousWindowShortcut": { App.app.showUiOrCycleSelection(.left) },
|
||||
"→": { App.app.cycleSelection(.right) },
|
||||
"←": { App.app.cycleSelection(.left) },
|
||||
"↑": { App.app.cycleSelection(.up) },
|
||||
"↓": { App.app.cycleSelection(.down) },
|
||||
"cancelShortcut": { App.app.hideUi() },
|
||||
"closeWindowShortcut": { App.app.closeSelectedWindow() },
|
||||
"minDeminWindowShortcut": { App.app.minDeminSelectedWindow() },
|
||||
@@ -86,7 +88,7 @@ class GeneralTab: NSViewController, PreferencePane {
|
||||
App.app.isFirstSummon = true
|
||||
}
|
||||
let fn = shortcutsActionsBlocks[controlId]!
|
||||
let isShortcutAffectingTheUi = ["previousWindowShortcut", "nextWindowShortcut", "←", "→", "holdShortcut"].contains(controlId)
|
||||
let isShortcutAffectingTheUi = ["previousWindowShortcut", "nextWindowShortcut", "←", "→", "↑", "↓", "holdShortcut"].contains(controlId)
|
||||
if isShortcutAffectingTheUi || isShortcutClosingTheUi {
|
||||
DispatchQueue.main.async { () -> () in fn() }
|
||||
} else {
|
||||
@@ -115,12 +117,11 @@ class GeneralTab: NSViewController, PreferencePane {
|
||||
}
|
||||
|
||||
@objc static func arrowKeysEnabledCallback(_ sender: NSControl) {
|
||||
let keys = ["←", "→", "↑", "↓"]
|
||||
if (sender as! NSButton).state == .on {
|
||||
addShortcut(.down, Shortcut(keyEquivalent: Preferences.holdShortcut + "→")!, "→")
|
||||
addShortcut(.down, Shortcut(keyEquivalent: Preferences.holdShortcut + "←")!, "←")
|
||||
keys.forEach { addShortcut(.down, Shortcut(keyEquivalent: Preferences.holdShortcut + $0)!, $0) }
|
||||
} else {
|
||||
removeShortcutIfExists("→", .down)
|
||||
removeShortcutIfExists("←", .down)
|
||||
keys.forEach { removeShortcutIfExists($0, .down) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user