aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/moe/nea/firmament/init/HandledScreenRiser.java117
-rw-r--r--src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java4
-rw-r--r--src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java21
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt12
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt120
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt13
-rw-r--r--src/main/kotlin/util/StringUtil.kt9
-rw-r--r--src/main/kotlin/util/customgui/CustomGui.kt12
8 files changed, 236 insertions, 72 deletions
diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
index 1fbbe45..355a666 100644
--- a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
+++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
@@ -32,12 +32,18 @@ public class HandledScreenRiser extends RiserUtils {
String keyReleased = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
Intermediary.methodName(Element::keyReleased),
keyReleasedDesc.getDescriptor());
+ // public boolean charTyped(char chr, int modifiers)
+ Type charTypedDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.INT_TYPE);
+ String charTyped = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
+ Intermediary.methodName(Element::charTyped),
+ charTypedDesc.getDescriptor());
@Override
public void addTinkerers() {
ClassTinkerers.addTransformation(HandledScreen, this::addMouseScroll, true);
ClassTinkerers.addTransformation(HandledScreen, this::addKeyReleased, true);
+ ClassTinkerers.addTransformation(HandledScreen, this::addCharTyped, true);
}
/**
@@ -70,86 +76,77 @@ public class HandledScreenRiser extends RiserUtils {
}
void addKeyReleased(ClassNode classNode) {
- var keyReleasedNode = findMethod(classNode, keyReleased, keyReleasedDesc);
+ addSuperInjector(
+ classNode, keyReleased, keyReleasedDesc, "keyReleased_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // ILOAD 1-3, load args
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
+ });
+ }
+
+ void addCharTyped(ClassNode classNode) {
+ addSuperInjector(
+ classNode, charTyped, charTypedDesc, "charTyped_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // ILOAD 1-2, load args. chars = ints
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
+ });
+ }
+
+ void addSuperInjector(
+ ClassNode classNode,
+ String name,
+ Type desc,
+ String firmamentName,
+ Consumer<InsnList> loadArgs
+ ) {
+ var keyReleasedNode = findMethod(classNode, name, desc);
if (keyReleasedNode == null) {
keyReleasedNode = new MethodNode(
Modifier.PUBLIC,
- keyReleased,
- keyReleasedDesc.getDescriptor(),
+ name,
+ desc.getDescriptor(),
null,
new String[0]
);
var insns = keyReleasedNode.instructions;
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // ILOAD 1-3, load args
- insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
+ loadArgs.accept(insns);
// INVOKESPECIAL call super method
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(),
- keyReleased, keyReleasedDesc.getDescriptor()));
+ name, desc.getDescriptor()));
// IRETURN return int on stack (booleans are int at runtime)
insns.add(new InsnNode(Opcodes.IRETURN));
classNode.methods.add(keyReleasedNode);
}
- insertTrueHandler(keyReleasedNode, insns -> {
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // ILOAD 1-3, load args
- insns.add(new VarInsnNode(Opcodes.ILOAD, 1));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
- insns.add(new VarInsnNode(Opcodes.ILOAD, 3));
- }, insns -> {
+ insertTrueHandler(keyReleasedNode, loadArgs, insns -> {
// INVOKEVIRTUAL call custom handler
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
getTypeForClassName(HandledScreen).getInternalName(),
- "keyReleased_firmament",
- keyReleasedDesc.getDescriptor()));
+ firmamentName,
+ desc.getDescriptor()));
});
+
}
void addMouseScroll(ClassNode classNode) {
- MethodNode mouseScrolledNode = findMethod(classNode, mouseScrolled, mouseScrolledDesc);
- if (mouseScrolledNode == null) {
- mouseScrolledNode = new MethodNode(
- Modifier.PUBLIC,
- mouseScrolled,
- mouseScrolledDesc.getDescriptor(),
- null,
- new String[0]
- );
- var insns = mouseScrolledNode.instructions;
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
- insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
- // INVOKESPECIAL call super method
- insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(), mouseScrolled, mouseScrolledDesc.getDescriptor()));
- // IRETURN return int on stack (booleans are int at runtime)
- insns.add(new InsnNode(Opcodes.IRETURN));
- classNode.methods.add(mouseScrolledNode);
- }
-
- insertTrueHandler(mouseScrolledNode, insns -> {
- // ALOAD 0, load this
- insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
- // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
- insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
- insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
- }, insns -> {
- // INVOKEVIRTUAL call custom handler
- insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
- getTypeForClassName(HandledScreen).getInternalName(),
- "mouseScrolled_firmament",
- mouseScrolledDesc.getDescriptor()));
-
- });
+ addSuperInjector(
+ classNode, mouseScrolled, mouseScrolledDesc, "mouseScrolled_firmament",
+ insns -> {
+ // ALOAD 0, load this
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
+ // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
+ insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
+ });
}
}
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
index 82f8f5d..e607ba3 100644
--- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
@@ -62,10 +62,6 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
}
}
- boolean keyReleased_firmament(int keyCode, int scanCode, int modifiers) {
- return HandledScreenKeyReleasedEvent.Companion.publish(new HandledScreenKeyReleasedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled();
- }
-
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/gui/DrawContext;II)V", shift = At.Shift.AFTER))
public void onAfterRenderForeground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
context.getMatrices().push();
diff --git a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
index 814f172..6e1090a 100644
--- a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
@@ -5,6 +5,7 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.events.HandledScreenKeyReleasedEvent;
import moe.nea.firmament.util.customgui.CoordRememberingSlot;
import moe.nea.firmament.util.customgui.CustomGui;
import moe.nea.firmament.util.customgui.HasCustomGui;
@@ -73,6 +74,16 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
return override != null && override.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
}
+ public boolean keyReleased_firmament(int keyCode, int scanCode, int modifiers) {
+ if (HandledScreenKeyReleasedEvent.Companion.publish(new HandledScreenKeyReleasedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled())
+ return true;
+ return override != null && override.keyReleased(keyCode, scanCode, modifiers);
+ }
+
+ public boolean charTyped_firmament(char chr, int modifiers) {
+ return override != null && override.charTyped(chr, modifiers);
+ }
+
@Inject(method = "init", at = @At("TAIL"))
private void onInit(CallbackInfo ci) {
if (override != null) {
@@ -179,6 +190,16 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
}
}
+ @Inject(method = "keyPressed", at = @At("HEAD"), cancellable = true)
+ private void overrideKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) {
+ if (override != null) {
+ if (override.keyPressed(keyCode, scanCode, modifiers)) {
+ cir.setReturnValue(true);
+ }
+ }
+ }
+
+
@Inject(
method = "mouseReleased",
at = @At("HEAD"), cancellable = true)
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
index 2be798b..6092e26 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
@@ -66,6 +66,18 @@ class StorageOverlayCustom(
return overview.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
+ override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return overview.keyReleased(keyCode, scanCode, modifiers)
+ }
+
+ override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return overview.keyPressed(keyCode, scanCode, modifiers)
+ }
+
+ override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ return overview.charTyped(chr, modifiers)
+ }
+
override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean {
return overview.mouseClicked(mouseX, mouseY, button, (handler as? StorageBackingHandle.Page)?.storagePageSlot)
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
index 4c624ef..cf1cf1d 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
@@ -1,15 +1,22 @@
package moe.nea.firmament.features.inventory.storageoverlay
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
import io.github.notenoughupdates.moulconfig.gui.GuiContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
import io.github.notenoughupdates.moulconfig.gui.MouseEvent
import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent
import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.observer.Property
+import java.util.TreeSet
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot
import net.minecraft.text.Text
import net.minecraft.util.Identifier
@@ -20,10 +27,16 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MoulConfigUtils.adopt
import moe.nea.firmament.util.MoulConfigUtils.clickMCComponentInPlace
import moe.nea.firmament.util.MoulConfigUtils.drawMCComponentInPlace
+import moe.nea.firmament.util.MoulConfigUtils.typeMCComponentInPlace
+import moe.nea.firmament.util.StringUtil.words
import moe.nea.firmament.util.assertTrueOr
import moe.nea.firmament.util.customgui.customGui
import moe.nea.firmament.util.mc.FakeSlot
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.drawGuiTexture
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.unformattedString
class StorageOverlayScreen : Screen(Text.literal("")) {
@@ -83,10 +96,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
horizontalAmount: Double,
verticalAmount: Double
): Boolean {
- scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat()
+ coerceScroll(StorageOverlay.adjustScrollSpeed(verticalAmount).toFloat())
+ return true
+ }
+ fun coerceScroll(offset: Float) {
+ scroll = (scroll + offset)
.coerceAtMost(getMaxScroll())
.coerceAtLeast(0F)
- return true
}
fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height
@@ -142,12 +158,26 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val guiContext = GuiContext(EmptyComponent())
private val knobStub = EmptyComponent()
- val editButton = PanelComponent(ColumnComponent(FirmButtonComponent(TextComponent("Edit"), action = ::editPages)),
- 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT)
+ val editButton = FirmButtonComponent(TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string), action = ::editPages)
+ val searchText = Property.of("") // TODO: sync with REI
+ val searchField = TextFieldComponent(searchText, 100, GetSetter.constant(true),
+ tr("firmament.storage-overlay.search.suggestion", "Search...").string,
+ IMinecraft.instance.defaultFontRenderer)
+ val controlComponent = PanelComponent(
+ ColumnComponent(
+ searchField,
+ editButton,
+ ),
+ 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT
+ )
init {
- guiContext.adopt(editButton)
+ searchText.addObserver { _, _ ->
+ layoutedForEach(StorageOverlay.Data.data ?: StorageData(), { _, _, _ -> })
+ coerceScroll(0F)
+ }
guiContext.adopt(knobStub)
+ guiContext.adopt(controlComponent)
}
fun drawControls(context: DrawContext, mouseX: Int, mouseY: Int) {
@@ -157,7 +187,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
measurements.controlY,
CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT)
context.drawMCComponentInPlace(
- editButton,
+ controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX, mouseY)
@@ -251,7 +281,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = false
return true
}
- if (clickMCComponentInPlace(editButton,
+ if (clickMCComponentInPlace(controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX.toInt(), mouseY.toInt(),
@@ -290,7 +320,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = true
return true
}
- if (clickMCComponentInPlace(editButton,
+ if (clickMCComponentInPlace(controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
mouseX.toInt(), mouseY.toInt(),
@@ -299,6 +329,78 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
return false
}
+ override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.CharTyped(chr)
+ )
+ ) {
+ return true
+ }
+ return super.charTyped(chr, modifiers)
+ }
+
+ override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.KeyPressed(keyCode, false)
+ )
+ ) {
+ return true
+ }
+ return super.keyReleased(keyCode, scanCode, modifiers)
+ }
+
+ override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ if (typeMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ KeyboardEvent.KeyPressed(keyCode, true)
+ )
+ ) {
+ return true
+ }
+ return super.keyPressed(keyCode, scanCode, modifiers)
+ }
+
+
+ var searchCache: String? = null
+ var filteredPagesCache = setOf<StoragePageSlot>()
+
+ fun getFilteredPages(): Set<StoragePageSlot> {
+ val searchValue = searchText.get()
+ val data = StorageOverlay.Data.data ?: return filteredPagesCache // Do not update cache if data is missing
+ if (searchCache == searchValue) return filteredPagesCache
+ val result =
+ data.storageInventories
+ .entries.asSequence()
+ .filter { it.value.inventory?.stacks?.any { matchesSearch(it, searchValue) } ?: true }
+ .map { it.key }
+ .toSet()
+ searchCache = searchValue
+ filteredPagesCache = result
+ return result
+ }
+
+
+ fun matchesSearch(itemStack: ItemStack, search: String): Boolean {
+ val searchWords = search.words().toCollection(TreeSet())
+ fun removePrefixes(value: String) {
+ searchWords.removeIf { value.contains(it, ignoreCase = true) }
+ }
+ itemStack.displayNameAccordingToNbt.unformattedString.words().forEach(::removePrefixes)
+ if (searchWords.isEmpty()) return true
+ itemStack.loreAccordingToNbt.forEach {
+ it.unformattedString.words().forEach(::removePrefixes)
+ }
+ return searchWords.isEmpty()
+ }
+
private inline fun layoutedForEach(
data: StorageData,
func: (
@@ -309,7 +411,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
var yOffset = -scroll.toInt()
var xOffset = 0
var maxHeight = 0
+ val filter = getFilteredPages()
for ((page, inventory) in data.storageInventories.entries) {
+ if (page !in filter) continue
val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight }
?: 18
maxHeight = maxOf(maxHeight, currentHeight)
diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt
index 2e52092..62bf3dd 100644
--- a/src/main/kotlin/util/MoulConfigUtils.kt
+++ b/src/main/kotlin/util/MoulConfigUtils.kt
@@ -7,6 +7,7 @@ import io.github.notenoughupdates.moulconfig.gui.GuiComponent
import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext
import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
import io.github.notenoughupdates.moulconfig.gui.MouseEvent
import io.github.notenoughupdates.moulconfig.observer.GetSetter
import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
@@ -247,6 +248,18 @@ object MoulConfigUtils {
}
}
+ fun typeMCComponentInPlace(
+ component: GuiComponent,
+ x: Int,
+ y: Int,
+ w: Int,
+ h: Int,
+ keyboardEvent: KeyboardEvent
+ ): Boolean {
+ val immContext = createInPlaceFullContext(null, IMinecraft.instance.mouseX, IMinecraft.instance.mouseY)
+ return component.keyboardEvent(keyboardEvent, immContext.translated(x, y, w, h))
+ }
+
fun clickMCComponentInPlace(
component: GuiComponent,
x: Int,
diff --git a/src/main/kotlin/util/StringUtil.kt b/src/main/kotlin/util/StringUtil.kt
index f080d5a..68e161a 100644
--- a/src/main/kotlin/util/StringUtil.kt
+++ b/src/main/kotlin/util/StringUtil.kt
@@ -10,4 +10,13 @@ object StringUtil {
}
fun Iterable<String>.unwords() = joinToString(" ")
+ fun nextLexicographicStringOfSameLength(string: String): String {
+ val next = StringBuilder(string)
+ while (next.lastOrNull() == Character.MAX_VALUE) next.setLength(next.length - 1)
+ if (next.isEmpty()) return "" // There is no upper bound. Fall back to the empty string
+ val lastIdx = next.indices.last
+ next[lastIdx] = (next[lastIdx] + 1)
+ return next.toString()
+ }
+
}
diff --git a/src/main/kotlin/util/customgui/CustomGui.kt b/src/main/kotlin/util/customgui/CustomGui.kt
index 5224448..35c60ac 100644
--- a/src/main/kotlin/util/customgui/CustomGui.kt
+++ b/src/main/kotlin/util/customgui/CustomGui.kt
@@ -76,4 +76,16 @@ abstract class CustomGui {
open fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
return false
}
+
+ open fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return false
+ }
+
+ open fun charTyped(chr: Char, modifiers: Int): Boolean {
+ return false
+ }
+
+ open fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return false
+ }
}