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

net.peanuuutz.fork.ui.scene.screen.ScreenCanvas.kt Maven / Gradle / Ivy

The newest version!
package net.peanuuutz.fork.ui.scene.screen

import net.minecraft.client.gl.ShaderProgram
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
import net.peanuuutz.fork.math.Vector3fs.PositiveZ
import net.peanuuutz.fork.math.toRadian
import net.peanuuutz.fork.render.screen.clip.ClipOp
import net.peanuuutz.fork.render.screen.draw.Drawer
import net.peanuuutz.fork.render.screen.draw.TextureFillMode
import net.peanuuutz.fork.render.screen.draw.shading.Shading
import net.peanuuutz.fork.render.text.TextConstants.DefaultFontColor
import net.peanuuutz.fork.render.vertex.rotate
import net.peanuuutz.fork.ui.scene.base.MatrixStackCanvas
import net.peanuuutz.fork.ui.scene.base.currentTransformation
import net.peanuuutz.fork.ui.scene.base.toNative
import net.peanuuutz.fork.ui.ui.draw.canvas.BaseCanvas
import net.peanuuutz.fork.ui.ui.draw.canvas.ClipSave
import net.peanuuutz.fork.ui.ui.draw.canvas.DrawStyle
import net.peanuuutz.fork.ui.ui.draw.canvas.DrawStyle.Fill
import net.peanuuutz.fork.ui.ui.draw.canvas.DrawStyle.Stroke
import net.peanuuutz.fork.ui.ui.draw.canvas.OutlineClipSave
import net.peanuuutz.fork.ui.ui.draw.canvas.Paint
import net.peanuuutz.fork.ui.ui.draw.canvas.PosColorTexVertices
import net.peanuuutz.fork.ui.ui.draw.canvas.PosColorVertices
import net.peanuuutz.fork.ui.ui.draw.canvas.PosTexVertices
import net.peanuuutz.fork.ui.ui.draw.canvas.Vertices
import net.peanuuutz.fork.ui.ui.draw.shape.MutableRect
import net.peanuuutz.fork.ui.ui.draw.shape.Outline
import net.peanuuutz.fork.ui.ui.draw.shape.Rect
import net.peanuuutz.fork.ui.ui.draw.shape.intersectBy
import net.peanuuutz.fork.ui.ui.draw.shape.isEmpty
import net.peanuuutz.fork.ui.ui.draw.shape.overlaps
import net.peanuuutz.fork.ui.ui.draw.shape.toMutableRect
import net.peanuuutz.fork.ui.ui.draw.shape.transformBy
import net.peanuuutz.fork.ui.ui.draw.text.SpanStyle
import net.peanuuutz.fork.ui.ui.draw.text.TextShadow
import net.peanuuutz.fork.ui.ui.draw.text.fontSizeOrDefault
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.util.common.Color
import org.joml.Matrix4fc

open class ScreenCanvas : BaseCanvas(), MatrixStackCanvas {
    fun canDraw(leftX: Float, topY: Float, rightX: Float, bottomY: Float): Boolean {
        val currentRegion = regionStack.lastOrNull() ?: return true
        if (currentRegion.isEmpty()) {
            return false
        }
        val candidateRegion = rectCache.apply {
            set(leftX, topY, rightX, bottomY)
            transformBy(currentTransformation)
        }
        return candidateRegion overlaps currentRegion
    }

    protected open fun drawLineWithShader(
        startX: Float,
        startY: Float,
        endX: Float,
        endY: Float,
        shader: Any,
        stroke: Stroke
    ) {
        val shading = shader as? Shading ?: return
        Drawer.drawLine(
            matrices = matrices,
            shading = shading,
            startX = startX,
            startY = startY,
            endX = endX,
            endY = endY,
            strokeWidth = stroke.width,
            strokeCap = stroke.cap
        )
    }

    protected open fun drawRectWithShader(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        shader: Any,
        style: DrawStyle
    ) {
        val shading = shader as? Shading ?: return
        when (style) {
            Fill -> {
                Drawer.fillRect(
                    matrices = matrices,
                    shading = shading,
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY
                )
            }
            is Stroke -> {
                Drawer.drawRect(
                    matrices = matrices,
                    shading = shading,
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY,
                    strokeWidth = style.width,
                    strokeJoin = style.join
                )
            }
        }
    }

    protected open fun drawRoundedRectWithShader(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        topLeftRadius: Float,
        bottomLeftRadius: Float,
        bottomRightRadius: Float,
        topRightRadius: Float,
        shader: Any,
        style: DrawStyle
    ) {
        val shading = shader as? Shading ?: return
        when (style) {
            Fill -> {
                Drawer.fillRoundedRect(
                    matrices = matrices,
                    shading = shading,
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY,
                    topLeftRadius = topLeftRadius,
                    bottomLeftRadius = bottomLeftRadius,
                    bottomRightRadius = bottomRightRadius,
                    topRightRadius = topRightRadius
                )
            }
            is Stroke -> {
                Drawer.drawRoundedRect(
                    matrices = matrices,
                    shading = shading,
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY,
                    topLeftRadius = topLeftRadius,
                    bottomLeftRadius = bottomLeftRadius,
                    bottomRightRadius = bottomRightRadius,
                    topRightRadius = topRightRadius,
                    strokeWidth = style.width
                )
            }
        }
    }

    protected open fun drawCircleWithShader(
        centerX: Float,
        centerY: Float,
        radius: Float,
        shader: Any,
        style: DrawStyle
    ) {
        val shading = shader as? Shading ?: return
        when (style) {
            Fill -> {
                Drawer.fillCircle(
                    matrices = matrices,
                    shading = shading,
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius
                )
            }
            is Stroke -> {
                Drawer.drawCircle(
                    matrices = matrices,
                    shading = shading,
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius,
                    strokeWidth = style.width
                )
            }
        }
    }

    protected open fun drawArcWithShader(
        centerX: Float,
        centerY: Float,
        radius: Float,
        startDegree: Float,
        sweepDegree: Float,
        shader: Any,
        style: DrawStyle
    ) {
        val shading = shader as? Shading ?: return
        when (style) {
            Fill -> {
                Drawer.fillArc(
                    matrices = matrices,
                    shading = shading,
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius,
                    startDegree = startDegree,
                    sweepDegree = sweepDegree
                )
            }
            is Stroke -> {
                Drawer.drawArc(
                    matrices = matrices,
                    shading = shading,
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius,
                    startDegree = startDegree,
                    sweepDegree = sweepDegree,
                    strokeWidth = style.width,
                    strokeCap = style.cap
                )
            }
        }
    }

    protected open fun drawTextureWithShader(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        textureWidth: Float,
        textureHeight: Float,
        textureRegionU: Float,
        textureRegionV: Float,
        textureRegionWidth: Float,
        textureRegionHeight: Float,
        shader: Any,
        fillMode: TextureFillMode,
        tintColor: Color
    ) {
        val texture = shader as? Identifier ?: return
        Drawer.fillTexture(
            matrices = matrices,
            texture = texture,
            textureWidth = textureWidth,
            textureHeight = textureHeight,
            leftX = leftX,
            topY = topY,
            textureRegionU = textureRegionU,
            textureRegionV = textureRegionV,
            textureRegionWidth = textureRegionWidth,
            textureRegionHeight = textureRegionHeight,
            rightX = rightX,
            bottomY = bottomY,
            fillMode = fillMode,
            tintColor = tintColor
        )
    }

    protected open fun drawVerticesWithShader(
        vertices: Vertices,
        shader: Any
    ) {
        val shaderProgram = shader as? ShaderProgram ?: return
        when (vertices) {
            is PosColorVertices -> nDrawVertices(vertices, shaderProgram)
            is PosTexVertices -> nDrawVertices(vertices, shaderProgram)
            is PosColorTexVertices -> nDrawVertices(vertices, shaderProgram)
        }
    }

    // ======== Internal ========

    final override val matrices: MatrixStack = MatrixStack()

    private val regionStack: ArrayDeque = ArrayDeque(8)

    private val rectCache: MutableRect = MutableRect()

    override var saveCount: Int = 0

    override fun save() {
        matrices.push()
        saveCount++
    }

    override fun onSaveClip(currentClip: ClipSave, parentClip: ClipSave?) {
        nSaveClip(currentClip)
        saveRegion(currentClip)
    }

    private fun saveRegion(currentClip: ClipSave) {
        // We only take care of the simplest case here
        if (currentClip.op != ClipOp.And || currentClip !is OutlineClipSave) {
            return
        }
        val currentRegion = currentClip.outline.bounds.toMutableRect().apply {
            transformBy(currentTransformation)
            regionStack.lastOrNull()?.let { intersectBy(it) }
        }
        regionStack.add(currentRegion)
    }

    override fun onRestoreClip(currentClip: ClipSave, parentClip: ClipSave?) {
        restoreRegion()
        nRestoreClip(currentClip)
    }

    private fun restoreRegion() {
        regionStack.removeLastOrNull()
    }

    override fun onSaveAlpha(currentAlpha: Float, parentAlpha: Float?) {
        nSaveAlpha(currentAlpha)
    }

    override fun onRestoreAlpha(currentAlpha: Float, parentAlpha: Float?) {
        nRestoreAlpha(currentAlpha)
    }

    override fun restore() {
        super.restore()
        saveCount--
        matrices.pop()
    }

    override fun translate(dx: Float, dy: Float) {
        matrices.translate(dx, dy, 0.0f)
    }

    override fun scale(sx: Float, sy: Float) {
        matrices.scale(sx, sy, 1.0f)
    }

    override fun rotate(degree: Float) {
        matrices.rotate(degree.toRadian(), PositiveZ)
    }

    override fun transformBy(matrix: Matrix4fc) {
        currentTransformation.mul(matrix)
    }

    override fun drawLine(
        startX: Float,
        startY: Float,
        endX: Float,
        endY: Float,
        paint: Paint
    ) {
        val stroke = paint.style as? Stroke ?: Stroke.Default
        val strokeRadius = stroke.width / 2
        val canDraw = canDraw(
            leftX = startX - strokeRadius,
            topY = startY - strokeRadius,
            rightX = endX + strokeRadius,
            bottomY = endY + strokeRadius
        )
        if (!canDraw) {
            return
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                Drawer.drawLine(
                    matrices = matrices,
                    color = color,
                    startX = startX,
                    startY = startY,
                    endX = endX,
                    endY = endY,
                    strokeWidth = stroke.width,
                    strokeCap = stroke.cap
                )
            } else {
                drawLineWithShader(
                    startX = startX,
                    startY = startY,
                    endX = endX,
                    endY = endY,
                    shader = shader,
                    stroke = stroke
                )
            }
        }
    }

    override fun drawRect(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        paint: Paint
    ) {
        when (val style = paint.style) {
            Fill -> {
                val canDraw = canDraw(leftX, topY, rightX, bottomY)
                if (!canDraw) {
                    return
                }
            }
            is Stroke -> {
                val strokeRadius = style.width / 2
                val canDraw = canDraw(
                    leftX = leftX - strokeRadius,
                    topY = topY - strokeRadius,
                    rightX = rightX + strokeRadius,
                    bottomY = bottomY + strokeRadius
                )
                if (!canDraw) {
                    return
                }
            }
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                when (val style = style) {
                    Fill -> {
                        Drawer.fillRect(
                            matrices = matrices,
                            color = color,
                            leftX = leftX,
                            topY = topY,
                            rightX = rightX,
                            bottomY = bottomY
                        )
                    }
                    is Stroke -> {
                        Drawer.drawRect(
                            matrices = matrices,
                            color = color,
                            leftX = leftX,
                            topY = topY,
                            rightX = rightX,
                            bottomY = bottomY,
                            strokeWidth = style.width,
                            strokeJoin = style.join
                        )
                    }
                }
            } else {
                drawRectWithShader(
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY,
                    shader = shader,
                    style = style
                )
            }
        }
    }

    override fun drawRoundedRect(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        topLeftRadius: Float,
        bottomLeftRadius: Float,
        bottomRightRadius: Float,
        topRightRadius: Float,
        paint: Paint
    ) {
        when (val style = paint.style) {
            Fill -> {
                val canDraw = canDraw(leftX, topY, rightX, bottomY)
                if (!canDraw) {
                    return
                }
            }
            is Stroke -> {
                val strokeRadius = style.width / 2
                val canDraw = canDraw(
                    leftX = leftX - strokeRadius,
                    topY = topY - strokeRadius,
                    rightX = rightX + strokeRadius,
                    bottomY = bottomY + strokeRadius
                )
                if (!canDraw) {
                    return
                }
            }
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                when (val style = style) {
                    Fill -> {
                        Drawer.fillRoundedRect(
                            matrices = matrices,
                            color = color,
                            leftX = leftX,
                            topY = topY,
                            rightX = rightX,
                            bottomY = bottomY,
                            topLeftRadius = topLeftRadius,
                            bottomLeftRadius = bottomLeftRadius,
                            bottomRightRadius = bottomRightRadius,
                            topRightRadius = topRightRadius
                        )
                    }
                    is Stroke -> {
                        Drawer.drawRoundedRect(
                            matrices = matrices,
                            color = color,
                            leftX = leftX,
                            topY = topY,
                            rightX = rightX,
                            bottomY = bottomY,
                            topLeftRadius = topLeftRadius,
                            bottomLeftRadius = bottomLeftRadius,
                            bottomRightRadius = bottomRightRadius,
                            topRightRadius = topRightRadius,
                            strokeWidth = style.width
                        )
                    }
                }
            } else {
                drawRoundedRectWithShader(
                    leftX = leftX,
                    topY = topY,
                    rightX = rightX,
                    bottomY = bottomY,
                    topLeftRadius = topLeftRadius,
                    bottomLeftRadius = bottomLeftRadius,
                    bottomRightRadius = bottomRightRadius,
                    topRightRadius = topRightRadius,
                    shader = shader,
                    style = style
                )
            }
        }
    }

    override fun drawCircle(
        centerX: Float,
        centerY: Float,
        radius: Float,
        paint: Paint
    ) {
        when (val style = paint.style) {
            Fill -> {
                val canDraw = canDraw(
                    leftX = centerX - radius,
                    topY = centerY - radius,
                    rightX = centerX + radius,
                    bottomY = centerY + radius
                )
                if (!canDraw) {
                    return
                }
            }
            is Stroke -> {
                val boundsRadius = radius + style.width / 2
                val canDraw = canDraw(
                    leftX = centerX - boundsRadius,
                    topY = centerY - boundsRadius,
                    rightX = centerX + boundsRadius,
                    bottomY = centerY + boundsRadius
                )
                if (!canDraw) {
                    return
                }
            }
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                when (val style = style) {
                    Fill -> {
                        Drawer.fillCircle(
                            matrices = matrices,
                            color = color,
                            centerX = centerX,
                            centerY = centerY,
                            radius = radius
                        )
                    }
                    is Stroke -> {
                        Drawer.drawCircle(
                            matrices = matrices,
                            color = color,
                            centerX = centerX,
                            centerY = centerY,
                            radius = radius,
                            strokeWidth = style.width
                        )
                    }
                }
            } else {
                drawCircleWithShader(
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius,
                    shader = shader,
                    style = style
                )
            }
        }
    }

    override fun drawArc(
        centerX: Float,
        centerY: Float,
        radius: Float,
        startDegree: Float,
        sweepDegree: Float,
        paint: Paint
    ) {
        when (val style = paint.style) {
            Fill -> {
                val canDraw = canDraw(
                    leftX = centerX - radius,
                    topY = centerY - radius,
                    rightX = centerX + radius,
                    bottomY = centerY + radius
                )
                if (!canDraw) {
                    return
                }
            }
            is Stroke -> {
                val boundsRadius = radius + style.width / 2
                val canDraw = canDraw(
                    leftX = centerX - boundsRadius,
                    topY = centerY - boundsRadius,
                    rightX = centerX + boundsRadius,
                    bottomY = centerY + boundsRadius
                )
                if (!canDraw) {
                    return
                }
            }
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                when (val style = style) {
                    Fill -> {
                        Drawer.fillArc(
                            matrices = matrices,
                            color = color,
                            centerX = centerX,
                            centerY = centerY,
                            radius = radius,
                            startDegree = startDegree,
                            sweepDegree = sweepDegree
                        )
                    }
                    is Stroke -> {
                        Drawer.drawArc(
                            matrices = matrices,
                            color = color,
                            centerX = centerX,
                            centerY = centerY,
                            radius = radius,
                            startDegree = startDegree,
                            sweepDegree = sweepDegree,
                            strokeWidth = style.width,
                            strokeCap = style.cap
                        )
                    }
                }
            } else {
                drawArcWithShader(
                    centerX = centerX,
                    centerY = centerY,
                    radius = radius,
                    startDegree = startDegree,
                    sweepDegree = sweepDegree,
                    shader = shader,
                    style = style
                )
            }
        }
    }

    override fun drawTexture(
        leftX: Float,
        topY: Float,
        rightX: Float,
        bottomY: Float,
        textureWidth: Float,
        textureHeight: Float,
        textureRegionU: Float,
        textureRegionV: Float,
        textureRegionWidth: Float,
        textureRegionHeight: Float,
        paint: Paint
    ) {
        val canDraw = canDraw(leftX, topY, rightX, bottomY)
        if (!canDraw) {
            return
        }
        val shader = paint.shader ?: return
        paint.applyTo {
            drawTextureWithShader(
                leftX = leftX,
                topY = topY,
                rightX = rightX,
                bottomY = bottomY,
                textureWidth = textureWidth,
                textureHeight = textureHeight,
                textureRegionU = textureRegionU,
                textureRegionV = textureRegionV,
                textureRegionWidth = textureRegionWidth,
                textureRegionHeight = textureRegionHeight,
                shader = shader,
                fillMode = textureFillMode,
                tintColor = color
            )
        }
    }

    override fun drawVertices(
        vertices: Vertices,
        paint: Paint
    ) {
        val canDraw = canDraw(vertices.bounds)
        if (!canDraw) {
            return
        }
        paint.applyTo {
            val shader = shader
            if (shader == null) {
                when (vertices) {
                    is PosColorVertices -> nDrawVertices(vertices)
                    is PosTexVertices -> nDrawVertices(vertices)
                    is PosColorTexVertices -> nDrawVertices(vertices)
                }
            } else {
                drawVerticesWithShader(
                    vertices = vertices,
                    shader = shader
                )
            }
        }
    }

    override fun drawTextSlice(
        leftX: Float,
        topY: Float,
        text: String,
        spanStyle: SpanStyle,
        paint: Paint
    ) {
        val fontSize = spanStyle.fontSizeOrDefault
        val canDraw = canDraw(
            leftX = leftX,
            topY = topY,
            rightX = Constraints.Unlimited.toFloat(), // Doesn't matter
            bottomY = topY + fontSize
        )
        if (!canDraw) {
            return
        }
        paint.applyTo {
            val nativeStyle = spanStyle.toNative()
            val shadow = spanStyle.shadow
            if (shadow != null) {
                val shadowTextColor = shadow.color.orElse(TextShadow.Default.color)
                Drawer.drawText(
                    matrices = matrices,
                    text = text,
                    leftX = leftX + shadow.offset.x,
                    topY = topY + shadow.offset.y,
                    style = nativeStyle.withColor(shadowTextColor.rgb),
                    fontSize = fontSize,
                    defaultColor = shadowTextColor
                )
            }
            Drawer.drawText(
                matrices = matrices,
                text = text,
                leftX = leftX,
                topY = topY,
                style = nativeStyle,
                fontSize = fontSize,
                defaultColor = spanStyle.color
                    .orElse(color)
                    .orElse(DefaultFontColor)
            )
        }
    }
}

// -------- Extensions --------

fun ScreenCanvas.canDraw(rect: Rect): Boolean {
    return canDraw(
        leftX = rect.leftX,
        topY = rect.topY,
        rightX = rect.rightX,
        bottomY = rect.bottomY
    )
}

fun ScreenCanvas.canDraw(outline: Outline): Boolean {
    return canDraw(outline.bounds)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy