fix: center progress thumb on pointer while seeking
This commit is contained in:
@@ -201,9 +201,9 @@ struct CastPlaybackCheck {
|
|||||||
_ = NSApplication.shared
|
_ = NSApplication.shared
|
||||||
NSApp.setActivationPolicy(.prohibited)
|
NSApp.setActivationPolicy(.prohibited)
|
||||||
require(castVolumeSliderFeedbackCheck(), "Native slider keeps user position during tracking and applies receiver volume when idle")
|
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" {
|
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
|
return
|
||||||
}
|
}
|
||||||
let source = URL(fileURLWithPath: CommandLine.arguments[1])
|
let source = URL(fileURLWithPath: CommandLine.arguments[1])
|
||||||
|
|||||||
@@ -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.
|
- **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/<opaque-id>/<slug>?ep=<episode>` with an explicit positive episode number; arbitrary streaming sites are not supported.
|
- **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/<opaque-id>/<slug>?ep=<episode>` 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.
|
- **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.
|
- **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.
|
- **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.
|
||||||
|
|||||||
@@ -2008,17 +2008,14 @@ private struct PlaybackSlider: NSViewRepresentable {
|
|||||||
let width = bar.width - cell.knobThickness
|
let width = bar.width - cell.knobThickness
|
||||||
guard width > 0, maxValue > minValue else { return }
|
guard width > 0, maxValue > minValue else { return }
|
||||||
let point = convert(event.locationInWindow, from: nil)
|
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) {
|
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)
|
let value = minValue + fraction * (maxValue - minValue)
|
||||||
guard value != doubleValue else { return }
|
guard value != doubleValue else { return }
|
||||||
doubleValue = value
|
doubleValue = value
|
||||||
if let action { sendAction(action, to: target) }
|
if let action { sendAction(action, to: target) }
|
||||||
}
|
}
|
||||||
if !grabbed { seek(point.x) }
|
seek(point.x)
|
||||||
while let next = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp]) {
|
while let next = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp]) {
|
||||||
let nextPoint = convert(next.locationInWindow, from: nil)
|
let nextPoint = convert(next.locationInWindow, from: nil)
|
||||||
if next.type == .leftMouseDragged || nextPoint.x != point.x { seek(nextPoint.x) }
|
if next.type == .leftMouseDragged || nextPoint.x != point.x { seek(nextPoint.x) }
|
||||||
@@ -2130,12 +2127,9 @@ func playbackSliderHitCheck() -> Bool {
|
|||||||
}
|
}
|
||||||
slider.mouseDown(with: down)
|
slider.mouseDown(with: down)
|
||||||
let movedKnob = cell.knobRect(flipped: slider.isFlipped)
|
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))
|
let expectedX = min(right, max(left, knob.midX + expectedMovement))
|
||||||
guard abs(movedKnob.midX - expectedX) < 0.5 else { return false }
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user