From 7bcd07d1582f388100cddd2374e7a43cd589681e Mon Sep 17 00:00:00 2001 From: Jacob Parker Date: Wed, 9 Aug 2023 21:27:32 +0100 Subject: [PATCH] OnChange for precision scrubbing --- Sources/Sliders/Base/PrecisionScrubbing.swift | 42 +++++++++++++++---- Sources/Sliders/Base/SliderGestureState.swift | 3 ++ Sources/Sliders/RangeSlider/RangeSlider.swift | 4 +- .../Style/RangeSliderStyleConfiguration.swift | 4 +- .../HorizontalRangeSliderStyle.swift | 21 ++++++++-- .../Style/ValueSliderStyleConfiguration.swift | 6 +-- .../HorizontalValueSliderStyle.swift | 7 +++- Sources/Sliders/ValueSlider/ValueSlider.swift | 6 +-- 8 files changed, 69 insertions(+), 24 deletions(-) diff --git a/Sources/Sliders/Base/PrecisionScrubbing.swift b/Sources/Sliders/Base/PrecisionScrubbing.swift index 21a38fb..1a467b7 100644 --- a/Sources/Sliders/Base/PrecisionScrubbing.swift +++ b/Sources/Sliders/Base/PrecisionScrubbing.swift @@ -1,23 +1,47 @@ import SwiftUI +public struct PrecisionScrubbingConfig { + let scrubValue: (Float) -> Float + let onChange: ((Float?) -> Void)? +} + struct PrecisionScrubbingKey: EnvironmentKey { - static let defaultValue: (Float) -> Float = { _ in 1 } + typealias Value = PrecisionScrubbingConfig + + static let defaultValue: PrecisionScrubbingConfig = PrecisionScrubbingConfig( + scrubValue: { _ in 1 }, + onChange: nil + ) } extension EnvironmentValues { - var precisionScrubbing: (Float) -> Float { + var precisionScrubbing: PrecisionScrubbingConfig { get { self[PrecisionScrubbingKey.self] } set { self[PrecisionScrubbingKey.self] = newValue } } } public extension View { - func precisionScrubbing( - _ getScrubValue: @escaping (Float) -> ScrubValue - ) -> some View - where ScrubValue.RawValue == Float { - self.environment(\.precisionScrubbing, { offset in - getScrubValue(offset).rawValue - }) + func precisionScrubbing>( + _ 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") + } + } + ) } } diff --git a/Sources/Sliders/Base/SliderGestureState.swift b/Sources/Sliders/Base/SliderGestureState.swift index 906570f..56dadd6 100644 --- a/Sources/Sliders/Base/SliderGestureState.swift +++ b/Sources/Sliders/Base/SliderGestureState.swift @@ -2,6 +2,7 @@ import Foundation import SwiftUI public struct SliderGestureState: Equatable { + var speed: Float? private var lastOffset: CGFloat private var accumulations: [Float:CGFloat] = [1: 0] @@ -19,6 +20,8 @@ public struct SliderGestureState: Equatable { 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 diff --git a/Sources/Sliders/RangeSlider/RangeSlider.swift b/Sources/Sliders/RangeSlider/RangeSlider.swift index 48efc1b..0403464 100644 --- a/Sources/Sliders/RangeSlider/RangeSlider.swift +++ b/Sources/Sliders/RangeSlider/RangeSlider.swift @@ -45,7 +45,7 @@ extension RangeSlider { step: CGFloat(step), distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound), onEditingChanged: onEditingChanged, - precisionScrubbing: { _ in 1 }, + precisionScrubbing: PrecisionScrubbingKey.defaultValue, dragOffset: .constant(0), lowerGestureState: .init(initialValue: nil), upperGestureState: .init(initialValue: nil) @@ -72,7 +72,7 @@ extension RangeSlider { step: CGFloat(step), distance: CGFloat(distance.lowerBound) ... CGFloat(distance.upperBound), onEditingChanged: onEditingChanged, - precisionScrubbing: { _ in 1 }, + precisionScrubbing: PrecisionScrubbingKey.defaultValue, dragOffset: .constant(0), lowerGestureState: .init(initialValue: nil), upperGestureState: .init(initialValue: nil) diff --git a/Sources/Sliders/RangeSlider/Style/RangeSliderStyleConfiguration.swift b/Sources/Sliders/RangeSlider/Style/RangeSliderStyleConfiguration.swift index 408caeb..0e10dde 100644 --- a/Sources/Sliders/RangeSlider/Style/RangeSliderStyleConfiguration.swift +++ b/Sources/Sliders/RangeSlider/Style/RangeSliderStyleConfiguration.swift @@ -6,12 +6,12 @@ public struct RangeSliderStyleConfiguration { public let step: CGFloat public let distance: ClosedRange public let onEditingChanged: (Bool) -> Void - public var precisionScrubbing: (Float) -> Float + public var precisionScrubbing: PrecisionScrubbingConfig public var dragOffset: Binding public var lowerGestureState: GestureState public var upperGestureState: GestureState - func with(precisionScrubbing: @escaping (Float) -> Float, dragOffset: Binding, lowerGestureState: GestureState, upperGestureState: GestureState) -> Self { + func with(precisionScrubbing: PrecisionScrubbingConfig, dragOffset: Binding, lowerGestureState: GestureState, upperGestureState: GestureState) -> Self { var mutSelf = self mutSelf.precisionScrubbing = precisionScrubbing mutSelf.dragOffset = dragOffset diff --git a/Sources/Sliders/RangeSlider/Styles/Horizontal/HorizontalRangeSliderStyle.swift b/Sources/Sliders/RangeSlider/Styles/Horizontal/HorizontalRangeSliderStyle.swift index 5474003..f4b666c 100644 --- a/Sources/Sliders/RangeSlider/Styles/Horizontal/HorizontalRangeSliderStyle.swift +++ b/Sources/Sliders/RangeSlider/Styles/Horizontal/HorizontalRangeSliderStyle.swift @@ -37,7 +37,19 @@ public struct HorizontalRangeSliderStyle some View { - GeometryReader { geometry in + let editing = configuration.lowerGestureState.wrappedValue != nil || configuration.upperGestureState.wrappedValue != nil + let precisionScrubbingSpeed = { () -> Float? in + switch ( + configuration.lowerGestureState.wrappedValue?.speed, + configuration.upperGestureState.wrappedValue?.speed + ) { + case (let lower?, let upper?): return min(lower, upper) + case (let only?, nil), (nil, let only?): return only + case (nil, nil): return nil + } + }() + + return GeometryReader { geometry in ZStack { self.track .environment(\.trackRange, configuration.range.wrappedValue) @@ -68,7 +80,7 @@ public struct HorizontalRangeSliderStyle public let step: CGFloat public let onEditingChanged: (Bool) -> Void - public var precisionScrubbing: (Float) -> Float + public var precisionScrubbing: PrecisionScrubbingConfig public var dragOffset: Binding public var gestureState: GestureState @@ -14,7 +14,7 @@ public struct ValueSliderStyleConfiguration { bounds: ClosedRange, step: CGFloat, onEditingChanged: @escaping (Bool) -> Void, - precisionScrubbing: @escaping (Float) -> Float, + precisionScrubbing: PrecisionScrubbingConfig, dragOffset: Binding, gestureState: GestureState ) { @@ -27,7 +27,7 @@ public struct ValueSliderStyleConfiguration { self.gestureState = gestureState } - func with(precisionScrubbing: @escaping (Float) -> Float, dragOffset: Binding, gestureState: GestureState) -> Self { + func with(precisionScrubbing: PrecisionScrubbingConfig, dragOffset: Binding, gestureState: GestureState) -> Self { var mutSelf = self mutSelf.precisionScrubbing = precisionScrubbing mutSelf.dragOffset = dragOffset diff --git a/Sources/Sliders/ValueSlider/Styles/Horizontal/HorizontalValueSliderStyle.swift b/Sources/Sliders/ValueSlider/Styles/Horizontal/HorizontalValueSliderStyle.swift index fb81f8b..308756c 100644 --- a/Sources/Sliders/ValueSlider/Styles/Horizontal/HorizontalValueSliderStyle.swift +++ b/Sources/Sliders/ValueSlider/Styles/Horizontal/HorizontalValueSliderStyle.swift @@ -35,7 +35,7 @@ public struct HorizontalValueSliderStyle: ValueSliderS .updating(configuration.gestureState) { value, state, transaction in state = (state ?? SliderGestureState(initialOffset: 0)).updating( with: value.location.x, - speed: configuration.precisionScrubbing(Float(value.translation.height)) + speed: configuration.precisionScrubbing.scrubValue(Float(value.translation.height)) ) } ) @@ -60,7 +60,7 @@ public struct HorizontalValueSliderStyle: ValueSliderS return SliderGestureState(initialOffset: value.location.x - x) }()).updating( with: value.location.x, - speed: configuration.precisionScrubbing(Float(value.translation.height)) + speed: configuration.precisionScrubbing.scrubValue(Float(value.translation.height)) ) } ) @@ -81,6 +81,9 @@ public struct HorizontalValueSliderStyle: ValueSliderS .onChange(of: configuration.gestureState.wrappedValue != nil) { editing in configuration.onEditingChanged(editing) } + .onChange(of: configuration.gestureState.wrappedValue?.speed) { speed in + configuration.precisionScrubbing.onChange?(speed) + } } .frame(minHeight: self.thumbInteractiveSize.height) } diff --git a/Sources/Sliders/ValueSlider/ValueSlider.swift b/Sources/Sliders/ValueSlider/ValueSlider.swift index 01691cd..1993acb 100644 --- a/Sources/Sliders/ValueSlider/ValueSlider.swift +++ b/Sources/Sliders/ValueSlider/ValueSlider.swift @@ -38,7 +38,7 @@ extension ValueSlider { bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound), step: CGFloat(step), onEditingChanged: onEditingChanged, - precisionScrubbing: { _ in 1 }, + precisionScrubbing: PrecisionScrubbingKey.defaultValue, dragOffset: .constant(0), gestureState: .init(initialValue: nil) ) @@ -59,7 +59,7 @@ extension ValueSlider { bounds: CGFloat(bounds.lowerBound)...CGFloat(bounds.upperBound), step: CGFloat(step), onEditingChanged: onEditingChanged, - precisionScrubbing: { _ in 1 }, + precisionScrubbing: PrecisionScrubbingKey.defaultValue, dragOffset: .constant(0), gestureState: .init(initialValue: nil) ) @@ -85,7 +85,7 @@ extension ValueSlider { bounds: CGFloat(bounds.lowerBound.value)...CGFloat(bounds.upperBound.value), step: CGFloat(step.value), onEditingChanged: onEditingChanged, - precisionScrubbing: { _ in 1 }, + precisionScrubbing: PrecisionScrubbingKey.defaultValue, dragOffset: .constant(0), gestureState: .init(initialValue: nil) )