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 6392a77..1167344 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java @@ -68,6 +68,8 @@ public interface ControllerBinding { JsonObject toJson(); + void tick(); + record KeyMappingOverride(KeyMapping keyMapping, BooleanSupplier toggleable) { } } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java index e23769d..5048a94 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java @@ -43,7 +43,7 @@ public class ControllerBindingImpl implements Control private static final Map, Set>> pressedBinds = new HashMap<>(); - private boolean fakePress, fakeRelease; + private int fakePressState = 0; private ControllerBindingImpl(Controller controller, IBind defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set contexts) { this.controller = controller; @@ -59,35 +59,33 @@ public class ControllerBindingImpl implements Control @Override public float state() { + if (fakePressState == 1) + return 1f; return bind.state(controller.state()); } @Override public float prevState() { + if (fakePressState == 2) + return 1f; return bind.state(controller.prevState()); } @Override public boolean held() { - if (fakePress) { - fakePress = false; - return true; - } - - return bind.held(controller.state()); + return fakePressState == 2 || bind.held(controller.state()); } @Override public boolean prevHeld() { - return bind.held(controller.prevState()); + return fakePressState == 3 || bind.held(controller.prevState()); } @Override public boolean justPressed() { if (hasBindPressed(this)) return false; - if ((held() && !prevHeld()) || fakePress) { - fakePress = false; + if ((held() && !prevHeld()) || fakePressState == 2) { addPressedBind(this); return true; } else { @@ -99,7 +97,7 @@ public class ControllerBindingImpl implements Control public boolean justReleased() { if (hasBindPressed(this)) return false; - if (!held() && prevHeld()) { + if ((!held() && prevHeld()) || fakePressState == 3) { addPressedBind(this); return true; } else { @@ -109,7 +107,15 @@ public class ControllerBindingImpl implements Control @Override public void fakePress() { - this.fakePress = true; + this.fakePressState = 1; + } + + @Override + public void tick() { + if (fakePressState > 0) + fakePressState++; + if (fakePressState >= 4) + fakePressState = 0; } public IBind currentBind() { diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 37dfb3c..6e02b4c 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -422,6 +422,9 @@ public class ControllerBindings { this.imitateVanillaClick(); } }); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + registry().values().forEach(ControllerBinding::tick); + }); ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> KeyMapping.releaseAll()); } diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialAction.java b/src/main/java/dev/isxander/controlify/bindings/RadialAction.java index 09dc21b..b2484e8 100644 --- a/src/main/java/dev/isxander/controlify/bindings/RadialAction.java +++ b/src/main/java/dev/isxander/controlify/bindings/RadialAction.java @@ -5,7 +5,7 @@ import net.minecraft.resources.ResourceLocation; public record RadialAction(ResourceLocation binding, ResourceLocation icon) { public static final RadialAction EMPTY = new RadialAction( - RadialMenuScreen.EMPTY, + RadialMenuScreen.EMPTY_ACTION, RadialIcons.EMPTY ); } diff --git a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java index 52390bd..6ddca6e 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java @@ -316,10 +316,10 @@ public class ControllerConfigScreenFactory { int action = i; radialMenuGroup.option(Option.createBuilder() .name(Component.translatable("controlify.gui.radial_menu_action", i + 1)) - .binding(RadialMenuScreen.EMPTY, () -> controller.config().radialActions[action].binding(), v -> controller.config().radialActions[action] = new RadialAction(v, controller.config().radialActions[action].icon())) + .binding(RadialMenuScreen.EMPTY_ACTION, () -> controller.config().radialActions[action].binding(), v -> controller.config().radialActions[action] = new RadialAction(v, controller.config().radialActions[action].icon())) .controller(opt -> CyclingListControllerBuilder.create(opt) - .values(Iterables.concat(Collections.singleton(RadialMenuScreen.EMPTY), controller.bindings().registry().keySet())) - .valueFormatter(id -> !RadialMenuScreen.EMPTY.equals(id) ? controller.bindings().get(id).name() : Component.literal("None"))) + .values(Iterables.concat(Collections.singleton(RadialMenuScreen.EMPTY_ACTION), controller.bindings().registry().keySet())) + .valueFormatter(id -> !RadialMenuScreen.EMPTY_ACTION.equals(id) ? controller.bindings().get(id).name() : Component.literal("None"))) .build()); } category.group(radialMenuGroup.build()); diff --git a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java index 1ea46c1..e10d882 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -2,18 +2,17 @@ package dev.isxander.controlify.gui.screen; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.bind.ControllerBinding; -import dev.isxander.controlify.api.guide.GuideActionNameSupplier; -import dev.isxander.controlify.bindings.GamepadBinds; import dev.isxander.controlify.bindings.RadialAction; import dev.isxander.controlify.bindings.RadialIcons; import dev.isxander.controlify.controller.Controller; -import dev.isxander.controlify.controller.gamepad.GamepadController; import dev.isxander.controlify.controller.gamepad.GamepadState; import dev.isxander.controlify.gui.guide.GuideAction; import dev.isxander.controlify.gui.guide.GuideActionRenderer; import dev.isxander.controlify.gui.layout.AnchorPoint; import dev.isxander.controlify.gui.layout.PositionedComponent; +import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ScreenControllerEventListener; +import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.sound.ControlifySounds; import dev.isxander.controlify.utils.Animator; import dev.isxander.controlify.utils.Easings; @@ -33,7 +32,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; import net.minecraft.util.Mth; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -41,7 +39,7 @@ import java.util.List; import java.util.Optional; public class RadialMenuScreen extends Screen implements ScreenControllerEventListener { - public static final ResourceLocation EMPTY = new ResourceLocation("controlify", "empty_action"); + public static final ResourceLocation EMPTY_ACTION = new ResourceLocation("controlify", "empty_action"); private final Controller controller; @@ -76,8 +74,8 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis Animator.AnimationInstance animation = new Animator.AnimationInstance(5, Easings::easeOutQuad); for (RadialButton radialButton : buttons) { - animation.addConsumer(radialButton::setX, centerX - 16, radialButton.getX()); - animation.addConsumer(radialButton::setY, centerY - 16, radialButton.getY()); + animation.addConsumer(radialButton::setX, centerX - 16, (float) radialButton.getX()); + animation.addConsumer(radialButton::setY, centerY - 16, (float) radialButton.getY()); } Animator.INSTANCE.play(animation); @@ -104,21 +102,12 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis if (this.controller != controller) return; if (!controller.bindings().RADIAL_MENU.held()) { - if (!isEditing) { - if (!editMode) { - if (selectedButton != -1 && buttons[selectedButton].invoke()) { - playClickSound(); - } - - onClose(); - } else { - RadialButton button = buttons[selectedButton]; - int x = button.x < width / 2 ? button.x - 110 : button.x + 42; - actionSelectList = new ActionSelectList(selectedButton, x, button.y, 100, 80); - addRenderableWidget(actionSelectList); - setFocused(actionSelectList); - isEditing = true; + if (!isEditing && !editMode) { + if (selectedButton != -1 && buttons[selectedButton].invoke()) { + playClickSound(); } + + onClose(); } } @@ -133,15 +122,9 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } } - if (isEditing) { - if (controller.bindings().GUI_PRESS.justPressed() || controller.bindings().GUI_BACK.justPressed()) { - finishEditing(); - } - } - if (!isEditing && controller.state() instanceof GamepadState state) { - float x = state.gamepadAxes().leftStickX(); - float y = state.gamepadAxes().leftStickY(); + float x = state.gamepadAxes().rightStickX(); + float y = state.gamepadAxes().rightStickY(); float threshold = controller.config().buttonActivationThreshold; if (Math.abs(x) >= threshold || Math.abs(y) >= threshold) { @@ -155,11 +138,15 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } for (int i = 0; i < buttons.length; i++) { - buttons[i].setFocused(i == selectedButton); + boolean selected = i == selectedButton; + buttons[i].setFocused(selected); + if (selected) { + this.setFocused(buttons[i]); + } } idleTicks = 0; - } else { + } else if (!editMode) { idleTicks++; if (idleTicks >= 20) { selectedButton = -1; @@ -177,7 +164,8 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis private void finishEditing() { isEditing = false; - onClose(); + removeWidget(actionSelectList); + actionSelectList = null; } @Override @@ -186,10 +174,11 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis super.onClose(); } - public class RadialButton implements Renderable, GuiEventListener, NarratableEntry { + public class RadialButton implements Renderable, GuiEventListener, NarratableEntry, ComponentProcessor { public static final ResourceLocation TEXTURE = Controlify.id("textures/gui/radial-buttons.png"); private int x, y; + private float translateX, translateY; private boolean focused; private final ControllerBinding binding; private final MultiLineLabel name; @@ -200,7 +189,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis this.y = y; RadialAction action = controller.config().radialActions[index]; - if (!EMPTY.equals(action.binding())) { + if (!EMPTY_ACTION.equals(action.binding())) { this.binding = controller.bindings().get(action.binding()); this.name = MultiLineLabel.create(font, this.binding.name(), 76); } else { @@ -213,13 +202,13 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { graphics.pose().pushPose(); - graphics.pose().translate(x, y, 0); + graphics.pose().translate(x + translateX, y + translateY, 0); graphics.pose().scale(2, 2, 1); graphics.blit(TEXTURE, 0, 0, focused ? 16 : 0, 0, 16, 16, 32, 16); graphics.pose().popPose(); graphics.pose().pushPose(); - graphics.pose().translate(x + 4, y + 4, 0); + graphics.pose().translate(x + translateX + 4, y + translateY + 4, 0); graphics.pose().scale(1.5f, 1.5f, 1); this.icon.draw(graphics, 0, 0); graphics.pose().popPose(); @@ -244,12 +233,14 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis return y; } - public void setX(int x) { - this.x = x; + public void setX(float x) { + this.x = (int) x; + this.translateX = x - this.x; } - public void setY(int y) { - this.y = y; + public void setY(float y) { + this.y = (int) y; + this.translateY = y - this.y; } @Override @@ -262,6 +253,22 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis this.focused = focused; } + @Override + public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) { + if (controller == RadialMenuScreen.this.controller) { + if (controller.bindings().GUI_PRESS.justPressed()) { + RadialButton button = buttons[selectedButton]; + int x = button.x < width / 2 ? button.x - 110 : button.x + 42; + actionSelectList = new ActionSelectList(selectedButton, x, button.y, 100, 80); + addRenderableWidget(actionSelectList); + RadialMenuScreen.this.setFocused(actionSelectList); + isEditing = true; + return true; + } + } + return false; + } + @Override public NarrationPriority narrationPriority() { return isFocused() ? NarrationPriority.FOCUSED : NarrationPriority.NONE; @@ -278,7 +285,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } } - public class ActionSelectList implements Renderable, ContainerEventHandler, NarratableEntry { + public class ActionSelectList implements Renderable, ContainerEventHandler, NarratableEntry, ComponentProcessor { private final int radialIndex; private int x, y; @@ -301,6 +308,13 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis controller.bindings().registry().forEach((id, binding) -> { children.add(new ActionEntry(id)); }); + + var selectedBind = controller.config().radialActions[radialIndex].binding(); + children.stream() + .filter(action -> action.binding.equals(selectedBind)) + .findAny() + .ifPresent(this::setFocused); + } @Override @@ -318,6 +332,18 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis graphics.renderOutline(x - 1, this.y - 1, width + 2, height + 2, 0x80ffffff); } + @Override + public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) { + if (controller == RadialMenuScreen.this.controller) { + if (controller.bindings().GUI_BACK.justPressed()) { + finishEditing(); + return true; + } + } + + return false; + } + @Override public List children() { return children; @@ -353,9 +379,6 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis else if (focusY + itemHeight > height) scrollOffset = Mth.clamp(index * itemHeight + itemHeight - height, 0, children().size() * itemHeight - height); } - - controller.config().radialActions[radialIndex] = new RadialAction(focus.binding, controller.config().radialActions[radialIndex].icon()); - Controlify.instance().config().setDirty(); } } @@ -379,7 +402,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } - public class ActionEntry implements GuiEventListener { + public class ActionEntry implements GuiEventListener, ComponentProcessor { private int x, y; private boolean focused; private final ResourceLocation binding; @@ -396,7 +419,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis if (focused) graphics.fill(x, y, x + width, y + itemHeight, 0xff000000); - graphics.drawString(RadialMenuScreen.this.font, name, x + 2, y + 1, -1); + graphics.drawString(RadialMenuScreen.this.font, name, x + 2, y + 1, focused ? -1 : 0xffa6a6a6); } @Override @@ -419,6 +442,23 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis public ScreenRectangle getRectangle() { return new ScreenRectangle(x, y, width, itemHeight); } + + @Override + public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) { + if (controller == RadialMenuScreen.this.controller) { + if (controller.bindings().GUI_PRESS.justPressed()) { + var icon = RadialIcons.getIcons().keySet().toArray(new ResourceLocation[0])[minecraft.level.random.nextInt(RadialIcons.getIcons().size())]; + controller.config().radialActions[radialIndex] = new RadialAction(binding, icon); + Controlify.instance().config().setDirty(); + + playClickSound(); + finishEditing(); + return true; + } + } + + return false; + } } } } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/ContainerEventHandlerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/ContainerEventHandlerMixin.java new file mode 100644 index 0000000..29d89b9 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/ContainerEventHandlerMixin.java @@ -0,0 +1,19 @@ +package dev.isxander.controlify.mixins.feature.screenop; + +import dev.isxander.controlify.screenop.CustomFocus; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ContainerEventHandler.class) +public interface ContainerEventHandlerMixin extends CustomFocus { + @Shadow + @Nullable GuiEventListener getFocused(); + + @Override + default GuiEventListener getCustomFocus() { + return this.getFocused(); + } +} diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index 4c8eb2e..d8871fb 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -53,6 +53,7 @@ "feature.rumble.levelevents.LevelRendererMixin", "feature.rumble.useitem.LivingEntityMixin", "feature.rumble.useitem.LocalPlayerMixin", + "feature.screenop.ContainerEventHandlerMixin", "feature.screenop.GameRendererMixin", "feature.screenop.MinecraftMixin", "feature.screenop.ScreenAccessor",