diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index 1e9d5952..c2d3cf19 100644 --- a/Resources/en.lproj/Localizable.strings +++ b/Resources/en.lproj/Localizable.strings @@ -101,6 +101,7 @@ "Ring" = "Ring"; "scanning local logs…" = "scanning local logs…"; "Settings" = "Settings"; +"Switch to %@ (⌘%d)" = "Switch to %@ (⌘%d)"; "Show on" = "Show on"; "Sparkline" = "Sparkline"; "Spacing" = "Spacing"; diff --git a/Resources/zh-Hans.lproj/Localizable.strings b/Resources/zh-Hans.lproj/Localizable.strings index e729569a..d96e12d3 100644 --- a/Resources/zh-Hans.lproj/Localizable.strings +++ b/Resources/zh-Hans.lproj/Localizable.strings @@ -101,6 +101,7 @@ "Ring" = "环形"; "scanning local logs…" = "正在扫描本地日志…"; "Settings" = "设置"; +"Switch to %@ (⌘%d)" = "切换到%@(⌘%d)"; "Show on" = "显示在"; "Sparkline" = "折线"; "Spacing" = "间距"; diff --git a/Sources/Views/PageIndicator.swift b/Sources/Views/PageIndicator.swift index 88a42f5e..35bbe62e 100644 --- a/Sources/Views/PageIndicator.swift +++ b/Sources/Views/PageIndicator.swift @@ -19,17 +19,21 @@ struct PageIndicator: View { private func dot(for screen: ScreenPref.Screen) -> some View { let isActive = screenPref.screen == screen - return Circle() - .fill(.white.opacity(isActive ? 0.78 : 0.22)) - .frame(width: 5, height: 5) - // Visual stays 5pt; hit area expands ~6pt outward so the dot - // is reachable without pixel-precise aim. - .contentShape(Rectangle().inset(by: -6)) - .onTapGesture { model.showScreen(screen) } - .accessibilityElement() - .accessibilityLabel(accessibilityLabel(for: screen)) - .accessibilityAddTraits(.isButton) - .accessibilityAddTraits(isActive ? .isSelected : []) + return Button { + model.showScreen(screen) + } label: { + Circle() + .fill(.white.opacity(isActive ? 0.78 : 0.22)) + .frame(width: 5, height: 5) + // Visual stays 5pt; hit area expands ~6pt outward so the dot + // is reachable without pixel-precise aim. + .contentShape(Rectangle().inset(by: -6)) + } + .buttonStyle(.plain) + .help(L10n.tr("Switch to %@ (⌘%d)", screen.pageLabel, screen.pageIndex + 1)) + .accessibilityLabel(accessibilityLabel(for: screen)) + .accessibilityAddTraits(.isButton) + .accessibilityAddTraits(isActive ? .isSelected : []) } private func accessibilityLabel(for screen: ScreenPref.Screen) -> String { diff --git a/Sources/Window/IslandWindowController.swift b/Sources/Window/IslandWindowController.swift index 55d5b35c..789c869a 100644 --- a/Sources/Window/IslandWindowController.swift +++ b/Sources/Window/IslandWindowController.swift @@ -136,13 +136,8 @@ final class IslandWindowController { if inside { NSApp.activate(ignoringOtherApps: true) window.makeKey() - cmdQMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in - if event.modifierFlags.contains(.command), - event.charactersIgnoringModifiers == "q" { - NSApp.terminate(nil) - return nil - } - return event + cmdQMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in + self?.handleKeyDown(event) ?? event } } else { if let m = cmdQMonitor { NSEvent.removeMonitor(m) } @@ -151,6 +146,40 @@ final class IslandWindowController { } } + private func handleKeyDown(_ event: NSEvent) -> NSEvent? { + guard window.isKeyWindow else { return event } + + let modifiers = event.modifierFlags + .intersection(.deviceIndependentFlagsMask) + .subtracting([.capsLock]) + if modifiers == .command, event.charactersIgnoringModifiers == "q" { + NSApp.terminate(nil) + return nil + } + + guard model.state == .expanded else { return event } + + if modifiers == .command, + let character = event.charactersIgnoringModifiers, + let index = ["1", "2", "3"].firstIndex(of: character) { + model.showScreen(ScreenPref.Screen.allCases[index]) + return nil + } + + let navigationModifiers = modifiers.subtracting([.function, .numericPad, .capsLock]) + guard navigationModifiers.isEmpty else { return event } + switch event.keyCode { + case 123: + model.rewindScreen() + return nil + case 124: + model.advanceScreen() + return nil + default: + return event + } + } + @MainActor private static func targetScreen() -> NSScreen? { DisplayInfo.currentTarget()?.screen