From fa1e1293c77bc4a95aa830a63fd5b2445e7bbd9f Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 7 May 2023 12:47:32 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9E=95=20Controller=20conflict=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/bind/ControllerBinding.java | 8 ++- .../api/bind/ControllerBindingBuilder.java | 3 + .../controlify/bindings/BindContext.java | 64 +++++++++++++++++++ .../controlify/bindings/BindContexts.java | 18 ++++++ .../bindings/ControllerBindingImpl.java | 28 ++++++-- .../bindings/ControllerBindings.java | 49 ++++++++++++++ .../yacl/YACLScreenProcessor.java | 6 +- .../config/gui/AbstractBindController.java | 31 +++++++-- .../controlify/config/gui/YACLHelper.java | 49 ++++++++++---- .../controlify/screenop/ScreenProcessor.java | 46 ++++++++----- 10 files changed, 258 insertions(+), 44 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/bindings/BindContext.java create mode 100644 src/main/java/dev/isxander/controlify/bindings/BindContexts.java diff --git a/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java b/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java index 0a0ef1c..1bc01af 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java @@ -1,12 +1,15 @@ package dev.isxander.controlify.api.bind; import com.google.gson.JsonObject; +import dev.isxander.controlify.bindings.BindContext; +import dev.isxander.controlify.bindings.IBind; import dev.isxander.yacl.api.Option; import net.minecraft.client.KeyMapping; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; +import java.util.Set; import java.util.function.BooleanSupplier; public interface ControllerBinding { @@ -46,17 +49,20 @@ public interface ControllerBinding { ResourceLocation id(); + Set contexts(); + /** * The vanilla override of the binding. Null if there is no override. */ @Nullable KeyMappingOverride override(); + IBind getBind(); void resetBind(); boolean isUnbound(); BindRenderer renderer(); - Option generateYACLOption(); + Option.Builder startYACLOption(); JsonObject toJson(); diff --git a/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java b/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java index 2965255..a1f24c0 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java @@ -1,5 +1,6 @@ package dev.isxander.controlify.api.bind; +import dev.isxander.controlify.bindings.BindContext; import dev.isxander.controlify.bindings.ControllerBindingImpl; import dev.isxander.controlify.bindings.GamepadBinds; import dev.isxander.controlify.bindings.IBind; @@ -73,6 +74,8 @@ public interface ControllerBindingBuilder { */ ControllerBindingBuilder category(Component category); + ControllerBindingBuilder context(BindContext... contexts); + /** * Specifies are vanilla override for the binding. * Will emulate presses of the vanilla keybind when the controller binding is pressed. diff --git a/src/main/java/dev/isxander/controlify/bindings/BindContext.java b/src/main/java/dev/isxander/controlify/bindings/BindContext.java new file mode 100644 index 0000000..62a9665 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/BindContext.java @@ -0,0 +1,64 @@ +package dev.isxander.controlify.bindings; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.resources.ResourceLocation; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public final class BindContext { + private final ResourceLocation context; + private final Set parents; + private final Set flattened; + + public BindContext(ResourceLocation context, Set parents) { + this.context = context; + this.parents = parents; + + Set flattened = new HashSet<>(); + flattened.add(context()); + parents().forEach(p -> flattened.addAll(p.flattened())); + this.flattened = ImmutableSet.copyOf(flattened); + } + + public ResourceLocation context() { + return context; + } + + public Set parents() { + return parents; + } + + public Set flattened() { + return flattened; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (BindContext) obj; + return Objects.equals(this.context, that.context) && + Objects.equals(this.parents, that.parents); + } + + @Override + public int hashCode() { + return Objects.hash(context, parents); + } + + @Override + public String toString() { + return "BindContext[" + + "context=" + context + ", " + + "parents=" + parents + ']'; + } + + public static Set flatten(Set contexts) { + return contexts.stream() + .flatMap(ctx -> ctx.flattened.stream()) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/BindContexts.java b/src/main/java/dev/isxander/controlify/bindings/BindContexts.java new file mode 100644 index 0000000..7572f89 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/BindContexts.java @@ -0,0 +1,18 @@ +package dev.isxander.controlify.bindings; + +import dev.isxander.controlify.Controlify; + +import java.util.Set; + +public final class BindContexts { + public static final BindContext + INGAME = ctx("ingame"), + GUI = ctx("gui"), + GUI_VMOUSE = ctx("gui_vmouse"), + CONTROLIFY_CONFIG = ctx("controlify_config", GUI), + INVENTORY = ctx("inventory", GUI_VMOUSE); + + private static BindContext ctx(String path, BindContext... parents) { + return new BindContext(Controlify.id(path), Set.of(parents)); + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java index d0933d9..29c1cf7 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java @@ -1,5 +1,6 @@ package dev.isxander.controlify.bindings; +import com.google.common.collect.ImmutableSet; import com.google.gson.JsonObject; import com.mojang.blaze3d.vertex.PoseStack; import dev.isxander.controlify.Controlify; @@ -36,11 +37,12 @@ public class ControllerBindingImpl implements Control private BindRenderer renderer; private final ResourceLocation id; private final Component name, description, category; + private final Set contexts; private final KeyMappingOverride override; private static final Map, Set>> pressedBinds = new HashMap<>(); - private ControllerBindingImpl(Controller controller, IBind defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category) { + private ControllerBindingImpl(Controller controller, IBind defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set contexts) { this.controller = controller; this.bind = this.defaultBind = defaultBind; this.renderer = new BindRendererImpl(bind); @@ -49,6 +51,7 @@ public class ControllerBindingImpl implements Control this.name = name; this.description = description; this.category = category; + this.contexts = ImmutableSet.copyOf(contexts); } @Override @@ -133,6 +136,16 @@ public class ControllerBindingImpl implements Control return category; } + @Override + public Set contexts() { + return contexts; + } + + @Override + public IBind getBind() { + return bind; + } + @Override public boolean isUnbound() { return bind instanceof EmptyBind; @@ -154,7 +167,7 @@ public class ControllerBindingImpl implements Control } @Override - public Option generateYACLOption() { + public Option.Builder startYACLOption() { Option.Builder> option = Option.createBuilder((Class>) (Class) IBind.class) .name(name()) .binding(defaultBind(), this::currentBind, this::setCurrentBind) @@ -166,7 +179,7 @@ public class ControllerBindingImpl implements Control ((Option.Builder>) (Object) option).controller(opt -> new JoystickBindController(opt, joystick)); } - return option.build(); + return option; } // FIXME: very hack solution please remove me @@ -197,6 +210,7 @@ public class ControllerBindingImpl implements Control private ResourceLocation id; private Component name = null, description = null, category = null; private KeyMappingOverride override = null; + private final Set contexts = new HashSet<>(); public ControllerBindingBuilderImpl(Controller controller) { this.controller = controller; @@ -248,6 +262,12 @@ public class ControllerBindingImpl implements Control return this; } + @Override + public ControllerBindingBuilder context(BindContext... contexts) { + this.contexts.addAll(Set.of(contexts)); + return this; + } + @Override public ControllerBindingBuilder vanillaOverride(KeyMapping keyMapping, BooleanSupplier toggleable) { this.override = new KeyMappingOverride(keyMapping, toggleable); @@ -276,7 +296,7 @@ public class ControllerBindingImpl implements Control } } - return new ControllerBindingImpl<>(controller, bind, id, override, name, description, category); + return new ControllerBindingImpl<>(controller, bind, id, override, name, description, category, contexts); } } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 7cf4d6e..308a266 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -78,47 +78,56 @@ public class ControllerBindings { .identifier("controlify", "walk_forward") .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(WALK_BACKWARD = ControllerBindingBuilder.create(controller) .identifier("controlify", "walk_backward") .defaultBind(GamepadBinds.LEFT_STICK_BACKWARD) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(WALK_LEFT = ControllerBindingBuilder.create(controller) .identifier("controlify", "strafe_left") .defaultBind(GamepadBinds.LEFT_STICK_LEFT) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(WALK_RIGHT = ControllerBindingBuilder.create(controller) .identifier("controlify", "strafe_right") .defaultBind(GamepadBinds.LEFT_STICK_RIGHT) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(LOOK_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "look_up") .defaultBind(GamepadBinds.RIGHT_STICK_FORWARD) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(LOOK_DOWN = ControllerBindingBuilder.create(controller) .identifier("controlify", "look_down") .defaultBind(GamepadBinds.RIGHT_STICK_BACKWARD) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(LOOK_LEFT = ControllerBindingBuilder.create(controller) .identifier("controlify", "look_left") .defaultBind(GamepadBinds.RIGHT_STICK_LEFT) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(LOOK_RIGHT = ControllerBindingBuilder.create(controller) .identifier("controlify", "look_right") .defaultBind(GamepadBinds.RIGHT_STICK_RIGHT) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); if (controller instanceof GamepadController gamepad && gamepad.hasGyro()) { register(GAMEPAD_GYRO_BUTTON = ControllerBindingBuilder.create(controller) .identifier("controlify", "gamepad_gyro_button") .defaultBind(new EmptyBind<>()) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); } else { GAMEPAD_GYRO_BUTTON = null; @@ -127,209 +136,249 @@ public class ControllerBindings { .identifier("controlify", "jump") .defaultBind(GamepadBinds.A_BUTTON) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(SPRINT = ControllerBindingBuilder.create(controller) .identifier("controlify", "sprint") .defaultBind(GamepadBinds.LEFT_STICK_PRESS) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keySprint, () -> controller.config().toggleSprint) .build()); register(SNEAK = ControllerBindingBuilder.create(controller) .identifier("controlify", "sneak") .defaultBind(GamepadBinds.RIGHT_STICK_PRESS) .category(MOVEMENT_CATEGORY) + .context(BindContexts.INGAME) .build()); register(ATTACK = ControllerBindingBuilder.create(controller) .identifier("controlify", "attack") .defaultBind(GamepadBinds.RIGHT_TRIGGER) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyAttack, () -> false) .build()); register(USE = ControllerBindingBuilder.create(controller) .identifier("controlify", "use") .defaultBind(GamepadBinds.LEFT_TRIGGER) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyUse, () -> false) .build()); register(DROP = ControllerBindingBuilder.create(controller) .identifier("controlify", "drop") .defaultBind(GamepadBinds.DPAD_DOWN) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME, BindContexts.INVENTORY) .build()); register(NEXT_SLOT = ControllerBindingBuilder.create(controller) .identifier("controlify", "next_slot") .defaultBind(GamepadBinds.RIGHT_BUMPER) .category(INVENTORY_CATEGORY) + .context(BindContexts.INGAME) .build()); register(PREV_SLOT = ControllerBindingBuilder.create(controller) .identifier("controlify", "prev_slot") .defaultBind(GamepadBinds.LEFT_BUMPER) .category(INVENTORY_CATEGORY) + .context(BindContexts.INGAME) .build()); register(PAUSE = ControllerBindingBuilder.create(controller) .identifier("controlify", "pause") .defaultBind(GamepadBinds.START) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME) .build()); register(INVENTORY = ControllerBindingBuilder.create(controller) .identifier("controlify", "inventory") .defaultBind(GamepadBinds.Y_BUTTON) .category(INVENTORY_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyInventory, () -> false) .build()); register(CHANGE_PERSPECTIVE = ControllerBindingBuilder.create(controller) .identifier("controlify", "change_perspective") .defaultBind(GamepadBinds.BACK) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyTogglePerspective, () -> false) .build()); register(SWAP_HANDS = ControllerBindingBuilder.create(controller) .identifier("controlify", "swap_hands") .defaultBind(GamepadBinds.X_BUTTON) .category(INVENTORY_CATEGORY) + .context(BindContexts.INGAME, BindContexts.INVENTORY) .vanillaOverride(options.keySwapOffhand, () -> false) .build()); register(OPEN_CHAT = ControllerBindingBuilder.create(controller) .identifier("controlify", "open_chat") .defaultBind(GamepadBinds.DPAD_UP) .category(MISC_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyChat, () -> false) .build()); register(GUI_PRESS = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_press") .defaultBind(GamepadBinds.A_BUTTON) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(GUI_BACK = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_back") .defaultBind(GamepadBinds.B_BUTTON) .category(GUI_CATEGORY) + .context(BindContexts.GUI, BindContexts.GUI_VMOUSE) .build()); register(GUI_NEXT_TAB = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_next_tab") .defaultBind(GamepadBinds.RIGHT_BUMPER) .category(GUI_CATEGORY) + .context(BindContexts.GUI, BindContexts.GUI_VMOUSE) .build()); register(GUI_PREV_TAB = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_prev_tab") .defaultBind(GamepadBinds.LEFT_BUMPER) .category(GUI_CATEGORY) + .context(BindContexts.GUI, BindContexts.GUI_VMOUSE) .build()); register(GUI_ABSTRACT_ACTION_1 = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_abstract_action_1") .defaultBind(GamepadBinds.X_BUTTON) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(GUI_ABSTRACT_ACTION_2 = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_abstract_action_2") .defaultBind(GamepadBinds.Y_BUTTON) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(PICK_BLOCK = ControllerBindingBuilder.create(controller) .identifier("controlify", "pick_block") .defaultBind(GamepadBinds.DPAD_LEFT) .category(GAMEPLAY_CATEGORY) + .context(BindContexts.INGAME) .vanillaOverride(options.keyPickItem, () -> false) .build()); register(TOGGLE_HUD_VISIBILITY = ControllerBindingBuilder.create(controller) .identifier("controlify", "toggle_hud_visibility") .defaultBind(new EmptyBind<>()) .category(MISC_CATEGORY) + .context(BindContexts.INGAME) .build()); register(SHOW_PLAYER_LIST = ControllerBindingBuilder.create(controller) .identifier("controlify", "show_player_list") .defaultBind(GamepadBinds.DPAD_RIGHT) .category(MISC_CATEGORY) + .context(BindContexts.INGAME) .build()); register(VMOUSE_MOVE_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_up") .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_MOVE_DOWN = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_down") .defaultBind(GamepadBinds.LEFT_STICK_BACKWARD) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_MOVE_LEFT = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_left") .defaultBind(GamepadBinds.LEFT_STICK_LEFT) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_MOVE_RIGHT = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_right") .defaultBind(GamepadBinds.LEFT_STICK_RIGHT) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_LCLICK = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_lclick") .defaultBind(GamepadBinds.A_BUTTON) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_RCLICK = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_rclick") .defaultBind(GamepadBinds.X_BUTTON) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_SHIFT_CLICK = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_shift_click") .defaultBind(GamepadBinds.Y_BUTTON) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_SCROLL_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_scroll_up") .defaultBind(GamepadBinds.RIGHT_STICK_FORWARD) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_SCROLL_DOWN = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_scroll_down") .defaultBind(GamepadBinds.RIGHT_STICK_BACKWARD) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_SHIFT = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_shift") .defaultBind(GamepadBinds.LEFT_STICK_PRESS) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(VMOUSE_TOGGLE = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_toggle") .defaultBind(GamepadBinds.BACK) .category(VMOUSE_CATEGORY) + .context(BindContexts.GUI_VMOUSE) .build()); register(GUI_NAVI_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_navi_up") .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(GUI_NAVI_DOWN = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_navi_down") .defaultBind(GamepadBinds.LEFT_STICK_BACKWARD) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(GUI_NAVI_LEFT = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_navi_left") .defaultBind(GamepadBinds.LEFT_STICK_LEFT) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(GUI_NAVI_RIGHT = ControllerBindingBuilder.create(controller) .identifier("controlify", "gui_navi_right") .defaultBind(GamepadBinds.LEFT_STICK_RIGHT) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(CYCLE_OPT_FORWARD = ControllerBindingBuilder.create(controller) .identifier("controlify", "cycle_opt_forward") .defaultBind(GamepadBinds.RIGHT_STICK_RIGHT) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(CYCLE_OPT_BACKWARD = ControllerBindingBuilder.create(controller) .identifier("controlify", "cycle_opt_backward") .defaultBind(GamepadBinds.RIGHT_STICK_LEFT) .category(GUI_CATEGORY) + .context(BindContexts.GUI) .build()); register(CLEAR_BINDING = ControllerBindingBuilder.create(controller) .identifier("controlify", "clear_binding") .defaultBind(GamepadBinds.RIGHT_STICK_PRESS) .category(GUI_CATEGORY) + .context(BindContexts.CONTROLIFY_CONFIG) .build()); for (var constructor : CUSTOM_BINDS.values()) { diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/YACLScreenProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/YACLScreenProcessor.java index 3035cf2..d26a68e 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/yacl/YACLScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/YACLScreenProcessor.java @@ -13,11 +13,10 @@ public class YACLScreenProcessor extends ScreenProcessor { } @Override - protected void handleComponentNavigation(Controller controller) { + protected void handleButtons(Controller controller) { if (controller.bindings().GUI_ABSTRACT_ACTION_1.justPressed()) { this.playClackSound(); screen.finishedSaveButton.onPress(); - return; } if (controller.bindings().GUI_NEXT_TAB.justPressed()) { @@ -31,12 +30,11 @@ public class YACLScreenProcessor extends ScreenProcessor { screen.changeCategory(idx); } - super.handleComponentNavigation(controller); + super.handleButtons(controller); } @Override public void onWidgetRebuild() { - // currently doesn't work because TextScaledButtonWidget overrides renderString ButtonGuideApi.addGuideToButton(screen.finishedSaveButton, bindings -> bindings.GUI_ABSTRACT_ACTION_1, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS); } diff --git a/src/main/java/dev/isxander/controlify/config/gui/AbstractBindController.java b/src/main/java/dev/isxander/controlify/config/gui/AbstractBindController.java index 4aaf6ef..817c1b3 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/AbstractBindController.java +++ b/src/main/java/dev/isxander/controlify/config/gui/AbstractBindController.java @@ -12,6 +12,7 @@ import dev.isxander.yacl.api.Option; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.gui.controllers.ControllerWidget; +import dev.isxander.yacl.gui.utils.GuiUtils; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; @@ -21,6 +22,7 @@ import java.util.Optional; public abstract class AbstractBindController implements Controller> { private final Option> option; public final dev.isxander.controlify.controller.Controller controller; + private boolean conflicting; public AbstractBindController(Option> option, dev.isxander.controlify.controller.Controller controller) { this.option = option; @@ -37,6 +39,14 @@ public abstract class AbstractBindController implemen return Component.empty(); } + public void setConflicting(boolean conflicting) { + this.conflicting = conflicting; + } + + public boolean getConflicting() { + return this.conflicting; + } + @Override public abstract AbstractBindControllerElement provideWidget(YACLScreen yaclScreen, Dimension dimension); @@ -85,14 +95,16 @@ public abstract class AbstractBindController implemen public boolean overrideControllerButtons(ScreenProcessor screen, dev.isxander.controlify.controller.Controller controller) { if (controller != control.controller) return true; - if (controller.bindings().CLEAR_BINDING.justPressed()) { - control.option().requestSet(new EmptyBind<>()); - return true; - } + if (!justTookInput && !awaitingControllerInput) { + if (controller.bindings().CLEAR_BINDING.justPressed()) { + control.option().requestSet(new EmptyBind<>()); + return true; + } - if (controller.bindings().GUI_PRESS.justPressed() && !awaitingControllerInput) { - ControllerBindHandler.setBindListener(this); - return awaitingControllerInput = true; + if (controller.bindings().GUI_PRESS.justPressed()) { + ControllerBindHandler.setBindListener(this); + return awaitingControllerInput = true; + } } if (justTookInput) { @@ -136,6 +148,11 @@ public abstract class AbstractBindController implemen return control.option().pendingValue().drawSize().width(); } + @Override + protected int getValueColor() { + return control.conflicting ? 0xFF5555 : super.getValueColor(); + } + public abstract Optional> getPressedBind(); } } diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java index 51728ca..32aca7b 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -1,21 +1,16 @@ package dev.isxander.controlify.config.gui; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.bind.ControllerBinding; -import dev.isxander.controlify.bindings.ControllerBindingImpl; -import dev.isxander.controlify.bindings.IBind; +import dev.isxander.controlify.bindings.BindContext; import dev.isxander.controlify.config.GlobalSettings; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerConfig; import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.controller.gamepad.GamepadController; -import dev.isxander.controlify.controller.gamepad.GamepadState; import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme; -import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.SingleJoystickController; -import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping; import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; import dev.isxander.controlify.reacharound.ReachAroundMode; @@ -29,7 +24,6 @@ import dev.isxander.yacl.gui.controllers.TickBoxController; import dev.isxander.yacl.gui.controllers.cycling.CyclingListController; import dev.isxander.yacl.gui.controllers.cycling.EnumController; import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; import dev.isxander.yacl.gui.controllers.string.StringController; import net.minecraft.ChatFormatting; import net.minecraft.Util; @@ -39,6 +33,7 @@ import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -69,7 +64,7 @@ public class YACLHelper { .name(Component.translatable("controlify.gui.load_vibration_natives")) .tooltip(Component.translatable("controlify.gui.load_vibration_natives.tooltip")) .tooltip(Component.translatable("controlify.gui.load_vibration_natives.tooltip.warning").withStyle(ChatFormatting.RED)) - .binding(GlobalSettings.DEFAULT.loadVibrationNatives, () -> globalSettings.loadVibrationNatives, v -> globalSettings.loadVibrationNatives = v) + .binding(true, () -> globalSettings.loadVibrationNatives, v -> globalSettings.loadVibrationNatives = v) .controller(opt -> new BooleanController(opt, BooleanController.YES_NO_FORMATTER, false)) .flag(OptionFlag.GAME_RESTART) .build()) @@ -114,7 +109,7 @@ public class YACLHelper { return yacl.build().generateScreen(parent); } - private static ConfigCategory createControllerCategory(Controller controller, Option globalVibrationOption) { + private static ConfigCategory createControllerCategory(Controller controller, Option globalVibrationOption) { if (!controller.canBeUsed()) { return PlaceholderCategory.createBuilder() .name(Component.literal(controller.name())) @@ -245,16 +240,45 @@ public class YACLHelper { var controlsGroup = OptionGroup.createBuilder() .name(Component.translatable("controlify.gui.group.controls")); + List optionBinds = new ArrayList<>(); groupBindings(controller.bindings().registry().values()).forEach((categoryName, bindGroup) -> { controlsGroup.option(LabelOption.create(categoryName)); - controlsGroup.options(bindGroup.stream().map(ControllerBinding::generateYACLOption).toList()); + controlsGroup.options(bindGroup.stream().map(binding -> { + Option.Builder option = binding.startYACLOption() + .listener((opt, val) -> updateConflictingBinds(optionBinds)); + + Option built = option.build(); + optionBinds.add(new OptionBindPair(built, binding)); + return built; + }).toList()); }); + updateConflictingBinds(optionBinds); category.group(controlsGroup.build()); return category.build(); } + private static void updateConflictingBinds(List all) { + all.forEach(pair -> ((AbstractBindController) pair.option().controller()).setConflicting(false)); + + for (OptionBindPair opt : all) { + var ctxs = BindContext.flatten(opt.binding().contexts()); + + List conflicting = all.stream() + .filter(pair -> pair.binding() != opt.binding()) + .filter(pair -> { + boolean contextsMatch = BindContext.flatten(pair.binding().contexts()) + .stream() + .anyMatch(ctxs::contains); + boolean bindMatches = pair.option().pendingValue().equals(opt.option().pendingValue()); + return contextsMatch && bindMatches; + }).toList(); + + conflicting.forEach(conflict -> ((AbstractBindController) conflict.option().controller()).setConflicting(true)); + } + } + private static OptionGroup makeVibrationGroup(Option globalVibrationOption, Controller controller) { boolean canRumble = controller.supportsRumble(); var config = controller.config(); @@ -428,8 +452,11 @@ public class YACLHelper { } } - private static Map> groupBindings(Collection bindings) { + private static Map> groupBindings(Collection bindings) { return bindings.stream() .collect(Collectors.groupingBy(ControllerBinding::category, LinkedHashMap::new, Collectors.toList())); } + + private record OptionBindPair(Option option, ControllerBinding binding) { + } } diff --git a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java index 6a5acbc..477ee44 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java @@ -34,8 +34,11 @@ public class ScreenProcessor { public void onControllerUpdate(Controller controller) { if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) { - handleComponentNavigation(controller); - handleButtons(controller); + if (!handleComponentNavOverride(controller)) + handleComponentNavigation(controller); + + if (!handleComponentButtonOverride(controller)) + handleButtons(controller); } else { handleScreenVMouse(controller); } @@ -58,13 +61,7 @@ public class ScreenProcessor { if (screen.getFocused() == null) setInitialFocus(); - var focusTree = getFocusTree(); - var focuses = List.copyOf(focusTree); - while (!focusTree.isEmpty()) { - var focused = focusTree.poll(); - var processor = ComponentProcessorProvider.provide(focused); - if (processor.overrideControllerNavigation(this, controller)) return; - } + var focuses = List.copyOf(getFocusTree()); var accessor = (ScreenAccessor) screen; @@ -114,15 +111,9 @@ public class ScreenProcessor { } protected void handleButtons(Controller controller) { - var focusTree = getFocusTree(); - while (!focusTree.isEmpty()) { - var focused = focusTree.poll(); - var processor = ComponentProcessorProvider.provide(focused); - if (processor.overrideControllerButtons(this, controller)) return; - } - - if (controller.bindings().GUI_PRESS.justPressed()) + if (controller.bindings().GUI_PRESS.justPressed()) { screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); + } if (controller.bindings().GUI_BACK.justPressed()) { this.playClackSound(); screen.onClose(); @@ -133,6 +124,27 @@ public class ScreenProcessor { } + protected boolean handleComponentButtonOverride(Controller controller) { + var focusTree = getFocusTree(); + while (!focusTree.isEmpty()) { + var focused = focusTree.poll(); + var processor = ComponentProcessorProvider.provide(focused); + if (processor.overrideControllerButtons(this, controller)) return true; + } + + return false; + } + + protected boolean handleComponentNavOverride(Controller controller) { + var focusTree = getFocusTree(); + while (!focusTree.isEmpty()) { + var focused = focusTree.poll(); + var processor = ComponentProcessorProvider.provide(focused); + if (processor.overrideControllerNavigation(this, controller)) return true; + } + return false; + } + protected void handleTabNavigation(Controller controller) { var nextTab = controller.bindings().GUI_NEXT_TAB.justPressed(); var prevTab = controller.bindings().GUI_PREV_TAB.justPressed();