From 909d4407f6567f693622ad44d903c175bf0c4603 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Sat, 7 Sep 2024 23:08:42 +1000 Subject: [PATCH] Added deselect focused entry on escape Added deselect focused entry on click on nothing Added ability to move player around while holding a configurable defer key --- .../client/gui/ArmorGlowScreen.java | 2 +- .../client/gui/ArmorPosesScreen.java | 2 +- .../client/gui/ArmorStandScreen.java | 2 +- .../client/gui/DeletePoseScreen.java | 2 +- .../armorposer/client/gui/MoveableScreen.java | 123 ++++++++++++++++++ .../armorposer/client/gui/SavePoseScreen.java | 2 +- .../armorposer/mixin/KeyMappingAccessor.java | 12 ++ .../armorposer/mixin/MouseHandleAccessor.java | 11 ++ .../platform/services/IPlatformHelper.java | 7 + .../assets/armorposer/lang/en_us.json | 4 +- .../mrbysco/armorposer/ArmorPoserClient.java | 5 + .../platform/FabricPlatformHelper.java | 7 + .../resources/armorposer.fabric.mixins.json | 4 +- .../com/mrbysco/armorposer/ArmorPoser.java | 15 +++ .../platform/NeoForgePlatformHelper.java | 8 ++ .../resources/armorposer.neoforge.mixins.json | 4 +- 16 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/com/mrbysco/armorposer/client/gui/MoveableScreen.java create mode 100644 common/src/main/java/com/mrbysco/armorposer/mixin/KeyMappingAccessor.java create mode 100644 common/src/main/java/com/mrbysco/armorposer/mixin/MouseHandleAccessor.java diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorGlowScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorGlowScreen.java index 72687ae..2523577 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorGlowScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorGlowScreen.java @@ -19,7 +19,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -public class ArmorGlowScreen extends Screen { +public class ArmorGlowScreen extends MoveableScreen { private static final int PADDING = 6; private ArmorGlowWidget armorListWidget; diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorPosesScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorPosesScreen.java index a604d61..9de0f02 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorPosesScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorPosesScreen.java @@ -21,7 +21,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -public class ArmorPosesScreen extends Screen { +public class ArmorPosesScreen extends MoveableScreen { private enum SortType { NORMAL, A_TO_Z, diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java index 35aa7c0..09360f5 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java @@ -33,7 +33,7 @@ import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.phys.Vec3; -public class ArmorStandScreen extends Screen { +public class ArmorStandScreen extends MoveableScreen { private static final WidgetSprites MIRROR_POSE_SPRITES = new WidgetSprites( ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "widget/mirror_pose"), ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "widget/mirror_pose_highlighted") ); diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/DeletePoseScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/DeletePoseScreen.java index c077432..56b3742 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/DeletePoseScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/DeletePoseScreen.java @@ -8,7 +8,7 @@ import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -public class DeletePoseScreen extends Screen { +public class DeletePoseScreen extends MoveableScreen { private final ArmorStandScreen parentScreen; private Button deleteButton; private final PoseListWidget.ListEntry entry; diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/MoveableScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/MoveableScreen.java new file mode 100644 index 0000000..3ae091d --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/MoveableScreen.java @@ -0,0 +1,123 @@ +package com.mrbysco.armorposer.client.gui; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mrbysco.armorposer.mixin.KeyMappingAccessor; +import com.mrbysco.armorposer.mixin.MouseHandleAccessor; +import com.mrbysco.armorposer.platform.Services; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +public abstract class MoveableScreen extends Screen { + private static final KeyMapping DEFER_CONTROL_KEY = Services.PLATFORM.registerKeyMapping(new KeyMapping("armorposer.keybind.deferControl", GLFW.GLFW_KEY_LEFT_ALT, "armorposer.keybinds")); + + private double mouseX; + private double mouseY; + private boolean isPressDown = false; + + private boolean wasDeferred = false; + protected MoveableScreen(Component title) { + super(title); + } + + @Override + public void afterKeyboardAction() { + super.afterKeyboardAction(); + this.isPressDown = true; + } + + @Override + public void removed() { + this.tickRelease(); + super.removed(); + } + + @Override + public boolean keyPressed(int key, int scanCode, int modifiers) { + boolean isPressDown = this.isPressDown; + this.isPressDown = false; + + //If a child is selected, just escape the text box when pressing escape + if (key == GLFW.GLFW_KEY_ESCAPE) { + for (var child : this.children()) { + if (child.isFocused()) { + child.setFocused(false); + return true; + } + } + } + + boolean consumed = super.keyPressed(key, scanCode, modifiers); + if (consumed) + return true; + + this.updateKeybind(); + if (DEFER_CONTROL_KEY.isDown()) { + //Else consume it ourselves + if (!this.wasDeferred) { + var mouseHandle = this.minecraft.mouseHandler; + this.wasDeferred = true; + this.mouseX = mouseHandle.xpos(); + this.mouseY = mouseHandle.ypos(); + mouseHandle.setIgnoreFirstMove(); + ((MouseHandleAccessor)mouseHandle).setMouseGrabbed(true); + InputConstants.grabOrReleaseMouse(this.minecraft.getWindow().getWindow(), 212995, ((double) this.minecraft.getWindow().getScreenWidth() / 2), ((double) this.minecraft.getWindow().getScreenHeight() / 2)); + } + + var keyEntry = InputConstants.getKey(key, scanCode); + if (isPressDown) { + KeyMapping.set(keyEntry, true); + KeyMapping.click(keyEntry); + } else { + KeyMapping.set(keyEntry, false); + } + return true; + } + return false; + } + + private void tickRelease() { + if (this.wasDeferred) { + this.wasDeferred = false; + this.minecraft.mouseHandler.setIgnoreFirstMove(); + ((MouseHandleAccessor)this.minecraft.mouseHandler).setMouseGrabbed(false); + InputConstants.grabOrReleaseMouse(this.minecraft.getWindow().getWindow(), 212993, this.mouseX, this.mouseY); + InputConstants.grabOrReleaseMouse(this.minecraft.getWindow().getWindow(), 212993, this.mouseX, this.mouseY); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + //Deselect focused entry if clicked and nothing hit + if (super.mouseClicked(mouseX, mouseY, button)) { + return true; + } + for (var child : this.children()) { + if (child.isFocused()) { + child.setFocused(false); + return true; + } + } + return false; + } + + @Override + public boolean keyReleased(int key, int scanCode, int modifiers) { + this.updateKeybind(); + if (!DEFER_CONTROL_KEY.isDown()) { + this.tickRelease(); + } + return super.keyReleased(key, scanCode, modifiers); + } + + private void updateKeybind() { + var key = ((KeyMappingAccessor)DEFER_CONTROL_KEY).getKey(); + if (key.getType() == InputConstants.Type.KEYSYM && key.getValue() != InputConstants.UNKNOWN.getValue()) { + DEFER_CONTROL_KEY.setDown(InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), key.getValue())); + } + } + + public static void earlyInit() {} +} diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/SavePoseScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/SavePoseScreen.java index aab2b86..b792f0b 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/SavePoseScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/SavePoseScreen.java @@ -9,7 +9,7 @@ import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -public class SavePoseScreen extends Screen { +public class SavePoseScreen extends MoveableScreen { private final ArmorStandScreen parentScreen; private Button saveButton; private EditBox nameField; diff --git a/common/src/main/java/com/mrbysco/armorposer/mixin/KeyMappingAccessor.java b/common/src/main/java/com/mrbysco/armorposer/mixin/KeyMappingAccessor.java new file mode 100644 index 0000000..65de8ef --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/mixin/KeyMappingAccessor.java @@ -0,0 +1,12 @@ +package com.mrbysco.armorposer.mixin; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(KeyMapping.class) +public interface KeyMappingAccessor { + @Accessor + InputConstants.Key getKey(); +} diff --git a/common/src/main/java/com/mrbysco/armorposer/mixin/MouseHandleAccessor.java b/common/src/main/java/com/mrbysco/armorposer/mixin/MouseHandleAccessor.java new file mode 100644 index 0000000..9b8fa99 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/mixin/MouseHandleAccessor.java @@ -0,0 +1,11 @@ +package com.mrbysco.armorposer.mixin; + +import net.minecraft.client.MouseHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MouseHandler.class) +public interface MouseHandleAccessor { + @Accessor + void setMouseGrabbed(boolean value); +} diff --git a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java index 87e4ac8..710acfa 100644 --- a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java +++ b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java @@ -1,6 +1,7 @@ package com.mrbysco.armorposer.platform.services; import com.mrbysco.armorposer.data.SwapData; +import net.minecraft.client.KeyMapping; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.decoration.ArmorStand; @@ -46,4 +47,10 @@ public interface IPlatformHelper { * @return The mod version */ String getModVersion(); + + /** + * Register a keyboard binding + * @return bound KeyMapping + */ + KeyMapping registerKeyMapping(KeyMapping mapping); } diff --git a/common/src/main/resources/assets/armorposer/lang/en_us.json b/common/src/main/resources/assets/armorposer/lang/en_us.json index d32b424..0d75f36 100644 --- a/common/src/main/resources/assets/armorposer/lang/en_us.json +++ b/common/src/main/resources/assets/armorposer/lang/en_us.json @@ -115,5 +115,7 @@ "text.autoconfig.armorposer.option.general.resizeWhitelist.@Tooltip": "List of players that are allowed to resize the Armor Stand when restrictResizeToOP is enabled", "text.autoconfig.armorposer.option.general.restrictResizeToOP": "Restrict Resize To OP", "text.autoconfig.armorposer.option.general.restrictResizeToOP.@Tooltip": "Restrict the ability to resize the Armor Stand to server operators", - "text.autoconfig.armorposer.title": "Armor Poser" + "text.autoconfig.armorposer.title": "Armor Poser", + "armorposer.keybind.deferControl": "Defer Control", + "armorposer.keybinds": "Armor Poser" } \ No newline at end of file diff --git a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java index de4b101..99cb1e0 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java @@ -1,5 +1,6 @@ package com.mrbysco.armorposer; +import com.mrbysco.armorposer.client.gui.MoveableScreen; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; @@ -8,9 +9,13 @@ import net.minecraft.world.entity.decoration.ArmorStand; public class ArmorPoserClient implements ClientModInitializer { + static { + MoveableScreen.earlyInit(); + } @Override public void onInitializeClient() { + ClientPlayNetworking.registerGlobalReceiver(ArmorStandScreenPayload.ID, (payload, context) -> { int entityID = payload.entityID(); diff --git a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java index 8122362..cd2d291 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java @@ -8,8 +8,10 @@ import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import com.mrbysco.armorposer.platform.services.IPlatformHelper; import me.shedaniel.autoconfig.AutoConfig; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.KeyMapping; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.decoration.ArmorStand; @@ -60,4 +62,9 @@ public List getResizeWhitelist() { public String getModVersion() { return FabricLoader.getInstance().getModContainer(Reference.MOD_ID).orElseThrow().getMetadata().getVersion().getFriendlyString(); } + + @Override + public KeyMapping registerKeyMapping(KeyMapping mapping) { + return KeyBindingHelper.registerKeyBinding(mapping); + } } diff --git a/fabric/src/main/resources/armorposer.fabric.mixins.json b/fabric/src/main/resources/armorposer.fabric.mixins.json index 9fd9884..2ff7b0e 100644 --- a/fabric/src/main/resources/armorposer.fabric.mixins.json +++ b/fabric/src/main/resources/armorposer.fabric.mixins.json @@ -8,7 +8,9 @@ "ArmorStandMixin" ], "client": [ - "MinecraftMixin" + "MinecraftMixin", + "MouseHandleAccessor", + "KeyMappingAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java index 6288196..f2fa879 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,23 +1,30 @@ package com.mrbysco.armorposer; +import com.mrbysco.armorposer.client.gui.MoveableScreen; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import com.mrbysco.armorposer.packets.handler.ClientPayloadHandler; import com.mrbysco.armorposer.packets.handler.ServerPayloadHandler; +import net.minecraft.client.KeyMapping; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfig.Type; +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import java.util.ArrayList; +import java.util.List; + @Mod(Reference.MOD_ID) public class ArmorPoser { + public static final List KEY_MAPPINGS = new ArrayList<>(); public ArmorPoser(IEventBus eventBus, ModContainer container, Dist dist) { container.registerConfig(Type.COMMON, PoserConfig.commonSpec); @@ -27,6 +34,9 @@ public ArmorPoser(IEventBus eventBus, ModContainer container, Dist dist) { if (dist.isClient()) { container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new); + + MoveableScreen.earlyInit(); + eventBus.addListener(this::setupKeyMappings); } } @@ -36,4 +46,9 @@ private void setupPackets(final RegisterPayloadHandlersEvent event) { registrar.playToServer(ArmorStandSwapPayload.ID, ArmorStandSwapPayload.CODEC, ServerPayloadHandler.getInstance()::handleSwapData); registrar.playToServer(ArmorStandSyncPayload.ID, ArmorStandSyncPayload.CODEC, ServerPayloadHandler.getInstance()::handleSyncData); } + + private void setupKeyMappings(RegisterKeyMappingsEvent event) { + KEY_MAPPINGS.forEach(event::register); + KEY_MAPPINGS.clear(); + } } \ No newline at end of file diff --git a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java index 0aab0a7..9b708d6 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java +++ b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java @@ -1,5 +1,6 @@ package com.mrbysco.armorposer.platform; +import com.mrbysco.armorposer.ArmorPoser; import com.mrbysco.armorposer.Reference; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.data.SwapData; @@ -7,6 +8,7 @@ import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import com.mrbysco.armorposer.platform.services.IPlatformHelper; +import net.minecraft.client.KeyMapping; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.decoration.ArmorStand; import net.neoforged.fml.ModList; @@ -55,4 +57,10 @@ public List getResizeWhitelist() { public String getModVersion() { return ModList.get().getModFileById(Reference.MOD_ID).versionString(); } + + @Override + public KeyMapping registerKeyMapping(KeyMapping mapping) { + ArmorPoser.KEY_MAPPINGS.add(mapping); + return mapping; + } } diff --git a/forge/src/main/resources/armorposer.neoforge.mixins.json b/forge/src/main/resources/armorposer.neoforge.mixins.json index 27b75e5..b2c3f97 100644 --- a/forge/src/main/resources/armorposer.neoforge.mixins.json +++ b/forge/src/main/resources/armorposer.neoforge.mixins.json @@ -6,7 +6,9 @@ "mixins": [ ], "client": [ - "MinecraftMixin" + "MinecraftMixin", + "MouseHandleAccessor", + "KeyMappingAccessor" ], "injectors": { "defaultRequire": 1