Added ColorOrPalette.

This commit is contained in:
2026-03-18 01:34:48 +08:00
parent ed6da28c24
commit 5b1941eceb
@@ -0,0 +1,293 @@
@file:OptIn(RDKTInternalAPI::class)
@file:JvmName("ColorOrPalettes")
package cn.rdlevel.rdkt.core.data
import cn.rdlevel.rdkt.core.annotations.RDKTInternalAPI
import cn.rdlevel.rdkt.core.serialization.PolymorphicDelegatedSerializer
import cn.rdlevel.rdkt.core.serialization.TransformSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic
/**
* A color that can be either a specific RGB color or a palette index.
*/
@Serializable(ColorOrPalette.Serializer::class)
public sealed interface ColorOrPalette {
public object Serializer : TransformSerializer<ColorOrPalette, String>("ColorOrPalette", String.serializer()) {
override fun toData(value: ColorOrPalette): String = when (value) {
is Palette -> "pal${value.index}"
is Color -> colorToHex(value.red, value.green, value.blue)
}
override fun fromData(data: String): ColorOrPalette {
return if (data.startsWith("pal")) {
val index = data.substring(3).toIntOrNull()
?: throw IllegalArgumentException("Invalid palette format: $data. Expected format is 'pal{index}'.")
Palette.of(index)
} else {
Color.of(data)
}
}
}
}
/**
* A color that can be either a specific RGBA color or a palette index.
*/
@Serializable(ColorWithAlphaOrPalette.Serializer::class)
public sealed interface ColorWithAlphaOrPalette : ColorOrPalette {
public object Serializer :
TransformSerializer<ColorWithAlphaOrPalette, String>("ColorWithAlphaOrPalette", String.serializer()) {
override fun toData(value: ColorWithAlphaOrPalette): String = when (value) {
is Palette -> "pal${value.index}"
is ColorWithAlpha -> colorToHex(value.red, value.green, value.blue, value.alpha)
}
override fun fromData(data: String): ColorWithAlphaOrPalette {
return if (data.startsWith("pal")) {
val index = data.substring(3).toIntOrNull()
?: throw IllegalArgumentException("Invalid palette format: $data. Expected format is 'pal{index}'.")
Palette.of(index)
} else {
ColorWithAlpha.of(data)
}
}
}
}
/**
* A color that is a palette index.
*
* @property index The index of the palette color, which must be between 0 and 20 inclusive.
*/
@Serializable(Palette.Serializer::class)
public sealed interface Palette : ColorWithAlphaOrPalette {
public val index: Int
public object Serializer : PolymorphicDelegatedSerializer<Palette, ColorOrPalette>(ColorOrPalette.serializer())
public companion object {
/**
* Creates a [Palette] instance with the specified index.
*/
@JvmStatic
public fun of(index: Int): Palette = PaletteImpl(index)
}
}
private fun Palette.checkIndex() {
require(index in 0..20) { "Palette index must be between 0 and 20, but was $index." }
}
private data class PaletteImpl(override val index: Int) : Palette {
init {
checkIndex()
}
override fun toString(): String = "pal$index"
}
/**
* A color that is a specific RGB color.
*
* @property red The red component of the color, which must be between 0 and 255 inclusive.
* @property green The green component of the color, which must be between 0 and 255 inclusive.
* @property blue The blue component of the color, which must be between 0 and 255 inclusive.
*/
@Serializable(Color.Serializer::class)
public sealed interface Color : ColorOrPalette {
public val red: Int
public val green: Int
public val blue: Int
/**
* Converts the color to a hexadecimal string representation.
* Depending on the implementation, this may return a 6-character hex string (for RGB) or an 8-character hex string (for RGBA).
*/
public fun toHex(): String
public object Serializer : PolymorphicDelegatedSerializer<Color, ColorOrPalette>(ColorOrPalette.serializer())
public companion object {
/**
* Creates a [Color] instance with the specified RGB components.
*/
@JvmStatic
public fun of(r: Int, g: Int, b: Int): Color = ColorImpl(r, g, b)
/**
* Creates a [Color] instance from a hexadecimal string representation. The hex string must be 6 characters long, representing the RGB components in the format RRGGBB.
*/
@JvmStatic
public fun of(hex: String): Color {
require(hex.length == 6) { "Hex color must be 6 characters long, but was ${hex.length}." }
val r = hex.substring(0, 2).toInt(16)
val g = hex.substring(2, 4).toInt(16)
val b = hex.substring(4, 6).toInt(16)
return of(r, g, b)
}
}
}
private fun Color.checkComponents() {
require(red in 0..255) { "Red component must be between 0 and 255, but was $red." }
require(green in 0..255) { "Green component must be between 0 and 255, but was $green." }
require(blue in 0..255) { "Blue component must be between 0 and 255, but was $blue." }
}
/**
* A color that is a specific RGBA color.
*
* @property alpha The alpha component of the color, which must be between 0 and 255 inclusive.
*/
@Serializable(ColorWithAlpha.Serializer::class)
public sealed interface ColorWithAlpha : Color, ColorWithAlphaOrPalette {
public val alpha: Int
public override fun toHex(): String
public object Serializer :
PolymorphicDelegatedSerializer<ColorWithAlpha, ColorWithAlphaOrPalette>(ColorWithAlphaOrPalette.serializer())
public companion object {
/**
* Creates a [ColorWithAlpha] instance with the specified RGBA components. The alpha component is optional and defaults to 255 (fully opaque) if not provided.
*/
@JvmStatic
@JvmOverloads
public fun of(r: Int, g: Int, b: Int, a: Int = 255): ColorWithAlpha = ColorImpl(r, g, b, a, true)
/**
* Creates a [ColorWithAlpha] instance from a hexadecimal string representation. The hex string must be 8 characters long, representing the RGBA components in the format RRGGBBAA.
*/
@JvmStatic
public fun of(hex: String): ColorWithAlpha {
require(hex.length == 8) { "Hex color with alpha must be 8 characters long, but was ${hex.length}." }
val r = hex.substring(0, 2).toInt(16)
val g = hex.substring(2, 4).toInt(16)
val b = hex.substring(4, 6).toInt(16)
val a = hex.substring(6, 8).toInt(16)
return of(r, g, b, a)
}
}
}
private fun ColorWithAlpha.checkComponentsWithAlpha() {
checkComponents()
require(alpha in 0..255) { "Alpha component must be between 0 and 255, but was $alpha." }
}
private data class ColorImpl(
override val red: Int,
override val green: Int,
override val blue: Int,
override val alpha: Int = 255,
private val withAlpha: Boolean = false,
) : ColorWithAlpha {
init {
checkComponentsWithAlpha()
}
override fun toHex(): String = if (withAlpha) colorToHex(red, green, blue, alpha) else colorToHex(red, green, blue)
override fun toString(): String = toHex()
}
private fun colorToHex(r: Int, g: Int, b: Int): String {
val rHex = r.toString(16).padStart(2, '0')
val gHex = g.toString(16).padStart(2, '0')
val bHex = b.toString(16).padStart(2, '0')
return "$rHex$gHex$bHex"
}
private fun colorToHex(r: Int, g: Int, b: Int, a: Int): String {
val rHex = r.toString(16).padStart(2, '0')
val gHex = g.toString(16).padStart(2, '0')
val bHex = b.toString(16).padStart(2, '0')
val aHex = a.toString(16).padStart(2, '0')
return "$rHex$gHex$bHex$aHex"
}
/**
* A palette color that allows specifying RGBA components.
* Instances of this class is created by [LevelSettings][cn.rdlevel.rdkt.core.settings.LevelSettings], and can be assigned to events that need colors to specify palette colors.
*/
public sealed interface PaletteColor : ColorWithAlpha, Palette {
override var red: Int
override var green: Int
override var blue: Int
override var alpha: Int
public var color: ColorWithAlpha
get() = this
set(value) {
red = value.red
green = value.green
blue = value.blue
alpha = value.alpha
}
/**
* Detaches the palette color from the palette and returns a new [ColorWithAlpha] instance with the same RGBA components.
* This is useful when you want to use the color of a palette color without referencing the palette index.
*/
public fun asColorWithAlpha(): ColorWithAlpha = ColorWithAlpha.of(red, green, blue, alpha)
}
private data class PaletteColorImpl(
override val index: Int,
override var red: Int,
override var green: Int,
override var blue: Int,
override var alpha: Int = 255,
) : PaletteColor {
init {
checkIndex()
checkComponentsWithAlpha()
}
override fun toHex(): String = colorToHex(red, green, blue, alpha)
override fun toString(): String = "pal$index=${toHex()}"
}
public object PaletteColorListSerializer : TransformSerializer<List<PaletteColor>, List<String>>(
"PaletteColorList",
ListSerializer(String.serializer())
) {
override fun toData(value: List<PaletteColor>): List<String> = value.map { it.toHex() }
override fun fromData(data: List<String>): List<PaletteColor> = data.mapIndexed { index, hexString ->
val color = ColorWithAlpha.of(hexString)
PaletteColorImpl(index, color.red, color.green, color.blue, color.alpha)
}
}
/**
* A list of palette colors. This is used for serializing and deserializing the palette colors in [LevelSettings][cn.rdlevel.rdkt.core.settings.LevelSettings].
* In [LevelSettings][cn.rdlevel.rdkt.core.settings.LevelSettings] this list must contain exactly 21 colors, which correspond to the palette indices from 0 to 20.
*/
public typealias PaletteColorList = @Serializable(PaletteColorListSerializer::class) List<PaletteColor>
/**
* Creates a [Palette] instance with the specified index.
*/
@JvmName("ofPalette")
public fun colorOf(paletteIndex: Int): Palette = Palette.of(paletteIndex)
/**
* Creates a [Color] instance with the specified RGB components.
*/
@JvmName("ofColor")
public fun colorOf(r: Int, g: Int, b: Int): Color = Color.of(r, g, b)
/**
* Creates a [ColorWithAlpha] instance with the specified RGBA components. The alpha component is optional and defaults to 255 (fully opaque) if not provided.
*/
@JvmName("ofColor")
public fun colorOf(r: Int, g: Int, b: Int, a: Int = 255): ColorWithAlpha = ColorWithAlpha.of(r, g, b, a)