diff --git a/common/src/main/java/com/mrbysco/armorposer/client/KeybindHandler.java b/common/src/main/java/com/mrbysco/armorposer/client/KeybindHandler.java new file mode 100644 index 0000000..c8815a2 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/client/KeybindHandler.java @@ -0,0 +1,14 @@ +package com.mrbysco.armorposer.client; + +import com.mrbysco.armorposer.platform.Services; +import net.minecraft.client.KeyMapping; +import org.lwjgl.glfw.GLFW; + +public class KeybindHandler { + public static final KeyMapping DEFER_CONTROL_KEY = Services.PLATFORM.registerKeyMapping( + new KeyMapping("armorposer.keybind.deferControl", GLFW.GLFW_KEY_LEFT_ALT, "armorposer.keybinds")); + + public static void loadClass() { + } + +} 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..780ed4a --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/MoveableScreen.java @@ -0,0 +1,128 @@ +package com.mrbysco.armorposer.client.gui; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mrbysco.armorposer.client.KeybindHandler; +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 double mouseX; + private double mouseY; + private boolean isPressDown = false; + + private boolean wasDeferred = false; + protected MoveableScreen(Component title) { + super(title); + Services.PLATFORM.onMoveableScreen(false); + } + + @Override + public void afterKeyboardAction() { + super.afterKeyboardAction(); + this.isPressDown = true; + } + + @Override + public void removed() { + this.tickRelease(); + super.removed(); + } + + @Override + public void onClose() { + Services.PLATFORM.onMoveableScreen(true); + super.onClose(); + } + + @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 (KeybindHandler.DEFER_CONTROL_KEY.isDown()) { + //Center the mouse so it doesn't cause button tooltips to show + //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).armorposer$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).armorposer$setMouseGrabbed(false); + 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 (!KeybindHandler.DEFER_CONTROL_KEY.isDown()) { + this.tickRelease(); + } + return super.keyReleased(key, scanCode, modifiers); + } + + private void updateKeybind() { + var key = ((KeyMappingAccessor)KeybindHandler.DEFER_CONTROL_KEY).armorposer$getKey(); + if (key.getType() == InputConstants.Type.KEYSYM && key.getValue() != InputConstants.UNKNOWN.getValue()) { + KeybindHandler.DEFER_CONTROL_KEY.setDown(InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), key.getValue())); + } + } +} 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..0c415de --- /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("key") + InputConstants.Key armorposer$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..e4cebfb --- /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("mouseGrabbed") + void armorposer$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..62bed8f 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,15 @@ public interface IPlatformHelper { * @return The mod version */ String getModVersion(); + + /** + * Register a keyboard binding + * @return bound KeyMapping + */ + KeyMapping registerKeyMapping(KeyMapping mapping); + + /** + * Executed when a moveable screen is opened or closed + */ + void onMoveableScreen(boolean close); } 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..0fd39ef 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoserClient.java @@ -1,5 +1,7 @@ package com.mrbysco.armorposer; +import com.mrbysco.armorposer.client.KeybindHandler; +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 +10,13 @@ import net.minecraft.world.entity.decoration.ArmorStand; public class ArmorPoserClient implements ClientModInitializer { + static { + KeybindHandler.loadClass(); + } @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..89d3ba1 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,14 @@ 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); + } + + @Override + public void onMoveableScreen(boolean close) { + //Not needed for Fabric + } } 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..3812134 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,5 +1,7 @@ package com.mrbysco.armorposer; +import com.mrbysco.armorposer.client.ClientHandler; +import com.mrbysco.armorposer.client.KeybindHandler; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; @@ -27,6 +29,9 @@ public ArmorPoser(IEventBus eventBus, ModContainer container, Dist dist) { if (dist.isClient()) { container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new); + + KeybindHandler.loadClass(); + eventBus.addListener(ClientHandler::setupKeyMappings); } } diff --git a/forge/src/main/java/com/mrbysco/armorposer/client/ClientHandler.java b/forge/src/main/java/com/mrbysco/armorposer/client/ClientHandler.java new file mode 100644 index 0000000..ea014b2 --- /dev/null +++ b/forge/src/main/java/com/mrbysco/armorposer/client/ClientHandler.java @@ -0,0 +1,16 @@ +package com.mrbysco.armorposer.client; + +import net.minecraft.client.KeyMapping; +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; + +import java.util.ArrayList; +import java.util.List; + +public class ClientHandler { + public static final List KEY_MAPPINGS = new ArrayList<>(); + + public static void setupKeyMappings(RegisterKeyMappingsEvent event) { + KEY_MAPPINGS.forEach(event::register); + KEY_MAPPINGS.clear(); + } +} 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..85cdcc6 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java +++ b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java @@ -1,16 +1,21 @@ package com.mrbysco.armorposer.platform; import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.client.ClientHandler; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; 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.client.Minecraft; +import net.minecraft.client.Options; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.decoration.ArmorStand; import net.neoforged.fml.ModList; import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.client.settings.KeyConflictContext; import net.neoforged.neoforge.network.PacketDistributor; import java.nio.file.Path; @@ -55,4 +60,20 @@ public List getResizeWhitelist() { public String getModVersion() { return ModList.get().getModFileById(Reference.MOD_ID).versionString(); } + + @Override + public KeyMapping registerKeyMapping(KeyMapping mapping) { + ClientHandler.KEY_MAPPINGS.add(mapping); + return mapping; + } + + @Override + public void onMoveableScreen(boolean close) { + Options options = Minecraft.getInstance().options; + options.keyUp.setKeyConflictContext(close ? KeyConflictContext.IN_GAME : KeyConflictContext.UNIVERSAL); + options.keyDown.setKeyConflictContext(close ? KeyConflictContext.IN_GAME : KeyConflictContext.UNIVERSAL); + options.keyLeft.setKeyConflictContext(close ? KeyConflictContext.IN_GAME : KeyConflictContext.UNIVERSAL); + options.keyRight.setKeyConflictContext(close ? KeyConflictContext.IN_GAME : KeyConflictContext.UNIVERSAL); + options.keyJump.setKeyConflictContext(close ? KeyConflictContext.IN_GAME : KeyConflictContext.UNIVERSAL); + } } diff --git a/forge/src/main/resources/armorposer.neoforge.mixins.json b/forge/src/main/resources/armorposer.neoforge.mixins.json index 27b75e5..edbd237 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" + "KeyMappingAccessor", + "MinecraftMixin", + "MouseHandleAccessor" ], "injectors": { "defaultRequire": 1