Skip to content

Commit f1bedc1

Browse files
neobuddy89joeyhuab
authored andcommitted
SystemUI: Fix up gradient related glitch in volume slider
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
1 parent c59acde commit f1bedc1

2 files changed

Lines changed: 88 additions & 171 deletions

File tree

packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ private fun VolumeDialogSlider(
136136
if (!rememberVolumeGradientEnabled()) {
137137
null
138138
} else if (rememberGradientColorMode() == 1) {
139-
val (customStart, _) = rememberGradientCustomColors()
140-
customStart
139+
val gradient = rememberGradientCustomColors()
140+
gradient.startColor
141141
} else {
142142
MaterialTheme.colorScheme.primary
143143
}

packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt

Lines changed: 86 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ import android.os.UserHandle
2121
import android.provider.Settings
2222
import androidx.compose.foundation.layout.Box
2323
import androidx.compose.foundation.layout.BoxScope
24-
import androidx.compose.foundation.layout.fillMaxHeight
2524
import androidx.compose.foundation.layout.fillMaxSize
26-
import androidx.compose.foundation.layout.fillMaxWidth
2725
import androidx.compose.foundation.layout.height
2826
import androidx.compose.foundation.layout.size
2927
import androidx.compose.foundation.layout.width
28+
import androidx.compose.foundation.shape.RoundedCornerShape
3029
import androidx.compose.material3.ExperimentalMaterial3Api
3130
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
3231
import androidx.compose.material3.LocalContentColor
@@ -45,11 +44,13 @@ import androidx.compose.runtime.remember
4544
import androidx.compose.runtime.setValue
4645
import androidx.compose.ui.Modifier
4746
import 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
5151
import androidx.compose.ui.graphics.Brush
5252
import androidx.compose.ui.graphics.Color
53+
import androidx.compose.ui.graphics.Outline
5354
import androidx.compose.ui.graphics.Path
5455
import androidx.compose.ui.layout.Layout
5556
import androidx.compose.ui.layout.Measurable
@@ -64,6 +65,7 @@ import androidx.compose.ui.unit.Dp
6465
import androidx.compose.ui.unit.LayoutDirection
6566
import androidx.compose.ui.unit.dp
6667
import androidx.compose.ui.util.fastFirst
68+
import com.android.compose.modifiers.thenIf
6769
import 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
435352
private fun TrackIcon(
436353
icon: (@Composable BoxScope.(sliderIconsState: SliderIconsState) -> Unit)?,

0 commit comments

Comments
 (0)