@@ -2,7 +2,10 @@ package com.smarttoolfactory.slider
22
33import androidx.compose.foundation.BorderStroke
44import androidx.compose.foundation.Canvas
5- import androidx.compose.foundation.layout.*
5+ import androidx.compose.foundation.layout.Box
6+ import androidx.compose.foundation.layout.fillMaxSize
7+ import androidx.compose.foundation.layout.offset
8+ import androidx.compose.foundation.layout.requiredSizeIn
69import androidx.compose.runtime.Composable
710import androidx.compose.runtime.mutableStateOf
811import androidx.compose.runtime.remember
@@ -20,10 +23,7 @@ import androidx.compose.ui.layout.Placeable
2023import androidx.compose.ui.layout.SubcomposeLayout
2124import androidx.compose.ui.platform.LocalDensity
2225import androidx.compose.ui.platform.LocalLayoutDirection
23- import androidx.compose.ui.unit.Dp
24- import androidx.compose.ui.unit.IntOffset
25- import androidx.compose.ui.unit.IntSize
26- import androidx.compose.ui.unit.LayoutDirection
26+ import androidx.compose.ui.unit.*
2727import com.smarttoolfactory.slider.gesture.pointerMotionEvents
2828
2929/* *
@@ -137,113 +137,110 @@ fun ColorfulIconSlider(
137137 thumb : @Composable () -> Unit
138138) {
139139
140- SliderComposeLayout (thumb = { thumb() }) { thumbSize ->
140+ SliderComposeLayout (
141+ modifier = modifier
142+ .minimumTouchTargetSize()
143+ .requiredSizeIn(
144+ minWidth = ThumbRadius * 2 ,
145+ minHeight = ThumbRadius * 2 ,
146+ ),
147+ thumb = { thumb() }) { thumbSize: IntSize , constraints: Constraints ->
141148
142149 require(steps >= 0 ) { " steps should be >= 0" }
143150 val onValueChangeState = rememberUpdatedState(onValueChange)
144151 val tickFractions = remember(steps) {
145152 stepsToTickFractions(steps)
146153 }
147154
148- BoxWithConstraints (
149- modifier = modifier
150- .minimumTouchTargetSize()
151- .requiredSizeIn(
152- minWidth = ThumbRadius * 2 ,
153- minHeight = ThumbRadius * 2 ,
154- ),
155- contentAlignment = Alignment .CenterStart
156- ) {
155+ val isRtl = LocalLayoutDirection .current == LayoutDirection .Rtl
157156
158- val isRtl = LocalLayoutDirection .current == LayoutDirection .Rtl
157+ val width = constraints.maxWidth.toFloat()
158+ val thumbRadiusInPx = (thumbSize.width / 2 ).toFloat()
159159
160- val width = constraints.maxWidth.toFloat()
161- val thumbRadiusInPx = (thumbSize.width / 2 ).toFloat()
160+ // Start of the track used for measuring progress,
161+ // it's line + radius of cap which is half of height of track
162+ // to draw this on canvas starting point of line
163+ // should be at trackStart + trackHeightInPx / 2 while drawing
164+ val trackStart: Float
165+ // End of the track that is used for measuring progress
166+ val trackEnd: Float
167+ val strokeRadius: Float
168+ with (LocalDensity .current) {
162169
163- // Start of the track used for measuring progress,
164- // it's line + radius of cap which is half of height of track
165- // to draw this on canvas starting point of line
166- // should be at trackStart + trackHeightInPx / 2 while drawing
167- val trackStart: Float
168- // End of the track that is used for measuring progress
169- val trackEnd: Float
170- val strokeRadius: Float
171- with (LocalDensity .current) {
170+ strokeRadius = trackHeight.toPx() / 2
171+ trackStart = thumbRadiusInPx.coerceAtLeast(strokeRadius)
172+ trackEnd = width - trackStart
173+ }
172174
173- strokeRadius = trackHeight.toPx() / 2
174- trackStart = thumbRadiusInPx.coerceAtLeast(strokeRadius)
175- trackEnd = width - trackStart
176- }
175+ // Sales and interpolates from offset from dragging to user value in valueRange
176+ fun scaleToUserValue (offset : Float ) =
177+ scale(trackStart, trackEnd, offset, valueRange.start, valueRange.endInclusive)
177178
178- // Sales and interpolates from offset from dragging to user value in valueRange
179- fun scaleToUserValue ( offset : Float ) =
180- scale(trackStart, trackEnd, offset, valueRange.start, valueRange.endInclusive)
179+ // Scales user value using valueRange to position on x axis on screen
180+ fun scaleToOffset ( userValue : Float ) =
181+ scale(valueRange.start, valueRange.endInclusive, userValue, trackStart, trackEnd )
181182
182- // Scales user value using valueRange to position on x axis on screen
183- fun scaleToOffset (userValue : Float ) =
184- scale(valueRange.start, valueRange.endInclusive, userValue, trackStart, trackEnd)
183+ val rawOffset = remember { mutableStateOf(scaleToOffset(value)) }
185184
186- val rawOffset = remember { mutableStateOf(scaleToOffset(value)) }
185+ CorrectValueSideEffect (
186+ ::scaleToOffset,
187+ valueRange,
188+ trackStart.. trackEnd,
189+ rawOffset,
190+ value
191+ )
187192
188- CorrectValueSideEffect (
189- ::scaleToOffset,
190- valueRange,
191- trackStart.. trackEnd,
192- rawOffset,
193- value
194- )
193+ val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
194+ val fraction = calculateFraction(valueRange.start, valueRange.endInclusive, coerced)
195+
196+ val dragModifier = Modifier .pointerMotionEvents(
197+ onDown = {
198+ if (enabled) {
199+ rawOffset.value = if (! isRtl) it.position.x else trackEnd - it.position.x
200+ val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
201+ onValueChangeState.value.invoke(
202+ scaleToUserValue(offsetInTrack),
203+ Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
204+ )
205+ it.consumeDownChange()
206+ }
207+ },
208+ onMove = {
209+ if (enabled) {
210+ rawOffset.value = if (! isRtl) it.position.x else trackEnd - it.position.x
211+ val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
212+ onValueChangeState.value.invoke(
213+ scaleToUserValue(offsetInTrack),
214+ Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
215+ )
216+ it.consumePositionChange()
217+ }
195218
196- val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
197- val fraction = calculateFraction(valueRange.start, valueRange.endInclusive, coerced)
198-
199- val dragModifier = Modifier .pointerMotionEvents(
200- onDown = {
201- if (enabled) {
202- rawOffset.value = if (! isRtl) it.position.x else trackEnd - it.position.x
203- val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
204- onValueChangeState.value.invoke(
205- scaleToUserValue(offsetInTrack),
206- Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
207- )
208- it.consumeDownChange()
209- }
210- },
211- onMove = {
212- if (enabled) {
213- rawOffset.value = if (! isRtl) it.position.x else trackEnd - it.position.x
214- val offsetInTrack = rawOffset.value.coerceIn(trackStart, trackEnd)
215- onValueChangeState.value.invoke(
216- scaleToUserValue(offsetInTrack),
217- Offset (rawOffset.value.coerceIn(trackStart, trackEnd), strokeRadius)
218- )
219- it.consumePositionChange()
220- }
221-
222- },
223- onUp = {
224- if (enabled) {
225- onValueChangeFinished?.invoke()
226- it.consumeDownChange()
227- }
219+ },
220+ onUp = {
221+ if (enabled) {
222+ onValueChangeFinished?.invoke()
223+ it.consumeDownChange()
228224 }
229- )
225+ }
226+ )
227+
228+ IconSliderImpl (
229+ enabled = enabled,
230+ fraction = fraction,
231+ trackStart = trackStart,
232+ trackEnd = trackEnd,
233+ tickFractions = tickFractions,
234+ colors = colors,
235+ trackHeight = trackHeight,
236+ thumbRadius = thumbRadiusInPx,
237+ thumb = thumb,
238+ coerceThumbInTrack = coerceThumbInTrack,
239+ drawInactiveTrack = drawInactiveTrack,
240+ borderStroke = borderStroke,
241+ modifier = dragModifier
242+ )
230243
231- IconSliderImpl (
232- enabled = enabled,
233- fraction = fraction,
234- trackStart = trackStart,
235- trackEnd = trackEnd,
236- tickFractions = tickFractions,
237- colors = colors,
238- trackHeight = trackHeight,
239- thumbRadius = thumbRadiusInPx,
240- thumb = thumb,
241- coerceThumbInTrack = coerceThumbInTrack,
242- drawInactiveTrack = drawInactiveTrack,
243- borderStroke = borderStroke,
244- modifier = dragModifier
245- )
246- }
247244 }
248245}
249246
@@ -437,17 +434,21 @@ private fun Track(
437434}
438435
439436enum class SlotsEnum {
440- Track , Thumb
437+ Slider , Thumb
441438}
442439
440+ /* *
441+ * [SubcomposeLayout] that measure [thumb] size to set Slider's track start and track width.
442+ * @param thumb thumb Composable
443+ * @param slider Slider composable that contains **thumb** and **track** of this Slider.
444+ */
443445@Composable
444446private fun SliderComposeLayout (
445447 modifier : Modifier = Modifier ,
446448 thumb : @Composable () -> Unit ,
447- track : @Composable (IntSize ) -> Unit
449+ slider : @Composable (IntSize , Constraints ) -> Unit
448450) {
449-
450- SubcomposeLayout (modifier = modifier) { constraints ->
451+ SubcomposeLayout (modifier = modifier) { constraints: Constraints ->
451452
452453 // Subcompose(compose only a section) main content and get Placeable
453454 val thumbPlaceable: Placeable = subcompose(SlotsEnum .Thumb , thumb).map {
@@ -458,8 +459,8 @@ private fun SliderComposeLayout(
458459 val thumbSize = IntSize (thumbPlaceable.width, thumbPlaceable.height)
459460
460461 // Whole Slider Composable
461- val sliderPlaceable: Placeable = subcompose(SlotsEnum .Track ) {
462- track (thumbSize)
462+ val sliderPlaceable: Placeable = subcompose(SlotsEnum .Slider ) {
463+ slider (thumbSize, constraints )
463464 }.map {
464465 it.measure(constraints)
465466 }.first()
0 commit comments