@@ -21,12 +21,11 @@ import android.os.UserHandle
2121import android.provider.Settings
2222import androidx.compose.foundation.layout.Box
2323import androidx.compose.foundation.layout.BoxScope
24- import androidx.compose.foundation.layout.fillMaxHeight
2524import androidx.compose.foundation.layout.fillMaxSize
26- import androidx.compose.foundation.layout.fillMaxWidth
2725import androidx.compose.foundation.layout.height
2826import androidx.compose.foundation.layout.size
2927import androidx.compose.foundation.layout.width
28+ import androidx.compose.foundation.shape.RoundedCornerShape
3029import androidx.compose.material3.ExperimentalMaterial3Api
3130import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
3231import androidx.compose.material3.LocalContentColor
@@ -45,11 +44,13 @@ import androidx.compose.runtime.remember
4544import androidx.compose.runtime.setValue
4645import androidx.compose.ui.Modifier
4746import androidx.compose.ui.draw.drawBehind
48- import androidx.compose.ui.geometry.CornerRadius
49- import androidx.compose.ui.geometry.Rect
50- import androidx.compose.ui.geometry.RoundRect
47+ import androidx.compose.ui.draw.drawWithCache
48+ import androidx.compose.ui.graphics.drawscope.clipPath
49+ import androidx.compose.ui.geometry.Offset
50+ import androidx.compose.ui.geometry.Size
5151import androidx.compose.ui.graphics.Brush
5252import androidx.compose.ui.graphics.Color
53+ import androidx.compose.ui.graphics.Outline
5354import androidx.compose.ui.graphics.Path
5455import androidx.compose.ui.layout.Layout
5556import androidx.compose.ui.layout.Measurable
@@ -64,6 +65,7 @@ import androidx.compose.ui.unit.Dp
6465import androidx.compose.ui.unit.LayoutDirection
6566import androidx.compose.ui.unit.dp
6667import androidx.compose.ui.util.fastFirst
68+ import com.android.compose.modifiers.thenIf
6769import kotlin.math.min
6870
6971@Composable
@@ -94,45 +96,73 @@ fun SliderTrack(
9496 gapSize = thumbTrackGapSize,
9597 )
9698 }
99+ val gradient = if (rememberGradientColorMode() == 1 ) {
100+ rememberGradientCustomColors()
101+ } else {
102+ VolumeGradient (
103+ MaterialTheme .colorScheme.primary,
104+ MaterialTheme .colorScheme.secondary
105+ )
106+ }
107+ val gradientEnabled = rememberVolumeGradientEnabled()
108+ val gradientBrush = remember(ignoreGradient, gradient, gradientEnabled) {
109+ if (! ignoreGradient && gradientEnabled) createGradientBrush(gradient) else null
110+ }
97111 Layout (
98112 measurePolicy = measurePolicy,
99113 content = {
100-
101- val gradientColors = if (rememberGradientColorMode() == 1 ) {
102- val (start, end) = rememberGradientCustomColors()
103- listOf (start, end)
104- } else {
105- listOf (
106- MaterialTheme .colorScheme.primary,
107- MaterialTheme .colorScheme.secondary
108- )
109- }
110-
111- val activeBrush =
112- if (! ignoreGradient && rememberVolumeGradientEnabled())
113- gradientColors
114- else
115- null
116-
117- GradientSliderTrack (
114+ SliderDefaults .Track (
118115 sliderState = sliderState,
119- isEnabled = isEnabled,
120116 colors = colors,
117+ enabled = isEnabled,
121118 trackCornerSize = trackCornerSize,
122119 trackInsideCornerSize = trackInsideCornerSize,
120+ drawStopIndicator = null ,
123121 thumbTrackGapSize = thumbTrackGapSize,
124- isVertical = isVertical,
125- gradientColors = activeBrush,
122+ drawTick = { _, _ -> },
126123 modifier =
127- Modifier
128- .then(
129- if (isVertical) {
130- Modifier .width(trackSize).fillMaxHeight()
131- } else {
132- Modifier .height(trackSize).fillMaxWidth()
124+ Modifier .then(
125+ if (isVertical) {
126+ Modifier .width(trackSize)
127+ } else {
128+ Modifier .height(trackSize)
129+ }
130+ ).thenIf(gradientBrush != null ) {
131+ Modifier .drawWithCache {
132+ val trackShape = RoundedCornerShape (trackCornerSize)
133+ val outline = trackShape.createOutline(size, layoutDirection, this )
134+ val clipPath = outline.asPath()
135+
136+ onDrawWithContent {
137+ val progress = sliderState.coercedValueAsFraction
138+ val gapPx = thumbTrackGapSize.toPx()
139+ val activeSize = if (isVertical) {
140+ Size (size.width, size.height * progress - gapPx)
141+ } else {
142+ Size (size.width * progress - gapPx, size.height)
143+ }
144+ val activeOffset = if (isVertical) {
145+ Offset (0f , size.height - activeSize.height)
146+ } else {
147+ Offset .Zero
148+ }
149+ clipPath(clipPath) {
150+ // Inactive track
151+ drawRect(
152+ color = colors.inactiveTrackColor,
153+ topLeft = Offset .Zero ,
154+ size = size
155+ )
156+ // Active track
157+ drawRect(
158+ brush = gradientBrush!! ,
159+ topLeft = activeOffset,
160+ size = activeSize
161+ )
162+ }
133163 }
134- )
135- .layoutId(Contents .Track ),
164+ }
165+ } .layoutId(Contents .Track ),
136166 )
137167
138168 TrackIcon (
@@ -168,8 +198,9 @@ fun SliderTrack(
168198 )
169199}
170200
171- private data class TrackGradient (
172- val brush : Brush ,
201+ data class VolumeGradient (
202+ val startColor : Color ,
203+ val endColor : Color ,
173204)
174205
175206@Composable
@@ -246,7 +277,7 @@ fun rememberGradientColorMode(): Int {
246277}
247278
248279@Composable
249- fun rememberGradientCustomColors (): Pair < Color , Color > {
280+ fun rememberGradientCustomColors (): VolumeGradient {
250281 val contentResolver = LocalContext .current.contentResolver
251282
252283 fun readStart (): Int = try {
@@ -294,143 +325,29 @@ fun rememberGradientCustomColors(): Pair<Color, Color> {
294325
295326 val start = if (startInt != 0 ) Color (startInt) else MaterialTheme .colorScheme.primary
296327 val end = if (endInt != 0 ) Color (endInt) else MaterialTheme .colorScheme.secondary
297- return start to end
298- }
299328
300- @Composable
301- @OptIn(ExperimentalMaterial3Api ::class )
302- private fun GradientSliderTrack (
303- sliderState : SliderState ,
304- isEnabled : Boolean ,
305- colors : SliderColors ,
306- trackCornerSize : Dp ,
307- trackInsideCornerSize : Dp ,
308- thumbTrackGapSize : Dp ,
309- isVertical : Boolean ,
310- gradientColors : List <Color >? ,
311- modifier : Modifier = Modifier ,
312- ) {
313- val isRtl = LocalLayoutDirection .current == LayoutDirection .Rtl
314-
315- val inactiveColor =
316- if (isEnabled) colors.inactiveTrackColor else colors.disabledInactiveTrackColor
317- val activeSolidColor =
318- if (isEnabled) colors.activeTrackColor else colors.disabledActiveTrackColor
319-
320- Box (
321- modifier =
322- modifier.drawBehind {
323- val w = size.width
324- val h = size.height
325- if (w <= 0f || h <= 0f ) return @drawBehind
326-
327- val frac = sliderState.coercedValueAsFraction.coerceIn(0f , 1f )
328- val outerR = trackCornerSize.toPx().coerceAtMost(minOf(w, h) / 2f )
329- val innerR = trackInsideCornerSize.toPx().coerceAtMost(minOf(w, h) / 2f )
330- val halfGap = (thumbTrackGapSize.toPx() / 2f ).coerceAtLeast(0f )
331-
332- drawRoundRect(
333- color = inactiveColor,
334- size = size,
335- cornerRadius = CornerRadius (outerR, outerR)
336- )
337-
338- if (! isVertical) {
339- val splitX = if (isRtl) w * (1f - frac) else w * frac
340-
341- val gapEff =
342- if (isRtl) {
343- minOf(halfGap, splitX)
344- } else {
345- minOf(halfGap, w - splitX)
346- }
347-
348- val activeStart = if (isRtl) splitX + gapEff else 0f
349- val activeEnd = if (isRtl) w else splitX - gapEff
350-
351- if (activeEnd <= activeStart) return @drawBehind
352-
353- val eps = 0.5f
354-
355- val hitsStart = activeStart <= eps
356- val hitsEnd = activeEnd >= (w - eps)
357-
358- val leftR =
359- if (isRtl) {
360- if (hitsStart) outerR else innerR // rounded only at max (when it reaches start)
361- } else {
362- outerR // far end always rounded
363- }
364-
365- val rightR =
366- if (isRtl) {
367- outerR // far end always rounded
368- } else {
369- if (hitsEnd) outerR else innerR // rounded only at max (when it reaches end)
370- }
371-
372- val path = Path ().apply {
373- addRoundRect(
374- RoundRect (
375- rect = Rect (activeStart, 0f , activeEnd, h),
376- topLeft = CornerRadius (leftR, leftR),
377- bottomLeft = CornerRadius (leftR, leftR),
378- topRight = CornerRadius (rightR, rightR),
379- bottomRight = CornerRadius (rightR, rightR),
380- )
381- )
382- }
383-
384- val brush =
385- if (gradientColors != null ) Brush .horizontalGradient(gradientColors)
386- else Brush .linearGradient(listOf (activeSolidColor, activeSolidColor))
387-
388- drawPath(path = path, brush = brush)
389- } else {
390- val frac = sliderState.coercedValueAsFraction.coerceIn(0f , 1f )
391- val splitY = h * (1f - frac)
392-
393- val gapEff = minOf(halfGap, splitY)
394-
395- val activeTop = splitY + gapEff
396- val activeBottom = h
397- if (activeBottom <= activeTop) return @drawBehind
398-
399- val eps = 0.5f
400- val hitsTop = activeTop <= eps
401-
402- val topR = if (hitsTop) outerR else innerR
403- val bottomR = outerR
404-
405- val path = Path ().apply {
406- addRoundRect(
407- RoundRect (
408- rect = Rect (0f , activeTop, w, activeBottom),
409- topLeft = CornerRadius (topR, topR),
410- bottomLeft = CornerRadius (bottomR, bottomR),
411- topRight = CornerRadius (topR, topR),
412- bottomRight = CornerRadius (bottomR, bottomR),
413- )
414- )
415- }
416-
417- val brush =
418- if (gradientColors != null ) {
419- Brush .verticalGradient(
420- colors = gradientColors,
421- startY = activeBottom,
422- endY = activeTop
423- )
424- } else {
425- Brush .linearGradient(listOf (activeSolidColor, activeSolidColor))
426- }
329+ return VolumeGradient (startColor = start, endColor = end)
330+ }
427331
428- drawPath(path = path, brush = brush)
429- }
430- }
332+ fun createGradientBrush (gradient : VolumeGradient ? ): Brush ? {
333+ if (gradient == null ) return null
334+
335+ return Brush .verticalGradient(
336+ colors = listOf (
337+ gradient.endColor,
338+ gradient.startColor
339+ )
431340 )
432341}
433342
343+ fun Outline.asPath (): Path {
344+ return when (this ) {
345+ is Outline .Generic -> path
346+ is Outline .Rounded -> Path ().apply { addRoundRect(roundRect) }
347+ is Outline .Rectangle -> Path ().apply { addRect(rect) }
348+ }
349+ }
350+
434351@Composable
435352private fun TrackIcon (
436353 icon : (@Composable BoxScope .(sliderIconsState: SliderIconsState ) -> Unit )? ,
0 commit comments