diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt index 99f9c457a..2fd29f08e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt @@ -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.math.RGBAColor import ru.dbotthepony.mc.otm.milliTime +import kotlin.math.roundToInt open class TextInputPanel( screen: S, @@ -158,6 +159,18 @@ open class TextInputPanel( private val undo = ArrayDeque() private val redo = ArrayDeque() + /** + * scroll in rows (up-down) + */ + private var scrollLines = 0 + + /** + * scroll in pixels (left-right) + */ + private var scrollPixels = 0f + + var rowSpacing = 2f + private fun triggerChangeCallback() { if (oldText != lines) { textCache = null @@ -1018,13 +1031,59 @@ open class TextInputPanel( 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) { if (!backgroundColor.isFullyTransparent) 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 - for ((i, line) in lines.withIndex()) { + for (i in scrollLines until lines.size) { + val line = lines[i] val selection = selections[i] font.drawAligned( @@ -1046,7 +1105,7 @@ open class TextInputPanel( 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.setShaderColor(cursorColor.red, cursorColor.green, cursorColor.blue, 0.4f) @@ -1060,8 +1119,8 @@ open class TextInputPanel( 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 + width, 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 + rowSpacing, 0f).endVertex() builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex() builder.vertex(stack.last().pose(), x, y, 0f).endVertex() @@ -1073,7 +1132,7 @@ open class TextInputPanel( RenderSystem.enableDepthTest() } - y += font.lineHeight + 2f + y += font.lineHeight + rowSpacing if (y > height - dockPadding.bottom) break @@ -1089,7 +1148,7 @@ open class TextInputPanel( text = "_", align = TextAlign.TOP_LEFT, 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 ) } else { @@ -1099,12 +1158,14 @@ open class TextInputPanel( text = "|", align = TextAlign.TOP_LEFT, 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 ) } } + stack.popPose() + if (debugDraw) { font.drawAligned( poseStack = stack, @@ -1122,7 +1183,7 @@ open class TextInputPanel( text = cursorRow.toString(), align = TextAlign.TOP_RIGHT, x = width - dockPadding.right, - y = dockPadding.top + font.lineHeight + 2f, + y = dockPadding.top + font.lineHeight + rowSpacing, color = cursorColor ) @@ -1132,7 +1193,7 @@ open class TextInputPanel( text = lines.size.toString(), align = TextAlign.TOP_RIGHT, x = width - dockPadding.right, - y = dockPadding.top + font.lineHeight * 2 + 4f, + y = dockPadding.top + font.lineHeight * 2f + rowSpacing * 2f, color = cursorColor ) } @@ -1181,19 +1242,20 @@ open class TextInputPanel( if (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) if (x <= 0f) return Vector2i(0, line) - else if (x >= font.width(sLine)) + else if (x >= width(sLine)) return Vector2i(sLine.length, line) - val cache = Char2IntOpenHashMap() var accumulatedWidth = 0f 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 - accumulatedWidth < width / 2)