-
Notifications
You must be signed in to change notification settings - Fork 30
/
SVGComponent.kt
140 lines (110 loc) · 4.53 KB
/
SVGComponent.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package gg.essential.elementa.components
import gg.essential.elementa.UIComponent
import gg.essential.elementa.components.image.ImageProvider
import gg.essential.elementa.svg.SVGParser
import gg.essential.elementa.svg.data.SVG
import gg.essential.universal.UGraphics
import gg.essential.universal.UMatrixStack
import org.lwjgl.BufferUtils
import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL15
import java.awt.Color
/**
* Component with native SVG rendering for high-res icons.
*
* Note that Elementa's SVG Renderer is quite primitive and only supports
* basic SVGs. To ensure compatibility, we recommend using icons from
* [Tabler](https://github.com/tabler/tabler-icons)
*/
//#if MC>=11700
//$$ @Deprecated("Not currently supported on GL3 Core / 1.17+, needs updating")
//#endif
class SVGComponent(private var svg: SVG) : UIComponent(), ImageProvider {
private var vboID = -1
private lateinit var vboData: List<VBOData>
private var needsReload = false
fun setSVG(svg: SVG) {
this.svg = svg
needsReload = true
}
override fun drawImage(matrixStack: UMatrixStack, x: Double, y: Double, width: Double, height: Double, color: Color) {
if (UGraphics.isCoreProfile()) {
// TODO heavily relies on legacy gl, at least need to use per-vertex color and convert lines/points to tris
return
}
if (!::vboData.isInitialized || needsReload) {
generateVBOData()
needsReload = false
}
val xScale = svg.width?.let { width / it } ?: 1.0
val yScale = svg.height?.let { height / it } ?: 1.0
val strokeWidth = (xScale * svg.strokeWidth).toFloat().coerceAtMost(10f)
matrixStack.push()
UGraphics.enableBlend()
@Suppress("DEPRECATION")
UGraphics.disableTexture2D()
GL11.glColor4f(color.red / 255f, color.green / 255f, color.blue / 255f, 1f)
matrixStack.translate(x, y, 0.0)
matrixStack.scale(xScale, yScale, 0.0)
GL11.glPointSize(strokeWidth)
GL11.glLineWidth(strokeWidth)
GL11.glEnable(GL11.GL_LINE_SMOOTH)
GL11.glEnable(GL11.GL_POINT_SMOOTH)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID)
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY)
GL11.glVertexPointer(2, GL11.GL_FLOAT, 0, 0)
matrixStack.runWithGlobalState { vboData.forEach { (drawType, startIndex, vertexCount, drawPoints) ->
GL11.glDrawArrays(drawType, startIndex, vertexCount)
if (drawPoints && svg.roundLineJoins) {
GL11.glDrawArrays(GL11.GL_POINTS, startIndex, vertexCount)
}
} }
GL11.glDisable(GL11.GL_LINE_SMOOTH)
GL11.glDisable(GL11.GL_POINT_SMOOTH)
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0)
@Suppress("DEPRECATION")
UGraphics.enableTexture2D()
matrixStack.pop()
}
override fun draw(matrixStack: UMatrixStack) {
beforeDraw(matrixStack)
val x = this.getLeft().toDouble()
val y = this.getTop().toDouble()
val width = this.getWidth().toDouble()
val height = this.getHeight().toDouble()
val color = this.getColor()
if (color.alpha == 0) {
return super.draw(matrixStack)
}
drawImage(matrixStack, x, y, width, height, color)
super.draw(matrixStack)
}
private fun generateVBOData() {
vboID = GL15.glGenBuffers()
val totalVertexCount = svg.elements.sumOf { it.getVertexCount() }
val vertexBuffer = BufferUtils.createFloatBuffer(totalVertexCount * 2)
var currPos = 0
vboData = svg.elements.map { el ->
val vertexCount = el.getVertexCount()
VBOData(el.createBuffer(vertexBuffer), currPos, vertexCount, el.drawSmoothPoints())
.also { currPos += vertexCount }
}
vertexBuffer.rewind()
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID)
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_STATIC_DRAW)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0)
}
fun finalize() {
val vboID = vboID
if (vboID != -1) {
Window.enqueueRenderOperation {
GL15.glDeleteBuffers(vboID)
}
}
}
companion object {
fun ofResource(resourcePath: String): SVGComponent = SVGComponent(SVGParser.parseFromResource(resourcePath))
private data class VBOData(val drawType: Int, val startIndex: Int, val count: Int, val drawPoints: Boolean)
}
}