fix: preserve native thumb geometry while seeking beside it
Remove the knobRect override: native slider geometry also affects drag positioning. Progress pointer tracking now shares the drawn thumb bounds, preserves the initial grab offset, and clamps to the track endpoints. Expanded native event regression covers adjacent clicks, thumb drags, track drags, and endpoint clamping; application build passes.
This commit is contained in:
+60
-18
@@ -1995,9 +1995,35 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
guard isEnabled else { return }
|
||||
tracking = true
|
||||
editingChanged(true)
|
||||
super.mouseDown(with: event)
|
||||
tracking = false
|
||||
editingChanged(false)
|
||||
defer {
|
||||
tracking = false
|
||||
editingChanged(false)
|
||||
}
|
||||
guard let cell = cell as? Cell, !cell.volume else {
|
||||
super.mouseDown(with: event)
|
||||
return
|
||||
}
|
||||
let bar = cell.barRect(flipped: isFlipped)
|
||||
let start = bar.minX + cell.knobThickness / 2
|
||||
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 value = minValue + fraction * (maxValue - minValue)
|
||||
guard value != doubleValue else { return }
|
||||
doubleValue = value
|
||||
if let action { sendAction(action, to: target) }
|
||||
}
|
||||
if !grabbed { 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) }
|
||||
if next.type == .leftMouseUp { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2005,10 +2031,11 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
var volume = false
|
||||
var bufferedRange: ClosedRange<Double>?
|
||||
var intervals: [MiruroInterval] = []
|
||||
override func knobRect(flipped: Bool) -> NSRect {
|
||||
let rect = super.knobRect(flipped: flipped)
|
||||
guard !volume else { return rect }
|
||||
return NSRect(x: rect.midX - 1.5, y: rect.midY - 7.5, width: 3, height: 15)
|
||||
func visibleKnobRect(_ rect: NSRect) -> NSRect {
|
||||
let width: CGFloat = volume ? 16 : 3
|
||||
let height: CGFloat = volume ? 12 : 15
|
||||
return NSRect(x: rect.midX - width / 2, y: rect.midY - height / 2,
|
||||
width: width, height: height)
|
||||
}
|
||||
override func drawBar(inside rect: NSRect, flipped: Bool) {
|
||||
let bar = NSRect(x: rect.minX + knobThickness / 2, y: rect.midY - 1.5,
|
||||
@@ -2051,9 +2078,7 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
}
|
||||
}
|
||||
override func drawKnob(_ rect: NSRect) {
|
||||
let knob = volume
|
||||
? NSRect(x: rect.midX - 8, y: rect.midY - 6, width: 16, height: 12)
|
||||
: rect
|
||||
let knob = visibleKnobRect(rect)
|
||||
let color = isEnabled ? (volume ? SunsetPalette.text : SunsetPalette.secondaryText)
|
||||
: SunsetPalette.disabledThumb
|
||||
color.setFill()
|
||||
@@ -2076,23 +2101,40 @@ func playbackSliderHitCheck() -> Bool {
|
||||
slider.isContinuous = true
|
||||
window.contentView!.addSubview(slider)
|
||||
window.display()
|
||||
for offset: CGFloat in [-4, -2, -1, 0, 1, 2, 4] {
|
||||
slider.doubleValue = 50
|
||||
let cell = slider.cell as! NSSliderCell
|
||||
let cell = slider.cell as! NSSliderCell
|
||||
slider.doubleValue = slider.minValue
|
||||
let left = cell.knobRect(flipped: slider.isFlipped).midX
|
||||
slider.doubleValue = slider.maxValue
|
||||
let right = cell.knobRect(flipped: slider.isFlipped).midX
|
||||
for (initial, offset, movement): (Double, CGFloat, CGFloat) in [
|
||||
(50, -4, 0), (50, -2, 0), (50, -1, 0), (50, 0, 0), (50, 1, 0), (50, 2, 0), (50, 4, 0),
|
||||
(20, -1, -20), (80, 1, 20), (20, -4, -20), (80, 4, 20),
|
||||
(20, 0, -500), (80, 0, 500)
|
||||
] {
|
||||
slider.doubleValue = initial
|
||||
let knob = cell.knobRect(flipped: slider.isFlipped)
|
||||
let point = slider.convert(NSPoint(x: knob.midX + offset, y: knob.midY), to: nil)
|
||||
let up = NSEvent.mouseEvent(with: .leftMouseUp, location: point, modifierFlags: [],
|
||||
let end = NSPoint(x: point.x + movement, y: point.y)
|
||||
let up = NSEvent.mouseEvent(with: .leftMouseUp, location: end, modifierFlags: [],
|
||||
timestamp: 0, windowNumber: window.windowNumber, context: nil,
|
||||
eventNumber: 1, clickCount: 1, pressure: 0)!
|
||||
let down = NSEvent.mouseEvent(with: .leftMouseDown, location: point, modifierFlags: [],
|
||||
timestamp: 0, windowNumber: window.windowNumber, context: nil,
|
||||
eventNumber: 0, clickCount: 1, pressure: 1)!
|
||||
NSApp.postEvent(up, atStart: true)
|
||||
if movement != 0 {
|
||||
let drag = NSEvent.mouseEvent(with: .leftMouseDragged, location: end, modifierFlags: [],
|
||||
timestamp: 0, windowNumber: window.windowNumber, context: nil,
|
||||
eventNumber: 1, clickCount: 1, pressure: 1)!
|
||||
NSApp.postEvent(drag, atStart: true)
|
||||
}
|
||||
slider.mouseDown(with: down)
|
||||
if abs(offset) <= 1 {
|
||||
guard slider.doubleValue == 50 else { return false }
|
||||
} else {
|
||||
guard (slider.doubleValue - 50) * offset > 0 else { return false }
|
||||
let movedKnob = cell.knobRect(flipped: slider.isFlipped)
|
||||
let expectedMovement = movement + (abs(offset) <= 1 ? 0 : 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
|
||||
|
||||
Reference in New Issue
Block a user