comparison android/DWindows.kt @ 2713:6594bb323ab5

Android: Import Colow Wheel by Anton Popov. Add copyrights to comply with the MIT license Color Wheel is under. Will keep this in until Android provides us with a native color picker.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Fri, 03 Dec 2021 00:00:28 +0000
parents 3cb5aa73dace
children 26bb1e4a97d0
comparison
equal deleted inserted replaced
2712:ada3ac4677f6 2713:6594bb323ab5
1 // (C) 2021 Brian Smith <brian@dbsoft.org>
2 // (C) 2019 Anton Popov
1 package org.dbsoft.dwindows 3 package org.dbsoft.dwindows
2 4
3 import android.R 5 import android.R
4 import android.annotation.SuppressLint 6 import android.annotation.SuppressLint
5 import android.app.Activity 7 import android.app.Activity
23 import android.provider.MediaStore 25 import android.provider.MediaStore
24 import android.text.InputFilter 26 import android.text.InputFilter
25 import android.text.InputFilter.LengthFilter 27 import android.text.InputFilter.LengthFilter
26 import android.text.InputType 28 import android.text.InputType
27 import android.text.method.PasswordTransformationMethod 29 import android.text.method.PasswordTransformationMethod
28 import android.util.Base64
29 import android.util.Log
30 import android.util.SparseBooleanArray
31 import android.util.TypedValue
32 import android.view.* 30 import android.view.*
33 import android.view.View.OnTouchListener 31 import android.view.View.OnTouchListener
34 import android.view.ViewGroup 32 import android.view.ViewGroup
35 import android.view.inputmethod.EditorInfo 33 import android.view.inputmethod.EditorInfo
36 import android.webkit.WebView 34 import android.webkit.WebView
61 import java.util.* 59 import java.util.*
62 import java.util.concurrent.locks.ReentrantLock 60 import java.util.concurrent.locks.ReentrantLock
63 import java.util.zip.ZipEntry 61 import java.util.zip.ZipEntry
64 import java.util.zip.ZipFile 62 import java.util.zip.ZipFile
65 import android.content.Intent 63 import android.content.Intent
66 64 import android.util.*
65 import android.util.Base64
66 import kotlin.math.*
67
68 // Color Wheel section
69 private val HUE_COLORS = intArrayOf(
70 Color.RED,
71 Color.YELLOW,
72 Color.GREEN,
73 Color.CYAN,
74 Color.BLUE,
75 Color.MAGENTA,
76 Color.RED
77 )
78
79 private val SATURATION_COLORS = intArrayOf(
80 Color.WHITE,
81 setAlpha(Color.WHITE, 0)
82 )
83
84 open class ColorWheel @JvmOverloads constructor(
85 context: Context,
86 attrs: AttributeSet? = null,
87 defStyleAttr: Int = 0
88 ) : View(context, attrs, defStyleAttr) {
89
90 private val hueGradient = GradientDrawable().apply {
91 gradientType = GradientDrawable.SWEEP_GRADIENT
92 shape = GradientDrawable.OVAL
93 colors = HUE_COLORS
94 }
95
96 private val saturationGradient = GradientDrawable().apply {
97 gradientType = GradientDrawable.RADIAL_GRADIENT
98 shape = GradientDrawable.OVAL
99 colors = SATURATION_COLORS
100 }
101
102 private val thumbDrawable = ThumbDrawable()
103 private val hsvColor = HsvColor(value = 1f)
104
105 private var wheelCenterX = 0
106 private var wheelCenterY = 0
107 private var wheelRadius = 0
108 private var downX = 0f
109 private var downY = 0f
110
111 var rgb
112 get() = hsvColor.rgb
113 set(rgb) {
114 hsvColor.rgb = rgb
115 hsvColor.set(value = 1f)
116 fireColorListener()
117 invalidate()
118 }
119
120 var thumbRadius
121 get() = thumbDrawable.radius
122 set(value) {
123 thumbDrawable.radius = value
124 invalidate()
125 }
126
127 var thumbColor
128 get() = thumbDrawable.thumbColor
129 set(value) {
130 thumbDrawable.thumbColor = value
131 invalidate()
132 }
133
134 var thumbStrokeColor
135 get() = thumbDrawable.strokeColor
136 set(value) {
137 thumbDrawable.strokeColor = value
138 invalidate()
139 }
140
141 var thumbColorCircleScale
142 get() = thumbDrawable.colorCircleScale
143 set(value) {
144 thumbDrawable.colorCircleScale = value
145 invalidate()
146 }
147
148 var colorChangeListener: ((Int) -> Unit)? = null
149
150 var interceptTouchEvent = true
151
152 init {
153 thumbRadius = 13
154 thumbColor = Color.WHITE
155 thumbStrokeColor = Color.DKGRAY
156 thumbColorCircleScale = 0.7f
157 }
158
159 fun setRgb(r: Int, g: Int, b: Int) {
160 rgb = Color.rgb(r, g, b)
161 }
162
163 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
164 val minDimension = minOf(
165 MeasureSpec.getSize(widthMeasureSpec),
166 MeasureSpec.getSize(heightMeasureSpec)
167 )
168
169 setMeasuredDimension(
170 resolveSize(minDimension, widthMeasureSpec),
171 resolveSize(minDimension, heightMeasureSpec)
172 )
173 }
174
175 override fun onDraw(canvas: Canvas) {
176 drawColorWheel(canvas)
177 drawThumb(canvas)
178 }
179
180 private fun drawColorWheel(canvas: Canvas) {
181 val hSpace = width - paddingLeft - paddingRight
182 val vSpace = height - paddingTop - paddingBottom
183
184 wheelCenterX = paddingLeft + hSpace / 2
185 wheelCenterY = paddingTop + vSpace / 2
186 wheelRadius = maxOf(minOf(hSpace, vSpace) / 2, 0)
187
188 val left = wheelCenterX - wheelRadius
189 val top = wheelCenterY - wheelRadius
190 val right = wheelCenterX + wheelRadius
191 val bottom = wheelCenterY + wheelRadius
192
193 hueGradient.setBounds(left, top, right, bottom)
194 saturationGradient.setBounds(left, top, right, bottom)
195 saturationGradient.gradientRadius = wheelRadius.toFloat()
196
197 hueGradient.draw(canvas)
198 saturationGradient.draw(canvas)
199 }
200
201 private fun drawThumb(canvas: Canvas) {
202 val r = hsvColor.saturation * wheelRadius
203 val hueRadians = toRadians(hsvColor.hue)
204 val x = cos(hueRadians) * r + wheelCenterX
205 val y = sin(hueRadians) * r + wheelCenterY
206
207 thumbDrawable.indicatorColor = hsvColor.rgb
208 thumbDrawable.setCoordinates(x, y)
209 thumbDrawable.draw(canvas)
210 }
211
212 override fun onTouchEvent(event: MotionEvent): Boolean {
213 when (event.actionMasked) {
214 MotionEvent.ACTION_DOWN -> onActionDown(event)
215 MotionEvent.ACTION_MOVE -> updateColorOnMotionEvent(event)
216 MotionEvent.ACTION_UP -> {
217 updateColorOnMotionEvent(event)
218 if (isTap(event, downX, downY)) performClick()
219 }
220 }
221
222 return true
223 }
224
225 private fun onActionDown(event: MotionEvent) {
226 parent.requestDisallowInterceptTouchEvent(interceptTouchEvent)
227 updateColorOnMotionEvent(event)
228 downX = event.x
229 downY = event.y
230 }
231
232 override fun performClick() = super.performClick()
233
234 private fun updateColorOnMotionEvent(event: MotionEvent) {
235 calculateColor(event)
236 fireColorListener()
237 invalidate()
238 }
239
240 private fun calculateColor(event: MotionEvent) {
241 val legX = event.x - wheelCenterX
242 val legY = event.y - wheelCenterY
243 val hypot = minOf(hypot(legX, legY), wheelRadius.toFloat())
244 val hue = (toDegrees(atan2(legY, legX)) + 360) % 360
245 val saturation = hypot / wheelRadius
246 hsvColor.set(hue, saturation, 1f)
247 }
248
249 private fun fireColorListener() {
250 colorChangeListener?.invoke(hsvColor.rgb)
251 }
252
253 override fun onSaveInstanceState(): Parcelable {
254 val superState = super.onSaveInstanceState()
255 val thumbState = thumbDrawable.saveState()
256 return ColorWheelState(superState, this, thumbState)
257 }
258
259 override fun onRestoreInstanceState(state: Parcelable) {
260 if (state is ColorWheelState) {
261 super.onRestoreInstanceState(state.superState)
262 readColorWheelState(state)
263 } else {
264 super.onRestoreInstanceState(state)
265 }
266 }
267
268 private fun readColorWheelState(state: ColorWheelState) {
269 thumbDrawable.restoreState(state.thumbState)
270 interceptTouchEvent = state.interceptTouchEvent
271 rgb = state.rgb
272 }
273 }
274
275 internal class ColorWheelState : View.BaseSavedState {
276
277 val thumbState: ThumbDrawableState
278 val interceptTouchEvent: Boolean
279 val rgb: Int
280
281 constructor(
282 superState: Parcelable?,
283 view: ColorWheel,
284 thumbState: ThumbDrawableState
285 ) : super(superState) {
286 this.thumbState = thumbState
287 interceptTouchEvent = view.interceptTouchEvent
288 rgb = view.rgb
289 }
290
291 constructor(source: Parcel) : super(source) {
292 thumbState = source.readThumbState()
293 interceptTouchEvent = source.readBooleanCompat()
294 rgb = source.readInt()
295 }
296
297 override fun writeToParcel(out: Parcel, flags: Int) {
298 super.writeToParcel(out, flags)
299 out.writeThumbState(thumbState, flags)
300 out.writeBooleanCompat(interceptTouchEvent)
301 out.writeInt(rgb)
302 }
303
304 companion object CREATOR : Parcelable.Creator<ColorWheelState> {
305
306 override fun createFromParcel(source: Parcel) = ColorWheelState(source)
307
308 override fun newArray(size: Int) = arrayOfNulls<ColorWheelState>(size)
309 }
310 }
311 internal fun Parcel.writeBooleanCompat(value: Boolean) {
312 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
313 this.writeBoolean(value)
314 } else {
315 this.writeInt(if (value) 1 else 0)
316 }
317 }
318
319 internal fun Parcel.readBooleanCompat(): Boolean {
320 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
321 this.readBoolean()
322 } else {
323 this.readInt() == 1
324 }
325 }
326 private const val MAX_ALPHA = 255
327
328 open class GradientSeekBar @JvmOverloads constructor(
329 context: Context,
330 attrs: AttributeSet? = null,
331 defStyleAttr: Int = 0
332 ) : View(context, attrs, defStyleAttr) {
333
334 private val gradientColors = IntArray(2)
335 private val thumbDrawable = ThumbDrawable()
336 private val gradientDrawable = GradientDrawable()
337 private val argbEvaluator = android.animation.ArgbEvaluator()
338
339 private lateinit var orientationStrategy: OrientationStrategy
340 private var downX = 0f
341 private var downY = 0f
342
343 var startColor
344 get() = gradientColors[0]
345 set(color) { setColors(start = color) }
346
347 var endColor
348 get() = gradientColors[1]
349 set(color) { setColors(end = color) }
350
351 var offset = 0f
352 set(offset) {
353 field = ensureOffsetWithinRange(offset)
354 calculateArgb()
355 }
356
357 var barSize = 0
358 set(width) {
359 field = width
360 requestLayout()
361 }
362
363 var cornersRadius = 0f
364 set(radius) {
365 field = radius
366 invalidate()
367 }
368
369 var orientation = Orientation.VERTICAL
370 set(orientation) {
371 field = orientation
372 orientationStrategy = createOrientationStrategy()
373 requestLayout()
374 }
375
376 var thumbColor
377 get() = thumbDrawable.thumbColor
378 set(value) {
379 thumbDrawable.thumbColor = value
380 invalidate()
381 }
382
383 var thumbStrokeColor
384 get() = thumbDrawable.strokeColor
385 set(value) {
386 thumbDrawable.strokeColor = value
387 invalidate()
388 }
389
390 var thumbColorCircleScale
391 get() = thumbDrawable.colorCircleScale
392 set(value) {
393 thumbDrawable.colorCircleScale = value
394 invalidate()
395 }
396
397 var thumbRadius
398 get() = thumbDrawable.radius
399 set(radius) {
400 thumbDrawable.radius = radius
401 requestLayout()
402 }
403
404 var argb = 0
405 private set
406
407 var colorChangeListener: ((Float, Int) -> Unit)? = null
408
409 var interceptTouchEvent = true
410
411 init {
412 thumbColor = Color.WHITE
413 thumbStrokeColor = Color.DKGRAY
414 thumbColorCircleScale = 0.7f
415 thumbRadius = 13
416 barSize = 10
417 cornersRadius = 5.0f
418 offset = 0f
419 orientation = Orientation.VERTICAL
420 setColors(Color.TRANSPARENT, Color.BLACK)
421 }
422
423 private fun createOrientationStrategy(): OrientationStrategy {
424 return when (orientation) {
425 Orientation.VERTICAL -> VerticalStrategy()
426 Orientation.HORIZONTAL -> HorizontalStrategy()
427 }
428 }
429
430 fun setColors(start: Int = startColor, end: Int = endColor) {
431 updateGradientColors(start, end)
432 calculateArgb()
433 }
434
435 private fun updateGradientColors(start: Int, end: Int) {
436 gradientColors[0] = start
437 gradientColors[1] = end
438 gradientDrawable.colors = gradientColors
439 }
440
441 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
442 val dimens = orientationStrategy.measure(this, widthMeasureSpec, heightMeasureSpec)
443 setMeasuredDimension(dimens.width(), dimens.height())
444 }
445
446 override fun onDraw(canvas: Canvas) {
447 drawGradientRect(canvas)
448 drawThumb(canvas)
449 }
450
451 private fun drawGradientRect(canvas: Canvas) {
452 gradientDrawable.orientation = orientationStrategy.gradientOrientation
453 gradientDrawable.bounds = orientationStrategy.getGradientBounds(this)
454 gradientDrawable.cornerRadius = cornersRadius
455 gradientDrawable.draw(canvas)
456 }
457
458 private fun drawThumb(canvas: Canvas) {
459 val coordinates = orientationStrategy.getThumbPosition(this, gradientDrawable.bounds)
460 thumbDrawable.indicatorColor = argb
461 thumbDrawable.setCoordinates(coordinates.x, coordinates.y)
462 thumbDrawable.draw(canvas)
463 }
464
465 override fun onTouchEvent(event: MotionEvent): Boolean {
466 when (event.actionMasked) {
467 MotionEvent.ACTION_DOWN -> onActionDown(event)
468 MotionEvent.ACTION_MOVE -> calculateOffsetOnMotionEvent(event)
469 MotionEvent.ACTION_UP -> {
470 calculateOffsetOnMotionEvent(event)
471 if (isTap(event, downX, downY)) performClick()
472 }
473 }
474
475 return true
476 }
477
478 private fun onActionDown(event: MotionEvent) {
479 parent.requestDisallowInterceptTouchEvent(interceptTouchEvent)
480 calculateOffsetOnMotionEvent(event)
481 downX = event.x
482 downY = event.y
483 }
484
485 override fun performClick() = super.performClick()
486
487 private fun calculateOffsetOnMotionEvent(event: MotionEvent) {
488 offset = orientationStrategy.getOffset(this, event, gradientDrawable.bounds)
489 }
490
491 private fun calculateArgb() {
492 argb = argbEvaluator.evaluate(offset, startColor, endColor) as Int
493 fireListener()
494 invalidate()
495 }
496
497 private fun fireListener() {
498 colorChangeListener?.invoke(offset, argb)
499 }
500
501 override fun onSaveInstanceState(): Parcelable {
502 val superState = super.onSaveInstanceState()
503 val thumbState = thumbDrawable.saveState()
504 return GradientSeekBarState(superState, this, thumbState)
505 }
506
507 override fun onRestoreInstanceState(state: Parcelable) {
508 if (state is GradientSeekBarState) {
509 super.onRestoreInstanceState(state.superState)
510 readGradientSeekBarState(state)
511 } else {
512 super.onRestoreInstanceState(state)
513 }
514 }
515
516 private fun readGradientSeekBarState(state: GradientSeekBarState) {
517 updateGradientColors(state.startColor, state.endColor)
518 offset = state.offset
519 barSize = state.barSize
520 cornersRadius = state.cornerRadius
521 orientation = Orientation.values()[state.orientation]
522 interceptTouchEvent = state.interceptTouchEvent
523 thumbDrawable.restoreState(state.thumbState)
524 }
525
526 private fun ensureOffsetWithinRange(offset: Float) = ensureWithinRange(offset, 0f, 1f)
527
528 enum class Orientation { VERTICAL, HORIZONTAL }
529 }
530
531 val GradientSeekBar.currentColorAlpha get() = Color.alpha(argb)
532
533 fun GradientSeekBar.setTransparentToColor(color: Int, respectAlpha: Boolean = true) {
534 if (respectAlpha) {
535 this.offset = Color.alpha(color) / MAX_ALPHA.toFloat()
536 }
537 this.setColors(
538 setAlpha(color, 0),
539 setAlpha(color, MAX_ALPHA)
540 )
541 }
542
543 inline fun GradientSeekBar.setAlphaChangeListener(
544 crossinline listener: (Float, Int, Int) -> Unit
545 ) {
546 this.colorChangeListener = { offset, color ->
547 listener(offset, color, this.currentColorAlpha)
548 }
549 }
550
551 fun GradientSeekBar.setBlackToColor(color: Int) {
552 this.setColors(Color.BLACK, color)
553 }
554
555 internal class GradientSeekBarState : View.BaseSavedState {
556
557 val startColor: Int
558 val endColor: Int
559 val offset: Float
560 val barSize: Int
561 val cornerRadius: Float
562 val orientation: Int
563 val interceptTouchEvent: Boolean
564 val thumbState: ThumbDrawableState
565
566 constructor(
567 superState: Parcelable?,
568 view: GradientSeekBar,
569 thumbState: ThumbDrawableState
570 ) : super(superState) {
571 startColor = view.startColor
572 endColor = view.endColor
573 offset = view.offset
574 barSize = view.barSize
575 cornerRadius = view.cornersRadius
576 orientation = view.orientation.ordinal
577 interceptTouchEvent = view.interceptTouchEvent
578 this.thumbState = thumbState
579 }
580
581 constructor(source: Parcel) : super(source) {
582 startColor = source.readInt()
583 endColor = source.readInt()
584 offset = source.readFloat()
585 barSize = source.readInt()
586 cornerRadius = source.readFloat()
587 orientation = source.readInt()
588 interceptTouchEvent = source.readBooleanCompat()
589 thumbState = source.readThumbState()
590 }
591
592 override fun writeToParcel(out: Parcel, flags: Int) {
593 super.writeToParcel(out, flags)
594 out.writeInt(startColor)
595 out.writeInt(endColor)
596 out.writeFloat(offset)
597 out.writeInt(barSize)
598 out.writeFloat(cornerRadius)
599 out.writeInt(orientation)
600 out.writeBooleanCompat(interceptTouchEvent)
601 out.writeThumbState(thumbState, flags)
602 }
603
604 companion object CREATOR : Parcelable.Creator<GradientSeekBarState> {
605
606 override fun createFromParcel(source: Parcel) = GradientSeekBarState(source)
607
608 override fun newArray(size: Int) = arrayOfNulls<GradientSeekBarState>(size)
609 }
610 }
611
612 internal class HorizontalStrategy : OrientationStrategy {
613
614 private val rect = Rect()
615 private val point = PointF()
616
617 override val gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
618
619 override fun measure(view: GradientSeekBar, widthSpec: Int, heightSpec: Int): Rect {
620 val widthSize = View.MeasureSpec.getSize(widthSpec)
621 val maxHeight = maxOf(view.barSize, view.thumbRadius * 2)
622 val preferredWidth = widthSize + view.paddingLeft + view.paddingRight
623 val preferredHeight = maxHeight + view.paddingTop + view.paddingBottom
624 val finalWidth = View.resolveSize(preferredWidth, widthSpec)
625 val finalHeight = View.resolveSize(preferredHeight, heightSpec)
626 return rect.apply { set(0, 0, finalWidth, finalHeight) }
627 }
628
629 override fun getGradientBounds(view: GradientSeekBar): Rect {
630 val availableHeight = view.height - view.paddingTop - view.paddingRight
631 val left = view.paddingLeft + view.thumbRadius
632 val right = view.width - view.paddingRight - view.thumbRadius
633 val top = view.paddingTop + (availableHeight - view.barSize) / 2
634 val bottom = top + view.barSize
635 return rect.apply { set(left, top, right, bottom) }
636 }
637
638 override fun getThumbPosition(view: GradientSeekBar, gradient: Rect): PointF {
639 val x = (gradient.left + view.offset * gradient.width())
640 val y = view.height / 2f
641 return point.apply { set(x, y) }
642 }
643
644 override fun getOffset(view: GradientSeekBar, event: MotionEvent, gradient: Rect): Float {
645 val checkedX = ensureWithinRange(event.x.roundToInt(), gradient.left, gradient.right)
646 val relativeX = (checkedX - gradient.left).toFloat()
647 return relativeX / gradient.width()
648 }
649 }
650
651 internal fun View.isTap(lastEvent: MotionEvent, initialX: Float, initialY: Float): Boolean {
652 val config = ViewConfiguration.get(context)
653 val duration = lastEvent.eventTime - lastEvent.downTime
654 val distance = hypot(lastEvent.x - initialX, lastEvent.y - initialY)
655 return duration < ViewConfiguration.getTapTimeout() && distance < config.scaledTouchSlop
656 }
657
658 internal const val PI = Math.PI.toFloat()
659
660 internal fun toRadians(degrees: Float) = degrees / 180f * PI
661
662 internal fun toDegrees(radians: Float) = radians * 180f / PI
663
664 internal fun <T> ensureWithinRange(
665 value: T,
666 start: T,
667 end: T
668 ): T where T : Number, T : Comparable<T> = minOf(maxOf(value, start), end)
669
670 internal fun setAlpha(argb: Int, alpha: Int) =
671 Color.argb(alpha, Color.red(argb), Color.green(argb), Color.blue(argb))
672
673 class HsvColor(hue: Float = 0f, saturation: Float = 0f, value: Float = 0f) {
674
675 private val hsv = floatArrayOf(
676 ensureHue(hue),
677 ensureSaturation(saturation),
678 ensureValue(value)
679 )
680
681 var hue
682 get() = hsv[0]
683 set(hue) { hsv[0] = ensureHue(hue) }
684
685 var saturation
686 get() = hsv[1]
687 set(saturation) { hsv[1] = ensureSaturation(saturation) }
688
689 var value
690 get() = hsv[2]
691 set(value) { hsv[2] = ensureValue(value) }
692
693 var rgb
694 get() = Color.HSVToColor(hsv)
695 set(rgb) { Color.colorToHSV(rgb, hsv) }
696
697 fun set(hue: Float = hsv[0], saturation: Float = hsv[1], value: Float = hsv[2]) {
698 hsv[0] = ensureHue(hue)
699 hsv[1] = ensureSaturation(saturation)
700 hsv[2] = ensureValue(value)
701 }
702
703 private fun ensureHue(hue: Float) = ensureWithinRange(hue, 0f, 360f)
704
705 private fun ensureValue(value: Float) = ensureWithinRange(value, 0f, 1f)
706
707 private fun ensureSaturation(saturation: Float) = ensureValue(saturation)
708 }
709
710 internal interface OrientationStrategy {
711
712 val gradientOrientation: GradientDrawable.Orientation
713
714 fun measure(view: GradientSeekBar, widthSpec: Int, heightSpec: Int): Rect
715
716 fun getGradientBounds(view: GradientSeekBar): Rect
717
718 fun getThumbPosition(view: GradientSeekBar, gradient: Rect): PointF
719
720 fun getOffset(view: GradientSeekBar, event: MotionEvent, gradient: Rect): Float
721 }
722
723 internal class ThumbDrawableState private constructor(
724 val radius: Int,
725 val thumbColor: Int,
726 val strokeColor: Int,
727 val colorCircleScale: Float
728 ) : Parcelable {
729
730 constructor(thumbDrawable: ThumbDrawable) : this(
731 thumbDrawable.radius,
732 thumbDrawable.thumbColor,
733 thumbDrawable.strokeColor,
734 thumbDrawable.colorCircleScale
735 )
736
737 constructor(parcel: Parcel) : this(
738 parcel.readInt(),
739 parcel.readInt(),
740 parcel.readInt(),
741 parcel.readFloat()
742 )
743
744 override fun writeToParcel(parcel: Parcel, flags: Int) {
745 parcel.writeInt(radius)
746 parcel.writeInt(thumbColor)
747 parcel.writeInt(strokeColor)
748 parcel.writeFloat(colorCircleScale)
749 }
750
751 override fun describeContents() = 0
752
753 companion object {
754
755 val EMPTY_STATE = ThumbDrawableState(0, 0, 0, 0f)
756
757 @JvmField
758 val CREATOR = object : Parcelable.Creator<ThumbDrawableState> {
759
760 override fun createFromParcel(parcel: Parcel) = ThumbDrawableState(parcel)
761
762 override fun newArray(size: Int) = arrayOfNulls<ThumbDrawableState>(size)
763 }
764 }
765 }
766
767 internal fun Parcel.writeThumbState(state: ThumbDrawableState, flags: Int) {
768 this.writeParcelable(state, flags)
769 }
770
771 internal fun Parcel.readThumbState(): ThumbDrawableState {
772 return this.readParcelable(ThumbDrawableState::class.java.classLoader)
773 ?: ThumbDrawableState.EMPTY_STATE
774 }
775
776 internal class ThumbDrawable {
777
778 private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { strokeWidth = 1f }
779 private var x = 0f
780 private var y = 0f
781
782 var indicatorColor = 0
783 var strokeColor = 0
784 var thumbColor = 0
785 var radius = 0
786
787 var colorCircleScale = 0f
788 set(value) { field = ensureWithinRange(value, 0f, 1f) }
789
790 fun setCoordinates(x: Float, y: Float) {
791 this.x = x
792 this.y = y
793 }
794
795 fun draw(canvas: Canvas) {
796 drawThumb(canvas)
797 drawStroke(canvas)
798 drawColorIndicator(canvas)
799 }
800
801 private fun drawThumb(canvas: Canvas) {
802 paint.color = thumbColor
803 paint.style = Paint.Style.FILL
804 canvas.drawCircle(x, y, radius.toFloat(), paint)
805 }
806
807 private fun drawStroke(canvas: Canvas) {
808 val strokeCircleRadius = radius - paint.strokeWidth / 2f
809
810 paint.color = strokeColor
811 paint.style = Paint.Style.STROKE
812 canvas.drawCircle(x, y, strokeCircleRadius, paint)
813 }
814
815 private fun drawColorIndicator(canvas: Canvas) {
816 val colorIndicatorCircleRadius = radius * colorCircleScale
817
818 paint.color = indicatorColor
819 paint.style = Paint.Style.FILL
820 canvas.drawCircle(x, y, colorIndicatorCircleRadius, paint)
821 }
822
823 fun restoreState(state: ThumbDrawableState) {
824 radius = state.radius
825 thumbColor = state.thumbColor
826 strokeColor = state.strokeColor
827 colorCircleScale = state.colorCircleScale
828 }
829
830 fun saveState() = ThumbDrawableState(this)
831 }
832
833 internal class VerticalStrategy : OrientationStrategy {
834
835 private val rect = Rect()
836 private val point = PointF()
837
838 override val gradientOrientation = GradientDrawable.Orientation.BOTTOM_TOP
839
840 override fun measure(view: GradientSeekBar, widthSpec: Int, heightSpec: Int): Rect {
841 val heightSize = View.MeasureSpec.getSize(heightSpec)
842 val maxWidth = maxOf(view.barSize, view.thumbRadius * 2)
843 val preferredWidth = maxWidth + view.paddingLeft + view.paddingRight
844 val preferredHeight = heightSize + view.paddingTop + view.paddingBottom
845 val finalWidth = View.resolveSize(preferredWidth, widthSpec)
846 val finalHeight = View.resolveSize(preferredHeight, heightSpec)
847 return rect.apply { set(0, 0, finalWidth, finalHeight) }
848 }
849
850 override fun getGradientBounds(view: GradientSeekBar): Rect {
851 val availableWidth = view.width - view.paddingLeft - view.paddingRight
852 val left = view.paddingLeft + (availableWidth - view.barSize) / 2
853 val right = left + view.barSize
854 val top = view.paddingTop + view.thumbRadius
855 val bottom = view.height - view.paddingBottom - view.thumbRadius
856 return rect.apply { set(left, top, right, bottom) }
857 }
858
859 override fun getThumbPosition(view: GradientSeekBar, gradient: Rect): PointF {
860 val y = (gradient.top + (1f - view.offset) * gradient.height())
861 val x = view.width / 2f
862 return point.apply { set(x, y) }
863 }
864
865 override fun getOffset(view: GradientSeekBar, event: MotionEvent, gradient: Rect): Float {
866 val checkedY = ensureWithinRange(event.y.roundToInt(), gradient.top, gradient.bottom)
867 val relativeY = (checkedY - gradient.top).toFloat()
868 return 1f - relativeY / gradient.height()
869 }
870 }
871
872 // Main Dynamic Windows section
67 object DWEvent { 873 object DWEvent {
68 const val TIMER = 0 874 const val TIMER = 0
69 const val CONFIGURE = 1 875 const val CONFIGURE = 1
70 const val KEY_PRESS = 2 876 const val KEY_PRESS = 2
71 const val BUTTON_PRESS = 3 877 const val BUTTON_PRESS = 3