From 40e2ea45a046487bbe1a982144f725679b73e29a Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Thu, 22 Oct 2026 12:37:01 +0200 Subject: [PATCH] fix: center progress thumb on pointer while seeking --- Checks/cast-playback.swift | 4 ++-- README.md | 2 +- Sources/App/TerebiApp.swift | 12 +++--------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Checks/cast-playback.swift b/Checks/cast-playback.swift index 33319ea..771a808 100644 --- a/Checks/cast-playback.swift +++ b/Checks/cast-playback.swift @@ -201,9 +201,9 @@ struct CastPlaybackCheck { _ = NSApplication.shared NSApp.setActivationPolicy(.prohibited) require(castVolumeSliderFeedbackCheck(), "Native slider keeps user position during tracking and applies receiver volume when idle") - require(playbackSliderHitCheck(), "Progress slider seeks beside the thumb and preserves cursor alignment throughout dragging") + require(playbackSliderHitCheck(), "Progress thumb centers on the cursor for clicks and drags, clamped to track endpoints") if CommandLine.arguments.count == 2, CommandLine.arguments[1] == "--slider-regression" { - print("PASS: adjacent seeking, thumb and track drag alignment, and volume tracking feedback") + print("PASS: centered progress clicks and drags, endpoint clamping, and volume tracking feedback") return } let source = URL(fileURLWithPath: CommandLine.arguments[1]) diff --git a/README.md b/README.md index 15b1288..6ed9641 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ To run without installing, use `make run`. The built `.build/terebi.app` is self - **Local files:** use **⌘O**, drag files onto the player, or choose Finder's **Open With**. Multiple files form a queue in natural filename order, with automatic advance. Use **View → Show Queue** to play, reorder or remove items; **+** or dropping files onto the sidebar appends them. The queue lasts only for the session; opening other media replaces it. - **Streams:** use **Open Stream…** (**⇧⌘O**) or drop a supported link. YouTube supports individual videos, not playlists or channels. Miruro links must use `https://barelystarted.miruro.tv/watch//?ep=` with an explicit positive episode number; arbitrary streaming sites are not supported. -- **Seeking:** click the progress track to seek, including immediately beside the visible thumb. Grabbing the thumb preserves the cursor's position within it throughout the drag; dragging from the track first seeks to the cursor. +- **Seeking:** click or drag the progress track or thumb to seek. The thumb centers on the cursor, clamped to the track endpoints. - **Miruro metadata:** titles and opening/ending intervals load independently of playback when available. Small rounded gaps mark interval boundaries in the slider, without visible labels; accessibility descriptions retain the interval names and times. Missing/invalid metadata leaves playback usable. Timings may differ between cuts. No automatic skipping is performed. - **Resume:** videos resume their saved position; finished videos restart from the beginning. Live and audio-only media do not save progress. - **Compact mode:** press **P** for a floating player; double-click it to restore. Drag noninteractive areas to move the window. Use the titlebar pin to keep the normal window on top. diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index 1f62eb4..697bbbd 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -2008,17 +2008,14 @@ private struct PlaybackSlider: NSViewRepresentable { let width = bar.width - cell.knobThickness guard width > 0, maxValue > minValue else { return } let point = convert(event.locationInWindow, from: nil) - let knob = cell.visibleKnobRect(cell.knobRect(flipped: isFlipped)) - let grabbed = NSBezierPath(roundedRect: knob, xRadius: 1, yRadius: 1).contains(point) - let offset = grabbed ? point.x - knob.midX : 0 func seek(_ x: CGFloat) { - let fraction = min(1, max(0, (x - offset - start) / width)) + let fraction = min(1, max(0, (x - start) / width)) let value = minValue + fraction * (maxValue - minValue) guard value != doubleValue else { return } doubleValue = value if let action { sendAction(action, to: target) } } - if !grabbed { seek(point.x) } + seek(point.x) while let next = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp]) { let nextPoint = convert(next.locationInWindow, from: nil) if next.type == .leftMouseDragged || nextPoint.x != point.x { seek(nextPoint.x) } @@ -2130,12 +2127,9 @@ func playbackSliderHitCheck() -> Bool { } slider.mouseDown(with: down) let movedKnob = cell.knobRect(flipped: slider.isFlipped) - let expectedMovement = movement + (abs(offset) <= 1 ? 0 : offset) + let expectedMovement = movement + offset let expectedX = min(right, max(left, knob.midX + expectedMovement)) guard abs(movedKnob.midX - expectedX) < 0.5 else { return false } - if abs(offset) <= 1 && movement == 0 { - guard slider.doubleValue == initial else { return false } - } } return true }