diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java index ab85090..8e70f45 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java @@ -35,7 +35,7 @@ public interface CompatHandler * @param screen The screen. * @return True if the mouse is requried on the specified screen, else false. */ - default boolean requireMouseOnScreen(@NotNull Screen screen) + default boolean requireMouseOnScreen(Screen screen) { return false; } diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java index 42fafd0..88733f6 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java @@ -55,7 +55,7 @@ public class LambdaControlsCompat * @param screen The screen. * @return True if the mouse is requried on the specified screen, else false. */ - public static boolean requireMouseOnScreen(@NotNull Screen screen) + public static boolean requireMouseOnScreen(Screen screen) { return HANDLERS.stream().anyMatch(handler -> handler.requireMouseOnScreen(screen)); } diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsMixinPlugin.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsMixinPlugin.java index 1836797..faedc10 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsMixinPlugin.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsMixinPlugin.java @@ -31,6 +31,7 @@ public class LambdaControlsMixinPlugin implements IMixinConfigPlugin public LambdaControlsMixinPlugin() { this.conditionalMixins.put("me.lambdaurora.lambdacontrols.client.compat.mixin.RecipeViewingScreenAccessor", LambdaControlsCompat.isReiPresent()); + this.conditionalMixins.put("me.lambdaurora.lambdacontrols.client.compat.mixin.VillagerRecipeViewingScreenAccessor", LambdaControlsCompat.isReiPresent()); } @Override diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java index b4c4f1c..2699138 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java @@ -12,17 +12,26 @@ package me.lambdaurora.lambdacontrols.client.compat; import me.lambdaurora.lambdacontrols.client.ButtonState; import me.lambdaurora.lambdacontrols.client.LambdaControlsClient; import me.lambdaurora.lambdacontrols.client.compat.mixin.RecipeViewingScreenAccessor; +import me.lambdaurora.lambdacontrols.client.compat.mixin.VillagerRecipeViewingScreenAccessor; import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding; +import me.lambdaurora.lambdacontrols.client.controller.InputHandlers; import me.lambdaurora.lambdacontrols.client.controller.InputManager; import me.lambdaurora.lambdacontrols.client.controller.PressAction; +import me.shedaniel.rei.api.RecipeCategory; +import me.shedaniel.rei.gui.ContainerScreenOverlay; import me.shedaniel.rei.gui.RecipeViewingScreen; import me.shedaniel.rei.gui.VillagerRecipeViewingScreen; +import me.shedaniel.rei.gui.widget.EntryListWidget; +import me.shedaniel.rei.impl.ScreenHelper; import net.minecraft.client.gui.screen.Screen; import net.minecraft.util.Identifier; +import org.aperlambda.lambdacommon.utils.LambdaReflection; import org.jetbrains.annotations.NotNull; -import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER; -import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER; +import java.util.List; +import java.util.Optional; + +import static org.lwjgl.glfw.GLFW.*; /** * Represents a compatibility handler for REI. @@ -33,47 +42,135 @@ import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER; */ public class ReiCompat implements CompatHandler { + private static EntryListWidget ENTRY_LIST_WIDGET; + public static ButtonBinding TAB_BACK; + @Override public void handle(@NotNull LambdaControlsClient mod) { - InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "tab_back")) + InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "category_back")) .buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) .filter((client, binding) -> isViewingScreen(client.currentScreen)) - .action(tabAction(false)) + .action(handleTab(false)) + .cooldown(true) + .build()); + InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "category_next")) + .buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) + .filter((client, binding) -> isViewingScreen(client.currentScreen)) + .action(handleTab(true)) .cooldown(true) .build()); - InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "tab_next")) - .buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) - .filter((client, binding) -> isViewingScreen(client.currentScreen)) - .action(tabAction(true)) + InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "page_back")) + .buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, false)) + .filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen)) + .action(handlePage(false)) + .cooldown(true) + .build()); + InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "page_next")) + .buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, true)) + .filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen)) + .action(handlePage(true)) + .cooldown(true) + .build()); + + // For some reasons this is broken. + InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "show_usage")) + .buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) + .filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen)) + .action((client, button, action) -> { + if (action != ButtonState.RELEASE) + return false; + Optional overlay = ScreenHelper.getOptionalOverlay(); + if (!overlay.isPresent()) + return false; + double mouseX = client.mouse.getX(); + double mouseY = client.mouse.getY(); + EntryListWidget widget = getEntryListWidget(); + if (widget == null) + return false; + return widget.mouseClicked(mouseX, mouseY, GLFW_MOUSE_BUTTON_2); + }) .cooldown(true) .build()); } @Override - public boolean requireMouseOnScreen(@NotNull Screen screen) + public boolean requireMouseOnScreen(Screen screen) { return isViewingScreen(screen); } - private static boolean isViewingScreen(@NotNull Screen screen) + private static boolean isViewingScreen(Screen screen) { return screen instanceof RecipeViewingScreen || screen instanceof VillagerRecipeViewingScreen; } - private static PressAction tabAction(boolean next) + private static EntryListWidget getEntryListWidget() + { + if (ENTRY_LIST_WIDGET == null) { + ENTRY_LIST_WIDGET = LambdaReflection.getFirstFieldOfType(ContainerScreenOverlay.class, EntryListWidget.class) + .map(field -> (EntryListWidget) LambdaReflection.getFieldValue(null, field)) + .orElse(null); + } + return ENTRY_LIST_WIDGET; + } + + private static PressAction handlePage(boolean next) { return (client, button, action) -> { if (action == ButtonState.RELEASE) return false; + Optional overlay = ScreenHelper.getOptionalOverlay(); + if (!overlay.isPresent()) + return false; + + EntryListWidget widget = getEntryListWidget(); + if (widget == null) + return false; + + if (next) + widget.nextPage(); + else + widget.previousPage(); + widget.updateEntriesPosition(); + + return true; + }; + } + + /** + * Returns the handler for category tabs buttons. + * + * @param next True if the action is to switch to the next tab. + * @return The handler. + */ + private static PressAction handleTab(boolean next) + { + return (client, button, action) -> { + if (action != ButtonState.RELEASE) + return false; + if (client.currentScreen instanceof RecipeViewingScreen) { RecipeViewingScreenAccessor screen = (RecipeViewingScreenAccessor) client.currentScreen; if (next) screen.getCategoryNext().onPressed(); else screen.getCategoryBack().onPressed(); + return true; + } else if (client.currentScreen instanceof VillagerRecipeViewingScreen) { + VillagerRecipeViewingScreenAccessor screen = (VillagerRecipeViewingScreenAccessor) client.currentScreen; + List> categories = screen.getCategories(); + int currentTab = screen.getSelectedCategoryIndex(); + int nextTab = currentTab + (next ? 1 : -1); + if (nextTab < 0) + nextTab = categories.size() - 1; + else if (nextTab >= categories.size()) + nextTab = 0; + screen.setSelectedCategoryIndex(nextTab); + screen.lambdacontrols_init(); + return true; } return false; }; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/mixin/VillagerRecipeViewingScreenAccessor.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/mixin/VillagerRecipeViewingScreenAccessor.java new file mode 100644 index 0000000..e7cdc2e --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/mixin/VillagerRecipeViewingScreenAccessor.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.compat.mixin; + +import me.shedaniel.rei.api.RecipeCategory; +import me.shedaniel.rei.gui.VillagerRecipeViewingScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; + +/** + * Represents an accessor to REI's VillagerRecipeViewingScreen. + * + * @author LambdAurora + * @version 1.2.0 + * @since 1.2.0 + */ +@Mixin(VillagerRecipeViewingScreen.class) +public interface VillagerRecipeViewingScreenAccessor +{ + @Accessor("categories") + List> getCategories(); + + @Accessor("selectedCategoryIndex") + int getSelectedCategoryIndex(); + + @Accessor("selectedCategoryIndex") + void setSelectedCategoryIndex(int selectedCategoryIndex); + + @Invoker("init") + void lambdacontrols_init(); +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java index 47db24a..c039e33 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java @@ -47,7 +47,7 @@ public class InputHandlers { } - public static PressAction handleHotbar(boolean right) + public static PressAction handleHotbar(boolean next) { return (client, button, action) -> { if (action == ButtonState.RELEASE) @@ -55,7 +55,7 @@ public class InputHandlers // When ingame if (client.currentScreen == null && client.player != null) { - if (right) + if (next) client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 8 ? 0 : client.player.inventory.selectedSlot + 1; else client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 0 ? 8 : client.player.inventory.selectedSlot - 1; @@ -63,7 +63,7 @@ public class InputHandlers } else if (client.currentScreen instanceof CreativeInventoryScreen) { CreativeInventoryScreenAccessor inventory = (CreativeInventoryScreenAccessor) client.currentScreen; int currentTab = inventory.getSelectedTab(); - int nextTab = currentTab + (right ? 1 : -1); + int nextTab = currentTab + (next ? 1 : -1); if (nextTab < 0) nextTab = ItemGroup.GROUPS.length - 1; else if (nextTab >= ItemGroup.GROUPS.length) @@ -74,7 +74,7 @@ public class InputHandlers RecipeBookWidgetAccessor recipeBook = (RecipeBookWidgetAccessor) ((InventoryScreen) client.currentScreen).getRecipeBookWidget(); List tabs = recipeBook.getTabButtons(); RecipeGroupButtonWidget currentTab = recipeBook.getCurrentTab(); - int nextTab = tabs.indexOf(currentTab) + (right ? 1 : -1); + int nextTab = tabs.indexOf(currentTab) + (next ? 1 : -1); if (nextTab < 0) nextTab = tabs.size() - 1; else if (nextTab >= tabs.size()) @@ -89,7 +89,7 @@ public class InputHandlers AdvancementTab tab = screen.getSelectedTab(); for (int i = 0; i < tabs.size(); i++) { if (tabs.get(i).equals(tab)) { - int nextTab = i + (right ? 1 : -1); + int nextTab = i + (next ? 1 : -1); if (nextTab < 0) nextTab = tabs.size() - 1; else if (nextTab >= tabs.size()) diff --git a/fabric/src/main/resources/lambdacontrols_compat.mixins.json b/fabric/src/main/resources/lambdacontrols_compat.mixins.json index 4fdee98..379700b 100644 --- a/fabric/src/main/resources/lambdacontrols_compat.mixins.json +++ b/fabric/src/main/resources/lambdacontrols_compat.mixins.json @@ -4,7 +4,8 @@ "plugin": "me.lambdaurora.lambdacontrols.client.compat.LambdaControlsMixinPlugin", "compatibilityLevel": "JAVA_8", "client": [ - "RecipeViewingScreenAccessor" + "RecipeViewingScreenAccessor", + "VillagerRecipeViewingScreenAccessor" ], "injectors": { "defaultRequire": 1