forked from Clones/Controlify
✏️ Move button guide to screen processor + vmouse improvements
This commit is contained in:
@ -72,6 +72,7 @@ dependencies {
|
||||
"fabric-lifecycle-events-v1",
|
||||
"fabric-key-binding-api-v1",
|
||||
"fabric-registry-sync-v0",
|
||||
"fabric-screen-api-v1",
|
||||
).forEach {
|
||||
modImplementation(fabricApi.module(it, libs.versions.fabric.api.get()))
|
||||
}
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -9,6 +9,7 @@ import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.gui.guide.ContainerButtonGuide;
|
||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
@ -149,7 +150,7 @@ public class Controlify implements ControlifyApi {
|
||||
LOGGER.info("No controllers found.");
|
||||
}
|
||||
|
||||
if (getCurrentController().isEmpty() && config().isFirstLaunch()) {
|
||||
if (getCurrentController().isEmpty()) {
|
||||
this.setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null));
|
||||
} else {
|
||||
// setCurrentController saves config
|
||||
@ -183,6 +184,7 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
this.inGameInputHandler = null;
|
||||
this.virtualMouseHandler = new VirtualMouseHandler();
|
||||
//ContainerButtonGuide.setup();
|
||||
|
||||
controllerHIDService = new ControllerHIDService();
|
||||
controllerHIDService.start();
|
||||
|
@ -8,9 +8,10 @@ public final class BindContexts {
|
||||
public static final BindContext
|
||||
INGAME = ctx("ingame"),
|
||||
GUI = ctx("gui"),
|
||||
GUI_VMOUSE = ctx("gui_vmouse"),
|
||||
GUI_VMOUSE_CURSOR_ONLY = ctx("gui_vmouse_cursor"),
|
||||
GUI_VMOUSE = ctx("gui_vmouse", GUI_VMOUSE_CURSOR_ONLY),
|
||||
CONTROLIFY_CONFIG = ctx("controlify_config", GUI),
|
||||
INVENTORY = ctx("inventory", GUI_VMOUSE);
|
||||
INVENTORY = ctx("inventory", GUI_VMOUSE_CURSOR_ONLY);
|
||||
|
||||
private static BindContext ctx(String path, BindContext... parents) {
|
||||
return new BindContext(Controlify.id(path), Set.of(parents));
|
||||
|
@ -275,25 +275,25 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
.identifier("controlify", "vmouse_move_up")
|
||||
.defaultBind(GamepadBinds.LEFT_STICK_FORWARD)
|
||||
.category(VMOUSE_CATEGORY)
|
||||
.context(BindContexts.GUI_VMOUSE)
|
||||
.context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
|
||||
.build());
|
||||
register(VMOUSE_MOVE_DOWN = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "vmouse_move_down")
|
||||
.defaultBind(GamepadBinds.LEFT_STICK_BACKWARD)
|
||||
.category(VMOUSE_CATEGORY)
|
||||
.context(BindContexts.GUI_VMOUSE)
|
||||
.context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
|
||||
.build());
|
||||
register(VMOUSE_MOVE_LEFT = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "vmouse_move_left")
|
||||
.defaultBind(GamepadBinds.LEFT_STICK_LEFT)
|
||||
.category(VMOUSE_CATEGORY)
|
||||
.context(BindContexts.GUI_VMOUSE)
|
||||
.context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
|
||||
.build());
|
||||
register(VMOUSE_MOVE_RIGHT = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "vmouse_move_right")
|
||||
.defaultBind(GamepadBinds.LEFT_STICK_RIGHT)
|
||||
.category(VMOUSE_CATEGORY)
|
||||
.context(BindContexts.GUI_VMOUSE)
|
||||
.context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
|
||||
.build());
|
||||
register(VMOUSE_LCLICK = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "vmouse_lclick")
|
||||
@ -335,7 +335,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
.identifier("controlify", "vmouse_toggle")
|
||||
.defaultBind(GamepadBinds.BACK)
|
||||
.category(VMOUSE_CATEGORY)
|
||||
.context(BindContexts.GUI_VMOUSE)
|
||||
.context(BindContexts.GUI_VMOUSE, BindContexts.GUI)
|
||||
.build());
|
||||
register(GUI_NAVI_UP = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "gui_navi_up")
|
||||
|
@ -1,4 +1,178 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
public class ContainerButtonGuide {
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.gui.layout.AnchorPoint;
|
||||
import dev.isxander.controlify.gui.layout.ColumnLayoutComponent;
|
||||
import dev.isxander.controlify.gui.layout.PositionedComponent;
|
||||
import dev.isxander.controlify.gui.layout.RowLayoutComponent;
|
||||
import dev.isxander.controlify.mixins.feature.guide.screen.AbstractContainerScreenAccessor;
|
||||
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ContainerButtonGuide {
|
||||
private static PositionedComponent<ColumnLayoutComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>> leftLayout;
|
||||
private static PositionedComponent<ColumnLayoutComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>> rightLayout;
|
||||
|
||||
public static void setup() {
|
||||
ScreenEvents.BEFORE_INIT.register((minecraft, screen, sw, sh) -> {
|
||||
removeLayout();
|
||||
|
||||
if (isScreenCompatible(screen)) {
|
||||
setupLayout();
|
||||
|
||||
ScreenEvents.afterRender(screen).register((s, stack, mouseX, mouseY, tickDelta) -> {
|
||||
// Fabric API provides the wrong matrixstack (which is translated -2000), behind
|
||||
// the ortho near plane, so we need to translate it back to the front.
|
||||
// https://github.com/FabricMC/fabric/pull/3061
|
||||
stack.pushPose();
|
||||
stack.translate(0, 0, 2000);
|
||||
|
||||
renderGuide((AbstractContainerScreen<?>) s, stack, tickDelta, mouseX, mouseY, sw, sh);
|
||||
|
||||
stack.popPose();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> {
|
||||
if (isScreenCompatible(Minecraft.getInstance().screen)) {
|
||||
if (mode == InputMode.CONTROLLER) {
|
||||
setupLayout();
|
||||
} else {
|
||||
removeLayout();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void setupLayout() {
|
||||
ControllerBindings<?> bindings = ControlifyApi.get().getCurrentController()
|
||||
.map(Controller::bindings)
|
||||
.orElseThrow();
|
||||
|
||||
leftLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>builder()
|
||||
.spacing(2)
|
||||
.colPadding(2, 2)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.LEFT)
|
||||
.element(RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_LCLICK, ctx -> {
|
||||
if (!ctx.holdingItem().isEmpty())
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
if (ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
if (ctx.holdingItem().getCount() > 1)
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_all"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_one"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.swap"));
|
||||
else if (ctx.cursorOutsideContainer())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.drop"));
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.GUI_BACK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.exit"));
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.build())
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_LEFT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_LEFT
|
||||
);
|
||||
|
||||
rightLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>builder()
|
||||
.spacing(2)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.RIGHT)
|
||||
.colPadding(2, 2)
|
||||
.element(RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_RCLICK, ctx -> {
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().getItem().getCount() > 1 && ctx.holdingItem().isEmpty())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_half"));
|
||||
if (ctx.hoveredSlot() != null && !ctx.holdingItem().isEmpty() && ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_one"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_SHIFT_CLICK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.quick_move"));
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.build())
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_RIGHT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_RIGHT
|
||||
);
|
||||
}
|
||||
|
||||
private static void removeLayout() {
|
||||
leftLayout = null;
|
||||
rightLayout = null;
|
||||
}
|
||||
|
||||
private static void renderGuide(AbstractContainerScreen<?> screen, PoseStack stack, float tickDelta, int mouseX, int mouseY, int screenWidth, int screenHeight) {
|
||||
if (leftLayout == null || rightLayout == null)
|
||||
return;
|
||||
|
||||
if (!ControlifyApi.get().getCurrentController().map(controller -> controller.config().showScreenGuide).orElse(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var accessor = (AbstractContainerScreenAccessor) screen;
|
||||
|
||||
ContainerGuideCtx ctx = new ContainerGuideCtx(accessor.getHoveredSlot(), screen.getMenu().getCarried(), accessor.invokeHasClickedOutside(mouseX, mouseY, accessor.getLeftPos(), accessor.getTopPos(), 0));
|
||||
|
||||
for (var row : leftLayout.getComponent().getChildComponents()) {
|
||||
for (var element : row.getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
}
|
||||
for (var row : rightLayout.getComponent().getChildComponents()) {
|
||||
for (var element : row.getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
leftLayout.updatePosition();
|
||||
rightLayout.updatePosition();
|
||||
|
||||
ControlifyCompat.ifBeginHudBatching();
|
||||
leftLayout.renderComponent(stack, tickDelta);
|
||||
rightLayout.renderComponent(stack, tickDelta);
|
||||
ControlifyCompat.ifEndHudBatching();
|
||||
}
|
||||
|
||||
private static boolean isScreenCompatible(Screen screen) {
|
||||
return screen instanceof AbstractContainerScreen<?>;
|
||||
}
|
||||
}
|
||||
|
@ -75,8 +75,8 @@ public class InGameButtonGuide implements IngameGuideRegistry {
|
||||
|
||||
ControlifyCompat.ifBeginHudBatching();
|
||||
|
||||
leftLayout.render(poseStack, tickDelta);
|
||||
rightLayout.render(poseStack, tickDelta);
|
||||
leftLayout.renderComponent(poseStack, tickDelta);
|
||||
rightLayout.renderComponent(poseStack, tickDelta);
|
||||
|
||||
ControlifyCompat.ifEndHudBatching();
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package dev.isxander.controlify.gui.layout;
|
||||
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
import org.joml.Vector2i;
|
||||
import org.joml.Vector2ic;
|
||||
|
||||
public enum AnchorPoint {
|
||||
TOP_LEFT(0, 0),
|
||||
@ -23,7 +20,7 @@ public enum AnchorPoint {
|
||||
this.anchorY = anchorY;
|
||||
}
|
||||
|
||||
public Vector2i getAnchorPosition(Vector2ic windowSize) {
|
||||
return new Vector2i((int) (windowSize.x() * anchorX), (int) (windowSize.y() * anchorY));
|
||||
public Vector2i getAnchorPosition(int w, int h) {
|
||||
return new Vector2i((int) (w * anchorX), (int) (h * anchorY));
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
package dev.isxander.controlify.gui.layout;
|
||||
|
||||
import com.mojang.blaze3d.platform.Window;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.joml.Vector2i;
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import org.joml.Vector2ic;
|
||||
|
||||
public class PositionedComponent<T extends RenderComponent> {
|
||||
public class PositionedComponent<T extends RenderComponent> implements Renderable {
|
||||
private final T component;
|
||||
|
||||
private int x, y;
|
||||
@ -21,19 +20,24 @@ public class PositionedComponent<T extends RenderComponent> {
|
||||
this.offsetY = offsetY;
|
||||
this.windowAnchor = windowAnchor;
|
||||
this.origin = origin;
|
||||
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
public void updatePosition() {
|
||||
Vector2ic windowPosition = windowAnchor.getAnchorPosition(windowSize());
|
||||
Vector2ic anchoredPosition = origin.getAnchorPosition(component.size());
|
||||
Vector2ic componentSize = component.size();
|
||||
|
||||
Vector2ic windowPosition = windowAnchor.getAnchorPosition(Minecraft.getInstance().getWindow().getGuiScaledWidth(), Minecraft.getInstance().getWindow().getGuiScaledHeight());
|
||||
Vector2ic anchoredPosition = origin.getAnchorPosition(componentSize.x(), componentSize.y());
|
||||
|
||||
this.x = windowPosition.x() + offsetX - anchoredPosition.x();
|
||||
this.y = windowPosition.y() + offsetY - anchoredPosition.y();
|
||||
}
|
||||
|
||||
public void render(PoseStack stack, float deltaTime) {
|
||||
@Override
|
||||
public void render(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||
this.renderComponent(matrices, delta);
|
||||
}
|
||||
|
||||
public void renderComponent(PoseStack stack, float deltaTime) {
|
||||
component.render(stack, x, y, deltaTime);
|
||||
}
|
||||
|
||||
@ -48,9 +52,4 @@ public class PositionedComponent<T extends RenderComponent> {
|
||||
public T getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
private Vector2i windowSize() {
|
||||
Window window = Minecraft.getInstance().getWindow();
|
||||
return new Vector2i(window.getGuiScaledWidth(), window.getGuiScaledHeight());
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,8 @@ public class RowLayoutComponent<T extends RenderComponent> extends AbstractLayou
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> elements(T... elements) {
|
||||
@SafeVarargs
|
||||
public final Builder<T> elements(T... elements) {
|
||||
this.elements.addAll(Arrays.asList(elements));
|
||||
return this;
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide.screen;
|
||||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(AbstractContainerScreen.class)
|
||||
public interface AbstractContainerScreenAccessor {
|
||||
@Accessor
|
||||
Slot getHoveredSlot();
|
||||
|
||||
@Invoker
|
||||
boolean invokeHasClickedOutside(double mouseX, double mouseY, int left, int top, int button);
|
||||
|
||||
@Accessor
|
||||
int getLeftPos();
|
||||
|
||||
@Accessor
|
||||
int getTopPos();
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide.screen;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.gui.guide.ContainerGuideCtx;
|
||||
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.ColumnLayoutComponent;
|
||||
import dev.isxander.controlify.gui.layout.PositionedComponent;
|
||||
import dev.isxander.controlify.gui.layout.RowLayoutComponent;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
// TODO: Move out of mixin
|
||||
@Mixin(AbstractContainerScreen.class)
|
||||
public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMenu> {
|
||||
@Shadow @Nullable protected Slot hoveredSlot;
|
||||
@Shadow @Final protected T menu;
|
||||
@Shadow protected int leftPos;
|
||||
@Shadow protected int topPos;
|
||||
@Shadow protected abstract boolean hasClickedOutside(double mouseX, double mouseY, int left, int top, int button);
|
||||
|
||||
@Unique private PositionedComponent<ColumnLayoutComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>> leftLayout;
|
||||
@Unique private PositionedComponent<ColumnLayoutComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>> rightLayout;
|
||||
|
||||
@Inject(method = "init", at = @At("RETURN"))
|
||||
private void initButtonGuide(CallbackInfo ci) {
|
||||
ControllerBindings<?> bindings = ControlifyApi.get().getCurrentController()
|
||||
.map(Controller::bindings)
|
||||
.orElse(null);
|
||||
|
||||
if (bindings == null)
|
||||
return;
|
||||
|
||||
leftLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>builder()
|
||||
.spacing(2)
|
||||
.colPadding(2, 2)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.LEFT)
|
||||
.element(RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_LCLICK, ctx -> {
|
||||
if (!ctx.holdingItem().isEmpty())
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
if (ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
if (ctx.holdingItem().getCount() > 1)
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_all"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_one"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.swap"));
|
||||
else if (ctx.cursorOutsideContainer())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.drop"));
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.elements(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.GUI_BACK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.exit"));
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.build())
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_LEFT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_LEFT
|
||||
);
|
||||
|
||||
rightLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>>builder()
|
||||
.spacing(2)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.RIGHT)
|
||||
.colPadding(2, 2)
|
||||
.element(RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_RCLICK, ctx -> {
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().getItem().getCount() > 1 && ctx.holdingItem().isEmpty())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_half"));
|
||||
if (ctx.hoveredSlot() != null && !ctx.holdingItem().isEmpty() && ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_one"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_SHIFT_CLICK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.quick_move"));
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.build())
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_RIGHT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_RIGHT
|
||||
);
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"))
|
||||
private void renderButtonGuide(PoseStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (!ControlifyApi.get().getCurrentController().map(controller -> controller.config().showScreenGuide).orElse(false)
|
||||
|| ControlifyApi.get().currentInputMode() != InputMode.CONTROLLER
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContainerGuideCtx ctx = new ContainerGuideCtx(hoveredSlot, menu.getCarried(), hasClickedOutside(mouseX, mouseY, this.leftPos, this.topPos, 0));
|
||||
|
||||
for (var row : leftLayout.getComponent().getChildComponents()) {
|
||||
for (var element : row.getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
}
|
||||
for (var row : rightLayout.getComponent().getChildComponents()) {
|
||||
for (var element : row.getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
leftLayout.updatePosition();
|
||||
rightLayout.updatePosition();
|
||||
|
||||
leftLayout.render(matrices, delta);
|
||||
rightLayout.render(matrices, delta);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package dev.isxander.controlify.mixins.feature.screenop;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
@Shadow @Final Minecraft minecraft;
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderWithTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;IIF)V", shift = At.Shift.AFTER))
|
||||
private void onPostRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, @Local(ordinal = 1) PoseStack poseStack) {
|
||||
ControlifyApi.get().getCurrentController().ifPresent(controller -> {
|
||||
if (minecraft.screen == null) return;
|
||||
ScreenProcessorProvider.provide(minecraft.screen).render(controller, poseStack, tickDelta);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
package dev.isxander.controlify.mixins.feature.screenop.vanilla;
|
||||
package dev.isxander.controlify.mixins.feature.screenop;
|
||||
|
||||
import net.minecraft.client.gui.ComponentPath;
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
|
||||
import net.minecraft.client.gui.navigation.ScreenDirection;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface ScreenAccessor {
|
||||
@Invoker
|
||||
@ -17,4 +21,7 @@ public interface ScreenAccessor {
|
||||
|
||||
@Invoker
|
||||
void invokeClearFocus();
|
||||
|
||||
@Accessor
|
||||
List<Renderable> getRenderables();
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
package dev.isxander.controlify.screenop;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.ScreenAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.ScreenAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.TabNavigationBarAccessor;
|
||||
import dev.isxander.controlify.sound.ControlifySounds;
|
||||
import dev.isxander.controlify.utils.NavigationHelper;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ComponentPath;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
@ -25,7 +28,7 @@ import java.util.*;
|
||||
public class ScreenProcessor<T extends Screen> {
|
||||
public final T screen;
|
||||
protected final NavigationHelper navigationHelper = new NavigationHelper(10, 3);
|
||||
protected final Minecraft minecraft = Minecraft.getInstance();
|
||||
protected static final Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
public ScreenProcessor(T screen) {
|
||||
this.screen = screen;
|
||||
@ -40,12 +43,17 @@ public class ScreenProcessor<T extends Screen> {
|
||||
if (!handleComponentButtonOverride(controller))
|
||||
handleButtons(controller);
|
||||
} else {
|
||||
handleScreenVMouse(controller);
|
||||
handleScreenVMouse(controller, Controlify.instance().virtualMouseHandler());
|
||||
}
|
||||
|
||||
handleTabNavigation(controller);
|
||||
}
|
||||
|
||||
public void render(Controller<?, ?> controller, PoseStack poseStack, float tickDelta) {
|
||||
var vmouse = Controlify.instance().virtualMouseHandler();
|
||||
this.render(controller, poseStack, tickDelta, vmouse.isVirtualMouseEnabled() ? Optional.of(vmouse) : Optional.empty());
|
||||
}
|
||||
|
||||
public void onInputModeChanged(InputMode mode) {
|
||||
switch (mode) {
|
||||
case KEYBOARD_MOUSE -> ((ScreenAccessor) screen).invokeClearFocus();
|
||||
@ -115,12 +123,12 @@ public class ScreenProcessor<T extends Screen> {
|
||||
screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0);
|
||||
}
|
||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||
this.playClackSound();
|
||||
playClackSound();
|
||||
screen.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller) {
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller, VirtualMouseHandler vmouse) {
|
||||
|
||||
}
|
||||
|
||||
@ -180,6 +188,10 @@ public class ScreenProcessor<T extends Screen> {
|
||||
}
|
||||
}
|
||||
|
||||
protected void render(Controller<?, ?> controller, PoseStack poseStack, float tickDelta, Optional<VirtualMouseHandler> vmouse) {
|
||||
|
||||
}
|
||||
|
||||
protected void setInitialFocus() {
|
||||
if (screen.getFocused() == null && Controlify.instance().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||
var accessor = (ScreenAccessor) screen;
|
||||
@ -191,8 +203,8 @@ public class ScreenProcessor<T extends Screen> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean forceVirtualMouse() {
|
||||
return false;
|
||||
public VirtualMouseBehaviour virtualMouseBehaviour() {
|
||||
return VirtualMouseBehaviour.DEFAULT;
|
||||
}
|
||||
|
||||
protected Queue<GuiEventListener> getFocusTree() {
|
||||
@ -211,7 +223,7 @@ public class ScreenProcessor<T extends Screen> {
|
||||
return tree;
|
||||
}
|
||||
|
||||
protected void playClackSound() {
|
||||
public static void playClackSound() {
|
||||
minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package dev.isxander.controlify.screenop;
|
||||
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ScreenProcessorProvider {
|
||||
ScreenProcessor<?> screenProcessor();
|
||||
|
||||
static ScreenProcessor<?> provide(Screen screen) {
|
||||
static ScreenProcessor<?> provide(@NotNull Screen screen) {
|
||||
Optional<ScreenProcessor<?>> optional = REGISTRY.get(screen);
|
||||
if (optional.isPresent()) return optional.get();
|
||||
|
||||
|
@ -1,14 +1,34 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.gui.guide.ContainerGuideCtx;
|
||||
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.gui.layout.RowLayoutComponent;
|
||||
import dev.isxander.controlify.mixins.feature.guide.screen.AbstractContainerScreenAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.ScreenAccessor;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AbstractContainerScreenProcessor<T extends AbstractContainerScreen<?>> extends ScreenProcessor<T> {
|
||||
private PositionedComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>> leftLayout;
|
||||
private PositionedComponent<RowLayoutComponent<GuideActionRenderer<ContainerGuideCtx>>> rightLayout;
|
||||
|
||||
private final Supplier<Slot> hoveredSlot;
|
||||
private final ClickSlotFunction clickSlotFunction;
|
||||
|
||||
@ -19,13 +39,134 @@ public class AbstractContainerScreenProcessor<T extends AbstractContainerScreen<
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller) {
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller, VirtualMouseHandler vmouse) {
|
||||
if (controller.bindings().DROP.justPressed()) {
|
||||
Slot slot = hoveredSlot.get();
|
||||
if (slot != null && slot.hasItem()) {
|
||||
clickSlotFunction.clickSlot(slot, slot.index, 0, ClickType.THROW);
|
||||
}
|
||||
}
|
||||
|
||||
if (leftLayout != null && rightLayout != null) {
|
||||
var accessor = (AbstractContainerScreenAccessor) screen;
|
||||
|
||||
ContainerGuideCtx ctx = new ContainerGuideCtx(hoveredSlot.get(), screen.getMenu().getCarried(), accessor.invokeHasClickedOutside(vmouse.getCurrentX(1f), vmouse.getCurrentY(1f), accessor.getLeftPos(), accessor.getTopPos(), 0));
|
||||
|
||||
for (var element : leftLayout.getComponent().getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
for (var element : rightLayout.getComponent().getChildComponents()) {
|
||||
element.updateName(ctx);
|
||||
}
|
||||
|
||||
leftLayout.updatePosition();
|
||||
rightLayout.updatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetRebuild() {
|
||||
ControllerBindings<?> bindings = ControlifyApi.get().getCurrentController()
|
||||
.map(Controller::bindings)
|
||||
.orElse(null);
|
||||
|
||||
if (bindings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
leftLayout = new PositionedComponent<>(
|
||||
RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_LCLICK, ctx -> {
|
||||
if (!ctx.holdingItem().isEmpty())
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
if (ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
if (ctx.holdingItem().getCount() > 1)
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_all"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.place_one"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.container.swap"));
|
||||
else if (ctx.cursorOutsideContainer())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.drop"));
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().hasItem())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.GUI_BACK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.exit"));
|
||||
}),
|
||||
false, false
|
||||
))
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_LEFT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_LEFT
|
||||
);
|
||||
|
||||
rightLayout = new PositionedComponent<>(
|
||||
RowLayoutComponent.<GuideActionRenderer<ContainerGuideCtx>>builder()
|
||||
.spacing(5)
|
||||
.rowPadding(0)
|
||||
.elementPosition(RowLayoutComponent.ElementPosition.MIDDLE)
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_RCLICK, ctx -> {
|
||||
if (ctx.hoveredSlot() != null && ctx.hoveredSlot().getItem().getCount() > 1 && ctx.holdingItem().isEmpty())
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_half"));
|
||||
if (ctx.hoveredSlot() != null && !ctx.holdingItem().isEmpty() && ctx.hoveredSlot().mayPlace(ctx.holdingItem()))
|
||||
return Optional.of(Component.translatable("controlify.guide.container.take_one"));
|
||||
return Optional.empty();
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.element(new GuideActionRenderer<>(
|
||||
new GuideAction<>(bindings.VMOUSE_SHIFT_CLICK, ctx -> {
|
||||
return Optional.of(Component.translatable("controlify.guide.container.quick_move"));
|
||||
}),
|
||||
true, false
|
||||
))
|
||||
.build(),
|
||||
AnchorPoint.BOTTOM_RIGHT,
|
||||
0, 0,
|
||||
AnchorPoint.BOTTOM_RIGHT
|
||||
);
|
||||
|
||||
if (ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER) {
|
||||
setRenderGuide(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputModeChanged(InputMode mode) {
|
||||
setRenderGuide(mode == InputMode.CONTROLLER);
|
||||
}
|
||||
|
||||
private void setRenderGuide(boolean render) {
|
||||
List<Renderable> renderables = ((ScreenAccessor) screen).getRenderables();
|
||||
|
||||
if (leftLayout == null || rightLayout == null)
|
||||
return;
|
||||
|
||||
if (render) {
|
||||
if (!renderables.contains(leftLayout))
|
||||
renderables.add(leftLayout);
|
||||
if (!renderables.contains(rightLayout))
|
||||
renderables.add(rightLayout);
|
||||
} else {
|
||||
renderables.remove(leftLayout);
|
||||
renderables.remove(rightLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualMouseBehaviour virtualMouseBehaviour() {
|
||||
return VirtualMouseBehaviour.CURSOR_ONLY;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.CreativeModeInventoryScreenAccessor;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.CreativeModeTabs;
|
||||
@ -15,7 +15,7 @@ public class CreativeModeInventoryScreenProcessor extends AbstractContainerScree
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller) {
|
||||
protected void handleScreenVMouse(Controller<?, ?> controller, VirtualMouseHandler vmouse) {
|
||||
var accessor = (CreativeModeInventoryScreenAccessor) screen;
|
||||
|
||||
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||
@ -31,6 +31,6 @@ public class CreativeModeInventoryScreenProcessor extends AbstractContainerScree
|
||||
accessor.invokeSelectTab(tabs.get(newIndex));
|
||||
}
|
||||
|
||||
super.handleScreenVMouse(controller);
|
||||
super.handleScreenVMouse(controller, vmouse);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package dev.isxander.controlify.virtualmouse;
|
||||
|
||||
public enum VirtualMouseBehaviour {
|
||||
DEFAULT,
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
CURSOR_ONLY;
|
||||
|
||||
public boolean hasCursor() {
|
||||
return this != DISABLED;
|
||||
}
|
||||
}
|
@ -9,13 +9,14 @@ import dev.isxander.controlify.api.vmousesnapping.ISnapBehaviour;
|
||||
import dev.isxander.controlify.api.vmousesnapping.SnapPoint;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiComponent;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
@ -40,7 +41,6 @@ public class VirtualMouseHandler {
|
||||
|
||||
private Set<SnapPoint> snapPoints;
|
||||
private SnapPoint lastSnappedPoint;
|
||||
private boolean snapping;
|
||||
|
||||
public VirtualMouseHandler() {
|
||||
this.minecraft = Minecraft.getInstance();
|
||||
@ -77,8 +77,6 @@ public class VirtualMouseHandler {
|
||||
if (impulseX == 0 && impulseY == 0) {
|
||||
if ((prevImpulseX != 0 || prevImpulseY != 0))
|
||||
snapToClosestPoint();
|
||||
} else {
|
||||
snapping = false;
|
||||
}
|
||||
|
||||
var sensitivity = controller.config().virtualMouseSensitivity;
|
||||
@ -91,6 +89,17 @@ public class VirtualMouseHandler {
|
||||
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
|
||||
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight());
|
||||
|
||||
if (controller.bindings().GUI_BACK.justPressed() && minecraft.screen != null) {
|
||||
ScreenProcessor.playClackSound();
|
||||
minecraft.screen.onClose();
|
||||
}
|
||||
|
||||
if (!ScreenProcessorProvider.provide(minecraft.screen).virtualMouseBehaviour().hasCursor()) {
|
||||
handleCompatibilityBinds(controller);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCompatibilityBinds(Controller<?, ?> controller) {
|
||||
scrollY += controller.bindings().VMOUSE_SCROLL_UP.state() - controller.bindings().VMOUSE_SCROLL_DOWN.state();
|
||||
|
||||
var mouseHandler = (MouseHandlerAccessor) minecraft.mouseHandler;
|
||||
@ -113,10 +122,6 @@ public class VirtualMouseHandler {
|
||||
} else if (controller.bindings().VMOUSE_SHIFT_CLICK.justReleased()) {
|
||||
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_LEFT, GLFW.GLFW_RELEASE, 0);
|
||||
}
|
||||
|
||||
if (controller.bindings().GUI_BACK.justPressed() && minecraft.screen != null) {
|
||||
minecraft.screen.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateMouse() {
|
||||
@ -164,7 +169,6 @@ public class VirtualMouseHandler {
|
||||
|
||||
if (closestSnapPoint != null) {
|
||||
lastSnappedPoint = closestSnapPoint;
|
||||
snapping = false;
|
||||
|
||||
targetX = currentX = closestSnapPoint.position().x() / scaleFactor.x();
|
||||
targetY = currentY = closestSnapPoint.position().y() / scaleFactor.y();
|
||||
@ -271,15 +275,30 @@ public class VirtualMouseHandler {
|
||||
public boolean requiresVirtualMouse() {
|
||||
var isController = Controlify.instance().currentInputMode() == InputMode.CONTROLLER;
|
||||
var hasScreen = minecraft.screen != null;
|
||||
var forceVirtualMouse = hasScreen && ScreenProcessorProvider.provide(minecraft.screen).forceVirtualMouse();
|
||||
var screenIsVMouseScreen = hasScreen && Controlify.instance().config().globalSettings().virtualMouseScreens.stream().anyMatch(s -> s.isAssignableFrom(minecraft.screen.getClass()));
|
||||
|
||||
return isController && hasScreen && (forceVirtualMouse || screenIsVMouseScreen);
|
||||
if (isController && hasScreen) {
|
||||
return switch (ScreenProcessorProvider.provide(minecraft.screen).virtualMouseBehaviour()) {
|
||||
case DEFAULT -> Controlify.instance().config().globalSettings().virtualMouseScreens.stream().anyMatch(s -> s.isAssignableFrom(minecraft.screen.getClass()));
|
||||
case ENABLED, CURSOR_ONLY -> true;
|
||||
case DISABLED -> false;
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void toggleVirtualMouse() {
|
||||
if (minecraft.screen == null) return;
|
||||
|
||||
if (ScreenProcessorProvider.provide(minecraft.screen).virtualMouseBehaviour() != VirtualMouseBehaviour.DEFAULT) {
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.vmouse_unavailable.title"),
|
||||
Component.translatable("controlify.toast.vmouse_unavailable.description"),
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var screens = Controlify.instance().config().globalSettings().virtualMouseScreens;
|
||||
var screenClass = minecraft.screen.getClass();
|
||||
if (screens.contains(screenClass)) {
|
||||
@ -287,22 +306,20 @@ public class VirtualMouseHandler {
|
||||
disableVirtualMouse();
|
||||
Controlify.instance().hideMouse(true, false);
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.vmouse_disabled.title"),
|
||||
Component.translatable("controlify.toast.vmouse_disabled.description")
|
||||
));
|
||||
Component.translatable("controlify.toast.vmouse_disabled.description"),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
screens.add(screenClass);
|
||||
enableVirtualMouse();
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.vmouse_enabled.title"),
|
||||
Component.translatable("controlify.toast.vmouse_enabled.description")
|
||||
));
|
||||
Component.translatable("controlify.toast.vmouse_enabled.description"),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
Controlify.instance().config().save();
|
||||
@ -311,4 +328,12 @@ public class VirtualMouseHandler {
|
||||
public boolean isVirtualMouseEnabled() {
|
||||
return virtualMouseEnabled;
|
||||
}
|
||||
|
||||
public int getCurrentX(float deltaTime) {
|
||||
return (int) Mth.lerp(deltaTime, currentX, targetX);
|
||||
}
|
||||
|
||||
public int getCurrentY(float deltaTime) {
|
||||
return (int) Mth.lerp(deltaTime, currentY, targetY);
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,8 @@
|
||||
"controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.",
|
||||
"controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled",
|
||||
"controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.",
|
||||
"controlify.toast.vmouse_unavailable.title": "Virtual Mouse Unavailable",
|
||||
"controlify.toast.vmouse_unavailable.description": "This screen is forcing a specific virtual mouse mode and you cannot change it.",
|
||||
"controlify.toast.ask_to_switch.title": "Switch Controller?",
|
||||
"controlify.toast.ask_to_switch.description": "A new controller named '%s' has just been connected. Press any button to switch to it.",
|
||||
"controlify.toast.default_controller_connected.title": "Controller Connected",
|
||||
|
@ -39,7 +39,7 @@
|
||||
"feature.guide.ingame.ClientPacketListenerMixin",
|
||||
"feature.guide.ingame.GuiMixin",
|
||||
"feature.guide.screen.AbstractButtonMixin",
|
||||
"feature.guide.screen.AbstractContainerScreenMixin",
|
||||
"feature.guide.screen.AbstractContainerScreenAccessor",
|
||||
"feature.guide.screen.AbstractWidgetMixin",
|
||||
"feature.guide.screen.TabNavigationBarMixin",
|
||||
"feature.oofinput.GameRendererMixin",
|
||||
@ -49,7 +49,9 @@
|
||||
"feature.rumble.explosion.ClientPacketListenerMixin",
|
||||
"feature.rumble.itembreak.LocalPlayerMixin",
|
||||
"feature.rumble.useitem.LocalPlayerMixin",
|
||||
"feature.screenop.GameRendererMixin",
|
||||
"feature.screenop.MinecraftMixin",
|
||||
"feature.screenop.ScreenAccessor",
|
||||
"feature.screenop.ScreenMixin",
|
||||
"feature.screenop.vanilla.AbstractButtonMixin",
|
||||
"feature.screenop.vanilla.AbstractContainerEventHandlerMixin",
|
||||
@ -64,7 +66,6 @@
|
||||
"feature.screenop.vanilla.JoinMultiplayerScreenMixin",
|
||||
"feature.screenop.vanilla.LanguageSelectionListEntryMixin",
|
||||
"feature.screenop.vanilla.OptionsSubScreenAccessor",
|
||||
"feature.screenop.vanilla.ScreenAccessor",
|
||||
"feature.screenop.vanilla.SelectWorldScreenAccessor",
|
||||
"feature.screenop.vanilla.SelectWorldScreenMixin",
|
||||
"feature.screenop.vanilla.ServerSelectionListEntryMixin",
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.isxander.controlify.test;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.Screenshot;
|
||||
|
||||
@ -69,7 +68,6 @@ public class ClientTestHelper {
|
||||
|
||||
public static FakeController createAndUseDummyController() {
|
||||
var controller = new FakeController();
|
||||
Controller.CONTROLLERS.put(controller.uid(), controller);
|
||||
controller.use();
|
||||
return controller;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickConfig;
|
||||
@ -194,7 +193,6 @@ public class FakeController implements JoystickController<JoystickConfig> {
|
||||
|
||||
public void finish() {
|
||||
Controlify.instance().setCurrentController(null);
|
||||
Controller.CONTROLLERS.remove(uid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user