diff --git a/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/ColorConverterHub.kt b/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/ColorConverterHub.kt index b274467..8963bde 100644 --- a/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/ColorConverterHub.kt +++ b/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/ColorConverterHub.kt @@ -14,6 +14,10 @@ object ColorConverterHub { ColorKey.CMYK, IntegerCMYKColorConverter() ) + registerConverter( + ColorKey.RGB, + IntegerRGBColorConverter() + ) registerConverter( ColorKey.LAB, IntegerLABColorConverter() diff --git a/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/IntegerRGBColorConverter.kt b/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/IntegerRGBColorConverter.kt new file mode 100644 index 0000000..e5346db --- /dev/null +++ b/andcolorpicker/src/main/java/codes/side/andcolorpicker/converter/IntegerRGBColorConverter.kt @@ -0,0 +1,39 @@ +package codes.side.andcolorpicker.converter + +import codes.side.andcolorpicker.model.Color +import codes.side.andcolorpicker.model.IntegerRGBColor + +class IntegerRGBColorConverter : ColorConverter { + + override fun convertToOpaqueColorInt(color: Color): Int { + TODO("Not yet implemented") + } + + override fun convertToColorInt(color: Color): Int { + require(color is IntegerRGBColor) { "Unsupported color type supplied" } + + return android.graphics.Color.argb( + color.intA, + color.intR, + color.intG, + color.intB + ) + } + + override fun convertToPureHueColorInt(color: Color): Int { + TODO("Not yet implemented") + } + + override fun setFromColorInt(color: Color, value: Int) { + require(color is IntegerRGBColor) { "Unsupported color type supplied" } + + color.copyValuesFrom( + intArrayOf( + android.graphics.Color.alpha(value), + android.graphics.Color.red(value), + android.graphics.Color.green(value), + android.graphics.Color.blue(value) + ) + ) + } +} diff --git a/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/IntegerRGBColor.kt b/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/IntegerRGBColor.kt index 3566945..dea62e6 100644 --- a/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/IntegerRGBColor.kt +++ b/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/IntegerRGBColor.kt @@ -1,13 +1,157 @@ package codes.side.andcolorpicker.model -class IntegerRGBColor : IntegerColor(COMPONENTS_COUNT) { +class IntegerRGBColor : IntegerColor( + COMPONENTS_COUNT, + DEFAULT_RGB_VALUES +) { companion object { private const val TAG = "IntegerRGBColor" - private const val COMPONENTS_COUNT = 3 + private val COMPONENTS_COUNT = Component.values().size + + private val DEFAULT_RGB_VALUES = Component + .values().map { it.defaultValue }.toIntArray() } override val colorKey = ColorKey.RGB override val alpha: Float - get() = TODO("Not yet implemented") + get() { + return intA / Component.A.maxValue.toFloat() + } + var intA: Int + get() { + return intValues[Component.A.index] + } + set(value) { + setValue( + Component.A.index, + value, + Component.A.minValue, + Component.A.maxValue + ) + } + + var floatR: Float + get() { + return intR.toFloat() + } + set(value) { + intR = value.toInt() + } + var intR: Int + get() { + return intValues[Component.R.index] + } + set(value) { + setValue( + Component.R.index, + value, + Component.R.minValue, + Component.R.maxValue + ) + } + + var floatG: Float + get() { + return intG.toFloat() + } + set(value) { + intG = value.toInt() + } + var intG: Int + get() { + return intValues[Component.G.index] + } + set(value) { + setValue( + Component.G.index, + value, + Component.G.minValue, + Component.G.maxValue + ) + } + + var floatB: Float + get() { + return intB.toFloat() + } + set(value) { + intB = value.toInt() + } + var intB: Int + get() { + return intValues[Component.B.index] + } + set(value) { + setValue( + Component.B.index, + value, + Component.B.minValue, + Component.B.maxValue + ) + } + + override fun clone(): IntegerRGBColor { + return super.clone() as IntegerRGBColor + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as IntegerRGBColor + + if (colorKey != other.colorKey) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + colorKey.hashCode() + return result + } + + // TODO: Make Component top-level? + // TODO: Make tree? + // TODO: Use range? + enum class Component( + val defaultValue: Int, + val minValue: Int, + val maxValue: Int + ) { + R( + 0, + 0, + 255 + ), + G( + 0, + 0, + 255 + ), + B( + 0, + 0, + 255 + ), + A( + 255, + 0, + 255 + ); + + // TODO: Review approach + val index: Int + get() { + return ordinal + } + + // TODO: Adapt for non-zero min values + val normalizedDefaultValue: Float + get() { + return defaultValue / maxValue.toFloat() + } + } } diff --git a/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/factory/RGBColorFactory.kt b/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/factory/RGBColorFactory.kt new file mode 100644 index 0000000..b8dcbaf --- /dev/null +++ b/andcolorpicker/src/main/java/codes/side/andcolorpicker/model/factory/RGBColorFactory.kt @@ -0,0 +1,13 @@ +package codes.side.andcolorpicker.model.factory + +import codes.side.andcolorpicker.model.IntegerRGBColor + +class RGBColorFactory : ColorFactory() { + override fun create(): IntegerRGBColor { + return IntegerRGBColor() + } + + override fun createColorFrom(color: IntegerRGBColor): IntegerRGBColor { + return color.clone() + } +} diff --git a/andcolorpicker/src/main/java/codes/side/andcolorpicker/rgb/RGBColorPickerSeekBar.kt b/andcolorpicker/src/main/java/codes/side/andcolorpicker/rgb/RGBColorPickerSeekBar.kt new file mode 100644 index 0000000..367dbf8 --- /dev/null +++ b/andcolorpicker/src/main/java/codes/side/andcolorpicker/rgb/RGBColorPickerSeekBar.kt @@ -0,0 +1,277 @@ +package codes.side.andcolorpicker.rgb + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.LayerDrawable +import android.util.AttributeSet +import androidx.core.graphics.ColorUtils +import codes.side.andcolorpicker.R +import codes.side.andcolorpicker.converter.IntegerRGBColorConverter +import codes.side.andcolorpicker.model.IntegerRGBColor +import codes.side.andcolorpicker.model.factory.RGBColorFactory +import codes.side.andcolorpicker.view.picker.ColorSeekBar +import codes.side.andcolorpicker.view.picker.GradientColorSeekBar + +class RGBColorPickerSeekBar @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = androidx.appcompat.R.attr.seekBarStyle +) : + GradientColorSeekBar( + RGBColorFactory(), + context, + attrs, + defStyle + ) { + companion object { + private const val TAG = "RGBColorPickerSeekBar" + + private val DEFAULT_MODE = Mode.MODE_R + private val DEFAULT_COLORING_MODE = ColoringMode.PURE_COLOR + } + + override val colorConverter: IntegerRGBColorConverter + get() = super.colorConverter as IntegerRGBColorConverter + + private var modeInitialized = false + private var _mode: Mode? = null + var mode: Mode + get() { + return requireNotNull(_mode) { "Mode is not initialized yet" } + } + set(value) { + modeInitialized = true + if (_mode == value) { + return + } + _mode = value + refreshProperties() + refreshProgressFromCurrentColor() + refreshProgressDrawable() + refreshThumb() + } + + private var coloringModeInitialized = false + private var _coloringMode: ColoringMode? = null + var coloringMode: ColoringMode + get() { + return requireNotNull(_coloringMode) { "Coloring mode is not initialized yet" } + } + set(value) { + coloringModeInitialized = true + if (_coloringMode == value) { + return + } + _coloringMode = value + refreshProgressDrawable() + refreshThumb() + } + + init { + init(attrs) + } + + private fun init(attrs: AttributeSet? = null) { + val typedArray = context.theme.obtainStyledAttributes( + attrs, + R.styleable.RGBColorPickerSeekBar, + 0, + 0 + ) + + mode = Mode.values()[typedArray.getInteger( + R.styleable.RGBColorPickerSeekBar_rgbMode, + DEFAULT_MODE.ordinal + )] + coloringMode = ColoringMode.values()[typedArray.getInteger( + R.styleable.RGBColorPickerSeekBar_rgbColoringMode, + DEFAULT_COLORING_MODE.ordinal + )] + + typedArray.recycle() + } + + override fun setMax(max: Int) { + if (modeInitialized && max != mode.absoluteProgress) { + throw IllegalArgumentException("Current mode supports ${mode.absoluteProgress} max value only, was $max") + } + super.setMax(max) + } + + override fun onUpdateColorFrom(color: IntegerRGBColor, value: IntegerRGBColor) { + color.setFrom(value) + } + + override fun onRefreshProperties() { + if (!modeInitialized) { + return + } + max = mode.maxProgress + } + + override fun onRefreshProgressDrawable(progressDrawable: LayerDrawable) { + if (!coloringModeInitialized || !modeInitialized) { + return + } + + (progressDrawable.getDrawable(0) as GradientDrawable).colors = + when (coloringMode) { + ColoringMode.PURE_COLOR, ColoringMode.PLAIN_COLOR -> mode.coloringModeCheckpointsMap[coloringMode] + ColoringMode.OUTPUT_COLOR -> when (mode) { + Mode.MODE_R -> TODO() + Mode.MODE_G -> TODO() + Mode.MODE_B -> TODO() + } + } + } + + override fun onRefreshThumb(thumbColoringDrawables: Set) { + thumbColoringDrawables.forEach { + when (it) { + is GradientDrawable -> { + paintThumbStroke(it) + } + is LayerDrawable -> { + paintThumbStroke(it.getDrawable(0) as GradientDrawable) + } + } + } + } + + override fun onRefreshColorFromProgress(color: IntegerRGBColor, progress: Int): Boolean { + if (!modeInitialized) { + return false + } + + return when (mode) { + Mode.MODE_R -> { + val currentH = color.intR + if (currentH != progress) { + color.intR = progress + true + } else { + false + } + } + Mode.MODE_G -> { + val currentS = color.intG + if (currentS != progress) { + color.intG = progress + true + } else { + false + } + } + Mode.MODE_B -> { + val currentL = color.intB + if (currentL != progress) { + color.intB = progress + true + } else { + false + } + } + } + } + + override fun onRefreshProgressFromColor(color: IntegerRGBColor): Int? { + if (!modeInitialized) { + return null + } + + return when (mode) { + Mode.MODE_R -> { + color.intR + } + Mode.MODE_G -> { + color.intG + } + Mode.MODE_B -> { + color.intB + } + } + } + + private fun paintThumbStroke(drawable: GradientDrawable) { + if (!coloringModeInitialized || !modeInitialized) { + return + } + + val currentProgress = progress + drawable.setStroke( + thumbStrokeWidthPx, + when (coloringMode) { + ColoringMode.PURE_COLOR, ColoringMode.PLAIN_COLOR -> { + val checkpoints = requireNotNull(mode.coloringModeCheckpointsMap[coloringMode]) + ColorUtils.blendARGB( + checkpoints.first(), + checkpoints.last(), + currentProgress / mode.maxProgress.toFloat() + ) + } + ColoringMode.OUTPUT_COLOR -> when (mode) { + Mode.MODE_R -> TODO() + Mode.MODE_G -> TODO() + Mode.MODE_B -> TODO() + } + } + ) + } + + enum class ColoringMode { + PURE_COLOR, + OUTPUT_COLOR, + PLAIN_COLOR, + } + + enum class Mode( + override val minProgress: Int, + override val maxProgress: Int, + val coloringModeCheckpointsMap: HashMap + ) : ColorSeekBar.Mode { + MODE_R( + IntegerRGBColor.Component.R.minValue, + IntegerRGBColor.Component.R.maxValue, + hashMapOf( + ColoringMode.PURE_COLOR to intArrayOf( + Color.BLACK, + Color.RED + ), + ColoringMode.PLAIN_COLOR to intArrayOf( + Color.RED, + Color.RED + ) + ) + ), + MODE_G( + IntegerRGBColor.Component.G.minValue, + IntegerRGBColor.Component.G.maxValue, + hashMapOf( + ColoringMode.PURE_COLOR to intArrayOf( + Color.BLACK, + Color.GREEN + ), + ColoringMode.PLAIN_COLOR to intArrayOf( + Color.GREEN, + Color.GREEN + ) + ) + ), + MODE_B( + IntegerRGBColor.Component.B.minValue, + IntegerRGBColor.Component.B.maxValue, + hashMapOf( + ColoringMode.PURE_COLOR to intArrayOf( + Color.BLACK, + Color.BLUE + ), + ColoringMode.PLAIN_COLOR to intArrayOf( + Color.BLUE, + Color.BLUE + ) + ) + ) + } +} diff --git a/andcolorpicker/src/main/res/values/attrs.xml b/andcolorpicker/src/main/res/values/attrs.xml index 974c654..a00cf58 100644 --- a/andcolorpicker/src/main/res/values/attrs.xml +++ b/andcolorpicker/src/main/res/values/attrs.xml @@ -23,6 +23,18 @@ + + + + + + + + + + + + diff --git a/andcolorpicker/src/main/res/values/strings.xml b/andcolorpicker/src/main/res/values/strings.xml index e7ef7b8..22fc4d8 100644 --- a/andcolorpicker/src/main/res/values/strings.xml +++ b/andcolorpicker/src/main/res/values/strings.xml @@ -8,6 +8,9 @@ Magenta Yellow Key + Red + Green + Blue [WIP] Pick a color Pick Cancel diff --git a/app/src/main/java/codes/side/andcolorpicker/app/activity/MainActivity.kt b/app/src/main/java/codes/side/andcolorpicker/app/activity/MainActivity.kt index 448445e..1fae624 100644 --- a/app/src/main/java/codes/side/andcolorpicker/app/activity/MainActivity.kt +++ b/app/src/main/java/codes/side/andcolorpicker/app/activity/MainActivity.kt @@ -206,7 +206,7 @@ class MainActivity : AppCompatActivity(), RGB_SEEK_BAR( "RGB SeekBar", MaterialDesignDx.Icon.gmf_space_bar, - { WipFragment() } + { RGBSeekBarFragment() } ), RGB_PLANE( "RGB Plane", diff --git a/app/src/main/java/codes/side/andcolorpicker/app/fragment/RGBSeekBarFragment.kt b/app/src/main/java/codes/side/andcolorpicker/app/fragment/RGBSeekBarFragment.kt new file mode 100644 index 0000000..fa85f8a --- /dev/null +++ b/app/src/main/java/codes/side/andcolorpicker/app/fragment/RGBSeekBarFragment.kt @@ -0,0 +1,68 @@ +package codes.side.andcolorpicker.app.fragment + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import codes.side.andcolorpicker.app.R +import codes.side.andcolorpicker.group.PickerGroup +import codes.side.andcolorpicker.group.registerPickers +import codes.side.andcolorpicker.model.IntegerRGBColor +import codes.side.andcolorpicker.rgb.RGBColorPickerSeekBar +import codes.side.andcolorpicker.view.picker.ColorSeekBar +import kotlinx.android.synthetic.main.fragment_rgb_seek_bar.* + +class RGBSeekBarFragment : Fragment(R.layout.fragment_rgb_seek_bar) { + + companion object { + private const val TAG = "RGBSeekBarFragment" + } + + private val pickerGroup = PickerGroup() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated( + view, + savedInstanceState + ) + + pickerGroup.registerPickers( + redRGBColorPickerSeekBar, + greenRGBColorPickerSeekBar, + blueRGBColorPickerSeekBar + ) + + pickerGroup.addListener( + object : + ColorSeekBar.DefaultOnColorPickListener, IntegerRGBColor>() { + override fun onColorChanged( + picker: ColorSeekBar, + color: IntegerRGBColor, + value: Int + ) { + swatchView.setSwatchColor( + color + ) + } + } + ) + + pickerGroup.setColor( + IntegerRGBColor().also { + it.intR = 30 + it.intG = 130 + it.intB = 230 + } + ) + + val radioColoringModesMap = hashMapOf( + R.id.pureRadioButton to RGBColorPickerSeekBar.ColoringMode.PURE_COLOR, + R.id.plainRadioButton to RGBColorPickerSeekBar.ColoringMode.PLAIN_COLOR + ) + coloringModeRadioGroup.setOnCheckedChangeListener { _, checkedId -> + pickerGroup.forEach { + (it as? RGBColorPickerSeekBar)?.coloringMode = + requireNotNull(radioColoringModesMap[checkedId]) + } + } + } +} diff --git a/app/src/main/res/layout/fragment_rgb_seek_bar.xml b/app/src/main/res/layout/fragment_rgb_seek_bar.xml new file mode 100644 index 0000000..7b5d9c1 --- /dev/null +++ b/app/src/main/res/layout/fragment_rgb_seek_bar.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b281d98..8d1f01f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Coloring mode Pure Output + Plain Randomize Work In Progress :D Show dialog