@@ -2,28 +2,42 @@ package com.smarttoolfactory.slider
22
33import androidx.compose.foundation.BorderStroke
44import androidx.compose.foundation.Canvas
5- import androidx.compose.foundation.gestures.detectDragGestures
6- import androidx.compose.foundation.gestures.detectTapGestures
7- import androidx.compose.foundation.layout.*
5+ import androidx.compose.foundation.gestures.Orientation
6+ import androidx.compose.foundation.gestures.draggable
7+ import androidx.compose.foundation.interaction.MutableInteractionSource
8+ import androidx.compose.foundation.layout.Box
9+ import androidx.compose.foundation.layout.fillMaxSize
10+ import androidx.compose.foundation.layout.heightIn
11+ import androidx.compose.foundation.layout.offset
12+ import androidx.compose.foundation.layout.requiredSizeIn
813import androidx.compose.material.minimumInteractiveComponentSize
914import androidx.compose.runtime.Composable
1015import androidx.compose.runtime.mutableStateOf
1116import androidx.compose.runtime.remember
17+ import androidx.compose.runtime.rememberCoroutineScope
1218import androidx.compose.runtime.rememberUpdatedState
1319import androidx.compose.ui.Alignment
1420import androidx.compose.ui.Modifier
1521import androidx.compose.ui.geometry.CornerRadius
1622import androidx.compose.ui.geometry.Offset
1723import androidx.compose.ui.geometry.Size
18- import androidx.compose.ui.graphics.*
24+ import androidx.compose.ui.graphics.Brush
25+ import androidx.compose.ui.graphics.Canvas
26+ import androidx.compose.ui.graphics.Color
27+ import androidx.compose.ui.graphics.PointMode
28+ import androidx.compose.ui.graphics.StrokeCap
1929import androidx.compose.ui.graphics.drawscope.Stroke
20- import androidx.compose.ui.input.pointer.PointerInputChange
21- import androidx.compose.ui.input.pointer.pointerInput
2230import androidx.compose.ui.layout.Placeable
2331import androidx.compose.ui.layout.SubcomposeLayout
2432import androidx.compose.ui.platform.LocalDensity
2533import androidx.compose.ui.platform.LocalLayoutDirection
26- import androidx.compose.ui.unit.*
34+ import androidx.compose.ui.unit.Constraints
35+ import androidx.compose.ui.unit.Dp
36+ import androidx.compose.ui.unit.IntOffset
37+ import androidx.compose.ui.unit.IntSize
38+ import androidx.compose.ui.unit.LayoutDirection
39+ import androidx.compose.ui.unit.coerceAtLeast
40+ import kotlinx.coroutines.launch
2741
2842/* *
2943 * Material Slider allows to choose height for track and thumb radius and selection between
@@ -133,6 +147,7 @@ fun ColorfulIconSlider(
133147 borderStroke : BorderStroke ? = null,
134148 drawInactiveTrack : Boolean = true,
135149 coerceThumbInTrack : Boolean = false,
150+ interactionSource : MutableInteractionSource = remember { MutableInteractionSource () },
136151 thumb : @Composable () -> Unit
137152) {
138153
@@ -181,8 +196,21 @@ fun ColorfulIconSlider(
181196 fun scaleToOffset (userValue : Float ) =
182197 scale(valueRange.start, valueRange.endInclusive, userValue, trackStart, trackEnd)
183198
199+ val scope = rememberCoroutineScope()
184200 val rawOffset = remember { mutableStateOf(scaleToOffset(value)) }
185-
201+ val pressOffset = remember { mutableStateOf(0f ) }
202+
203+ val draggableState = remember(trackStart, trackEnd, valueRange) {
204+ SliderDraggableState {
205+ rawOffset.value = (rawOffset.value + it + pressOffset.value)
206+ pressOffset.value = 0f
207+ val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
208+ onValueChangeState.value.invoke(
209+ scaleToUserValue(offsetInTrack),
210+ Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
211+ )
212+ }
213+ }
186214 CorrectValueSideEffect (
187215 ::scaleToOffset,
188216 valueRange,
@@ -194,43 +222,42 @@ fun ColorfulIconSlider(
194222 val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
195223 val fraction = calculateFraction(valueRange.start, valueRange.endInclusive, coerced)
196224
197- val dragModifier = Modifier
198- .pointerInput(Unit ) {
199- detectDragGestures(
200- onDrag = { change: PointerInputChange , _: Offset ->
201- if (enabled) {
202- rawOffset.value =
203- if (! isRtl) change.position.x else trackEnd - change.position.x
204- val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
205- onValueChangeState.value.invoke(
206- scaleToUserValue(offsetInTrack),
207- Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
208- )
209- }
210-
211- },
212- onDragEnd = {
213- if (enabled) {
214- onValueChangeFinished?.invoke()
215- }
216- }
217- )
218- }
219- .pointerInput(Unit ) {
220- detectTapGestures { position: Offset ->
221- if (enabled) {
222- rawOffset.value =
223- if (! isRtl) position.x else trackEnd - position.x
224- val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
225- onValueChangeState.value.invoke(
226- scaleToUserValue(offsetInTrack),
227- Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
228- )
229- onValueChangeFinished?.invoke()
230- }
225+ val gestureEndAction = rememberUpdatedState< (Float ) -> Unit > { velocity: Float ->
226+ val current = rawOffset.value
227+ val target = snapValueToTick(current, tickFractions, trackStart, trackEnd)
228+ if (current != target) {
229+ scope.launch {
230+ animateToTarget(draggableState, current, target, velocity)
231+ onValueChangeFinished?.invoke()
231232 }
233+ } else if (! draggableState.isDragging) {
234+ // check ifDragging in case the change is still in progress (touch -> drag case)
235+ onValueChangeFinished?.invoke()
232236 }
233-
237+ }
238+ val press = Modifier .sliderTapModifier(
239+ draggableState = draggableState,
240+ interactionSource = interactionSource,
241+ maxPx = constraints.maxWidth.toFloat(),
242+ isRtl = isRtl,
243+ rawOffset = rawOffset,
244+ gestureEndAction = gestureEndAction,
245+ pressOffset = pressOffset,
246+ enabled = enabled
247+ )
248+ val drag = Modifier .draggable(
249+ orientation = Orientation .Horizontal ,
250+ reverseDirection = isRtl,
251+ enabled = enabled,
252+ interactionSource = interactionSource,
253+ onDragStopped = { _ ->
254+ if (enabled) {
255+ onValueChangeFinished?.invoke()
256+ }
257+ },
258+ startDragImmediately = draggableState.isDragging,
259+ state = draggableState
260+ )
234261 IconSliderImpl (
235262 enabled = enabled,
236263 fraction = fraction,
@@ -245,9 +272,8 @@ fun ColorfulIconSlider(
245272 coerceThumbInTrack = coerceThumbInTrack,
246273 drawInactiveTrack = drawInactiveTrack,
247274 borderStroke = borderStroke,
248- modifier = dragModifier
275+ modifier = press.then(drag)
249276 )
250-
251277 }
252278}
253279
@@ -389,13 +415,15 @@ private fun Track(
389415 )
390416
391417 // InActive Track
392- drawLine(
393- brush = inactiveTrackColor,
394- start = sliderStart,
395- end = sliderEnd,
396- strokeWidth = trackHeight,
397- cap = StrokeCap .Round
398- )
418+ if (drawInactiveTrack) {
419+ drawLine(
420+ brush = inactiveTrackColor,
421+ start = sliderStart,
422+ end = sliderEnd,
423+ strokeWidth = trackHeight,
424+ cap = StrokeCap .Round
425+ )
426+ }
399427
400428 // Active Track
401429 drawLine(
0 commit comments