From d068c52e04d83389e097440cb4eef7a2bb3fe23c Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 19 Oct 2026 19:09:11 +0200 Subject: [PATCH] fix: route the URL shortcut to the inline input --- Checks/app-startup.swift | 7 ++++++ README.md | 2 +- Sources/App/PlayerActions.swift | 3 +++ Sources/App/TerebiApp.swift | 40 ++++++++++++++------------------- TODO.md | 6 +++-- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/Checks/app-startup.swift b/Checks/app-startup.swift index 11a6198..266b0b8 100644 --- a/Checks/app-startup.swift +++ b/Checks/app-startup.swift @@ -79,6 +79,13 @@ private enum WindowDragCheck { } 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] { let event = NSEvent.mouseEvent(with: type, location: NSPoint(x: 400, y: 272), modifierFlags: [], timestamp: ProcessInfo.processInfo.systemUptime, diff --git a/README.md b/README.md index bbaa9e8..a06fe6a 100644 --- a/README.md +++ b/README.md @@ -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. -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 | | --- | --- | diff --git a/Sources/App/PlayerActions.swift b/Sources/App/PlayerActions.swift index 1e1aee9..1159c88 100644 --- a/Sources/App/PlayerActions.swift +++ b/Sources/App/PlayerActions.swift @@ -94,6 +94,9 @@ extension PlayerModel { private func handleKeyEvent(_ event: NSEvent, in window: NSWindow, chord: KeyChord) -> Bool { 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 if let text = responder as? NSTextView, text.isEditable || text.isSelectable { return false } if let text = responder as? NSTextField, text.isEditable || text.isSelectable { return false } diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index acf6036..407930f 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -29,6 +29,7 @@ final class PlayerModel: ObservableObject { @Published private(set) var state = PlayerState() @Published private(set) var fileName: String? + @Published var showsYouTubeInput = false @Published private(set) var fileIcon: NSImage? @Published private(set) var commandError: String? @Published private(set) var queue = PlaybackQueue() @@ -292,23 +293,6 @@ final class PlayerModel: ObservableObject { 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) { guard canOpen else { return } @@ -320,6 +304,7 @@ final class PlayerModel: ObservableObject { revealControls() return } + showsYouTubeInput = false clearQueue() resolveYouTube(url, height: nil) } @@ -963,7 +948,7 @@ final class PlayerModel: ObservableObject { window?.level = isOnTop ? .floating : .normal revealControls() case .openFile: openPanel() - case .openYoutube: openYouTubePanel() + case .openYoutube: showsYouTubeInput.toggle() case .closeFile: closeFile() case .quit: NSApp.terminate(nil) case .showBufferInfo: setShowsBufferInfo(true) @@ -2094,7 +2079,7 @@ private struct PlayerContent: View { @Environment(\.accessibilityReduceMotion) private var reduceMotion @ViewState private var showsRemainingTime = false @ViewState private var dropTargeted = false - @ViewState private var showsYouTubeInput = false + private var showsYouTubeInput: Bool { model.showsYouTubeInput } @ViewState private var youTubeURL = "" @FocusState private var youTubeInputFocused: Bool @ViewState private var controlSize = CGSize(width: 440, height: 68) @@ -2133,9 +2118,7 @@ private struct PlayerContent: View { } private func toggleYouTubeInput() { - if showsYouTubeInput { youTubeInputFocused = false } - showsYouTubeInput.toggle() - if showsYouTubeInput { youTubeInputFocused = true } + model.perform(.openYoutube) } private func startScreen(in size: CGSize) -> some View { @@ -2227,7 +2210,7 @@ private struct PlayerContent: View { .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)) .disabled(!model.canOpen) .padding(.horizontal, size.width < 600 ? 14 : 30) @@ -2808,6 +2791,17 @@ private struct PlayerContent: View { .tint(Color(SunsetPalette.accent)) .background(WindowChrome(model: model)) .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 { diff --git a/TODO.md b/TODO.md index 8a908db..dc93c05 100644 --- a/TODO.md +++ b/TODO.md @@ -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 - [ ] on the start screen: - [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 - - 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) \ No newline at end of file + - 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 \ No newline at end of file