@@ -24,7 +24,8 @@ import androidx.compose.foundation.systemGestureExclusion
24
24
import androidx.compose.material.ripple.rememberRipple
25
25
import androidx.compose.material3.ExperimentalMaterial3Api
26
26
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
27
- import androidx.compose.material3.SliderPositions
27
+ import androidx.compose.material3.RangeSliderState
28
+ import androidx.compose.material3.SliderState
28
29
import androidx.compose.runtime.Composable
29
30
import androidx.compose.runtime.CompositionLocalProvider
30
31
import androidx.compose.runtime.LaunchedEffect
@@ -41,8 +42,9 @@ import androidx.compose.ui.composed
41
42
import androidx.compose.ui.geometry.Offset
42
43
import androidx.compose.ui.geometry.Rect
43
44
import androidx.compose.ui.geometry.lerp
44
- import androidx.compose.ui.graphics.PointMode
45
+ import androidx.compose.ui.graphics.Color
45
46
import androidx.compose.ui.graphics.StrokeCap
47
+ import androidx.compose.ui.graphics.drawscope.DrawScope
46
48
import androidx.compose.ui.platform.LocalDensity
47
49
import androidx.compose.ui.semantics.disabled
48
50
import androidx.compose.ui.semantics.semantics
@@ -109,8 +111,8 @@ public fun Slider(
109
111
enabled = enabled,
110
112
)
111
113
},
112
- track = { sliderPositions ->
113
- Track (enabled, sliderPositions )
114
+ track = { sliderState ->
115
+ Track (sliderState, enabled )
114
116
},
115
117
)
116
118
}
@@ -166,8 +168,8 @@ public fun RangeSlider(
166
168
enabled = enabled,
167
169
)
168
170
},
169
- track = { sliderPositions ->
170
- Track (enabled, sliderPositions )
171
+ track = { sliderState ->
172
+ Track (sliderState, enabled )
171
173
},
172
174
steps = steps,
173
175
)
@@ -280,8 +282,65 @@ private fun Thumb(
280
282
)
281
283
}
282
284
285
+ @OptIn(ExperimentalMaterial3Api ::class )
286
+ @Composable
287
+ private fun Track (
288
+ sliderState : SliderState ,
289
+ enabled : Boolean ,
290
+ ) {
291
+ Track (
292
+ steps = sliderState.steps,
293
+ activeRangeStart = {
294
+ calcFraction(
295
+ sliderState.valueRange.start,
296
+ sliderState.valueRange.endInclusive,
297
+ 0f ,
298
+ )
299
+ },
300
+ activeRangeEnd = {
301
+ calcFraction(
302
+ sliderState.valueRange.start,
303
+ sliderState.valueRange.endInclusive,
304
+ 0f ,
305
+ )
306
+ },
307
+ enabled = enabled,
308
+ )
309
+ }
310
+
311
+ @OptIn(ExperimentalMaterial3Api ::class )
312
+ @Composable
313
+ private fun Track (
314
+ rangeSliderState : RangeSliderState ,
315
+ enabled : Boolean ,
316
+ ) {
317
+ Track (
318
+ steps = rangeSliderState.steps,
319
+ activeRangeStart = {
320
+ calcFraction(
321
+ rangeSliderState.valueRange.start,
322
+ rangeSliderState.valueRange.endInclusive,
323
+ rangeSliderState.activeRangeStart,
324
+ )
325
+ },
326
+ activeRangeEnd = {
327
+ calcFraction(
328
+ rangeSliderState.valueRange.start,
329
+ rangeSliderState.valueRange.endInclusive,
330
+ rangeSliderState.activeRangeEnd,
331
+ )
332
+ },
333
+ enabled = enabled,
334
+ )
335
+ }
336
+
283
337
@Composable
284
- private fun Track (enabled : Boolean , sliderPositions : SliderPositions ) {
338
+ private fun Track (
339
+ steps : Int ,
340
+ activeRangeStart : () -> Float ,
341
+ activeRangeEnd : () -> Float ,
342
+ enabled : Boolean ,
343
+ ) {
285
344
val activeTrackColor = when (enabled) {
286
345
true -> OrbitTheme .colors.info.normal
287
346
false -> OrbitTheme .colors.content.disabled.copy(alpha = 0.38f )
@@ -298,60 +357,80 @@ private fun Track(enabled: Boolean, sliderPositions: SliderPositions) {
298
357
true -> contentColorFor(inactiveTrackColor).copy(alpha = 0.38f )
299
358
false -> contentColorFor(OrbitTheme .colors.surface.strong).copy(alpha = 0.24f )
300
359
}
360
+ val tickFractions = remember(steps) {
361
+ if (steps == 0 ) floatArrayOf() else FloatArray (steps + 2 ) { it.toFloat() / (steps + 1 ) }
362
+ }
301
363
Canvas (
302
364
Modifier
303
365
.fillMaxWidth()
304
- .height(TrackSize ),
366
+ .height(TrackHeight ),
305
367
) {
306
- val isRtl = layoutDirection == LayoutDirection .Rtl
307
- val sliderLeft = Offset (0f - 9.9 .dp.roundToPx(), center.y)
308
- val sliderRight = Offset (size.width + 9.9 .dp.roundToPx(), center.y)
309
- val sliderStart = if (isRtl) sliderRight else sliderLeft
310
- val sliderEnd = if (isRtl) sliderLeft else sliderRight
311
- val dotStart = if (isRtl) Offset (size.width, center.y) else Offset (0f , center.y)
312
- val dotEnd = if (isRtl) Offset (0f , center.y) else Offset (size.width, center.y)
313
- val tickSize = TickSize .toPx()
314
- val trackStrokeWidth = TrackSize .toPx()
315
- drawLine(
368
+ drawTrack(
369
+ tickFractions,
370
+ activeRangeStart(),
371
+ activeRangeEnd(),
316
372
inactiveTrackColor,
317
- sliderStart,
318
- sliderEnd,
319
- trackStrokeWidth,
320
- StrokeCap .Round ,
321
- )
322
- val sliderValueEnd = Offset (
323
- sliderStart.x +
324
- (sliderEnd.x - sliderStart.x) * sliderPositions.activeRange.endInclusive,
325
- center.y,
373
+ activeTrackColor,
374
+ inactiveTickColor,
375
+ activeTickColor,
326
376
)
377
+ }
378
+ }
327
379
328
- val sliderValueStart = Offset (
329
- sliderStart.x +
330
- (sliderEnd.x - sliderStart.x) * sliderPositions.activeRange.start,
331
- center.y,
332
- )
380
+ private fun calcFraction (a : Float , b : Float , pos : Float ) =
381
+ (if (b - a == 0f ) 0f else (pos - a) / (b - a)).coerceIn(0f , 1f )
333
382
334
- drawLine(
335
- activeTrackColor,
336
- sliderValueStart,
337
- sliderValueEnd,
338
- trackStrokeWidth,
339
- StrokeCap .Round ,
383
+ private fun DrawScope.drawTrack (
384
+ tickFractions : FloatArray ,
385
+ activeRangeStart : Float ,
386
+ activeRangeEnd : Float ,
387
+ inactiveTrackColor : Color ,
388
+ activeTrackColor : Color ,
389
+ inactiveTickColor : Color ,
390
+ activeTickColor : Color ,
391
+ ) {
392
+ val isRtl = layoutDirection == LayoutDirection .Rtl
393
+ val sliderLeft = Offset (0f - 9.9 .dp.roundToPx(), center.y)
394
+ val sliderRight = Offset (size.width + 9.9 .dp.roundToPx(), center.y)
395
+ val sliderStart = if (isRtl) sliderRight else sliderLeft
396
+ val sliderEnd = if (isRtl) sliderLeft else sliderRight
397
+ val dotStart = if (isRtl) Offset (size.width, center.y) else Offset (0f , center.y)
398
+ val dotEnd = if (isRtl) Offset (0f , center.y) else Offset (size.width, center.y)
399
+ val tickSize = TickSize .toPx()
400
+ val trackStrokeWidth = TrackHeight .toPx()
401
+ drawLine(
402
+ inactiveTrackColor,
403
+ sliderStart,
404
+ sliderEnd,
405
+ trackStrokeWidth,
406
+ StrokeCap .Round ,
407
+ )
408
+ val sliderValueEnd = Offset (
409
+ sliderStart.x +
410
+ (sliderEnd.x - sliderStart.x) * activeRangeEnd,
411
+ center.y,
412
+ )
413
+
414
+ val sliderValueStart = Offset (
415
+ sliderStart.x +
416
+ (sliderEnd.x - sliderStart.x) * activeRangeStart,
417
+ center.y,
418
+ )
419
+
420
+ drawLine(
421
+ activeTrackColor,
422
+ sliderValueStart,
423
+ sliderValueEnd,
424
+ trackStrokeWidth,
425
+ StrokeCap .Round ,
426
+ )
427
+ for (tick in tickFractions) {
428
+ val outsideFraction = tick > activeRangeEnd || tick < activeRangeStart
429
+ drawCircle(
430
+ color = if (outsideFraction) inactiveTickColor else activeTickColor,
431
+ center = Offset (lerp(dotStart, dotEnd, tick).x, center.y),
432
+ radius = tickSize / 2f ,
340
433
)
341
- sliderPositions.tickFractions.groupBy {
342
- it > sliderPositions.activeRange.endInclusive ||
343
- it < sliderPositions.activeRange.start
344
- }.forEach { (outsideFraction, list) ->
345
- drawPoints(
346
- list.map {
347
- Offset (lerp(dotStart, dotEnd, it).x, center.y)
348
- },
349
- PointMode .Points ,
350
- (if (outsideFraction) inactiveTickColor else activeTickColor),
351
- tickSize,
352
- StrokeCap .Round ,
353
- )
354
- }
355
434
}
356
435
}
357
436
@@ -419,7 +498,7 @@ private fun Modifier.sliderSystemGestureExclusion(enabled: Boolean): Modifier {
419
498
}
420
499
}
421
500
422
- private val TrackSize = 4 .dp
501
+ private val TrackHeight = 4 .dp
423
502
private val TickSize = 2 .dp
424
503
425
504
@OrbitPreviews
0 commit comments