forked from Clones/Controlify
➕ Start work on radial menu
This commit is contained in:
@ -43,6 +43,8 @@ public interface ControllerBinding {
|
||||
*/
|
||||
boolean justReleased();
|
||||
|
||||
void fakePress();
|
||||
|
||||
Component name();
|
||||
Component description();
|
||||
Component category();
|
||||
|
@ -43,6 +43,8 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
||||
|
||||
private static final Map<Controller<?, ?>, Set<IBind<?>>> pressedBinds = new HashMap<>();
|
||||
|
||||
private boolean fakePress, fakeRelease;
|
||||
|
||||
private ControllerBindingImpl(Controller<T, ?> controller, IBind<T> defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set<BindContext> contexts) {
|
||||
this.controller = controller;
|
||||
this.bind = this.defaultBind = defaultBind;
|
||||
@ -67,6 +69,11 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
||||
|
||||
@Override
|
||||
public boolean held() {
|
||||
if (fakePress) {
|
||||
fakePress = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return bind.held(controller.state());
|
||||
}
|
||||
|
||||
@ -79,7 +86,8 @@ public class ControllerBindingImpl<T extends ControllerState> 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<T extends ControllerState> implements Control
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fakePress() {
|
||||
this.fakePress = true;
|
||||
}
|
||||
|
||||
public IBind<T> currentBind() {
|
||||
return bind;
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
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<T extends ControllerState> {
|
||||
.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)
|
||||
|
@ -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
|
||||
);
|
||||
}
|
@ -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<ResourceLocation, Icon> icons = Util.make(() -> {
|
||||
Map<ResourceLocation, Icon> map = new HashMap<>();
|
||||
|
||||
map.put(EMPTY, (graphics, x, y) -> {});
|
||||
addItems(map);
|
||||
addPotionEffects(map);
|
||||
|
||||
return map;
|
||||
});
|
||||
|
||||
public static Map<ResourceLocation, Icon> getIcons() {
|
||||
return icons;
|
||||
}
|
||||
|
||||
private static void addItems(Map<ResourceLocation, Icon> map) {
|
||||
BuiltInRegistries.ITEM.entrySet().forEach(entry -> {
|
||||
ResourceKey<Item> 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<ResourceLocation, Icon> map) {
|
||||
MobEffectTextureManager mobEffectTextureManager = minecraft.getMobEffectTextures();
|
||||
|
||||
BuiltInRegistries.MOB_EFFECT.entrySet().forEach(entry -> {
|
||||
ResourceKey<MobEffect> 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<T extends RenderComponent> implements Renderable {
|
||||
public class PositionedComponent<T extends RenderComponent> implements Renderable, GuiEventListener, NarratableEntry {
|
||||
private final T component;
|
||||
|
||||
private int x, y;
|
||||
@ -52,4 +54,24 @@ public class PositionedComponent<T extends RenderComponent> 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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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.<ResourceLocation>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<OptionBindPair> optionBinds = new ArrayList<>();
|
||||
groupBindings(controller.bindings().registry().values()).forEach((categoryName, bindGroup) -> {
|
||||
var controlsGroup = OptionGroup.createBuilder()
|
||||
|
@ -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<GuideActionRenderer<Object>> 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<ActionEntry> 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<ActionEntry> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 261 B |
@ -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",
|
||||
|
Reference in New Issue
Block a user