fix: route the URL shortcut to the inline input

This commit is contained in:
2026-10-19 19:09:11 +02:00
parent f6485bf221
commit d068c52e04
5 changed files with 32 additions and 26 deletions
+7
View File
@@ -79,6 +79,13 @@ private enum WindowDragCheck {
} }
static func clickStartURL(in window: NSWindow) { static func clickStartURL(in window: NSWindow) {
if phase != 0 {
let event = NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber,
context: nil, characters: "O", charactersIgnoringModifiers: "o", isARepeat: false, keyCode: 31)!
NSApp.postEvent(event, atStart: false)
return
}
for type in [NSEvent.EventType.leftMouseDown, .leftMouseUp] { for type in [NSEvent.EventType.leftMouseDown, .leftMouseUp] {
let event = NSEvent.mouseEvent(with: type, location: NSPoint(x: 400, y: 272), let event = NSEvent.mouseEvent(with: type, location: NSPoint(x: 400, y: 272),
modifierFlags: [], timestamp: ProcessInfo.processInfo.systemUptime, modifierFlags: [], timestamp: ProcessInfo.processInfo.systemUptime,
+1 -1
View File
@@ -41,7 +41,7 @@ The start screen uses a fixed 640 × 400-point two-pane window: Terebi's identit
Start-screen actions use 14-point text and begin 64 points below the top edge. Branding uses a 16-point app name and 12-point version; the drop hint uses 13-point text. Open actions have no idle fill; their background appears on hover or press. Start-screen actions use 14-point text and begin 64 points below the top edge. Branding uses a 16-point app name and 12-point version; the drop hint uses 13-point text. Open actions have no idle fill; their background appears on hover or press.
URL-field expansion and collapse use a 250 ms ease-in/out fade with a subtle top-anchored scale; recent rows move with the transition without resizing the window. Reduce Motion makes the change immediate. Hiding or rapidly toggling the field preserves its draft, and hiding releases keyboard focus. URL-field expansion and collapse use a 200 ms ease-in/out fade with a subtle top-anchored scale; recent rows move with the transition without resizing the window. Reduce Motion makes the change immediate. The Open URL button and ⇧⌘O toggle the same inline input, retaining its draft and releasing keyboard focus when hidden. During playback, the input appears over the player without interrupting playback; no separate URL dialog opens.
| Action | Default shortcut | | Action | Default shortcut |
| --- | --- | | --- | --- |
+3
View File
@@ -94,6 +94,9 @@ extension PlayerModel {
private func handleKeyEvent(_ event: NSEvent, in window: NSWindow, chord: KeyChord) -> Bool { private func handleKeyEvent(_ event: NSEvent, in window: NSWindow, chord: KeyChord) -> Bool {
guard event.window === window, NSApp.modalWindow == nil, window.attachedSheet == nil else { return false } guard event.window === window, NSApp.modalWindow == nil, window.attachedSheet == nil else { return false }
if chord.modifiers.contains(.command), keyBindings.shortcuts(for: .openYoutube).contains(chord) {
return perform(.openYoutube)
}
let responder = window.firstResponder let responder = window.firstResponder
if let text = responder as? NSTextView, text.isEditable || text.isSelectable { return false } if let text = responder as? NSTextView, text.isEditable || text.isSelectable { return false }
if let text = responder as? NSTextField, text.isEditable || text.isSelectable { return false } if let text = responder as? NSTextField, text.isEditable || text.isSelectable { return false }
+17 -23
View File
@@ -29,6 +29,7 @@ final class PlayerModel: ObservableObject {
@Published private(set) var state = PlayerState() @Published private(set) var state = PlayerState()
@Published private(set) var fileName: String? @Published private(set) var fileName: String?
@Published var showsYouTubeInput = false
@Published private(set) var fileIcon: NSImage? @Published private(set) var fileIcon: NSImage?
@Published private(set) var commandError: String? @Published private(set) var commandError: String?
@Published private(set) var queue = PlaybackQueue() @Published private(set) var queue = PlaybackQueue()
@@ -292,23 +293,6 @@ final class PlayerModel: ObservableObject {
if append { addToQueue(panel.urls) } else { openFiles(panel.urls) } if append { addToQueue(panel.urls) } else { openFiles(panel.urls) }
} }
func openYouTubePanel() {
guard canOpen else { return }
defer { window?.makeFirstResponder(nil) }
revealControls()
let alert = NSAlert()
alert.messageText = "Open YouTube Video"
alert.informativeText = "Paste a YouTube video URL."
alert.addButton(withTitle: "Open")
alert.addButton(withTitle: "Cancel")
let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24))
field.placeholderString = "https://www.youtube.com/watch?v=…"
field.setAccessibilityLabel("YouTube video URL")
alert.accessoryView = field
alert.window.initialFirstResponder = field
guard alert.runModal() == .alertFirstButtonReturn else { return }
openYouTube(field.stringValue)
}
func openYouTube(_ text: String) { func openYouTube(_ text: String) {
guard canOpen else { return } guard canOpen else { return }
@@ -320,6 +304,7 @@ final class PlayerModel: ObservableObject {
revealControls() revealControls()
return return
} }
showsYouTubeInput = false
clearQueue() clearQueue()
resolveYouTube(url, height: nil) resolveYouTube(url, height: nil)
} }
@@ -963,7 +948,7 @@ final class PlayerModel: ObservableObject {
window?.level = isOnTop ? .floating : .normal window?.level = isOnTop ? .floating : .normal
revealControls() revealControls()
case .openFile: openPanel() case .openFile: openPanel()
case .openYoutube: openYouTubePanel() case .openYoutube: showsYouTubeInput.toggle()
case .closeFile: closeFile() case .closeFile: closeFile()
case .quit: NSApp.terminate(nil) case .quit: NSApp.terminate(nil)
case .showBufferInfo: setShowsBufferInfo(true) case .showBufferInfo: setShowsBufferInfo(true)
@@ -2094,7 +2079,7 @@ private struct PlayerContent: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion @Environment(\.accessibilityReduceMotion) private var reduceMotion
@ViewState private var showsRemainingTime = false @ViewState private var showsRemainingTime = false
@ViewState private var dropTargeted = false @ViewState private var dropTargeted = false
@ViewState private var showsYouTubeInput = false private var showsYouTubeInput: Bool { model.showsYouTubeInput }
@ViewState private var youTubeURL = "" @ViewState private var youTubeURL = ""
@FocusState private var youTubeInputFocused: Bool @FocusState private var youTubeInputFocused: Bool
@ViewState private var controlSize = CGSize(width: 440, height: 68) @ViewState private var controlSize = CGSize(width: 440, height: 68)
@@ -2133,9 +2118,7 @@ private struct PlayerContent: View {
} }
private func toggleYouTubeInput() { private func toggleYouTubeInput() {
if showsYouTubeInput { youTubeInputFocused = false } model.perform(.openYoutube)
showsYouTubeInput.toggle()
if showsYouTubeInput { youTubeInputFocused = true }
} }
private func startScreen(in size: CGSize) -> some View { private func startScreen(in size: CGSize) -> some View {
@@ -2227,7 +2210,7 @@ private struct PlayerContent: View {
.padding(.top, 20) .padding(.top, 20)
} }
} }
.animation(reduceMotion ? nil : .easeInOut(duration: 0.25), value: showsYouTubeInput) .animation(reduceMotion ? nil : .easeInOut(duration: 0.2), value: showsYouTubeInput)
.font(.system(size: 14, weight: .medium)) .font(.system(size: 14, weight: .medium))
.disabled(!model.canOpen) .disabled(!model.canOpen)
.padding(.horizontal, size.width < 600 ? 14 : 30) .padding(.horizontal, size.width < 600 ? 14 : 30)
@@ -2808,6 +2791,17 @@ private struct PlayerContent: View {
.tint(Color(SunsetPalette.accent)) .tint(Color(SunsetPalette.accent))
.background(WindowChrome(model: model)) .background(WindowChrome(model: model))
.onChange(of: windowDragging) { _, active in model.setControlsDragging(active) } .onChange(of: windowDragging) { _, active in model.setControlsDragging(active) }
.onChange(of: showsYouTubeInput) { _, shown in youTubeInputFocused = shown }
.overlay(alignment: .top) {
if model.fileName != nil && showsYouTubeInput {
startURLInput
.padding(12)
.background(PlayerGlass().allowsHitTesting(false))
.frame(maxWidth: 420)
.padding(.top, 44)
.transition(.opacity)
}
}
} }
private var windowDragBackground: some View { private var windowDragBackground: some View {
+4 -2
View File
@@ -14,6 +14,8 @@
- scoped native background dragging across start/playback/queue/error/Cast/compact views, with passive loading/audio overlays passing input through; 20 native input-routing probes passed, including controls and row-reorder exclusions; physical pointer dragging and Cast-screen interaction still need manual verification - scoped native background dragging across start/playback/queue/error/Cast/compact views, with passive loading/audio overlays passing input through; 20 native input-routing probes passed, including controls and row-reorder exclusions; physical pointer dragging and Cast-screen interaction still need manual verification
- [ ] on the start screen: - [ ] on the start screen:
- [x] make the youtube url input field that appears when clicking the youtube button appear with a nice transition - [x] make the youtube url input field that appears when clicking the youtube button appear with a nice transition
- 250 ms fade/scale with animated row reflow; native focus, draft-preservation and rapid-reversal checks passed, including an isolated Reduce Motion branch smoke check; animation feel still needs manual verification - 200 ms fade/scale with animated row reflow; the button and ⇧⌘O toggle the same inline input, including while editing; native focus, draft-preservation and rapid-reversal checks cover mouse/shortcut interoperation
- [ ] change the youtube button to general streaming - [ ] change the youtube button to general streaming
- we can figure out the stream and how to handle it from the url (i.e. if youtube url, handle as youtube stream; if miruro url, handle accordingly) - we can figure out the stream and how to handle it from the url (i.e. if youtube url, handle as youtube stream; if miruro url, handle accordingly)
- [ ] check whether we're using appropriately new apple ui APIs / SDKs for the project.
- we should strive to use modern and idiomatic SDKs and APIs including SwiftUI among others