From 8efac5bdef5d4500cdda9a794726011794dd033f Mon Sep 17 00:00:00 2001 From: isXander Date: Mon, 24 Jul 2023 22:19:53 +0100 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9E=95=20Start=20work=20on=20radial=20me?= =?UTF-8?q?nu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/bind/ControllerBinding.java | 2 + .../bindings/ControllerBindingImpl.java | 15 +- .../bindings/ControllerBindings.java | 7 + .../controlify/bindings/RadialAction.java | 11 + .../controlify/bindings/RadialIcons.java | 80 ++++ .../controlify/config/ControlifyConfig.java | 2 + .../controller/ControllerConfig.java | 14 + .../gui/layout/PositionedComponent.java | 26 +- .../screen/ControllerConfigScreenFactory.java | 23 +- .../gui/screen/RadialMenuScreen.java | 424 ++++++++++++++++++ .../controlify/ingame/InGameInputHandler.java | 5 + .../mixins/feature/bind/GuiMixin.java | 21 + .../assets/controlify/lang/en_us.json | 3 + .../textures/gui/radial-buttons.png | Bin 0 -> 261 bytes src/main/resources/controlify.mixins.json | 19 +- 15 files changed, 636 insertions(+), 16 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/bindings/RadialAction.java create mode 100644 src/main/java/dev/isxander/controlify/bindings/RadialIcons.java create mode 100644 src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/bind/GuiMixin.java create mode 100644 src/main/resources/assets/controlify/textures/gui/radial-buttons.png 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 752d84c..6392a77 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java @@ -43,6 +43,8 @@ public interface ControllerBinding { */ boolean justReleased(); + void fakePress(); + Component name(); Component description(); Component category(); diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java index 8450f23..e23769d 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java @@ -43,6 +43,8 @@ public class ControllerBindingImpl implements Control private static final Map, Set>> pressedBinds = new HashMap<>(); + private boolean fakePress, fakeRelease; + 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; @@ -67,6 +69,11 @@ public class ControllerBindingImpl implements Control @Override public boolean held() { + if (fakePress) { + fakePress = false; + return true; + } + return bind.held(controller.state()); } @@ -79,7 +86,8 @@ public class ControllerBindingImpl implements Control public boolean justPressed() { if (hasBindPressed(this)) return false; - if (held() && !prevHeld()) { + if ((held() && !prevHeld()) || fakePress) { + fakePress = false; addPressedBind(this); return true; } else { @@ -99,6 +107,11 @@ public class ControllerBindingImpl implements Control } } + @Override + public void fakePress() { + this.fakePress = true; + } + public IBind currentBind() { return bind; } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 339c917..37dfb3c 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -59,6 +59,7 @@ public class ControllerBindings { PICK_BLOCK, TOGGLE_HUD_VISIBILITY, SHOW_PLAYER_LIST, + RADIAL_MENU, VMOUSE_MOVE_UP, VMOUSE_MOVE_DOWN, VMOUSE_MOVE_LEFT, VMOUSE_MOVE_RIGHT, VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_SHIFT_CLICK, VMOUSE_SCROLL_UP, VMOUSE_SCROLL_DOWN, @@ -295,6 +296,12 @@ public class ControllerBindings { .category(MISC_CATEGORY) .context(BindContexts.INGAME) .build()); + register(RADIAL_MENU = ControllerBindingBuilder.create(controller) + .identifier("controlify", "radial_menu") + .defaultBind(GamepadBinds.DPAD_DOWN) + .category(MISC_CATEGORY) + .context(BindContexts.INGAME) + .build()); register(VMOUSE_MOVE_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_up") .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialAction.java b/src/main/java/dev/isxander/controlify/bindings/RadialAction.java new file mode 100644 index 0000000..09dc21b --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/RadialAction.java @@ -0,0 +1,11 @@ +package dev.isxander.controlify.bindings; + +import dev.isxander.controlify.gui.screen.RadialMenuScreen; +import net.minecraft.resources.ResourceLocation; + +public record RadialAction(ResourceLocation binding, ResourceLocation icon) { + public static final RadialAction EMPTY = new RadialAction( + RadialMenuScreen.EMPTY, + RadialIcons.EMPTY + ); +} diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java new file mode 100644 index 0000000..b5898ec --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java @@ -0,0 +1,80 @@ +package dev.isxander.controlify.bindings; + +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.MobEffectTextureManager; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +public final class RadialIcons { + private static final Minecraft minecraft = Minecraft.getInstance(); + + public static final ResourceLocation EMPTY = new ResourceLocation("controlify", "empty"); + + private static final Map icons = Util.make(() -> { + Map map = new HashMap<>(); + + map.put(EMPTY, (graphics, x, y) -> {}); + addItems(map); + addPotionEffects(map); + + return map; + }); + + public static Map getIcons() { + return icons; + } + + private static void addItems(Map map) { + BuiltInRegistries.ITEM.entrySet().forEach(entry -> { + ResourceKey key = entry.getKey(); + ItemStack stack = entry.getValue().getDefaultInstance(); + + map.put(prefixLocation("item", key.location()), (graphics, x, y) -> { + graphics.renderItem(stack, x, y); + }); + }); + } + + private static void addPotionEffects(Map map) { + MobEffectTextureManager mobEffectTextureManager = minecraft.getMobEffectTextures(); + + BuiltInRegistries.MOB_EFFECT.entrySet().forEach(entry -> { + ResourceKey key = entry.getKey(); + MobEffect effect = entry.getValue(); + + TextureAtlasSprite sprite = mobEffectTextureManager.get(effect); + map.put(prefixLocation("effect", key.location()), (graphics, x, y) -> { + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0); + graphics.pose().scale(0.88f, 0.88f, 1f); + + graphics.blit(0, 0, 0, 18, 18, sprite); + + graphics.pose().popPose(); + }); + }); + } + + private static ResourceLocation prefixLocation(String prefix, ResourceLocation location) { + return new ResourceLocation(location.getNamespace(), prefix + "/" + location.getPath()); + } + + @FunctionalInterface + public interface Icon { + void draw(GuiGraphics graphics, int x, int y); + } +} diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index b4e1e3e..47fda1b 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -8,6 +8,7 @@ import dev.isxander.controlify.controller.joystick.CompoundJoystickInfo; import dev.isxander.controlify.utils.DebugLog; import dev.isxander.controlify.utils.Log; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -26,6 +27,7 @@ public class ControlifyConfig { .setPrettyPrinting() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeHierarchyAdapter(Class.class, new ClassTypeAdapter()) + .registerTypeHierarchyAdapter(ResourceLocation.class, new ResourceLocation.Serializer()) .create(); private final Controlify controlify; diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java index 5220650..914b0f3 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java @@ -2,7 +2,10 @@ package dev.isxander.controlify.controller; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import dev.isxander.controlify.bindings.RadialAction; +import dev.isxander.controlify.gui.screen.RadialMenuScreen; import dev.isxander.controlify.rumble.RumbleSource; +import net.minecraft.resources.ResourceLocation; import java.io.Serializable; @@ -36,6 +39,17 @@ public abstract class ControllerConfig implements Serializable { public boolean mixedInput = false; + public RadialAction[] radialActions = new RadialAction[]{ + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + RadialAction.EMPTY, + }; + public abstract void setDeadzone(int axis, float deadzone); public abstract float getDeadzone(int axis); diff --git a/src/main/java/dev/isxander/controlify/gui/layout/PositionedComponent.java b/src/main/java/dev/isxander/controlify/gui/layout/PositionedComponent.java index b612e34..7caf0bb 100644 --- a/src/main/java/dev/isxander/controlify/gui/layout/PositionedComponent.java +++ b/src/main/java/dev/isxander/controlify/gui/layout/PositionedComponent.java @@ -1,11 +1,13 @@ package dev.isxander.controlify.gui.layout; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; import org.joml.Vector2ic; -public class PositionedComponent implements Renderable { +public class PositionedComponent implements Renderable, GuiEventListener, NarratableEntry { private final T component; private int x, y; @@ -52,4 +54,24 @@ public class PositionedComponent implements Renderabl public T getComponent() { return component; } + + @Override + public void setFocused(boolean focused) { + + } + + @Override + public boolean isFocused() { + return false; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.NONE; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + + } } 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 bb4bf7f..52390bd 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java @@ -1,9 +1,11 @@ package dev.isxander.controlify.gui.screen; +import com.google.common.collect.Iterables; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.bind.ControllerBinding; import dev.isxander.controlify.bindings.BindContext; import dev.isxander.controlify.bindings.EmptyBind; +import dev.isxander.controlify.bindings.RadialAction; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerConfig; import dev.isxander.controlify.controller.gamepad.GamepadController; @@ -16,10 +18,7 @@ import dev.isxander.controlify.rumble.BasicRumbleEffect; import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.rumble.RumbleState; import dev.isxander.yacl3.api.*; -import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; -import dev.isxander.yacl3.api.controller.StringControllerBuilder; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.api.controller.*; import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.client.Minecraft; @@ -309,6 +308,22 @@ public class ControllerConfigScreenFactory { var category = ConfigCategory.createBuilder() .name(Component.translatable("controlify.gui.group.controls")); + var radialMenuGroup = OptionGroup.createBuilder() + .name(Component.translatable("controlify.gui.group.radial_menu")) + .collapsed(true); + radialMenuGroup.option(controller.bindings().RADIAL_MENU.startYACLOption().build()); + for (int i = 0; i < controller.config().radialActions.length; i++) { + 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())) + .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"))) + .build()); + } + category.group(radialMenuGroup.build()); + List optionBinds = new ArrayList<>(); groupBindings(controller.bindings().registry().values()).forEach((categoryName, bindGroup) -> { var controlsGroup = OptionGroup.createBuilder() diff --git a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java new file mode 100644 index 0000000..1ea46c1 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -0,0 +1,424 @@ +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.ScreenControllerEventListener; +import dev.isxander.controlify.sound.ControlifySounds; +import dev.isxander.controlify.utils.Animator; +import dev.isxander.controlify.utils.Easings; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +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; +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"); + + private final Controller controller; + + public RadialMenuScreen(Controller controller) { + super(Component.empty()); + this.controller = controller; + } + + private final RadialButton[] buttons = new RadialButton[8]; + private int selectedButton = -1; + private int idleTicks; + private boolean editMode; + private boolean isEditing; + + private PositionedComponent> editModeGuide; + private ActionSelectList actionSelectList; + + @Override + protected void init() { + int centerX = this.width / 2; + int centerY = this.height / 2; + + RadialButton button; + addRenderableWidget(buttons[0] = button = new RadialButton(0, centerX - 16, centerY - 64 - 8)); + addRenderableWidget(buttons[1] = button = new RadialButton(1, button.x + 32 + 8, button.y + 16)); + addRenderableWidget(buttons[2] = button = new RadialButton(2, button.x + 16, button.y + 32 + 8)); + addRenderableWidget(buttons[3] = button = new RadialButton(3, button.x - 16, button.y + 32 + 8)); + addRenderableWidget(buttons[4] = button = new RadialButton(4, button.x - 32 - 8, button.y + 16)); + addRenderableWidget(buttons[5] = button = new RadialButton(5, button.x - 32 - 8, button.y - 16)); + addRenderableWidget(buttons[6] = button = new RadialButton(6, button.x - 16, button.y - 32 - 8)); + addRenderableWidget(buttons[7] = new RadialButton(7, button.x + 16, button.y - 32 - 8)); + + 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()); + } + Animator.INSTANCE.play(animation); + + editModeGuide = addRenderableWidget(new PositionedComponent<>( + new GuideActionRenderer<>( + new GuideAction<>( + controller.bindings().GUI_ABSTRACT_ACTION_2, + obj -> Optional.of(Component.literal(!editMode ? "Edit Mode" : "Done Editing")) + ), + false, + true + ), + AnchorPoint.BOTTOM_CENTER, + 0, -10, + AnchorPoint.BOTTOM_CENTER + )); + + editModeGuide.getComponent().updateName(null); + editModeGuide.updatePosition(width, height); + } + + @Override + public void onControllerInput(Controller controller) { + 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 (controller.bindings().GUI_ABSTRACT_ACTION_2.justPressed()) { + editMode = !editMode; + editModeGuide.getComponent().updateName(null); + editModeGuide.updatePosition(width, height); + playClickSound(); + + if (!editMode) { + finishEditing(); + } + } + + 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 threshold = controller.config().buttonActivationThreshold; + + if (Math.abs(x) >= threshold || Math.abs(y) >= threshold) { + float angle = Mth.wrapDegrees(Mth.RAD_TO_DEG * (float) Mth.atan2(y, x) - 90f) + 180f; + float each = 360f / buttons.length; + + int newSelected = Mth.floor((angle + each / 2f) / each) % buttons.length; + if (newSelected != selectedButton) { + selectedButton = newSelected; + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(ControlifySounds.SCREEN_FOCUS_CHANGE, 1f)); + } + + for (int i = 0; i < buttons.length; i++) { + buttons[i].setFocused(i == selectedButton); + } + + idleTicks = 0; + } else { + idleTicks++; + if (idleTicks >= 20) { + selectedButton = -1; + for (RadialButton button : buttons) { + button.setFocused(false); + } + } + } + } + } + + private void playClickSound() { + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f)); + } + + private void finishEditing() { + isEditing = false; + onClose(); + } + + @Override + public void onClose() { + Controlify.instance().config().saveIfDirty(); + super.onClose(); + } + + public class RadialButton implements Renderable, GuiEventListener, NarratableEntry { + public static final ResourceLocation TEXTURE = Controlify.id("textures/gui/radial-buttons.png"); + + private int x, y; + private boolean focused; + private final ControllerBinding binding; + private final MultiLineLabel name; + private final RadialIcons.Icon icon; + + private RadialButton(int index, int x, int y) { + this.x = x; + this.y = y; + + RadialAction action = controller.config().radialActions[index]; + if (!EMPTY.equals(action.binding())) { + this.binding = controller.bindings().get(action.binding()); + this.name = MultiLineLabel.create(font, this.binding.name(), 76); + } else { + this.binding = null; + this.name = MultiLineLabel.EMPTY; + } + this.icon = RadialIcons.getIcons().get(action.icon()); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 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().scale(1.5f, 1.5f, 1); + this.icon.draw(graphics, 0, 0); + graphics.pose().popPose(); + + if (focused) + name.renderCentered(graphics, width / 2, height / 2 - font.lineHeight / 2 - ((name.getLineCount() - 1) * font.lineHeight / 2)); + } + + public boolean invoke() { + if (binding != null) { + binding.fakePress(); + return true; + } + return false; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public void setX(int x) { + this.x = x; + } + + public void setY(int y) { + this.y = y; + } + + @Override + public boolean isFocused() { + return focused; + } + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public NarrationPriority narrationPriority() { + return isFocused() ? NarrationPriority.FOCUSED : NarrationPriority.NONE; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + + } + + @Override + public ScreenRectangle getRectangle() { + return new ScreenRectangle(x, y, 32, 32); + } + } + + public class ActionSelectList implements Renderable, ContainerEventHandler, NarratableEntry { + private final int radialIndex; + + private int x, y; + private int width, height; + private final int itemHeight = 10; + private int scrollOffset; + + private boolean focused; + private ActionEntry focusedEntry; + + private final List children = new ArrayList<>(); + + public ActionSelectList(int index, int x, int y, int width, int height) { + this.radialIndex = index; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + controller.bindings().registry().forEach((id, binding) -> { + children.add(new ActionEntry(id)); + }); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + graphics.fill(x, y, x + width, y + height, 0x80000000); + + graphics.enableScissor(x, y, x + width, y + height); + int y = this.y - scrollOffset; + for (ActionEntry child : children) { + child.render(graphics, x, y, width, itemHeight, mouseX, mouseY, delta); + y += itemHeight; + } + graphics.disableScissor(); + + graphics.renderOutline(x - 1, this.y - 1, width + 2, height + 2, 0x80ffffff); + } + + @Override + public List children() { + return children; + } + + @Override + public boolean isDragging() { + return false; + } + + @Override + public void setDragging(boolean dragging) { + + } + + @Nullable + @Override + public GuiEventListener getFocused() { + return focusedEntry; + } + + @Override + public void setFocused(@Nullable GuiEventListener child) { + ActionEntry focus = (ActionEntry) child; + this.focusedEntry = focus; + + if (focus != null) { + int index = children().indexOf(child); + if (index != -1) { + int focusY = index * itemHeight - scrollOffset; + if (focusY < 0) + scrollOffset = Mth.clamp(index * itemHeight, 0, children().size() * itemHeight - height); + 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(); + } + } + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public boolean isFocused() { + return focused; + } + + @Override + public NarrationPriority narrationPriority() { + return focused ? NarrationPriority.FOCUSED : NarrationPriority.NONE; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + + } + + public class ActionEntry implements GuiEventListener { + private int x, y; + private boolean focused; + private final ResourceLocation binding; + private final Component name; + + public ActionEntry(ResourceLocation binding) { + this.binding = binding; + this.name = controller.bindings().get(binding).name(); + } + + public void render(GuiGraphics graphics, int x, int y, int width, int itemHeight, int mouseX, int mouseY, float delta) { + this.x = x; + this.y = y; + + if (focused) + graphics.fill(x, y, x + width, y + itemHeight, 0xff000000); + graphics.drawString(RadialMenuScreen.this.font, name, x + 2, y + 1, -1); + } + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public boolean isFocused() { + return focused; + } + + @Nullable + @Override + public ComponentPath nextFocusPath(FocusNavigationEvent event) { + return !focused ? ComponentPath.leaf(this) : null; + } + + @Override + public ScreenRectangle getRectangle() { + return new ScreenRectangle(x, y, width, itemHeight); + } + } + } +} diff --git a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java index f8f7b0c..7501472 100644 --- a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java +++ b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java @@ -7,6 +7,7 @@ import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.api.event.ControlifyEvents; import dev.isxander.controlify.controller.gamepad.GamepadController; import dev.isxander.controlify.controller.gamepad.GamepadState; +import dev.isxander.controlify.gui.screen.RadialMenuScreen; import dev.isxander.controlify.utils.Animator; import dev.isxander.controlify.utils.Easings; import dev.isxander.controlify.utils.NavigationHelper; @@ -106,6 +107,10 @@ public class InGameInputHandler { } shouldShowPlayerList = controller.bindings().SHOW_PLAYER_LIST.held(); + + if (controller.bindings().RADIAL_MENU.justPressed()) { + minecraft.setScreen(new RadialMenuScreen(controller)); + } } protected void handlePlayerLookInput() { diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/bind/GuiMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/bind/GuiMixin.java new file mode 100644 index 0000000..5c8423d --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/bind/GuiMixin.java @@ -0,0 +1,21 @@ +package dev.isxander.controlify.mixins.feature.bind; + +import com.llamalad7.mixinextras.injector.WrapWithCondition; +import dev.isxander.controlify.gui.screen.RadialMenuScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphics; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Gui.class) +public class GuiMixin { + @Shadow @Final private Minecraft minecraft; + + @WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderCrosshair(Lnet/minecraft/client/gui/GuiGraphics;)V")) + private boolean shouldRenderCrosshair(Gui instance, GuiGraphics graphics) { + return !(minecraft.screen instanceof RadialMenuScreen); + } +} diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 6714c78..6a2c754 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -109,6 +109,8 @@ "controlify.gui.test_vibration": "Test Vibration", "controlify.gui.test_vibration.tooltip": "Test the vibration of your controller.", + "controlify.gui.group.radial_menu": "Radial Menu", + "controlify.gui.radial_menu_action": "Action #%s", "controlify.gui.group.controls": "Controls", "controlify.gui.group.controls.tooltip": "Adjust the controller controls.", "controlify.gui.bind_input_awaiting": "Press any button", @@ -211,6 +213,7 @@ "controlify.binding.controlify.pick_block": "Pick Block", "controlify.binding.controlify.toggle_hud_visibility": "Toggle HUD Visibility", "controlify.binding.controlify.show_player_list": "Show Player List", + "controlify.binding.controlify.radial_menu": "Radial Menu", "controlify.binding.controlify.vmouse_move_up": "VMouse Move Up", "controlify.binding.controlify.vmouse_move_down": "VMouse Move Down", "controlify.binding.controlify.vmouse_move_left": "VMouse Move Left", diff --git a/src/main/resources/assets/controlify/textures/gui/radial-buttons.png b/src/main/resources/assets/controlify/textures/gui/radial-buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..798cdc8aa822fd34e11658b718d223d5671e7e06 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYe6k~CayA#8@b22Z1oTZ*Fjv*eM zZ>KI4Jfa}t5`W1fMn=p%bY=6ZM;k0;LLAsF4^B|-+;M)U#5KdizjtSqKU&>v=ezxT ze2s0v%G(@^7>)jmTk5R9UkMNYtWbWT@ z`;6h7<+M~r4aR#{I1HG27?cn2EWJH7C`sda%{7B5dDo-5e@+oHbbDZ?7G(NNeBZU# z`bQ=7lS96qlBs7fJL3JVdfkGrKnL@sMtG+A`Z8z%*&IL&0+)g(gD6i|KbLh*2~7Y% CK4Jy{ literal 0 HcmV?d00001 diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index 4caac03..4c8eb2e 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -7,37 +7,33 @@ }, "compatibilityLevel": "JAVA_17", "client": [ + "compat.fapi.KeyBindingRegistryImplAccessor", "compat.iris.BaseOptionElementWidgetMixin", "compat.sodium.CycleControlElementMixin", "compat.sodium.SliderControlElementMixin", - "compat.sodium.TickBoxControlElementMixin", - "core.GLXMixin", - "feature.fixes.boatfix.BoatMixin", - "feature.rumble.explosion.LightningBoltMixin", - "feature.rumble.fishing.FishingHookMixin", - "feature.rumble.itembreak.LivingEntityMixin", - "feature.rumble.levelevents.LevelRendererMixin", - "feature.rumble.useitem.LivingEntityMixin", - "compat.fapi.KeyBindingRegistryImplAccessor", "compat.sodium.SodiumOptionsGUIAccessor", "compat.sodium.SodiumOptionsGUIMixin", + "compat.sodium.TickBoxControlElementMixin", "compat.yacl.CyclingControllerElementMixin", "compat.yacl.SliderControllerElementMixin", "compat.yacl.YACLScreenCategoryTabAccessor", "compat.yacl.YACLScreenCategoryTabMixin", "compat.yacl.YACLScreenMixin", "core.ClientPacketListenerMixin", + "core.GLXMixin", "core.GuiMixin", "core.KeyboardHandlerMixin", "core.MinecraftMixin", "core.MouseHandlerMixin", "feature.accessibility.LocalPlayerMixin", "feature.autoswitch.ToastComponentAccessor", + "feature.bind.GuiMixin", "feature.bind.KeyMappingAccessor", "feature.bind.KeyMappingMixin", "feature.bind.ToggleKeyMappingAccessor", "feature.chatkbheight.ChatComponentMixin", "feature.chatkbheight.ChatScreenMixin", + "feature.fixes.boatfix.BoatMixin", "feature.fixes.boatfix.LocalPlayerMixin", "feature.guide.ingame.ClientPacketListenerMixin", "feature.guide.ingame.GuiMixin", @@ -50,7 +46,12 @@ "feature.rumble.blockbreak.MultiPlayerGameModeMixin", "feature.rumble.damage.LocalPlayerMixin", "feature.rumble.explosion.ClientPacketListenerMixin", + "feature.rumble.explosion.LightningBoltMixin", + "feature.rumble.fishing.FishingHookMixin", + "feature.rumble.itembreak.LivingEntityMixin", "feature.rumble.itembreak.LocalPlayerMixin", + "feature.rumble.levelevents.LevelRendererMixin", + "feature.rumble.useitem.LivingEntityMixin", "feature.rumble.useitem.LocalPlayerMixin", "feature.screenop.GameRendererMixin", "feature.screenop.MinecraftMixin", From dd058d3983e3756574a448c9d4dce3a9400f883f Mon Sep 17 00:00:00 2001 From: isXander Date: Fri, 28 Jul 2023 18:08:47 +0100 Subject: [PATCH 2/9] More radial changes --- .../api/bind/ControllerBinding.java | 2 + .../bindings/ControllerBindingImpl.java | 30 ++-- .../bindings/ControllerBindings.java | 3 + .../controlify/bindings/RadialAction.java | 2 +- .../screen/ControllerConfigScreenFactory.java | 6 +- .../gui/screen/RadialMenuScreen.java | 132 ++++++++++++------ .../screenop/ContainerEventHandlerMixin.java | 19 +++ src/main/resources/controlify.mixins.json | 1 + 8 files changed, 133 insertions(+), 62 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/screenop/ContainerEventHandlerMixin.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 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", From 2048a094777b7cb2f5c23c8217757f45be6db49f Mon Sep 17 00:00:00 2001 From: isXander Date: Fri, 28 Jul 2023 23:40:33 +0100 Subject: [PATCH 3/9] Only allow handpicked bindings for radial menu (and all modded binds) with hand-picked icons for all. --- .../api/bind/ControllerBinding.java | 4 ++++ .../api/bind/ControllerBindingBuilder.java | 7 +++--- .../bindings/ControllerBindingImpl.java | 23 ++++++++++++++----- .../bindings/ControllerBindings.java | 14 +++++++++++ .../controlify/bindings/RadialAction.java | 11 --------- .../controlify/bindings/RadialIcons.java | 20 ++++++++++++---- .../controller/ControllerConfig.java | 19 ++++++++------- .../gui/screen/RadialMenuScreen.java | 23 ++++++++++--------- 8 files changed, 75 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/dev/isxander/controlify/bindings/RadialAction.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 1167344..ec165a3 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBinding.java @@ -3,12 +3,14 @@ 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.controlify.bindings.RadialIcons; import dev.isxander.yacl3.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.Optional; import java.util.Set; import java.util.function.BooleanSupplier; @@ -70,6 +72,8 @@ public interface ControllerBinding { void tick(); + Optional radialIcon(); + record KeyMappingOverride(KeyMapping keyMapping, BooleanSupplier toggleable) { } } 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 a1f24c0..91e9152 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControllerBindingBuilder.java @@ -1,9 +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; +import dev.isxander.controlify.bindings.*; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerState; import net.minecraft.client.KeyMapping; @@ -76,6 +73,8 @@ public interface ControllerBindingBuilder { ControllerBindingBuilder context(BindContext... contexts); + ControllerBindingBuilder radialCandidate(ResourceLocation icon); + /** * 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/ControllerBindingImpl.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java index 5048a94..730dcbc 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindingImpl.java @@ -25,10 +25,7 @@ import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BooleanSupplier; public class ControllerBindingImpl implements ControllerBinding { @@ -39,13 +36,14 @@ public class ControllerBindingImpl implements Control private final ResourceLocation id; private final Component name, description, category; private final Set contexts; + private final ResourceLocation radialIcon; private final KeyMappingOverride override; private static final Map, Set>> pressedBinds = new HashMap<>(); private int fakePressState = 0; - private ControllerBindingImpl(Controller controller, IBind defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set contexts) { + private ControllerBindingImpl(Controller controller, IBind defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set contexts, ResourceLocation icon) { this.controller = controller; this.bind = this.defaultBind = defaultBind; this.renderer = new BindRendererImpl(bind); @@ -55,6 +53,7 @@ public class ControllerBindingImpl implements Control this.description = description; this.category = category; this.contexts = ImmutableSet.copyOf(contexts); + this.radialIcon = icon; } @Override @@ -118,6 +117,11 @@ public class ControllerBindingImpl implements Control fakePressState = 0; } + @Override + public Optional radialIcon() { + return Optional.ofNullable(this.radialIcon); + } + public IBind currentBind() { return bind; } @@ -233,6 +237,7 @@ public class ControllerBindingImpl implements Control private Component name = null, description = null, category = null; private KeyMappingOverride override = null; private final Set contexts = new HashSet<>(); + private ResourceLocation radialIcon = null; public ControllerBindingBuilderImpl(Controller controller) { this.controller = controller; @@ -290,6 +295,12 @@ public class ControllerBindingImpl implements Control return this; } + @Override + public ControllerBindingBuilder radialCandidate(ResourceLocation icon) { + this.radialIcon = icon; + return this; + } + @Override public ControllerBindingBuilder vanillaOverride(KeyMapping keyMapping, BooleanSupplier toggleable) { this.override = new KeyMappingOverride(keyMapping, toggleable); @@ -318,7 +329,7 @@ public class ControllerBindingImpl implements Control } } - return new ControllerBindingImpl<>(controller, bind, id, override, name, description, category, contexts); + return new ControllerBindingImpl<>(controller, bind, id, override, name, description, category, contexts, radialIcon); } } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 6e02b4c..0767292 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -20,6 +20,8 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.ToggleKeyMapping; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.item.Items; import java.util.*; import java.util.function.BooleanSupplier; @@ -140,6 +142,7 @@ public class ControllerBindings { .defaultBind(GamepadBinds.A_BUTTON) .category(MOVEMENT_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getEffect(MobEffects.JUMP)) .build()); register(SPRINT = ControllerBindingBuilder.create(controller) .identifier("controlify", "sprint") @@ -173,12 +176,14 @@ public class ControllerBindings { .defaultBind(GamepadBinds.DPAD_DOWN) .category(GAMEPLAY_CATEGORY) .context(BindContexts.INGAME, BindContexts.INVENTORY) + .radialCandidate(RadialIcons.getItem(Items.BARRIER)) .build()); register(DROP_STACK = ControllerBindingBuilder.create(controller) .identifier("controlify", "drop_stack") .defaultBind(new EmptyBind<>()) .category(GAMEPLAY_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.TNT)) .build()); register(NEXT_SLOT = ControllerBindingBuilder.create(controller) .identifier("controlify", "next_slot") @@ -197,30 +202,35 @@ public class ControllerBindings { .defaultBind(GamepadBinds.START) .category(GAMEPLAY_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.STRUCTURE_VOID)) .build()); register(INVENTORY = ControllerBindingBuilder.create(controller) .identifier("controlify", "inventory") .defaultBind(GamepadBinds.Y_BUTTON) .category(INVENTORY_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.CHEST)) .build()); register(CHANGE_PERSPECTIVE = ControllerBindingBuilder.create(controller) .identifier("controlify", "change_perspective") .defaultBind(GamepadBinds.BACK) .category(GAMEPLAY_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.PAINTING)) .build()); register(SWAP_HANDS = ControllerBindingBuilder.create(controller) .identifier("controlify", "swap_hands") .defaultBind(GamepadBinds.X_BUTTON) .category(INVENTORY_CATEGORY) .context(BindContexts.INGAME, BindContexts.INVENTORY) + .radialCandidate(RadialIcons.getItem(Items.BONE)) .build()); register(OPEN_CHAT = ControllerBindingBuilder.create(controller) .identifier("controlify", "open_chat") .defaultBind(GamepadBinds.DPAD_UP) .category(MISC_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.WRITABLE_BOOK)) .vanillaOverride(options.keyChat, () -> false) .build()); register(GUI_PRESS = ControllerBindingBuilder.create(controller) @@ -282,6 +292,7 @@ public class ControllerBindings { .defaultBind(GamepadBinds.DPAD_LEFT) .category(GAMEPLAY_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.STICK)) .vanillaOverride(options.keyPickItem, () -> false) .build()); register(TOGGLE_HUD_VISIBILITY = ControllerBindingBuilder.create(controller) @@ -289,12 +300,14 @@ public class ControllerBindings { .defaultBind(new EmptyBind<>()) .category(MISC_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getEffect(MobEffects.INVISIBILITY)) .build()); register(SHOW_PLAYER_LIST = ControllerBindingBuilder.create(controller) .identifier("controlify", "show_player_list") .defaultBind(GamepadBinds.DPAD_RIGHT) .category(MISC_CATEGORY) .context(BindContexts.INGAME) + .radialCandidate(RadialIcons.getItem(Items.PLAYER_HEAD)) .build()); register(RADIAL_MENU = ControllerBindingBuilder.create(controller) .identifier("controlify", "radial_menu") @@ -520,6 +533,7 @@ public class ControllerBindings { .name(Component.translatable(keyMapping.getName())) .description(Component.translatable("controlify.custom_binding.vanilla_description").withStyle(ChatFormatting.GRAY)) .category(Component.translatable(keyMapping.getCategory())) + .radialCandidate(RadialIcons.FABRIC_ICON) .vanillaOverride(keyMapping, toggleOverride) .build(); diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialAction.java b/src/main/java/dev/isxander/controlify/bindings/RadialAction.java deleted file mode 100644 index b2484e8..0000000 --- a/src/main/java/dev/isxander/controlify/bindings/RadialAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.controlify.bindings; - -import dev.isxander.controlify.gui.screen.RadialMenuScreen; -import net.minecraft.resources.ResourceLocation; - -public record RadialAction(ResourceLocation binding, ResourceLocation icon) { - public static final RadialAction EMPTY = new RadialAction( - RadialMenuScreen.EMPTY_ACTION, - RadialIcons.EMPTY - ); -} diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java index b5898ec..a8f65ed 100644 --- a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java +++ b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java @@ -5,15 +5,11 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.MobEffectTextureManager; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.effect.MobEffect; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import java.util.HashMap; @@ -23,11 +19,19 @@ public final class RadialIcons { private static final Minecraft minecraft = Minecraft.getInstance(); public static final ResourceLocation EMPTY = new ResourceLocation("controlify", "empty"); + public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabricloader", "icon"); private static final Map icons = Util.make(() -> { Map map = new HashMap<>(); map.put(EMPTY, (graphics, x, y) -> {}); + map.put(FABRIC_ICON, ((graphics, x, y) -> { + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0); + graphics.pose().scale(0.5f, 0.5f, 1f); + graphics.blit(FABRIC_ICON, 0, 0, 0, 0, 32, 32); + graphics.pose().popPose(); + })); addItems(map); addPotionEffects(map); @@ -38,6 +42,14 @@ public final class RadialIcons { return icons; } + public static ResourceLocation getItem(Item item) { + return prefixLocation("item", BuiltInRegistries.ITEM.getKey(item)); + } + + public static ResourceLocation getEffect(MobEffect effect) { + return prefixLocation("effect", BuiltInRegistries.MOB_EFFECT.getKey(effect)); + } + private static void addItems(Map map) { BuiltInRegistries.ITEM.entrySet().forEach(entry -> { ResourceKey key = entry.getKey(); diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java index 914b0f3..6ddba16 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java @@ -2,7 +2,6 @@ package dev.isxander.controlify.controller; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import dev.isxander.controlify.bindings.RadialAction; import dev.isxander.controlify.gui.screen.RadialMenuScreen; import dev.isxander.controlify.rumble.RumbleSource; import net.minecraft.resources.ResourceLocation; @@ -39,15 +38,15 @@ public abstract class ControllerConfig implements Serializable { public boolean mixedInput = false; - public RadialAction[] radialActions = new RadialAction[]{ - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, - RadialAction.EMPTY, + public ResourceLocation[] radialActions = new ResourceLocation[]{ + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, + RadialMenuScreen.EMPTY_ACTION, }; public abstract void setDeadzone(int axis, float deadzone); 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 e10d882..aafb03d 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -2,7 +2,6 @@ package dev.isxander.controlify.gui.screen; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.bind.ControllerBinding; -import dev.isxander.controlify.bindings.RadialAction; import dev.isxander.controlify.bindings.RadialIcons; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.gamepad.GamepadState; @@ -36,6 +35,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; public class RadialMenuScreen extends Screen implements ScreenControllerEventListener { @@ -188,15 +188,16 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis this.x = x; this.y = y; - RadialAction action = controller.config().radialActions[index]; - if (!EMPTY_ACTION.equals(action.binding())) { - this.binding = controller.bindings().get(action.binding()); + ResourceLocation binding = controller.config().radialActions[index]; + if (!EMPTY_ACTION.equals(binding)) { + this.binding = controller.bindings().get(binding); + this.icon = RadialIcons.getIcons().get(this.binding.radialIcon().orElseThrow()); this.name = MultiLineLabel.create(font, this.binding.name(), 76); } else { this.binding = null; this.name = MultiLineLabel.EMPTY; + this.icon = RadialIcons.getIcons().get(RadialIcons.EMPTY); } - this.icon = RadialIcons.getIcons().get(action.icon()); } @Override @@ -305,11 +306,12 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis this.width = width; this.height = height; - controller.bindings().registry().forEach((id, binding) -> { - children.add(new ActionEntry(id)); - }); + controller.bindings().registry().entrySet().stream() + .filter(entry -> entry.getValue().radialIcon().isPresent()) + .map(Map.Entry::getKey) + .forEach(id -> children.add(new ActionEntry(id))); - var selectedBind = controller.config().radialActions[radialIndex].binding(); + var selectedBind = controller.config().radialActions[radialIndex]; children.stream() .filter(action -> action.binding.equals(selectedBind)) .findAny() @@ -447,8 +449,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis 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); + controller.config().radialActions[radialIndex] = binding; Controlify.instance().config().setDirty(); playClickSound(); From 2f6fa8f97a4b29f391ad5ecba07cbd1f4f7e3442 Mon Sep 17 00:00:00 2001 From: isXander Date: Fri, 28 Jul 2023 23:45:36 +0100 Subject: [PATCH 4/9] Remove old radial options from YACL config --- .../screen/ControllerConfigScreenFactory.java | 17 ----------------- 1 file changed, 17 deletions(-) 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 6ddca6e..5dbbbcf 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java @@ -5,7 +5,6 @@ import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.bind.ControllerBinding; import dev.isxander.controlify.bindings.BindContext; import dev.isxander.controlify.bindings.EmptyBind; -import dev.isxander.controlify.bindings.RadialAction; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerConfig; import dev.isxander.controlify.controller.gamepad.GamepadController; @@ -308,22 +307,6 @@ public class ControllerConfigScreenFactory { var category = ConfigCategory.createBuilder() .name(Component.translatable("controlify.gui.group.controls")); - var radialMenuGroup = OptionGroup.createBuilder() - .name(Component.translatable("controlify.gui.group.radial_menu")) - .collapsed(true); - radialMenuGroup.option(controller.bindings().RADIAL_MENU.startYACLOption().build()); - for (int i = 0; i < controller.config().radialActions.length; i++) { - int action = i; - radialMenuGroup.option(Option.createBuilder() - .name(Component.translatable("controlify.gui.radial_menu_action", i + 1)) - .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_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()); - List optionBinds = new ArrayList<>(); groupBindings(controller.bindings().registry().values()).forEach((categoryName, bindGroup) -> { var controlsGroup = OptionGroup.createBuilder() From ec7a20606ad0b127f7239da03a63f937a74cafe7 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 6 Aug 2023 19:52:37 +0100 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=90=9B=20Radial=20menu:=20fix=20the?= =?UTF-8?q?=20icon=20for=20modded=20keybinds=20not=20working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/dev/isxander/controlify/bindings/RadialIcons.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java index a8f65ed..413a762 100644 --- a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java +++ b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java @@ -19,7 +19,7 @@ public final class RadialIcons { private static final Minecraft minecraft = Minecraft.getInstance(); public static final ResourceLocation EMPTY = new ResourceLocation("controlify", "empty"); - public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabricloader", "icon"); + public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabric", "icon"); private static final Map icons = Util.make(() -> { Map map = new HashMap<>(); From 0d487c780d4938e2cf3045f53fa1867db82a4ba4 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 6 Aug 2023 19:53:13 +0100 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Radial=20menu:=20move?= =?UTF-8?q?=20editing=20of=20actions=20into=20the=20config=20screen=20as?= =?UTF-8?q?=20it's=20more=20intuitive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/ControllerConfigScreenFactory.java | 14 ++ .../gui/screen/RadialMenuScreen.java | 121 ++++++++++-------- .../controlify/ingame/InGameInputHandler.java | 2 +- .../assets/controlify/lang/en_us.json | 7 +- 4 files changed, 85 insertions(+), 59 deletions(-) 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 5dbbbcf..d14d5d5 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java @@ -308,6 +308,20 @@ public class ControllerConfigScreenFactory { .name(Component.translatable("controlify.gui.group.controls")); List optionBinds = new ArrayList<>(); + + ButtonOption editRadialButton = ButtonOption.createBuilder() + .name(Component.translatable("controlify.gui.radial_menu")) + .description(OptionDescription.of(Component.translatable("controlify.gui.radial_menu.tooltip"))) + .action((screen, opt) -> Minecraft.getInstance().setScreen(new RadialMenuScreen(controller, true, screen))) + .text(Component.translatable("controlify.gui.radial_menu.btn_text")) + .build(); + Option radialBind = controller.bindings().RADIAL_MENU.startYACLOption() + .listener((opt, val) -> updateConflictingBinds(optionBinds)) + .build(); + optionBinds.add(new OptionBindPair(radialBind, controller.bindings().RADIAL_MENU)); + category.option(editRadialButton); + category.option(radialBind); + groupBindings(controller.bindings().registry().values()).forEach((categoryName, bindGroup) -> { var controlsGroup = OptionGroup.createBuilder() .name(categoryName); 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 aafb03d..849c42a 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -27,6 +27,7 @@ import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; @@ -42,21 +43,23 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis public static final ResourceLocation EMPTY_ACTION = new ResourceLocation("controlify", "empty_action"); private final Controller controller; - - public RadialMenuScreen(Controller controller) { - super(Component.empty()); - this.controller = controller; - } + private final boolean editMode; + private final Screen parent; private final RadialButton[] buttons = new RadialButton[8]; private int selectedButton = -1; private int idleTicks; - private boolean editMode; private boolean isEditing; - private PositionedComponent> editModeGuide; private ActionSelectList actionSelectList; + public RadialMenuScreen(Controller controller, boolean editMode, Screen parent) { + super(Component.empty()); + this.controller = controller; + this.editMode = editMode; + this.parent = parent; + } + @Override protected void init() { int centerX = this.width / 2; @@ -79,47 +82,36 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } Animator.INSTANCE.play(animation); - editModeGuide = addRenderableWidget(new PositionedComponent<>( - new GuideActionRenderer<>( - new GuideAction<>( - controller.bindings().GUI_ABSTRACT_ACTION_2, - obj -> Optional.of(Component.literal(!editMode ? "Edit Mode" : "Done Editing")) - ), - false, - true - ), - AnchorPoint.BOTTOM_CENTER, - 0, -10, - AnchorPoint.BOTTOM_CENTER - )); + if (editMode) { + var exitGuide = addRenderableWidget(new PositionedComponent<>( + new GuideActionRenderer<>( + new GuideAction<>( + controller.bindings().GUI_BACK, + obj -> Optional.of(CommonComponents.GUI_DONE) + ), + false, + true + ), + AnchorPoint.BOTTOM_CENTER, + 0, -10, + AnchorPoint.BOTTOM_CENTER + )); - editModeGuide.getComponent().updateName(null); - editModeGuide.updatePosition(width, height); + exitGuide.getComponent().updateName(null); + exitGuide.updatePosition(width, height); + } } @Override public void onControllerInput(Controller controller) { if (this.controller != controller) return; - if (!controller.bindings().RADIAL_MENU.held()) { - if (!isEditing && !editMode) { - if (selectedButton != -1 && buttons[selectedButton].invoke()) { - playClickSound(); - } - - onClose(); + if (!editMode && !controller.bindings().RADIAL_MENU.held()) { + if (selectedButton != -1 && buttons[selectedButton].invoke()) { + playClickSound(); } - } - if (controller.bindings().GUI_ABSTRACT_ACTION_2.justPressed()) { - editMode = !editMode; - editModeGuide.getComponent().updateName(null); - editModeGuide.updatePosition(width, height); - playClickSound(); - - if (!editMode) { - finishEditing(); - } + onClose(); } if (!isEditing && controller.state() instanceof GamepadState state) { @@ -158,6 +150,18 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } } + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (minecraft.level == null) + renderDirtBackground(graphics); + + super.render(graphics, mouseX, mouseY, delta); + + if (!editMode) { + graphics.drawCenteredString(font, Component.translatable("controlify.radial_menu.configure_hint"), width / 2, height - 10 - font.lineHeight, -1); + } + } + private void playClickSound() { minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f)); } @@ -171,7 +175,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Override public void onClose() { Controlify.instance().config().saveIfDirty(); - super.onClose(); + minecraft.setScreen(parent); } public class RadialButton implements Renderable, GuiEventListener, NarratableEntry, ComponentProcessor { @@ -180,24 +184,15 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis private int x, y; private float translateX, translateY; private boolean focused; - private final ControllerBinding binding; - private final MultiLineLabel name; - private final RadialIcons.Icon icon; + private ControllerBinding binding; + private MultiLineLabel name; + private RadialIcons.Icon icon; - private RadialButton(int index, int x, int y) { - this.x = x; - this.y = y; + private RadialButton(int index, float x, float y) { + this.setX(x); + this.setY(y); - ResourceLocation binding = controller.config().radialActions[index]; - if (!EMPTY_ACTION.equals(binding)) { - this.binding = controller.bindings().get(binding); - this.icon = RadialIcons.getIcons().get(this.binding.radialIcon().orElseThrow()); - this.name = MultiLineLabel.create(font, this.binding.name(), 76); - } else { - this.binding = null; - this.name = MultiLineLabel.EMPTY; - this.icon = RadialIcons.getIcons().get(RadialIcons.EMPTY); - } + this.setAction(controller.config().radialActions[index]); } @Override @@ -226,6 +221,18 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis return false; } + public void setAction(ResourceLocation binding) { + if (!EMPTY_ACTION.equals(binding)) { + this.binding = controller.bindings().get(binding); + this.icon = RadialIcons.getIcons().get(this.binding.radialIcon().orElseThrow()); + this.name = MultiLineLabel.create(font, this.binding.name(), 76); + } else { + this.binding = null; + this.name = MultiLineLabel.EMPTY; + this.icon = RadialIcons.getIcons().get(RadialIcons.EMPTY); + } + } + public int getX() { return x; } @@ -452,6 +459,8 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis controller.config().radialActions[radialIndex] = binding; Controlify.instance().config().setDirty(); + buttons[radialIndex].setAction(binding); + playClickSound(); finishEditing(); return true; diff --git a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java index 7501472..c6f97ad 100644 --- a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java +++ b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java @@ -109,7 +109,7 @@ public class InGameInputHandler { shouldShowPlayerList = controller.bindings().SHOW_PLAYER_LIST.held(); if (controller.bindings().RADIAL_MENU.justPressed()) { - minecraft.setScreen(new RadialMenuScreen(controller)); + minecraft.setScreen(new RadialMenuScreen(controller, false, null)); } } diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 6a2c754..b5b9213 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -109,8 +109,9 @@ "controlify.gui.test_vibration": "Test Vibration", "controlify.gui.test_vibration.tooltip": "Test the vibration of your controller.", - "controlify.gui.group.radial_menu": "Radial Menu", - "controlify.gui.radial_menu_action": "Action #%s", + "controlify.gui.radial_menu": "Radial Menu", + "controlify.gui.radial_menu.tooltip": "Open up the radial menu to configure what each button does.", + "controlify.gui.radial_menu.btn_text": "CONFIGURE", "controlify.gui.group.controls": "Controls", "controlify.gui.group.controls.tooltip": "Adjust the controller controls.", "controlify.gui.bind_input_awaiting": "Press any button", @@ -282,6 +283,8 @@ "controlify.calibration.later": "Maybe Later", "controlify.calibration.later.tooltip": "You must calibrate to use the controller. Pressing this will deactivate the controller and you will have to use it again to calibrate.", + "controlify.radial_menu.configure_hint": "Configure actions in the controller settings.", + "controlify.beta.title": "Controlify Beta Notice", "controlify.beta.message": "You are currently using Controlify Beta.\n\nThis mod is a work in progress and will contain many bugs. Please, if you spot a bug in this mod or have a suggestion to make it even better, please create an issue on the %s!\n\nYou can always find the link to the issue tracker in Controlify's settings menu.", "controlify.beta.message.link": "issue tracker", From efc08f9923be71bc77c3f1e9ba89ef01249ae48f Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 6 Aug 2023 21:56:56 +0100 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Finishing=20touches=20?= =?UTF-8?q?on=20radial=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bindings/ControllerBindings.java | 35 ++++++- .../controlify/bindings/RadialIcons.java | 6 +- .../controlify/config/ControlifyConfig.java | 2 +- .../controller/AbstractController.java | 1 + .../controller/ControllerConfig.java | 40 ++++++-- .../gui/guide/InGameButtonGuide.java | 5 + .../gui/screen/RadialMenuScreen.java | 96 ++++++++++++++----- .../controlify/ingame/InGameInputHandler.java | 4 +- 8 files changed, 146 insertions(+), 43 deletions(-) diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 0767292..ef0603b 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -39,6 +39,7 @@ public class ControllerBindings { public static final Component VMOUSE_CATEGORY = Component.translatable("controlify.binding_category.vmouse"); public static final Component GUI_CATEGORY = Component.translatable("controlify.binding_category.gui"); public static final Component MISC_CATEGORY = Component.translatable("key.categories.misc"); + public static final Component RADIAL_CATEGORY = Component.translatable("controlify.gui.radial_menu"); public final ControllerBinding WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT, @@ -61,7 +62,7 @@ public class ControllerBindings { PICK_BLOCK, TOGGLE_HUD_VISIBILITY, SHOW_PLAYER_LIST, - RADIAL_MENU, + RADIAL_MENU, RADIAL_AXIS_UP, RADIAL_AXIS_DOWN, RADIAL_AXIS_LEFT, RADIAL_AXIS_RIGHT, VMOUSE_MOVE_UP, VMOUSE_MOVE_DOWN, VMOUSE_MOVE_LEFT, VMOUSE_MOVE_RIGHT, VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_SHIFT_CLICK, VMOUSE_SCROLL_UP, VMOUSE_SCROLL_DOWN, @@ -222,7 +223,7 @@ public class ControllerBindings { .identifier("controlify", "swap_hands") .defaultBind(GamepadBinds.X_BUTTON) .category(INVENTORY_CATEGORY) - .context(BindContexts.INGAME, BindContexts.INVENTORY) + .context(BindContexts.INGAME) .radialCandidate(RadialIcons.getItem(Items.BONE)) .build()); register(OPEN_CHAT = ControllerBindingBuilder.create(controller) @@ -304,17 +305,41 @@ public class ControllerBindings { .build()); register(SHOW_PLAYER_LIST = ControllerBindingBuilder.create(controller) .identifier("controlify", "show_player_list") - .defaultBind(GamepadBinds.DPAD_RIGHT) + .defaultBind(new EmptyBind<>()) .category(MISC_CATEGORY) .context(BindContexts.INGAME) .radialCandidate(RadialIcons.getItem(Items.PLAYER_HEAD)) .build()); register(RADIAL_MENU = ControllerBindingBuilder.create(controller) .identifier("controlify", "radial_menu") - .defaultBind(GamepadBinds.DPAD_DOWN) - .category(MISC_CATEGORY) + .defaultBind(GamepadBinds.DPAD_RIGHT) + .category(RADIAL_CATEGORY) .context(BindContexts.INGAME) .build()); + register(RADIAL_AXIS_UP = ControllerBindingBuilder.create(controller) + .identifier("controlify", "radial_axis_up") + .defaultBind(GamepadBinds.RIGHT_STICK_FORWARD) + .category(RADIAL_CATEGORY) + .context(BindContexts.GUI) + .build()); + register(RADIAL_AXIS_DOWN = ControllerBindingBuilder.create(controller) + .identifier("controlify", "radial_axis_down") + .defaultBind(GamepadBinds.RIGHT_STICK_BACKWARD) + .category(RADIAL_CATEGORY) + .context(BindContexts.GUI) + .build()); + register(RADIAL_AXIS_LEFT = ControllerBindingBuilder.create(controller) + .identifier("controlify", "radial_axis_left") + .defaultBind(GamepadBinds.RIGHT_STICK_LEFT) + .category(RADIAL_CATEGORY) + .context(BindContexts.GUI) + .build()); + register(RADIAL_AXIS_RIGHT = ControllerBindingBuilder.create(controller) + .identifier("controlify", "radial_axis_right") + .defaultBind(GamepadBinds.RIGHT_STICK_RIGHT) + .category(RADIAL_CATEGORY) + .context(BindContexts.GUI) + .build()); register(VMOUSE_MOVE_UP = ControllerBindingBuilder.create(controller) .identifier("controlify", "vmouse_move_up") .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java index 413a762..300d384 100644 --- a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java +++ b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java @@ -19,19 +19,19 @@ public final class RadialIcons { private static final Minecraft minecraft = Minecraft.getInstance(); public static final ResourceLocation EMPTY = new ResourceLocation("controlify", "empty"); - public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabric", "icon"); + public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabric-resource-loader-v0", "icon.png"); private static final Map icons = Util.make(() -> { Map map = new HashMap<>(); map.put(EMPTY, (graphics, x, y) -> {}); - map.put(FABRIC_ICON, ((graphics, x, y) -> { + map.put(FABRIC_ICON, (graphics, x, y) -> { graphics.pose().pushPose(); graphics.pose().translate(x, y, 0); graphics.pose().scale(0.5f, 0.5f, 1f); graphics.blit(FABRIC_ICON, 0, 0, 0, 0, 32, 32); graphics.pose().popPose(); - })); + }); addItems(map); addPotionEffects(map); diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index 47fda1b..2ee5e09 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -155,8 +155,8 @@ public class ControlifyConfig { private void applyControllerConfig(Controller controller, JsonObject object) { try { - controller.setConfig(GSON, object.getAsJsonObject("config")); dirty |= !controller.bindings().fromJson(object.getAsJsonObject("bindings")); + controller.setConfig(GSON, object.getAsJsonObject("config")); } catch (Exception e) { Log.LOGGER.error("Failed to load controller data for " + controller.uid() + ". Resetting to default!", e); controller.resetConfig(); diff --git a/src/main/java/dev/isxander/controlify/controller/AbstractController.java b/src/main/java/dev/isxander/controlify/controller/AbstractController.java index 260bc6c..8a55622 100644 --- a/src/main/java/dev/isxander/controlify/controller/AbstractController.java +++ b/src/main/java/dev/isxander/controlify/controller/AbstractController.java @@ -120,6 +120,7 @@ public abstract class AbstractController bindings) { + boolean changed = false; + for (int i = 0; i < radialActions.length; i++) { + ResourceLocation action = radialActions[i]; + if (!RadialMenuScreen.EMPTY_ACTION.equals(action) && (action == null || !bindings.registry().containsKey(action) || bindings.registry().get(action).radialIcon().isEmpty())) { + setDefaultRadialAction(bindings, i); + changed = true; + } + } + if (changed) + Controlify.instance().config().setDirty(); + + return !changed; + } + + private void setDefaultRadialAction(ControllerBindings bindings, int index) { + radialActions[index] = switch (index) { + case 0 -> bindings.TOGGLE_HUD_VISIBILITY.id(); + case 1 -> bindings.CHANGE_PERSPECTIVE.id(); + case 2 -> bindings.DROP_STACK.id(); + case 3 -> bindings.OPEN_CHAT.id(); + case 4 -> bindings.SWAP_HANDS.id(); + case 5 -> bindings.PICK_BLOCK.id(); + case 6 -> bindings.PAUSE.id(); + case 7 -> bindings.SHOW_PLAYER_LIST.id(); + default -> RadialMenuScreen.EMPTY_ACTION; + }; + } } diff --git a/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java b/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java index 6baafe0..f4b3f3b 100644 --- a/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java +++ b/src/main/java/dev/isxander/controlify/gui/guide/InGameButtonGuide.java @@ -193,6 +193,11 @@ public class InGameButtonGuide implements IngameGuideRegistry { return Optional.of(Component.translatable("controlify.guide.ingame.inventory")); return Optional.empty(); }); + registerGuideAction(controller.bindings().RADIAL_MENU, ActionLocation.RIGHT, ctx -> { + if (ctx.client().screen == null) + return Optional.of(Component.translatable("controlify.gui.radial_menu")); + return Optional.empty(); + }); registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> { var hitResult = ctx.hitResult(); if (hitResult.getType() == HitResult.Type.ENTITY) 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 849c42a..449a6a5 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -1,10 +1,10 @@ package dev.isxander.controlify.gui.screen; import dev.isxander.controlify.Controlify; +import dev.isxander.controlify.api.bind.BindRenderer; import dev.isxander.controlify.api.bind.ControllerBinding; import dev.isxander.controlify.bindings.RadialIcons; import dev.isxander.controlify.controller.Controller; -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; @@ -12,9 +12,11 @@ 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.screenop.ScreenProcessorProvider; import dev.isxander.controlify.sound.ControlifySounds; import dev.isxander.controlify.utils.Animator; import dev.isxander.controlify.utils.Easings; +import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.MultiLineLabel; @@ -22,6 +24,7 @@ import net.minecraft.client.gui.components.Renderable; import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.ScreenRectangle; @@ -39,7 +42,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -public class RadialMenuScreen extends Screen implements ScreenControllerEventListener { +public class RadialMenuScreen extends Screen implements ScreenControllerEventListener, ScreenProcessorProvider { public static final ResourceLocation EMPTY_ACTION = new ResourceLocation("controlify", "empty_action"); private final Controller controller; @@ -53,6 +56,8 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis private ActionSelectList actionSelectList; + private final Processor processor = new Processor(this); + public RadialMenuScreen(Controller controller, boolean editMode, Screen parent) { super(Component.empty()); this.controller = controller; @@ -73,7 +78,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis addRenderableWidget(buttons[4] = button = new RadialButton(4, button.x - 32 - 8, button.y + 16)); addRenderableWidget(buttons[5] = button = new RadialButton(5, button.x - 32 - 8, button.y - 16)); addRenderableWidget(buttons[6] = button = new RadialButton(6, button.x - 16, button.y - 32 - 8)); - addRenderableWidget(buttons[7] = new RadialButton(7, button.x + 16, button.y - 32 - 8)); + addRenderableWidget(buttons[7] = new RadialButton(7, button.x + 16, button.y - 32 - 8)); Animator.AnimationInstance animation = new Animator.AnimationInstance(5, Easings::easeOutQuad); for (RadialButton radialButton : buttons) { @@ -114,9 +119,13 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis onClose(); } - if (!isEditing && controller.state() instanceof GamepadState state) { - float x = state.gamepadAxes().rightStickX(); - float y = state.gamepadAxes().rightStickY(); + if (editMode && controller.bindings().GUI_BACK.justPressed()) { + onClose(); + } + + if (!isEditing) { + float x = controller.bindings().RADIAL_AXIS_RIGHT.state() - controller.bindings().RADIAL_AXIS_LEFT.state(); + float y = controller.bindings().RADIAL_AXIS_DOWN.state() - controller.bindings().RADIAL_AXIS_UP.state(); float threshold = controller.config().buttonActivationThreshold; if (Math.abs(x) >= threshold || Math.abs(y) >= threshold) { @@ -152,13 +161,19 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - if (minecraft.level == null) + if (editMode) renderDirtBackground(graphics); super.render(graphics, mouseX, mouseY, delta); if (!editMode) { - graphics.drawCenteredString(font, Component.translatable("controlify.radial_menu.configure_hint"), width / 2, height - 10 - font.lineHeight, -1); + graphics.drawCenteredString( + font, + Component.translatable("controlify.radial_menu.configure_hint"), + width / 2, + height - 39, + -1 + ); } } @@ -178,6 +193,16 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis minecraft.setScreen(parent); } + @Override + public boolean isPauseScreen() { + return editMode; + } + + @Override + public ScreenProcessor screenProcessor() { + return this.processor; + } + public class RadialButton implements Renderable, GuiEventListener, NarratableEntry, ComponentProcessor { public static final ResourceLocation TEXTURE = Controlify.id("textures/gui/radial-buttons.png"); @@ -199,14 +224,23 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { graphics.pose().pushPose(); graphics.pose().translate(x + translateX, y + translateY, 0); + + graphics.pose().pushPose(); 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 + translateX + 4, y + translateY + 4, 0); - graphics.pose().scale(1.5f, 1.5f, 1); - this.icon.draw(graphics, 0, 0); + if (!editMode || !focused) { + graphics.pose().pushPose(); + graphics.pose().translate(4, 4, 0); + graphics.pose().scale(1.5f, 1.5f, 1); + this.icon.draw(graphics, 0, 0); + graphics.pose().popPose(); + } else { + BindRenderer renderer = controller.bindings().GUI_PRESS.renderer(); + renderer.render(graphics, 16 - renderer.size().width() / 2, 16); + } + graphics.pose().popPose(); if (focused) @@ -263,16 +297,14 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @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; - } + if (editMode && controller == RadialMenuScreen.this.controller && 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; } @@ -284,7 +316,8 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Override public void updateNarration(NarrationElementOutput builder) { - + if (binding != null) + builder.add(NarratedElementType.TITLE, binding.name()); } @Override @@ -370,7 +403,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Nullable @Override - public GuiEventListener getFocused() { + public ActionEntry getFocused() { return focusedEntry; } @@ -408,7 +441,9 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis @Override public void updateNarration(NarrationElementOutput builder) { - + if (getFocused() != null) { + builder.add(NarratedElementType.TITLE, getFocused().name); + } } public class ActionEntry implements GuiEventListener, ComponentProcessor { @@ -471,4 +506,15 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } } } + + public static class Processor extends ScreenProcessor { + public Processor(RadialMenuScreen screen) { + super(screen); + } + + @Override + public VirtualMouseBehaviour virtualMouseBehaviour() { + return VirtualMouseBehaviour.DISABLED; + } + } } diff --git a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java index c6f97ad..fe49484 100644 --- a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java +++ b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java @@ -106,7 +106,9 @@ public class InGameInputHandler { minecraft.options.hideGui = !minecraft.options.hideGui; } - shouldShowPlayerList = controller.bindings().SHOW_PLAYER_LIST.held(); + if (controller.bindings().SHOW_PLAYER_LIST.justPressed()) { + shouldShowPlayerList = !shouldShowPlayerList; + } if (controller.bindings().RADIAL_MENU.justPressed()) { minecraft.setScreen(new RadialMenuScreen(controller, false, null)); From 6bbab1eb3c290185edc1f5cdd9e1cfece8f5ceba Mon Sep 17 00:00:00 2001 From: isXander Date: Mon, 7 Aug 2023 20:12:59 +0100 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=90=9B=20Radial=20menu:=20fix=20not?= =?UTF-8?q?=20being=20able=20to=20back=20out=20of=20edit=20screen=20after?= =?UTF-8?q?=20changing=20an=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/isxander/controlify/gui/screen/RadialMenuScreen.java | 2 ++ 1 file changed, 2 insertions(+) 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 449a6a5..1698935 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/RadialMenuScreen.java @@ -120,6 +120,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis } if (editMode && controller.bindings().GUI_BACK.justPressed()) { + playClickSound(); onClose(); } @@ -184,6 +185,7 @@ public class RadialMenuScreen extends Screen implements ScreenControllerEventLis private void finishEditing() { isEditing = false; removeWidget(actionSelectList); + this.setFocused(null); actionSelectList = null; } From 15f575567c5c284fbd26b08b9fa97d99d5aabf79 Mon Sep 17 00:00:00 2001 From: isXander Date: Mon, 7 Aug 2023 22:23:34 +0100 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=90=9B=20Fix=20fabric=20radial=20icon?= =?UTF-8?q?=20not=20rendering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/dev/isxander/controlify/bindings/RadialIcons.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java index 300d384..da57ff4 100644 --- a/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java +++ b/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java @@ -29,7 +29,7 @@ public final class RadialIcons { graphics.pose().pushPose(); graphics.pose().translate(x, y, 0); graphics.pose().scale(0.5f, 0.5f, 1f); - graphics.blit(FABRIC_ICON, 0, 0, 0, 0, 32, 32); + graphics.blit(FABRIC_ICON, 0, 0, 0, 0, 32, 32, 32, 32); graphics.pose().popPose(); }); addItems(map);