All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.RoundedRectangleElement.kt Maven / Gradle / Ivy

There is a newer version: 0.19.1
Show newest version
package com.juul.krayon.element

import com.juul.krayon.kanvas.Kanvas
import com.juul.krayon.kanvas.Paint
import com.juul.krayon.kanvas.Path

public class RoundedRectangleElement : InteractableElement() {

    override val tag: String get() = "rounded-rectangle"

    public var left: Float by attributes.withDefault { 0f }
    public var top: Float by attributes.withDefault { 0f }
    public var right: Float by attributes.withDefault { 0f }
    public var bottom: Float by attributes.withDefault { 0f }

    public var topLeftRadius: Float by attributes.withDefault { 0f }
    public var topRightRadius: Float by attributes.withDefault { 0f }
    public var bottomLeftRadius: Float by attributes.withDefault { 0f }
    public var bottomRightRadius: Float by attributes.withDefault { 0f }

    public var paint: Paint by attributes.withDefault { DEFAULT_FILL }

    // TODO: Cache the generated path, lazily generating it only when it changes.

    override fun draw(kanvas: Kanvas) {
        kanvas.drawPath(generatePath(), paint)
    }

    override fun getInteractionPath(): Path = generatePath()

    public companion object : ElementBuilder, ElementSelector {
        override fun build(): RoundedRectangleElement = RoundedRectangleElement()

        override fun trySelect(element: Element): RoundedRectangleElement? = element as? RoundedRectangleElement
    }
}

/** Transforms a radius such that requested radii which are larger than their rectangle behave nicely. */
private fun safeRadius(desired: Float, horizontalNeighbor: Float, verticalNeighbor: Float, width: Float, height: Float): Float {
    val requiresHorizontalAdjustment = width < (desired + horizontalNeighbor)
    val requiresVerticalAdjustment = height < (desired + verticalNeighbor)
    return if (requiresHorizontalAdjustment && requiresVerticalAdjustment) {
        minOf(
            width * desired / (desired + horizontalNeighbor),
            height * desired / (desired + verticalNeighbor),
        )
    } else if (requiresHorizontalAdjustment) {
        width * desired / (desired + horizontalNeighbor)
    } else if (requiresVerticalAdjustment) {
        height * desired / (desired + verticalNeighbor)
    } else {
        desired
    }
}

private fun RoundedRectangleElement.generatePath(): Path {
    val width = (right - left).coerceAtLeast(0f)
    val height = (bottom - top).coerceAtLeast(0f)
    val tlr = safeRadius(topLeftRadius, horizontalNeighbor = topRightRadius, verticalNeighbor = bottomLeftRadius, width, height)
    val trr = safeRadius(topRightRadius, horizontalNeighbor = topLeftRadius, verticalNeighbor = bottomRightRadius, width, height)
    val blr = safeRadius(bottomLeftRadius, horizontalNeighbor = bottomRightRadius, verticalNeighbor = topLeftRadius, width, height)
    val brr = safeRadius(bottomRightRadius, horizontalNeighbor = bottomLeftRadius, verticalNeighbor = topRightRadius, width, height)
    return Path {
        moveTo(left + tlr, top)
        lineTo(right - trr, top)
        arcTo(right - 2 * trr, top, right, top + 2 * trr, -90f, 90f, false)
        lineTo(right, bottom - brr)
        arcTo(right - 2 * brr, bottom - 2 * brr, right, bottom, 0f, 90f, false)
        lineTo(left + blr, bottom)
        arcTo(left, bottom - 2 * blr, left + 2 * blr, bottom, 90f, 90f, false)
        lineTo(left, top + tlr)
        arcTo(left, top, left + 2 * tlr, top + 2 * tlr, 180f, 90f, false)
        close()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy