-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathUIText.kt
133 lines (107 loc) · 4.93 KB
/
UIText.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
package gg.essential.elementa.components
import gg.essential.elementa.UIComponent
import gg.essential.elementa.UIConstraints
import gg.essential.elementa.constraints.CenterConstraint
import gg.essential.elementa.dsl.width
import gg.essential.elementa.state.BasicState
import gg.essential.elementa.state.MappedState
import gg.essential.elementa.state.State
import gg.essential.elementa.state.pixels
import gg.essential.universal.UGraphics
import gg.essential.universal.UMatrixStack
import java.awt.Color
/**
* Simple text component that draws its given `text` at the scale determined by
* this component's width & height constraints.
*/
open class UIText
constructor(
text: State<String>,
shadow: State<Boolean> = BasicState(true),
shadowColor: State<Color?> = BasicState(null)
) : UIComponent() {
@JvmOverloads constructor(
text: String = "",
shadow: Boolean = true,
shadowColor: Color? = null
) : this(BasicState(text), BasicState(shadow), BasicState(shadowColor))
private val textState: MappedState<String, String> = text.map { it } // extra map so we can easily rebind it
private val shadowState: MappedState<Boolean, Boolean> = shadow.map { it }
private val shadowColorState: MappedState<Color?, Color?> = shadowColor.map { it }
private val textScaleState = constraints.asState { getTextScale() }
/** Guess on whether we should be trying to center or top-align this component. See [BELOW_LINE_HEIGHT]. */
private val verticallyCenteredState = constraints.asState { y is CenterConstraint }
private val fontProviderState = constraints.asState { fontProvider }
private var textWidthState = textState.zip(textScaleState.zip(fontProviderState)).map { (text, opts) ->
val (textScale, fontProvider) = opts
text.width(textScale, fontProvider) / textScale
}
private fun <T> UIConstraints.asState(selector: UIConstraints.() -> T) = BasicState(selector(constraints)).also {
constraints.addObserver { _, _ -> it.set(selector(constraints)) }
}
init {
setWidth(textWidthState.pixels())
setHeight(shadowState.zip(verticallyCenteredState.zip(fontProviderState)).map { (shadow, opts) ->
val (verticallyCentered, fontProvider) = opts
val above = (if (verticallyCentered) fontProvider.getBelowLineHeight() else 0f)
val center = fontProvider.getBaseLineHeight()
val below = fontProvider.getBelowLineHeight() + (if (shadow) fontProvider.getShadowHeight() else 0f)
above + center + below
}.pixels())
}
fun bindText(newTextState: State<String>) = apply {
textState.rebind(newTextState)
}
fun bindShadow(newShadowState: State<Boolean>) = apply {
shadowState.rebind(newShadowState)
}
fun bindShadowColor(newShadowColorState: State<Color?>) = apply {
shadowColorState.rebind(newShadowColorState)
}
fun getText() = textState.get()
fun setText(text: String) = apply { textState.set(text) }
fun getShadow() = shadowState.get()
fun setShadow(shadow: Boolean) = apply { shadowState.set(shadow) }
@Deprecated("Wrong return type", level = DeprecationLevel.HIDDEN)
@JvmName("getShadowColor")
fun getShadowColorState(): State<Color?> = shadowColorState
fun getShadowColor(): Color? = shadowColorState.get()
fun setShadowColor(shadowColor: Color?) = apply { shadowColorState.set(shadowColor) }
/**
* Returns the text width if no scale is applied to the text
*/
fun getTextWidth() = textWidthState.get()
override fun getWidth(): Float {
return super.getWidth() * getTextScale()
}
override fun getHeight(): Float {
return super.getHeight() * getTextScale()
}
override fun draw(matrixStack: UMatrixStack) {
val textWidth = textWidthState.get()
// If you're wondering why we check if the text's width is 0 instead of if the string is empty:
// It's better to check the width derived from the font provider, as the string may just be full of characters
// that can't be rendered (as they aren't supported by current font).
// This check prevents issues from occurring later, e.g. when calculating the scale of the text.
if (textWidth == 0f)
return
beforeDrawCompat(matrixStack)
val scale = getWidth() / textWidth
val x = getLeft()
val y = getTop() + (if (verticallyCenteredState.get()) fontProviderState.get().getBelowLineHeight() * scale else 0f)
val color = getColor()
// We aren't visible, don't draw
if (color.alpha <= 10) {
return super.draw(matrixStack)
}
UGraphics.enableBlend()
val shadow = shadowState.get()
val shadowColor = shadowColorState.get()
getFontProvider().drawString(
matrixStack,
textState.get(), color, x, y,
10f, scale, shadow, shadowColor
)
super.draw(matrixStack)
}
}