diff options
author | Linnea Gräf <nea@nea.moe> | 2024-11-18 20:53:20 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-11-18 20:53:20 +0100 |
commit | 139fa705b36b845759cff9318191ff1cca526f2a (patch) | |
tree | a8e297627a078ac97abe03f0e56ea7e3744db042 /src/main | |
parent | d37d13dacad45af717e1f97488e739ef617fd89d (diff) | |
download | Firmament-139fa705b36b845759cff9318191ff1cca526f2a.tar.gz Firmament-139fa705b36b845759cff9318191ff1cca526f2a.tar.bz2 Firmament-139fa705b36b845759cff9318191ff1cca526f2a.zip |
feat: Add Storage overlay search
Diffstat (limited to 'src/main')
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 + } } |