Exit Full View

Games Cupboard / gamescupboard-client / src / commonMain / kotlin / uk / co / nickthecoder / gamescupboard / client / SkinBitmapBuilder.kt

package uk.co.nickthecoder.gamescupboard.client

import com.soywiz.korge.ui.UiSkinKind
import com.soywiz.korge.ui.UiSkinType
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.Colors
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.paint.Paint
import com.soywiz.korma.geom.degrees
import com.soywiz.korma.geom.vector.*

/**
 * Most of this code was taken from com.soywiz.korge.ui.UISkin.
 * But instead of building a single skin, we can create skins of any shape and color scheme.
 *
 * NOTE. UISkins uses a hard-coded value of 16 for the margins of the NinePatches.
 * So you cannot use (lineWidth + radius) larger than 16.
 *
 * NOTE. I don't fully understand how the KorGE's UI components use this bitmap. Beware!
 */
abstract class SkinBitmapBuilder {

    fun createBitmap(): Bitmap32 =
        NativeImage(512, 512).context2d {
            for (kind in listOf(UiSkinKind.BUTTON, UiSkinKind.RADIO)) {
                for (n in 0 until 4) {
                    drawImage(buildButton(UiSkinType(n), kind), (kind.id * 256) + (n * 64), 0)
                }
            }
            for (n in 0 until 5) {
                for (enabled in listOf(false, true)) {
                    drawImage(buildShape(n, enabled), 64.0 * (if (enabled) 0 else 1), 64 + 64.0 * n)
                }
            }
        }.mipmaps(true).toBMP32IfRequired()

    abstract fun buildShape(index: Int, enabled: Boolean): Bitmap
    abstract fun buildButton(index: UiSkinType, kind: UiSkinKind): Bitmap
}

class CustomisableSkinBitmapBuilder(
    val lineWidth: Int = 4,
    val radius: Int = 6,

    // For buildDefaultShape.
    val shapedEnabled: RGBA,
    val shapedDisabled: RGBA,

    // For buildDefaultButton
    val normal: RGBA,
    val over: RGBA,
    val down: RGBA,
    val disabled: RGBA,

    val borderNormal: RGBA,
    val borderOver: RGBA,
    val borderDown: RGBA,
    val borderDisabled: RGBA,

    /**
     * I have no clue what this is for! I've kept it here for consistency with the code from UISkins.
     * For some reason, when building buttons, UISkins uses an index of 5, which
     * is outside the range 0..4 of [UiSkinType]. Hmm.
     */
    val fifthType: RGBA = Colors["#3c3e3e"]

) : SkinBitmapBuilder() {

    override fun buildShape(index: Int, enabled: Boolean): Bitmap {
        val color = com.soywiz.korim.paint.ColorPaint(if (enabled) this.shapedEnabled else shapedDisabled)
        val lineWidth = this@CustomisableSkinBitmapBuilder.lineWidth.toDouble()

        return NativeImage(64, 64).context2d {
            when (index) {
                0 -> {
                    translate(27, 41)
                    rotate(45.degrees)
                    stroke(color, lineWidth = lineWidth, lineCap = LineCap.BUTT) {
                        moveTo(-16, 0)
                        lineTo(0, 0)
                        lineTo(0, -32)
                    }
                }

                else -> {
                    translate(32, 32)
                    rotate((45 + 90 * (index - 1)).degrees)
                    val offsetX = -8
                    val offsetY = -8
                    val lineLength = 20
                    stroke(color, lineWidth = lineWidth, lineCap = LineCap.SQUARE) {
                        line(offsetX + 0, offsetY + 0, offsetX + 0, offsetY + lineLength)
                        line(offsetX + 0, offsetY + 0, offsetX + lineLength, offsetY + 0)
                    }
                }
            }
        }
    }

    override fun buildButton(index: UiSkinType, kind: UiSkinKind): Bitmap {
        val doubleLineWidth = this.lineWidth * 2
        val lineWidth = this.lineWidth

        return NativeImage(64, 64).context2d {
            val fill: Paint = when (index) {
                UiSkinType.NORMAL -> normal
                UiSkinType.OVER -> over
                UiSkinType.DOWN -> down
                UiSkinType.DISABLED -> disabled
                // NOTE, in to original, there was an error thrown. Don't do this!
                // I think it is never used, and was only there because UiSkinType is not an enum.
                else -> fifthType
            }

            val border: Paint = when (index) {
                UiSkinType.NORMAL -> borderNormal
                UiSkinType.OVER -> borderOver
                UiSkinType.DOWN -> borderDown
                UiSkinType.DISABLED -> borderDisabled
                // I have no clue what this fifth type is used for. My guss is, it isn't!
                else -> fifthType
            }
            fill(fill) {
                stroke(border, lineWidth = doubleLineWidth.toDouble()) {
                    when (kind) {
                        UiSkinKind.RADIO -> circle(32.0, 32.0, 28.0)
                        UiSkinKind.BUTTON -> roundRect(
                            lineWidth, lineWidth,
                            64 - doubleLineWidth, 64 - doubleLineWidth,
                            radius, radius
                        )
                    }
                }
            }
        }
    }
}