net.peanuuutz.fork.ui.scene.screen.ScreenCanvas.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fork-ui Show documentation
Show all versions of fork-ui Show documentation
Comprehensive API designed for Minecraft modders
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)
}