Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes #70

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
47 changes: 47 additions & 0 deletions Sources/Sliders/Base/PrecisionScrubbing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import SwiftUI

public struct PrecisionScrubbingConfig {
let scrubValue: (Float) -> Float
let onChange: ((Float?) -> Void)?
}

struct PrecisionScrubbingKey: EnvironmentKey {
typealias Value = PrecisionScrubbingConfig

static let defaultValue: PrecisionScrubbingConfig = PrecisionScrubbingConfig(
scrubValue: { _ in 1 },
onChange: nil
)
}

extension EnvironmentValues {
var precisionScrubbing: PrecisionScrubbingConfig {
get { self[PrecisionScrubbingKey.self] }
set { self[PrecisionScrubbingKey.self] = newValue }
}
}

public extension View {
func precisionScrubbing<ScrubValue: RawRepresentable<Float>>(
_ scrubValue: @escaping (Float) -> ScrubValue,
onChange: ((ScrubValue?) -> Void)? = nil
) -> some View {
self.environment(
\.precisionScrubbing,
PrecisionScrubbingConfig { offset in
scrubValue(offset).rawValue
} onChange: { value in
guard let value else {
onChange?(nil)
return
}

if let value = ScrubValue(rawValue: value) {
onChange?(value)
} else {
print("Invalid conversion")
}
}
)
}
}
42 changes: 42 additions & 0 deletions Sources/Sliders/Base/SliderGestureState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation
import SwiftUI

public struct SliderGestureState: Equatable {
var speed: Float?
private var lastOffset: CGFloat
private var accumulations: [Float:CGFloat] = [1: 0]

var offset: CGFloat {
accumulations.reduce(0) { accum, tuple in
let (speed, value) = tuple
return accum + value / CGFloat(speed)
}
}

init(initialOffset: CGFloat) {
self.lastOffset = initialOffset
}

func updating(with offset: CGFloat, speed: Float) -> Self {
var mutSelf = self

mutSelf.speed = speed

var accumulations = self.accumulations.reduce([:]) { (accum: [Float:CGFloat], element) in
let (elementSpeed, elementValue) = element

var out = accum

let appliedSpeed = min(elementSpeed, speed)
out[appliedSpeed] = (out[appliedSpeed] ?? 0) + elementValue

return out
}
accumulations[speed] = (accumulations[speed] ?? 0) + offset - lastOffset
mutSelf.accumulations = accumulations

mutSelf.lastOffset = offset

return mutSelf
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public struct RectangularPointSliderStyle<Track: View, Thumb: View>: PointSlider
)
)
.gesture(
DragGesture()
DragGesture(minimumDistance: 0)
.onChanged { gestureValue in
configuration.onEditingChanged(true)

Expand Down
20 changes: 17 additions & 3 deletions Sources/Sliders/RangeSlider/RangeSlider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import SwiftUI
public struct RangeSlider: View {
@Environment(\.rangeSliderStyle) private var style
@State private var dragOffset: CGFloat?
@Environment(\.precisionScrubbing) private var precisionScrubbing
@GestureState private var lowerGestureState: SliderGestureState?
@GestureState private var upperGestureState: SliderGestureState?

private var configuration: RangeSliderStyleConfiguration

public var body: some View {
self.style.makeBody(configuration:
self.configuration.with(dragOffset: self.$dragOffset)
self.configuration.with(
precisionScrubbing: self.precisionScrubbing,
dragOffset: self.$dragOffset,
lowerGestureState: self.$lowerGestureState,
upperGestureState: self.$upperGestureState
)
)
}
}
Expand Down Expand Up @@ -37,7 +45,10 @@ extension RangeSlider {
step: CGFloat(step),
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0)
precisionScrubbing: PrecisionScrubbingKey.defaultValue,
dragOffset: .constant(0),
lowerGestureState: .init(initialValue: nil),
upperGestureState: .init(initialValue: nil)
)
)
}
Expand All @@ -61,7 +72,10 @@ extension RangeSlider {
step: CGFloat(step),
distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound),
onEditingChanged: onEditingChanged,
dragOffset: .constant(0)
precisionScrubbing: PrecisionScrubbingKey.defaultValue,
dragOffset: .constant(0),
lowerGestureState: .init(initialValue: nil),
upperGestureState: .init(initialValue: nil)
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ public struct RangeSliderStyleConfiguration {
public let step: CGFloat
public let distance: ClosedRange<CGFloat>
public let onEditingChanged: (Bool) -> Void
public var precisionScrubbing: PrecisionScrubbingConfig
public var dragOffset: Binding<CGFloat?>
public var lowerGestureState: GestureState<SliderGestureState?>
public var upperGestureState: GestureState<SliderGestureState?>

func with(dragOffset: Binding<CGFloat?>) -> Self {
func with(precisionScrubbing: PrecisionScrubbingConfig, dragOffset: Binding<CGFloat?>, lowerGestureState: GestureState<SliderGestureState?>, upperGestureState: GestureState<SliderGestureState?>) -> Self {
var mutSelf = self
mutSelf.precisionScrubbing = precisionScrubbing
mutSelf.dragOffset = dragOffset
mutSelf.lowerGestureState = lowerGestureState
mutSelf.upperGestureState = upperGestureState
return mutSelf
}
}
Loading