Visual and audio feedback when trying to type stuff out of bounds
Add creative holo sign bounds
This commit is contained in:
parent
082f1478f5
commit
bc9a78c514
@ -30,7 +30,7 @@ class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryB
|
||||
override val redstoneControl = SynchronizedRedstoneControl(syncher) { _, _ -> setChanged() }
|
||||
|
||||
var signText by syncher.string("", setter = { access, value ->
|
||||
setChanged()
|
||||
markDirtyFast()
|
||||
access.accept(value)
|
||||
}).delegate
|
||||
|
||||
@ -96,24 +96,25 @@ class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryB
|
||||
|
||||
override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
|
||||
super.loadAdditional(nbt, registry)
|
||||
|
||||
if (!isLocked) {
|
||||
signText = truncate(signText)
|
||||
}
|
||||
signText = truncate(signText, isLocked)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_MAX_NEWLINES = 10
|
||||
const val DEFAULT_MAX_LINE_LENGTH = 60
|
||||
const val CREATIVE_MAX_NEWLINES = 60
|
||||
const val CREATIVE_MAX_LINE_LENGTH = 240
|
||||
private val NEWLINES = Regex("\r?\n")
|
||||
|
||||
fun truncate(input: String): String {
|
||||
fun truncate(input: String, isLocked: Boolean): String {
|
||||
val maxLines = if (isLocked) CREATIVE_MAX_NEWLINES else DEFAULT_MAX_NEWLINES
|
||||
val maxLength = if (isLocked) CREATIVE_MAX_LINE_LENGTH else DEFAULT_MAX_LINE_LENGTH
|
||||
val lines = input.split(NEWLINES)
|
||||
val result = ArrayList<String>(lines.size.coerceAtMost(DEFAULT_MAX_NEWLINES))
|
||||
val result = ArrayList<String>(lines.size.coerceAtMost(maxLines))
|
||||
|
||||
for (i in 0 until lines.size.coerceAtMost(DEFAULT_MAX_NEWLINES)) {
|
||||
if (lines[i].length > DEFAULT_MAX_LINE_LENGTH) {
|
||||
result.add(lines[i].substring(0, DEFAULT_MAX_LINE_LENGTH))
|
||||
for (i in 0 until lines.size.coerceAtMost(maxLines)) {
|
||||
if (lines[i].length > maxLength) {
|
||||
result.add(lines[i].substring(0, maxLength))
|
||||
} else {
|
||||
result.add(lines[i])
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.input.NetworkedStringInputPanel
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity
|
||||
import ru.dbotthepony.mc.otm.client.render.Widgets18
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.button.BooleanButtonPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel
|
||||
@ -17,7 +18,7 @@ import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu
|
||||
|
||||
class HoloSignScreen(menu: HoloSignMenu, inventory: Inventory, title: Component) : MatteryScreen<HoloSignMenu>(menu, title) {
|
||||
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
|
||||
val frame = FramePanel(this, null, 0f, 0f, 200f, 200f, getTitle())
|
||||
val frame = FramePanel(this, null, 0f, 0f, minecraft!!.window.guiScaledWidth * 0.8f, minecraft!!.window.guiScaledHeight * 0.8f, getTitle())
|
||||
|
||||
frame.makeCloseButton()
|
||||
frame.onClose { onClose() }
|
||||
@ -25,9 +26,19 @@ class HoloSignScreen(menu: HoloSignMenu, inventory: Inventory, title: Component)
|
||||
tooltips.add(TranslatableComponent("otm.gui.lock_holo_screen.tip"))
|
||||
}
|
||||
|
||||
val input = NetworkedStringInputPanel(this, frame, backend = menu.text)
|
||||
input.dock = Dock.FILL
|
||||
input.isMultiLine = true
|
||||
object : NetworkedStringInputPanel<HoloSignScreen>(this@HoloSignScreen, frame, backend = menu.text) {
|
||||
init {
|
||||
dock = Dock.FILL
|
||||
isMultiLine = true
|
||||
}
|
||||
|
||||
override var maxLineLength: Int
|
||||
get() = if (menu.locked.value) HoloSignBlockEntity.CREATIVE_MAX_LINE_LENGTH else HoloSignBlockEntity.DEFAULT_MAX_LINE_LENGTH
|
||||
set(value) {}
|
||||
override var maxLines: Int
|
||||
get() = if (menu.locked.value) HoloSignBlockEntity.CREATIVE_MAX_NEWLINES else HoloSignBlockEntity.DEFAULT_MAX_NEWLINES
|
||||
set(value) {}
|
||||
}
|
||||
|
||||
val controls = makeDeviceControls(this, frame, redstoneConfig = menu.redstone)
|
||||
|
||||
|
@ -616,7 +616,7 @@ open class ColorPickerPanel<out S : Screen>(
|
||||
}
|
||||
|
||||
override fun acceptsCharacter(codepoint: Char, mods: Int, index: Int): Boolean {
|
||||
return RGBAColor.isHexCharacter(codepoint)
|
||||
return super.acceptsCharacter(codepoint, mods, index) && RGBAColor.isHexCharacter(codepoint)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.client.render.vertex
|
||||
import ru.dbotthepony.mc.otm.milliTime
|
||||
import java.util.function.Predicate
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
open class TextInputPanel<out S : Screen>(
|
||||
@ -87,7 +88,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy
|
||||
private val cursorLine = this@TextInputPanel.cursorLine
|
||||
private val cursorCharacter = this@TextInputPanel.cursorRow
|
||||
private val selections = Int2ObjectAVLTreeMap(this@TextInputPanel.selections)
|
||||
private val selections = this@TextInputPanel.selections.clone()
|
||||
private val multiLine = this@TextInputPanel.isMultiLine
|
||||
|
||||
fun apply() {
|
||||
@ -155,7 +156,19 @@ open class TextInputPanel<out S : Screen>(
|
||||
open var isActive = true
|
||||
|
||||
override val cursorType: CursorType
|
||||
get() = if (isActive) CursorType.BEAM else CursorType.NOT_ALLOWED
|
||||
get() = CursorType.BEAM
|
||||
|
||||
open var maxLineLength = Int.MAX_VALUE
|
||||
set(value) {
|
||||
require(value in 1 .. Int.MAX_VALUE) { "Max line length value is out of bounds: $value" }
|
||||
field = value
|
||||
}
|
||||
|
||||
open var maxLines = Int.MAX_VALUE
|
||||
set(value) {
|
||||
require(value in 1 .. Int.MAX_VALUE) { "Max lines value is out of bounds: $value" }
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
scissor = true
|
||||
@ -463,8 +476,6 @@ open class TextInputPanel<out S : Screen>(
|
||||
lines.clear()
|
||||
redo.clear()
|
||||
undo.clear()
|
||||
cursorLine = 0
|
||||
cursorRow = 0
|
||||
textCache = null
|
||||
|
||||
if (isMultiLine) {
|
||||
@ -472,6 +483,9 @@ open class TextInputPanel<out S : Screen>(
|
||||
} else {
|
||||
lines.add(value.replace(NEWLINES, ""))
|
||||
}
|
||||
|
||||
cursorLine = minOf(cursorLine, lines.size - 1)
|
||||
cursorRow = minOf(this[cursorLine]?.length ?: 0, cursorRow)
|
||||
}
|
||||
|
||||
data class CursorAdvanceResult(
|
||||
@ -675,6 +689,12 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
if (key == InputConstants.KEY_RETURN) {
|
||||
if (isMultiLine) {
|
||||
if (lines.size >= maxLines) {
|
||||
playGuiClickSound()
|
||||
rejectCharacterTimer = milliTime + SHAKE_MILLIS
|
||||
return true
|
||||
}
|
||||
|
||||
if (!minecraft.window.isShiftDown && !minecraft.window.isCtrlDown)
|
||||
wipeSelection()
|
||||
|
||||
@ -729,26 +749,34 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_LEFT) {
|
||||
if (!minecraft.window.isShiftDown) {
|
||||
advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||
selections.clear()
|
||||
} else {
|
||||
if (minecraft.window.isShiftDown) {
|
||||
if (this[cursorLine] == null)
|
||||
return true
|
||||
|
||||
simulateSelectLeft(minecraft.window.isCtrlDown)
|
||||
} else if (selections.isNotEmpty()) {
|
||||
val (index, selection) = selections.firstEntry()
|
||||
cursorLine = index
|
||||
cursorRow = selection.cursor
|
||||
selections.clear()
|
||||
} else {
|
||||
advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_RIGHT) {
|
||||
if (!minecraft.window.isShiftDown) {
|
||||
advanceCursorRight(minecraft.window.isCtrlDown)
|
||||
selections.clear()
|
||||
} else {
|
||||
if (minecraft.window.isShiftDown) {
|
||||
if (this[cursorLine] == null)
|
||||
return true
|
||||
|
||||
simulateSelectRight(minecraft.window.isCtrlDown)
|
||||
} else if (selections.isNotEmpty()) {
|
||||
val (index, selection) = selections.lastEntry()
|
||||
cursorLine = index
|
||||
cursorRow = selection.cursor + selection.shift
|
||||
selections.clear()
|
||||
} else {
|
||||
advanceCursorRight(minecraft.window.isCtrlDown)
|
||||
}
|
||||
|
||||
return true
|
||||
@ -911,12 +939,31 @@ open class TextInputPanel<out S : Screen>(
|
||||
wipeSelection()
|
||||
pushbackSnapshot()
|
||||
|
||||
var shouldPlayClick = false
|
||||
|
||||
fun trimLine(input: String): String {
|
||||
if (input.length < maxLineLength) {
|
||||
return input
|
||||
} else {
|
||||
shouldPlayClick = true
|
||||
return input.substring(0, maxLineLength)
|
||||
}
|
||||
}
|
||||
|
||||
if (isMultiLine) {
|
||||
if (cursorLine >= maxLines) {
|
||||
playGuiClickSound()
|
||||
rejectCharacterTimer = milliTime + SHAKE_MILLIS
|
||||
return true
|
||||
}
|
||||
|
||||
var index = cursorRow + (0 until cursorLine).iterator().map { this[it]?.length ?: 0 }.reduce(0, Int::plus)
|
||||
val insert = minecraft.keyboardHandler.clipboard.replace("\t", " ").filter { acceptsCharacter(it, 0, index++) }.split(NEWLINES).toMutableList()
|
||||
val actualLastSize = insert.lastOrNull()?.length ?: 0
|
||||
val line = this[cursorLine]
|
||||
|
||||
insert[0] = trimLine(insert[0])
|
||||
|
||||
if (line == null) {
|
||||
insertLine(cursorLine, insert[0])
|
||||
cursorRow = insert[0].length
|
||||
@ -925,9 +972,9 @@ open class TextInputPanel<out S : Screen>(
|
||||
cursorRow = insert[0].length
|
||||
} else {
|
||||
if (insert.size == 1) {
|
||||
this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert[0] + line.substring(cursorRow.coerceAtMost(line.length))
|
||||
this[cursorLine] = trimLine(line.substring(0, cursorRow.coerceAtMost(line.length)) + insert[0] + line.substring(cursorRow.coerceAtMost(line.length)))
|
||||
} else {
|
||||
this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert[0]
|
||||
this[cursorLine] = trimLine(line.substring(0, cursorRow.coerceAtMost(line.length)) + insert[0])
|
||||
insert[insert.size - 1] += line.substring(cursorRow.coerceAtMost(line.length))
|
||||
}
|
||||
|
||||
@ -935,21 +982,31 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
|
||||
for (i in 1 until insert.size - 1) {
|
||||
if (cursorLine + 1 >= maxLines) {
|
||||
shouldPlayClick = true
|
||||
break
|
||||
}
|
||||
|
||||
insert[i] = trimLine(insert[i])
|
||||
insertLine(++cursorLine, insert[i])
|
||||
cursorRow = insert[i].length
|
||||
}
|
||||
|
||||
if (insert.size >= 2) {
|
||||
val line2 = this[++cursorLine]
|
||||
val last = insert.last()
|
||||
if (cursorLine + 1 < maxLines) {
|
||||
val line2 = this[++cursorLine]
|
||||
val last = insert.last()
|
||||
|
||||
if (line2 == null) {
|
||||
insertLine(cursorLine, last)
|
||||
if (line2 == null) {
|
||||
insertLine(cursorLine, trimLine(last))
|
||||
} else {
|
||||
this[cursorLine] = trimLine(last + line2)
|
||||
}
|
||||
|
||||
cursorRow = actualLastSize
|
||||
} else {
|
||||
this[cursorLine] = last + line2
|
||||
shouldPlayClick = true
|
||||
}
|
||||
|
||||
cursorRow = actualLastSize
|
||||
}
|
||||
} else {
|
||||
var index = cursorRow + (0 until cursorLine).iterator().map { this[it]?.length ?: 0 }.reduce(0, Int::plus)
|
||||
@ -957,17 +1014,26 @@ open class TextInputPanel<out S : Screen>(
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null) {
|
||||
insertLine(cursorLine, insert)
|
||||
cursorRow = insert.length
|
||||
val trim = trimLine(insert)
|
||||
insertLine(cursorLine, trim)
|
||||
cursorRow = trim.length
|
||||
} else if (line.isEmpty()) {
|
||||
this[cursorLine] = insert
|
||||
cursorRow = insert.length
|
||||
val trim = trimLine(insert)
|
||||
this[cursorLine] = trim
|
||||
cursorRow = trim.length
|
||||
} else {
|
||||
this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert + line.substring(cursorRow.coerceAtMost(line.length))
|
||||
cursorRow += insert.length
|
||||
val potential = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert + line.substring(cursorRow.coerceAtMost(line.length))
|
||||
val final = trimLine(potential)
|
||||
this[cursorLine] = final
|
||||
cursorRow += maxOf(0, insert.length - (potential.length - final.length))
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPlayClick) {
|
||||
playGuiClickSound()
|
||||
rejectCharacterTimer = milliTime + SHAKE_MILLIS
|
||||
}
|
||||
|
||||
pushbackSnapshot()
|
||||
triggerChangeCallback()
|
||||
return true
|
||||
@ -1078,6 +1144,11 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
if (!isMultiLine)
|
||||
cursorLine = 0
|
||||
else if (cursorLine >= maxLines) {
|
||||
playGuiClickSound()
|
||||
rejectCharacterTimer = milliTime + SHAKE_MILLIS
|
||||
return true
|
||||
}
|
||||
|
||||
var line = this[cursorLine]
|
||||
|
||||
@ -1091,7 +1162,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
for (i in 0 until cursorLine) index += this[i]?.length ?: 0
|
||||
index += cursorRow
|
||||
|
||||
if (!acceptsCharacter(codepoint, mods, index)) {
|
||||
if (cursorRow >= maxLineLength || !acceptsCharacter(codepoint, mods, index)) {
|
||||
playGuiClickSound()
|
||||
rejectCharacterTimer = milliTime + SHAKE_MILLIS
|
||||
return true
|
||||
@ -1195,6 +1266,22 @@ open class TextInputPanel<out S : Screen>(
|
||||
)
|
||||
}
|
||||
|
||||
if (maxLines < 100000) {
|
||||
val p = maxLines * (font.lineHeight + rowSpacing) + dockPaddingTop
|
||||
graphics.drawLine(0f, p, 1000000f, p, 2f, color = RGBAColor.GRAY)
|
||||
}
|
||||
|
||||
// TODO: Minecraft font is not mono spaced, so we can't properly render per-line character bounds
|
||||
if (selectedLine != null && abs(selectedLine.length - maxLineLength) < 200) {
|
||||
val lineWidth = if (selectedLine.length >= maxLineLength) {
|
||||
font.width(selectedLine.substring(0, maxLineLength)).toFloat()
|
||||
} else {
|
||||
font.width(selectedLine + "a".repeat(maxLineLength - selectedLine.length)).toFloat()
|
||||
}
|
||||
|
||||
graphics.drawLine(lineWidth + 4f, 0f, lineWidth + 4f, 1000000f, 2f, color = RGBAColor.GRAY)
|
||||
}
|
||||
|
||||
for (i in scrollLines until lines.size) {
|
||||
val line = lines[i]
|
||||
val selection = selections[i]
|
||||
@ -1303,9 +1390,6 @@ open class TextInputPanel<out S : Screen>(
|
||||
protected set
|
||||
|
||||
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
|
||||
if (!isActive)
|
||||
return true
|
||||
|
||||
if (isEverFocused())
|
||||
selections.clear()
|
||||
|
||||
@ -1321,7 +1405,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
isSelecting = true
|
||||
tryToGrabMouseInput()
|
||||
} else if (button == InputConstants.MOUSE_BUTTON_RIGHT && !isMultiLine) {
|
||||
} else if (button == InputConstants.MOUSE_BUTTON_RIGHT && !isMultiLine && isActive) {
|
||||
text = ""
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ class HoloSignMenu(
|
||||
textAutoScale.filter { it.isCreative || !locked.value }
|
||||
|
||||
if (tile != null) {
|
||||
text.withConsumer { if (tile.isLocked) tile.signText = it else tile.signText = HoloSignBlockEntity.truncate(it) }.withSupplier(tile::signText)
|
||||
text.withConsumer { tile.signText = HoloSignBlockEntity.truncate(it, tile.isLocked) }.withSupplier(tile::signText)
|
||||
textRed.withConsumer { tile.textRed = it.coerceIn(0f, 1f) }.withSupplier(tile::textRed)
|
||||
textGreen.withConsumer { tile.textGreen = it.coerceIn(0f, 1f) }.withSupplier(tile::textGreen)
|
||||
textBlue.withConsumer { tile.textBlue = it.coerceIn(0f, 1f) }.withSupplier(tile::textBlue)
|
||||
|
Loading…
Reference in New Issue
Block a user