@@ -27,9 +27,11 @@ import androidx.compose.ui.graphics.Brush
2727import androidx.compose.ui.graphics.Color
2828import androidx.compose.ui.graphics.TileMode
2929import androidx.compose.ui.graphics.drawscope.DrawScope
30+ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3031import androidx.compose.ui.input.pointer.pointerInput
3132import androidx.compose.ui.layout.onGloballyPositioned
3233import androidx.compose.ui.platform.LocalDensity
34+ import androidx.compose.ui.platform.LocalHapticFeedback
3335import androidx.compose.ui.unit.Dp
3436import androidx.compose.ui.unit.dp
3537import top.yukonga.miuix.kmp.theme.MiuixTheme
@@ -42,13 +44,15 @@ import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape
4244 * @param initialColor The initial color of the picker.
4345 * @param onColorChanged The callback to be called when the color changes.
4446 * @param showPreview Whether to show the color preview.
47+ * @param enableHapticEffect Whether to enable haptic feedback.
4548 * @param modifier The modifier to be applied to the color picker.
4649 */
4750@Composable
4851fun ColorPicker (
4952 initialColor : Color = MiuixTheme .colorScheme.primary,
5053 onColorChanged : (Color ) -> Unit = {},
5154 showPreview : Boolean = true,
55+ enableHapticEffect : Boolean = true,
5256 modifier : Modifier = Modifier
5357) {
5458 var initialSetup by remember { mutableStateOf(true ) }
@@ -102,7 +106,8 @@ fun ColorPicker(
102106 // Hue selection
103107 HueSlider (
104108 currentHue = currentHue,
105- onHueChanged = { newHue -> currentHue = newHue * 360f }
109+ onHueChanged = { newHue -> currentHue = newHue * 360f },
110+ enableHapticEffect = enableHapticEffect
106111 )
107112
108113 Spacer (modifier = Modifier .height(12 .dp))
@@ -111,7 +116,8 @@ fun ColorPicker(
111116 SaturationSlider (
112117 currentHue = currentHue,
113118 currentSaturation = currentSaturation,
114- onSaturationChanged = { currentSaturation = it }
119+ onSaturationChanged = { currentSaturation = it },
120+ enableHapticEffect = enableHapticEffect
115121 )
116122
117123 Spacer (modifier = Modifier .height(12 .dp))
@@ -121,7 +127,8 @@ fun ColorPicker(
121127 currentHue = currentHue,
122128 currentSaturation = currentSaturation,
123129 currentValue = currentValue,
124- onValueChanged = { currentValue = it }
130+ onValueChanged = { currentValue = it },
131+ enableHapticEffect = enableHapticEffect
125132 )
126133
127134 Spacer (modifier = Modifier .height(12 .dp))
@@ -132,7 +139,8 @@ fun ColorPicker(
132139 currentSaturation = currentSaturation,
133140 currentValue = currentValue,
134141 currentAlpha = currentAlpha,
135- onAlphaChanged = { currentAlpha = it }
142+ onAlphaChanged = { currentAlpha = it },
143+ enableHapticEffect = enableHapticEffect
136144 )
137145}
138146
@@ -141,18 +149,21 @@ fun ColorPicker(
141149 *
142150 * @param currentHue The current hue value.
143151 * @param onHueChanged The callback to be called when the hue changes.
152+ * @param enableHapticEffect Whether to enable haptic feedback.
144153 */
145154@Composable
146155fun HueSlider (
147156 currentHue : Float ,
148157 onHueChanged : (Float ) -> Unit ,
158+ enableHapticEffect : Boolean = true
149159) {
150160 val hueColors = List (36 ) { i -> Color .hsv(i * 10f , 1f , 1f ) }
151161 ColorSlider (
152162 value = currentHue / 360f ,
153163 onValueChanged = onHueChanged,
154164 drawBrushColors = hueColors,
155- modifier = Modifier .fillMaxWidth()
165+ modifier = Modifier .fillMaxWidth(),
166+ enableHapticEffect = enableHapticEffect
156167 )
157168}
158169
@@ -162,18 +173,21 @@ fun HueSlider(
162173 * @param currentHue The current hue value.
163174 * @param currentSaturation The current saturation value.
164175 * @param onSaturationChanged The callback to be called when the saturation changes.
176+ * @param enableHapticEffect Whether to enable haptic feedback.
165177 */
166178@Composable
167179fun SaturationSlider (
168180 currentHue : Float ,
169181 currentSaturation : Float ,
170182 onSaturationChanged : (Float ) -> Unit ,
183+ enableHapticEffect : Boolean = true
171184) {
172185 ColorSlider (
173186 value = currentSaturation,
174187 onValueChanged = onSaturationChanged,
175188 drawBrushColors = listOf (Color .hsv(currentHue, 0f , 1f , 1f ), Color .hsv(currentHue, 1f , 1f , 1f )),
176- modifier = Modifier .fillMaxWidth()
189+ modifier = Modifier .fillMaxWidth(),
190+ enableHapticEffect = enableHapticEffect
177191 )
178192}
179193
@@ -185,19 +199,22 @@ fun SaturationSlider(
185199 * @param currentSaturation The current saturation value.
186200 * @param currentValue The current value value.
187201 * @param onValueChanged The callback to be called when the value changes.
202+ * @param enableHapticEffect Whether to enable haptic feedback.
188203 */
189204@Composable
190205fun ValueSlider (
191206 currentHue : Float ,
192207 currentSaturation : Float ,
193208 currentValue : Float ,
194209 onValueChanged : (Float ) -> Unit ,
210+ enableHapticEffect : Boolean = true
195211) {
196212 ColorSlider (
197213 value = currentValue,
198214 onValueChanged = onValueChanged,
199215 drawBrushColors = listOf (Color .Black , Color .hsv(currentHue, currentSaturation, 1f )),
200- modifier = Modifier .fillMaxWidth()
216+ modifier = Modifier .fillMaxWidth(),
217+ enableHapticEffect = enableHapticEffect
201218 )
202219}
203220
@@ -209,6 +226,7 @@ fun ValueSlider(
209226 * @param currentValue The current value value.
210227 * @param currentAlpha The current alpha value.
211228 * @param onAlphaChanged The callback to be called when the alpha changes.
229+ * @param enableHapticEffect Whether to enable haptic feedback.
212230 */
213231@Composable
214232fun AlphaSlider (
@@ -217,6 +235,7 @@ fun AlphaSlider(
217235 currentValue : Float ,
218236 currentAlpha : Float ,
219237 onAlphaChanged : (Float ) -> Unit ,
238+ enableHapticEffect : Boolean = true
220239) {
221240 val baseColor = Color .hsv(currentHue, currentSaturation, currentValue)
222241 val startColor = remember(baseColor) { baseColor.copy(alpha = 0f ) }
@@ -258,6 +277,7 @@ fun AlphaSlider(
258277 onValueChanged = onAlphaChanged,
259278 drawBrushColors = listOf (startColor, endColor),
260279 modifier = Modifier .fillMaxWidth(),
280+ enableHapticEffect = enableHapticEffect,
261281 drawBackground = checkerBrush::draw
262282 )
263283}
@@ -271,6 +291,7 @@ private fun ColorSlider(
271291 onValueChanged : (Float ) -> Unit ,
272292 drawBrushColors : List <Color >,
273293 modifier : Modifier = Modifier ,
294+ enableHapticEffect : Boolean = true,
274295 drawBackground : (DrawScope .(width: Float , height: Float ) -> Unit )? = null
275296) {
276297 val density = LocalDensity .current
@@ -281,10 +302,19 @@ private fun ColorSlider(
281302 val borderShape = remember { SmoothRoundedCornerShape (50 .dp) }
282303 val borderStroke = remember { 0.5 .dp }
283304 val borderColor = remember { Color .Gray .copy(0.1f ) }
305+ val hapticFeedback = LocalHapticFeedback .current
306+ var lastHapticStep by remember { mutableStateOf(0f ) }
284307
285308 val dragHandler = remember(onValueChanged, sliderSizePx) {
286309 { posX: Float , width: Float ->
287- handleSliderInteraction(posX, width, sliderSizePx, onValueChanged)
310+ handleSliderInteraction(posX, width, sliderSizePx).coerceIn(0f , 1f )
311+ }
312+ }
313+
314+ fun performHapticFeedbackIfNeeded (valueChanged : Float ) {
315+ if (enableHapticEffect && valueChanged != lastHapticStep) {
316+ hapticFeedback.performHapticFeedback(HapticFeedbackType .TextHandleMove )
317+ lastHapticStep = valueChanged
288318 }
289319 }
290320
@@ -303,7 +333,9 @@ private fun ColorSlider(
303333 .pointerInput(dragHandler) {
304334 detectHorizontalDragGestures { change, _ ->
305335 change.consume()
306- dragHandler(change.position.x, size.width.toFloat())
336+ val currentValue = dragHandler(change.position.x, size.width.toFloat())
337+ onValueChanged(currentValue)
338+ performHapticFeedbackIfNeeded(currentValue)
307339 }
308340 }
309341 ) {
@@ -329,7 +361,6 @@ private fun ColorSlider(
329361 }
330362}
331363
332-
333364@Composable
334365private fun SliderIndicator (
335366 modifier : Modifier ,
@@ -360,12 +391,11 @@ private fun SliderIndicator(
360391private fun handleSliderInteraction (
361392 positionX : Float ,
362393 totalWidth : Float ,
363- sliderSizePx : Float ,
364- onValueChanged : (Float ) -> Unit
365- ) {
394+ sliderSizePx : Float
395+ ): Float {
366396 val sliderHalfSizePx = sliderSizePx / 2
367397 val effectiveWidth = totalWidth - sliderSizePx
368398 val constrainedX = positionX.coerceIn(sliderHalfSizePx, totalWidth - sliderHalfSizePx)
369399 val newPosition = (constrainedX - sliderHalfSizePx) / effectiveWidth
370- onValueChanged( newPosition.coerceIn(0f , 1f ) )
400+ return newPosition.coerceIn(0f , 1f )
371401}
0 commit comments