WIP: add shelf range navigation keys
This commit is contained in:
@@ -26,6 +26,15 @@ enum ClipboardPanelShortcutAction: Equatable {
|
||||
case toggleStack
|
||||
}
|
||||
|
||||
enum ClipboardPanelNavigationAction: Equatable {
|
||||
case first
|
||||
case last
|
||||
case next
|
||||
case pageNext
|
||||
case pagePrevious
|
||||
case previous
|
||||
}
|
||||
|
||||
final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
|
||||
private enum Animation {
|
||||
static let showDuration: TimeInterval = 0.16
|
||||
@@ -375,6 +384,10 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
|
||||
return nil
|
||||
}
|
||||
guard self.shouldHandlePanelKeyEvent(event) else { return event }
|
||||
if let action = Self.navigationShortcutAction(forKeyCode: event.keyCode, modifiers: event.modifierFlags) {
|
||||
self.performNavigationAction(action)
|
||||
return nil
|
||||
}
|
||||
switch event.keyCode {
|
||||
case 53:
|
||||
self.hide()
|
||||
@@ -388,12 +401,6 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
|
||||
case 51, 117:
|
||||
self.viewModel.deleteSelected()
|
||||
return nil
|
||||
case 123:
|
||||
self.viewModel.moveSelection(-1)
|
||||
return nil
|
||||
case 124:
|
||||
self.viewModel.moveSelection(1)
|
||||
return nil
|
||||
case 35:
|
||||
self.viewModel.togglePinSelected()
|
||||
return nil
|
||||
@@ -403,6 +410,24 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
|
||||
}
|
||||
}
|
||||
|
||||
private func performNavigationAction(_ action: ClipboardPanelNavigationAction) {
|
||||
switch action {
|
||||
case .first:
|
||||
viewModel.selectFirstItem()
|
||||
case .last:
|
||||
viewModel.selectLastItem()
|
||||
case .next:
|
||||
viewModel.moveSelection(1)
|
||||
case .pageNext:
|
||||
viewModel.moveSelection(panelView.visibleCardPageStep)
|
||||
case .pagePrevious:
|
||||
viewModel.moveSelection(-panelView.visibleCardPageStep)
|
||||
case .previous:
|
||||
viewModel.moveSelection(-1)
|
||||
}
|
||||
panelView.focusSelectedCardForKeyboardNavigation()
|
||||
}
|
||||
|
||||
private func performShortcutAction(_ action: ClipboardPanelShortcutAction) {
|
||||
switch action {
|
||||
case .copy:
|
||||
@@ -467,6 +492,20 @@ final class ClipboardPanelController: NSObject, NSWindowDelegate, QLPreviewPanel
|
||||
return quickPasteKeyCodes[keyCode]
|
||||
}
|
||||
|
||||
static func navigationShortcutAction(forKeyCode keyCode: UInt16, modifiers: NSEvent.ModifierFlags) -> ClipboardPanelNavigationAction? {
|
||||
let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask)
|
||||
guard relevantModifiers.isEmpty else { return nil }
|
||||
switch keyCode {
|
||||
case 115: return .first
|
||||
case 119: return .last
|
||||
case 124: return .next
|
||||
case 121: return .pageNext
|
||||
case 116: return .pagePrevious
|
||||
case 123: return .previous
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func quickPastePlainTextIndex(forKeyCode keyCode: UInt16, modifiers: NSEvent.ModifierFlags) -> Int? {
|
||||
let relevantModifiers = modifiers.intersection(.deviceIndependentFlagsMask)
|
||||
guard relevantModifiers == [.command, .shift] else { return nil }
|
||||
|
||||
@@ -722,6 +722,19 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
card.onSelect = { [weak self] selected in
|
||||
self?.viewModel.selectItem(at: selected)
|
||||
}
|
||||
card.onMoveSelection = { [weak self] delta in
|
||||
self?.moveSelectionFromFocusedCard(delta)
|
||||
}
|
||||
card.onPageSelection = { [weak self] direction in
|
||||
guard let self else { return }
|
||||
self.moveSelectionFromFocusedCard(direction * self.visibleCardPageStep)
|
||||
}
|
||||
card.onSelectFirst = { [weak self] in
|
||||
self?.selectFirstCardFromFocusedCard()
|
||||
}
|
||||
card.onSelectLast = { [weak self] in
|
||||
self?.selectLastCardFromFocusedCard()
|
||||
}
|
||||
card.onPaste = { [weak self] selected in
|
||||
self?.viewModel.selectItem(at: selected)
|
||||
self?.viewModel.pasteSelected()
|
||||
@@ -1156,6 +1169,16 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
needsLayout = true
|
||||
}
|
||||
|
||||
var visibleCardPageStep: Int {
|
||||
let span = cardDensity.layout.width + cardDensity.cardSpacing
|
||||
guard span > 0 else { return 1 }
|
||||
return max(1, Int(floor(scrollView.contentView.bounds.width / span)))
|
||||
}
|
||||
|
||||
func focusSelectedCardForKeyboardNavigation() {
|
||||
focusSelectedCard()
|
||||
}
|
||||
|
||||
func prepareForShow() {
|
||||
if !searchField.stringValue.isEmpty {
|
||||
searchField.stringValue = ""
|
||||
@@ -1283,6 +1306,10 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
scrollView.documentView?.frame.width ?? 0
|
||||
}
|
||||
|
||||
var debugVisibleCardPageStep: Int {
|
||||
visibleCardPageStep
|
||||
}
|
||||
|
||||
var debugCardRailOverflowFadeVisibility: [Bool] {
|
||||
scrollView.overflowFadeVisibility
|
||||
}
|
||||
@@ -1381,6 +1408,10 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
debugPressFocusedResponder(characters: " ", keyCode: 49)
|
||||
}
|
||||
|
||||
func debugPressFocusedResponderKeyCode(_ keyCode: UInt16) {
|
||||
debugPressFocusedResponder(characters: "", keyCode: keyCode)
|
||||
}
|
||||
|
||||
private func debugPressFocusedResponder(characters: String, keyCode: UInt16) {
|
||||
guard let window,
|
||||
let event = NSEvent.keyEvent(
|
||||
@@ -1547,6 +1578,29 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
viewModel.searchText = searchField.stringValue
|
||||
}
|
||||
|
||||
private func moveSelectionFromFocusedCard(_ delta: Int) {
|
||||
viewModel.moveSelection(delta)
|
||||
focusSelectedCard()
|
||||
}
|
||||
|
||||
private func selectFirstCardFromFocusedCard() {
|
||||
viewModel.selectFirstItem()
|
||||
focusSelectedCard()
|
||||
}
|
||||
|
||||
private func selectLastCardFromFocusedCard() {
|
||||
viewModel.selectLastItem()
|
||||
focusSelectedCard()
|
||||
}
|
||||
|
||||
private func focusSelectedCard() {
|
||||
guard viewModel.selectedIndex >= 0,
|
||||
viewModel.selectedIndex < cardViews.count else {
|
||||
return
|
||||
}
|
||||
window?.makeFirstResponder(cardViews[viewModel.selectedIndex])
|
||||
}
|
||||
|
||||
@objc private func closePanel() {
|
||||
onClose()
|
||||
}
|
||||
@@ -2148,6 +2202,10 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
}
|
||||
|
||||
var onSelect: (Int) -> Void = { _ in }
|
||||
var onMoveSelection: (Int) -> Void = { _ in }
|
||||
var onPageSelection: (Int) -> Void = { _ in }
|
||||
var onSelectFirst: () -> Void = {}
|
||||
var onSelectLast: () -> Void = {}
|
||||
var onPaste: (Int) -> Void = { _ in }
|
||||
var onCopy: (Int) -> Void = { _ in }
|
||||
var onPastePlainText: (Int) -> Void = { _ in }
|
||||
@@ -2289,6 +2347,18 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
} else {
|
||||
onPaste(index)
|
||||
}
|
||||
case 115:
|
||||
onSelectFirst()
|
||||
case 116:
|
||||
onPageSelection(-1)
|
||||
case 119:
|
||||
onSelectLast()
|
||||
case 121:
|
||||
onPageSelection(1)
|
||||
case 123:
|
||||
onMoveSelection(-1)
|
||||
case 124:
|
||||
onMoveSelection(1)
|
||||
default:
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
|
||||
@@ -211,6 +211,17 @@ final class ClipboardPanelViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
func selectLastItem() {
|
||||
guard !visibleItems.isEmpty else { return }
|
||||
selectedItemID = nil
|
||||
let lastIndex = visibleItems.count - 1
|
||||
if selectedIndex == lastIndex {
|
||||
notifyMain { self.onSelectedIndexChanged?(self.selectedIndex) }
|
||||
} else {
|
||||
selectedIndex = lastIndex
|
||||
}
|
||||
}
|
||||
|
||||
func moveSelection(_ delta: Int) {
|
||||
let count = visibleItems.count
|
||||
guard count > 0 else { return }
|
||||
|
||||
Reference in New Issue
Block a user