Skip to content

Commit 27428fc

Browse files
committed
library: ColorPicker: Switch to use SliderHapticEffect
1 parent a6f73c3 commit 27428fc

File tree

3 files changed

+89
-66
lines changed

3 files changed

+89
-66
lines changed

composeApp/src/commonMain/kotlin/component/OtherComponent.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import top.yukonga.miuix.kmp.basic.Icon
3333
import top.yukonga.miuix.kmp.basic.InfiniteProgressIndicator
3434
import top.yukonga.miuix.kmp.basic.LinearProgressIndicator
3535
import top.yukonga.miuix.kmp.basic.Slider
36-
import top.yukonga.miuix.kmp.basic.SliderDefaults.SliderHapticEffect
36+
import top.yukonga.miuix.kmp.basic.SliderDefaults
3737
import top.yukonga.miuix.kmp.basic.SmallTitle
3838
import top.yukonga.miuix.kmp.basic.TabRow
3939
import top.yukonga.miuix.kmp.basic.Text
@@ -270,7 +270,7 @@ fun OtherComponent(padding: PaddingValues) {
270270
Slider(
271271
progress = progressHaptic,
272272
onProgressChange = { newProgress -> progressHaptic = newProgress },
273-
hapticEffect = SliderHapticEffect.Step,
273+
hapticEffect = SliderDefaults.SliderHapticEffect.Step,
274274
modifier = Modifier
275275
.padding(horizontal = 12.dp)
276276
.padding(bottom = 12.dp)
@@ -335,7 +335,8 @@ fun OtherComponent(padding: PaddingValues) {
335335

336336
ColorPicker(
337337
initialColor = selectedColor,
338-
onColorChanged = { selectedColor = it }
338+
onColorChanged = { selectedColor = it },
339+
hapticEffect = SliderDefaults.SliderHapticEffect.Step
339340
)
340341
}
341342
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ColorPicker.kt

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@ import androidx.compose.ui.graphics.Brush
2727
import androidx.compose.ui.graphics.Color
2828
import androidx.compose.ui.graphics.TileMode
2929
import androidx.compose.ui.graphics.drawscope.DrawScope
30-
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3130
import androidx.compose.ui.input.pointer.pointerInput
3231
import androidx.compose.ui.layout.onGloballyPositioned
3332
import androidx.compose.ui.platform.LocalDensity
3433
import androidx.compose.ui.platform.LocalHapticFeedback
3534
import androidx.compose.ui.unit.Dp
3635
import androidx.compose.ui.unit.dp
37-
import top.yukonga.miuix.kmp.theme.MiuixTheme
3836
import top.yukonga.miuix.kmp.utils.ColorUtils
3937
import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape
4038

@@ -44,15 +42,15 @@ import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape
4442
* @param initialColor The initial color of the picker.
4543
* @param onColorChanged The callback to be called when the color changes.
4644
* @param showPreview Whether to show the color preview.
47-
* @param enableHapticEffect Whether to enable haptic feedback.
45+
* @param hapticEffect The haptic effect of the [ColorSlider].
4846
* @param modifier The modifier to be applied to the color picker.
4947
*/
5048
@Composable
5149
fun ColorPicker(
52-
initialColor: Color = MiuixTheme.colorScheme.primary,
53-
onColorChanged: (Color) -> Unit = {},
50+
initialColor: Color,
51+
onColorChanged: (Color) -> Unit,
5452
showPreview: Boolean = true,
55-
enableHapticEffect: Boolean = true,
53+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect,
5654
modifier: Modifier = Modifier
5755
) {
5856
var initialSetup by remember { mutableStateOf(true) }
@@ -107,7 +105,7 @@ fun ColorPicker(
107105
HueSlider(
108106
currentHue = currentHue,
109107
onHueChanged = { newHue -> currentHue = newHue * 360f },
110-
enableHapticEffect = enableHapticEffect
108+
hapticEffect = hapticEffect
111109
)
112110

113111
Spacer(modifier = Modifier.height(12.dp))
@@ -117,7 +115,7 @@ fun ColorPicker(
117115
currentHue = currentHue,
118116
currentSaturation = currentSaturation,
119117
onSaturationChanged = { currentSaturation = it },
120-
enableHapticEffect = enableHapticEffect
118+
hapticEffect = hapticEffect
121119
)
122120

123121
Spacer(modifier = Modifier.height(12.dp))
@@ -128,7 +126,7 @@ fun ColorPicker(
128126
currentSaturation = currentSaturation,
129127
currentValue = currentValue,
130128
onValueChanged = { currentValue = it },
131-
enableHapticEffect = enableHapticEffect
129+
hapticEffect = hapticEffect
132130
)
133131

134132
Spacer(modifier = Modifier.height(12.dp))
@@ -140,7 +138,7 @@ fun ColorPicker(
140138
currentValue = currentValue,
141139
currentAlpha = currentAlpha,
142140
onAlphaChanged = { currentAlpha = it },
143-
enableHapticEffect = enableHapticEffect
141+
hapticEffect = hapticEffect
144142
)
145143
}
146144

@@ -149,21 +147,21 @@ fun ColorPicker(
149147
*
150148
* @param currentHue The current hue value.
151149
* @param onHueChanged The callback to be called when the hue changes.
152-
* @param enableHapticEffect Whether to enable haptic feedback.
150+
* @param hapticEffect The haptic effect of the [HueSlider].
153151
*/
154152
@Composable
155153
fun HueSlider(
156154
currentHue: Float,
157155
onHueChanged: (Float) -> Unit,
158-
enableHapticEffect: Boolean = true
156+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
159157
) {
160158
val hueColors = List(36) { i -> Color.hsv(i * 10f, 1f, 1f) }
161159
ColorSlider(
162160
value = currentHue / 360f,
163161
onValueChanged = onHueChanged,
164162
drawBrushColors = hueColors,
165163
modifier = Modifier.fillMaxWidth(),
166-
enableHapticEffect = enableHapticEffect
164+
hapticEffect = hapticEffect
167165
)
168166
}
169167

@@ -173,21 +171,21 @@ fun HueSlider(
173171
* @param currentHue The current hue value.
174172
* @param currentSaturation The current saturation value.
175173
* @param onSaturationChanged The callback to be called when the saturation changes.
176-
* @param enableHapticEffect Whether to enable haptic feedback.
174+
* @param hapticEffect The haptic effect of the [SaturationSlider].
177175
*/
178176
@Composable
179177
fun SaturationSlider(
180178
currentHue: Float,
181179
currentSaturation: Float,
182180
onSaturationChanged: (Float) -> Unit,
183-
enableHapticEffect: Boolean = true
181+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
184182
) {
185183
ColorSlider(
186184
value = currentSaturation,
187185
onValueChanged = onSaturationChanged,
188186
drawBrushColors = listOf(Color.hsv(currentHue, 0f, 1f, 1f), Color.hsv(currentHue, 1f, 1f, 1f)),
189187
modifier = Modifier.fillMaxWidth(),
190-
enableHapticEffect = enableHapticEffect
188+
hapticEffect = hapticEffect
191189
)
192190
}
193191

@@ -199,22 +197,22 @@ fun SaturationSlider(
199197
* @param currentSaturation The current saturation value.
200198
* @param currentValue The current value value.
201199
* @param onValueChanged The callback to be called when the value changes.
202-
* @param enableHapticEffect Whether to enable haptic feedback.
200+
* @param hapticEffect The haptic effect of the [ValueSlider].
203201
*/
204202
@Composable
205203
fun ValueSlider(
206204
currentHue: Float,
207205
currentSaturation: Float,
208206
currentValue: Float,
209207
onValueChanged: (Float) -> Unit,
210-
enableHapticEffect: Boolean = true
208+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
211209
) {
212210
ColorSlider(
213211
value = currentValue,
214212
onValueChanged = onValueChanged,
215213
drawBrushColors = listOf(Color.Black, Color.hsv(currentHue, currentSaturation, 1f)),
216214
modifier = Modifier.fillMaxWidth(),
217-
enableHapticEffect = enableHapticEffect
215+
hapticEffect = hapticEffect
218216
)
219217
}
220218

@@ -226,7 +224,7 @@ fun ValueSlider(
226224
* @param currentValue The current value value.
227225
* @param currentAlpha The current alpha value.
228226
* @param onAlphaChanged The callback to be called when the alpha changes.
229-
* @param enableHapticEffect Whether to enable haptic feedback.
227+
* @param hapticEffect The haptic effect of the [AlphaSlider].
230228
*/
231229
@Composable
232230
fun AlphaSlider(
@@ -235,7 +233,7 @@ fun AlphaSlider(
235233
currentValue: Float,
236234
currentAlpha: Float,
237235
onAlphaChanged: (Float) -> Unit,
238-
enableHapticEffect: Boolean = true
236+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
239237
) {
240238
val baseColor = Color.hsv(currentHue, currentSaturation, currentValue)
241239
val startColor = remember(baseColor) { baseColor.copy(alpha = 0f) }
@@ -277,7 +275,7 @@ fun AlphaSlider(
277275
onValueChanged = onAlphaChanged,
278276
drawBrushColors = listOf(startColor, endColor),
279277
modifier = Modifier.fillMaxWidth(),
280-
enableHapticEffect = enableHapticEffect,
278+
hapticEffect = hapticEffect,
281279
drawBackground = checkerBrush::draw
282280
)
283281
}
@@ -291,7 +289,7 @@ private fun ColorSlider(
291289
onValueChanged: (Float) -> Unit,
292290
drawBrushColors: List<Color>,
293291
modifier: Modifier = Modifier,
294-
enableHapticEffect: Boolean = true,
292+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect,
295293
drawBackground: (DrawScope.(width: Float, height: Float) -> Unit)? = null
296294
) {
297295
val density = LocalDensity.current
@@ -303,21 +301,14 @@ private fun ColorSlider(
303301
val borderStroke = remember { 0.5.dp }
304302
val borderColor = remember { Color.Gray.copy(0.1f) }
305303
val hapticFeedback = LocalHapticFeedback.current
306-
var lastHapticStep by remember { mutableStateOf(0f) }
304+
val hapticState = remember { SliderHapticState() }
307305

308306
val dragHandler = remember(onValueChanged, sliderSizePx) {
309307
{ posX: Float, width: Float ->
310308
handleSliderInteraction(posX, width, sliderSizePx).coerceIn(0f, 1f)
311309
}
312310
}
313311

314-
fun performHapticFeedbackIfNeeded(valueChanged: Float) {
315-
if (enableHapticEffect && valueChanged != lastHapticStep) {
316-
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
317-
lastHapticStep = valueChanged
318-
}
319-
}
320-
321312
Box(
322313
modifier = modifier
323314
.height(26.dp)
@@ -331,12 +322,19 @@ private fun ColorSlider(
331322
sliderWidth = with(density) { coordinates.size.width.toDp() }
332323
}
333324
.pointerInput(dragHandler) {
334-
detectHorizontalDragGestures { change, _ ->
335-
change.consume()
336-
val currentValue = dragHandler(change.position.x, size.width.toFloat())
337-
onValueChanged(currentValue)
338-
performHapticFeedbackIfNeeded(currentValue)
339-
}
325+
detectHorizontalDragGestures(
326+
onDragStart = { offset ->
327+
val currentValue = dragHandler(offset.x, size.width.toFloat())
328+
onValueChanged(currentValue)
329+
hapticState.reset(currentValue)
330+
},
331+
onHorizontalDrag = { change, _ ->
332+
change.consume()
333+
val currentValue = dragHandler(change.position.x, size.width.toFloat())
334+
onValueChanged(currentValue)
335+
hapticState.handleHapticFeedback(currentValue, hapticEffect, hapticFeedback)
336+
}
337+
)
340338
}
341339
) {
342340
drawBackground?.invoke(this, size.width, size.height)

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/Slider.kt

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ import androidx.compose.ui.geometry.CornerRadius
2828
import androidx.compose.ui.geometry.Offset
2929
import androidx.compose.ui.geometry.Size
3030
import androidx.compose.ui.graphics.Color
31+
import androidx.compose.ui.hapticfeedback.HapticFeedback
3132
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3233
import androidx.compose.ui.input.pointer.pointerInput
3334
import androidx.compose.ui.platform.LocalHapticFeedback
3435
import androidx.compose.ui.unit.Dp
3536
import androidx.compose.ui.unit.dp
36-
import top.yukonga.miuix.kmp.basic.SliderDefaults.SliderHapticEffect
3737
import top.yukonga.miuix.kmp.theme.MiuixTheme
3838
import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape
3939
import kotlin.math.pow
@@ -54,7 +54,6 @@ import kotlin.math.round
5454
* @param effect Whether to show the effect of the [Slider].
5555
* @param decimalPlaces The number of decimal places to be displayed in the drag indicator.
5656
* @param hapticEffect The haptic effect of the [Slider].
57-
* @param hapticEffectStep The step of the haptic effect when using [SliderHapticEffect.Step].
5857
*/
5958
@Composable
6059
fun Slider(
@@ -68,17 +67,16 @@ fun Slider(
6867
colors: SliderColors = SliderDefaults.sliderColors(),
6968
effect: Boolean = false,
7069
decimalPlaces: Int = 2,
71-
hapticEffect: SliderHapticEffect = SliderDefaults.DefaultHapticEffect,
72-
hapticEffectStep: Float = 0.01f
70+
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
7371
) {
7472
val hapticFeedback = LocalHapticFeedback.current
7573
var dragOffset by remember { mutableStateOf(0f) }
7674
var isDragging by remember { mutableStateOf(false) }
7775
var currentValue by remember { mutableStateOf(progress) }
78-
var hapticTriggered by remember { mutableStateOf(false) }
79-
var lastHapticStep by remember { mutableStateOf(0f) }
8076
val updatedOnProgressChange by rememberUpdatedState(onProgressChange)
8177
val factor = remember(decimalPlaces) { 10f.pow(decimalPlaces) }
78+
val hapticState = remember { SliderHapticState() }
79+
8280
val calculateProgress = remember(minValue, maxValue, factor) {
8381
{ offset: Float, width: Int ->
8482
val newValue = (offset / width) * (maxValue - minValue) + minValue
@@ -95,30 +93,13 @@ fun Slider(
9593
dragOffset = offset.x
9694
currentValue = calculateProgress(dragOffset, size.width)
9795
updatedOnProgressChange(currentValue)
98-
hapticTriggered = false
99-
lastHapticStep = (currentValue / hapticEffectStep).toInt() * hapticEffectStep
96+
hapticState.reset(currentValue)
10097
},
10198
onHorizontalDrag = { _, dragAmount ->
10299
dragOffset = (dragOffset + dragAmount).coerceIn(0f, size.width.toFloat())
103100
currentValue = calculateProgress(dragOffset, size.width)
104101
updatedOnProgressChange(currentValue)
105-
106-
if (hapticEffect != SliderHapticEffect.None) {
107-
if ((currentValue == minValue || currentValue == maxValue) && !hapticTriggered) {
108-
hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureEnd)
109-
hapticTriggered = true
110-
} else if (currentValue != minValue && currentValue != maxValue) {
111-
hapticTriggered = false
112-
}
113-
}
114-
115-
if (hapticEffect == SliderHapticEffect.Step) {
116-
val currentStep = (currentValue / hapticEffectStep).toInt() * hapticEffectStep
117-
if (currentStep != lastHapticStep && currentValue != minValue && currentValue != maxValue) {
118-
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
119-
lastHapticStep = currentStep
120-
}
121-
}
102+
hapticState.handleHapticFeedback(currentValue, hapticEffect, hapticFeedback)
122103
},
123104
onDragEnd = {
124105
isDragging = false
@@ -145,6 +126,49 @@ fun Slider(
145126
}
146127
}
147128

129+
/**
130+
* Encapsulates the state for haptic feedback in the slider
131+
*/
132+
class SliderHapticState {
133+
private var edgeFeedbackTriggered: Boolean = false
134+
private var lastStep: Float = 0f
135+
136+
fun reset(currentValue: Float) {
137+
edgeFeedbackTriggered = false
138+
lastStep = currentValue
139+
}
140+
141+
fun handleHapticFeedback(
142+
currentValue: Float,
143+
hapticEffect: SliderDefaults.SliderHapticEffect,
144+
hapticFeedback: HapticFeedback
145+
) {
146+
println("currentValue: $currentValue")
147+
if (hapticEffect == SliderDefaults.SliderHapticEffect.None) return
148+
149+
// Handle edge feedback
150+
if (hapticEffect != SliderDefaults.SliderHapticEffect.None) {
151+
val isAtEdge = currentValue == 0f || currentValue == 1f
152+
if (isAtEdge && !edgeFeedbackTriggered) {
153+
hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureThresholdActivate)
154+
edgeFeedbackTriggered = true
155+
} else if (!isAtEdge) {
156+
edgeFeedbackTriggered = false
157+
}
158+
}
159+
160+
// Handle step feedback
161+
if (hapticEffect == SliderDefaults.SliderHapticEffect.Step) {
162+
val isNotAtEdge = currentValue != 0f && currentValue != 1f
163+
164+
if (currentValue != lastStep && isNotAtEdge) {
165+
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
166+
lastStep = currentValue
167+
}
168+
}
169+
}
170+
}
171+
148172
@Composable
149173
fun SliderBackground(
150174
modifier: Modifier,
@@ -190,7 +214,7 @@ object SliderDefaults {
190214
val MinHeight = 30.dp
191215

192216
/**
193-
* The default haptic effect of the [Slider].
217+
* The haptic effect of the [Slider].
194218
*/
195219
enum class SliderHapticEffect {
196220
None, // No haptic effect

0 commit comments

Comments
 (0)