Line and row scrolling
This commit is contained in:
parent
fd84c40c1e
commit
64a54add95
@ -27,6 +27,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
|
|||||||
import ru.dbotthepony.mc.otm.core.addAll
|
import ru.dbotthepony.mc.otm.core.addAll
|
||||||
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
||||||
import ru.dbotthepony.mc.otm.milliTime
|
import ru.dbotthepony.mc.otm.milliTime
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
open class TextInputPanel<out S : Screen>(
|
open class TextInputPanel<out S : Screen>(
|
||||||
screen: S,
|
screen: S,
|
||||||
@ -158,6 +159,18 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
private val undo = ArrayDeque<Snapshot>()
|
private val undo = ArrayDeque<Snapshot>()
|
||||||
private val redo = ArrayDeque<Snapshot>()
|
private val redo = ArrayDeque<Snapshot>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scroll in rows (up-down)
|
||||||
|
*/
|
||||||
|
private var scrollLines = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scroll in pixels (left-right)
|
||||||
|
*/
|
||||||
|
private var scrollPixels = 0f
|
||||||
|
|
||||||
|
var rowSpacing = 2f
|
||||||
|
|
||||||
private fun triggerChangeCallback() {
|
private fun triggerChangeCallback() {
|
||||||
if (oldText != lines) {
|
if (oldText != lines) {
|
||||||
textCache = null
|
textCache = null
|
||||||
@ -1018,13 +1031,59 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val characterWidthCache = Char2IntOpenHashMap()
|
||||||
|
|
||||||
|
private fun width(char: Char): Int {
|
||||||
|
return characterWidthCache.computeIfAbsent(char, Char2IntFunction { font.width(it.toString()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun width(text: String, beginning: Int = 0, end: Int = text.length - 1): Int {
|
||||||
|
var accumulate = 0
|
||||||
|
|
||||||
|
for (i in beginning.coerceAtLeast(0) .. end.coerceAtMost(text.length - 1)) {
|
||||||
|
accumulate += width(text[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulate
|
||||||
|
}
|
||||||
|
|
||||||
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
||||||
if (!backgroundColor.isFullyTransparent)
|
if (!backgroundColor.isFullyTransparent)
|
||||||
drawRect(stack, 0f, 0f, width, height, backgroundColor)
|
drawRect(stack, 0f, 0f, width, height, backgroundColor)
|
||||||
|
|
||||||
|
if (multiLine) {
|
||||||
|
val heightInLines = ((height - dockPadding.top - dockPadding.bottom) / (font.lineHeight + rowSpacing)).toInt()
|
||||||
|
|
||||||
|
if (heightInLines > 0) {
|
||||||
|
if (cursorLine < scrollLines) {
|
||||||
|
scrollLines = (cursorLine - 1).coerceAtLeast(0)
|
||||||
|
} else if (heightInLines + scrollLines < cursorLine) {
|
||||||
|
scrollLines = (cursorLine - heightInLines).coerceIn(0, lines.size - 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollLines = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedLine = this[cursorLine]
|
||||||
|
|
||||||
|
if (selectedLine != null) {
|
||||||
|
val w = width(selectedLine, end = cursorRow) - scrollPixels
|
||||||
|
|
||||||
|
if (w < 0f) {
|
||||||
|
scrollPixels = (scrollPixels - 30f).coerceAtLeast(0f)
|
||||||
|
} else if (w >= width - dockPadding.right) {
|
||||||
|
scrollPixels += 30f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.pushPose()
|
||||||
|
stack.translate(-scrollPixels, 0f, 0f)
|
||||||
|
|
||||||
var y = dockPadding.top
|
var y = dockPadding.top
|
||||||
|
|
||||||
for ((i, line) in lines.withIndex()) {
|
for (i in scrollLines until lines.size) {
|
||||||
|
val line = lines[i]
|
||||||
val selection = selections[i]
|
val selection = selections[i]
|
||||||
|
|
||||||
font.drawAligned(
|
font.drawAligned(
|
||||||
@ -1046,7 +1105,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
x += font.width(before).toFloat()
|
x += font.width(before).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
val width = if (selection.coversNewline(line) && i != lines.size - 1) this.width - x else font.width(selected).toFloat()
|
val width = if (selection.coversNewline(line) && i != lines.size - 1) this.width - x + scrollPixels else font.width(selected).toFloat()
|
||||||
|
|
||||||
RenderSystem.setShader(GameRenderer::getPositionShader)
|
RenderSystem.setShader(GameRenderer::getPositionShader)
|
||||||
RenderSystem.setShaderColor(cursorColor.red, cursorColor.green, cursorColor.blue, 0.4f)
|
RenderSystem.setShaderColor(cursorColor.red, cursorColor.green, cursorColor.blue, 0.4f)
|
||||||
@ -1060,8 +1119,8 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
|
|
||||||
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
|
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
|
||||||
|
|
||||||
builder.vertex(stack.last().pose(), x, y + font.lineHeight + 2f, 0f).endVertex()
|
builder.vertex(stack.last().pose(), x, y + font.lineHeight + rowSpacing, 0f).endVertex()
|
||||||
builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + 2f, 0f).endVertex()
|
builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + rowSpacing, 0f).endVertex()
|
||||||
builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex()
|
builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex()
|
||||||
builder.vertex(stack.last().pose(), x, y, 0f).endVertex()
|
builder.vertex(stack.last().pose(), x, y, 0f).endVertex()
|
||||||
|
|
||||||
@ -1073,7 +1132,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
RenderSystem.enableDepthTest()
|
RenderSystem.enableDepthTest()
|
||||||
}
|
}
|
||||||
|
|
||||||
y += font.lineHeight + 2f
|
y += font.lineHeight + rowSpacing
|
||||||
|
|
||||||
if (y > height - dockPadding.bottom)
|
if (y > height - dockPadding.bottom)
|
||||||
break
|
break
|
||||||
@ -1089,7 +1148,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
text = "_",
|
text = "_",
|
||||||
align = TextAlign.TOP_LEFT,
|
align = TextAlign.TOP_LEFT,
|
||||||
x = dockPadding.left + (if (activeLine == null) 0f else font.width(activeLine).toFloat()),
|
x = dockPadding.left + (if (activeLine == null) 0f else font.width(activeLine).toFloat()),
|
||||||
y = dockPadding.top + cursorLine * (font.lineHeight + 2f),
|
y = dockPadding.top + (cursorLine - scrollLines) * (font.lineHeight + rowSpacing),
|
||||||
color = cursorColor
|
color = cursorColor
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -1099,12 +1158,14 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
text = "|",
|
text = "|",
|
||||||
align = TextAlign.TOP_LEFT,
|
align = TextAlign.TOP_LEFT,
|
||||||
x = dockPadding.left + font.width(activeLine.substring(0, cursorRow)).toFloat() - 1f,
|
x = dockPadding.left + font.width(activeLine.substring(0, cursorRow)).toFloat() - 1f,
|
||||||
y = dockPadding.top + cursorLine * (font.lineHeight + 2f),
|
y = dockPadding.top + (cursorLine - scrollLines) * (font.lineHeight + rowSpacing),
|
||||||
color = cursorColor
|
color = cursorColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stack.popPose()
|
||||||
|
|
||||||
if (debugDraw) {
|
if (debugDraw) {
|
||||||
font.drawAligned(
|
font.drawAligned(
|
||||||
poseStack = stack,
|
poseStack = stack,
|
||||||
@ -1122,7 +1183,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
text = cursorRow.toString(),
|
text = cursorRow.toString(),
|
||||||
align = TextAlign.TOP_RIGHT,
|
align = TextAlign.TOP_RIGHT,
|
||||||
x = width - dockPadding.right,
|
x = width - dockPadding.right,
|
||||||
y = dockPadding.top + font.lineHeight + 2f,
|
y = dockPadding.top + font.lineHeight + rowSpacing,
|
||||||
color = cursorColor
|
color = cursorColor
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1132,7 +1193,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
text = lines.size.toString(),
|
text = lines.size.toString(),
|
||||||
align = TextAlign.TOP_RIGHT,
|
align = TextAlign.TOP_RIGHT,
|
||||||
x = width - dockPadding.right,
|
x = width - dockPadding.right,
|
||||||
y = dockPadding.top + font.lineHeight * 2 + 4f,
|
y = dockPadding.top + font.lineHeight * 2f + rowSpacing * 2f,
|
||||||
color = cursorColor
|
color = cursorColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1181,19 +1242,20 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
if (x > width - dockPadding.right)
|
if (x > width - dockPadding.right)
|
||||||
x = width - dockPadding.right
|
x = width - dockPadding.right
|
||||||
|
|
||||||
val line = (y / (font.lineHeight + 2f)).toInt().coerceIn(0, lines.size - 1)
|
x += scrollPixels
|
||||||
|
|
||||||
|
val line = (scrollLines + (y / (font.lineHeight + rowSpacing)).toInt()).coerceIn(0, lines.size - 1)
|
||||||
val sLine = this[line] ?: return Vector2i(0, line)
|
val sLine = this[line] ?: return Vector2i(0, line)
|
||||||
|
|
||||||
if (x <= 0f)
|
if (x <= 0f)
|
||||||
return Vector2i(0, line)
|
return Vector2i(0, line)
|
||||||
else if (x >= font.width(sLine))
|
else if (x >= width(sLine))
|
||||||
return Vector2i(sLine.length, line)
|
return Vector2i(sLine.length, line)
|
||||||
|
|
||||||
val cache = Char2IntOpenHashMap()
|
|
||||||
var accumulatedWidth = 0f
|
var accumulatedWidth = 0f
|
||||||
|
|
||||||
for ((i, char) in sLine.withIndex()) {
|
for ((i, char) in sLine.withIndex()) {
|
||||||
val width = cache.computeIfAbsent(char, Char2IntFunction { font.width(it.toString()) })
|
val width = width(char)
|
||||||
|
|
||||||
if (x in accumulatedWidth .. accumulatedWidth + width) {
|
if (x in accumulatedWidth .. accumulatedWidth + width) {
|
||||||
if (x - accumulatedWidth < width / 2)
|
if (x - accumulatedWidth < width / 2)
|
||||||
|
Loading…
Reference in New Issue
Block a user