Skip to content

Commit a8d0a1c

Browse files
committed
feat(Slider): Add initial version of the disabled range feature
1 parent 5eeffd0 commit a8d0a1c

File tree

3 files changed

+51
-12
lines changed

3 files changed

+51
-12
lines changed

demo/src/main/java/io/monstarlab/mosaic/features/SliderDemo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ fun SliderDemo() = Scaffold(modifier = Modifier) {
4545
label = "Linear division strategy",
4646
range = 0f..1000f,
4747
valuesDistribution = SliderValueDistribution.Linear,
48+
disabledRange = 0f..500f,
4849
)
4950
MosaicSliderDemo(
5051
label = "Parabolic",
5152
range = 0f..1000f,
53+
disabledRange = 800f..1000f,
5254
valuesDistribution = SliderValueDistribution.parabolic(
5355
a = (1000 - 100 * 0.1f) / (1000 * 1000),
5456
b = 0.1f,
@@ -64,6 +66,7 @@ fun MosaicSliderDemo(
6466
range: ClosedFloatingPointRange<Float>,
6567
valuesDistribution: SliderValueDistribution,
6668
modifier: Modifier = Modifier,
69+
disabledRange: ClosedFloatingPointRange<Float> = 0f..0f,
6770
) {
6871
Column(modifier) {
6972
Text(text = label)
@@ -81,9 +84,10 @@ fun MosaicSliderDemo(
8184
modifier = Modifier.weight(0.8f),
8285
value = value,
8386
onValueChange = { value = it },
84-
colors = SliderColors(Color.Black),
87+
colors = SliderColors(Color.Black, disabled = Color.Red),
8588
range = range,
8689
valueDistribution = valuesDistribution,
90+
disabledRange = disabledRange,
8791
thumb = {
8892
Box(
8993
modifier = Modifier

slider/src/main/java/io/monstarlab/mosaic/slider/Slider.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,17 @@ public fun Slider(
3838
modifier: Modifier = Modifier,
3939
valueDistribution: SliderValueDistribution = SliderValueDistribution.Linear,
4040
range: ClosedFloatingPointRange<Float> = 0f..1f,
41+
disabledRange: ClosedFloatingPointRange<Float> = EmptyRange,
4142
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
4243
thumb: @Composable () -> Unit = { DefaultSliderThumb(colors = colors) },
4344
) {
44-
val state = rememberSliderState(value, valueDistribution)
45+
val state = rememberSliderState(value, valueDistribution, disabledRange)
4546
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
4647

48+
state.onValueChange = onValueChange
4749
state.value = value
4850
state.range = range
49-
state.onValueChange = onValueChange
51+
state.disabledRange = disabledRange
5052

5153
val tap = Modifier.pointerInput(state, interactionSource) {
5254
detectTapGestures(
@@ -74,6 +76,7 @@ public fun Slider(
7476
SliderTrack(
7577
progress = state.valueAsFraction,
7678
colors = colors,
79+
disabledRange = state.disabledRangeAsFractions,
7780
)
7881
},
7982
onDimensionsResolved = state::updateDimensions,
@@ -93,12 +96,15 @@ internal fun DefaultSliderThumb(colors: SliderColors) {
9396
)
9497
}
9598

99+
internal val EmptyRange = 0f..0f
100+
96101
@Preview
97102
@Composable
98103
private fun PreviewSlider() {
99104
Slider(
100105
value = 0.5f,
101106
onValueChange = {},
102107
colors = SliderColors(Color.Yellow),
108+
disabledRange = 0.8f..1f,
103109
)
104110
}

slider/src/main/java/io/monstarlab/mosaic/slider/SliderState.kt

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,37 @@ import kotlinx.coroutines.coroutineScope
1919
*/
2020
public class SliderState(
2121
value: Float,
22+
disabledRange: ClosedFloatingPointRange<Float>,
2223
private val valueDistribution: SliderValueDistribution,
2324
) : DraggableState {
2425

2526
internal var range: ClosedFloatingPointRange<Float> = 0f..1f
27+
internal var disabledRange by mutableStateOf(disabledRange)
2628
internal var onValueChange: ((Float) -> Unit)? = null
2729
internal var isDragging by mutableStateOf(false)
2830
private set
29-
3031
private var totalWidth by mutableFloatStateOf(0f)
3132
private var thumbWidth by mutableFloatStateOf(0f)
3233

3334
private var rawOffset by mutableFloatStateOf(scaleToOffset(value))
3435
private val scrollMutex = MutatorMutex()
3536

36-
internal val valueAsFraction: Float get() {
37-
return calcFraction(0f, totalWidth, rawOffset)
38-
}
37+
internal val valueAsFraction: Float
38+
get() {
39+
return calcFraction(0f, totalWidth, rawOffset)
40+
}
41+
42+
internal val disabledRangeAsFractions: ClosedFloatingPointRange<Float>
43+
get() = coerceRange(disabledRange)
3944

4045
/**
4146
* Current value of the slider
4247
* If value of the slider is out of the [range] it will be coerced into it
4348
*/
4449
public var value: Float
45-
get() = scaleToUserValue(rawOffset).coerceIn(range)
50+
get() = scaleToUserValue(rawOffset)
4651
set(value) {
47-
rawOffset = scaleToOffset(value.coerceIn(range))
52+
rawOffset = scaleToOffset(value)
4853
}
4954

5055
private val dragScope: DragScope = object : DragScope {
@@ -67,7 +72,6 @@ public class SliderState(
6772
}
6873

6974
internal fun handlePress(offset: Offset) {
70-
println(offset)
7175
val userValue = scaleToUserValue(offset.x)
7276
handleValueUpdate(userValue, offset.x)
7377
}
@@ -85,17 +89,41 @@ public class SliderState(
8589
}
8690
}
8791

92+
private fun coerceRange(
93+
subrange: ClosedFloatingPointRange<Float>,
94+
): ClosedFloatingPointRange<Float> {
95+
if (subrange.isEmpty()) return subrange
96+
val start = valueDistribution.interpolate(range.start)
97+
val end = valueDistribution.interpolate(range.endInclusive)
98+
val subStart = valueDistribution.interpolate(subrange.start)
99+
val subEnd = valueDistribution.interpolate(subrange.endInclusive)
100+
return calcFraction(start, end, subStart)..calcFraction(start, end, subEnd)
101+
}
102+
88103
private fun scaleToUserValue(offset: Float): Float {
89104
val rangeStart = valueDistribution.interpolate(range.start)
90105
val rangeEnd = valueDistribution.interpolate(range.endInclusive)
91106
val scaledUserValue = scale(0f, totalWidth, offset, rangeStart, rangeEnd)
92107
return valueDistribution.inverse(scaledUserValue)
108+
.coerceIn(range)
109+
.coerceIntoDisabledRange()
110+
}
111+
112+
private fun Float.coerceIntoDisabledRange(): Float {
113+
if (disabledRange.isEmpty()) return this
114+
// check if disabled range is on the left or right
115+
return if (disabledRange.start == range.start) {
116+
coerceAtLeast(disabledRange.endInclusive)
117+
} else {
118+
coerceAtMost(disabledRange.start)
119+
}
93120
}
94121

95122
private fun scaleToOffset(value: Float): Float {
123+
val coerced = value.coerceIn(range).coerceIntoDisabledRange()
96124
val rangeStart = valueDistribution.interpolate(range.start)
97125
val rangeEnd = valueDistribution.interpolate(range.endInclusive)
98-
val interpolated = valueDistribution.interpolate(value)
126+
val interpolated = valueDistribution.interpolate(coerced)
99127
return scale(rangeStart, rangeEnd, interpolated, 0f, totalWidth)
100128
}
101129
}
@@ -104,6 +132,7 @@ public class SliderState(
104132
public fun rememberSliderState(
105133
value: Float,
106134
valueDistribution: SliderValueDistribution = SliderValueDistribution.Linear,
135+
disabledRange: ClosedFloatingPointRange<Float> = EmptyRange,
107136
): SliderState {
108-
return remember { SliderState(value, valueDistribution) }
137+
return remember { SliderState(value, disabledRange, valueDistribution) }
109138
}

0 commit comments

Comments
 (0)