1
0
forked from Clones/Controlify

✏️ Move button guide to screen processor + vmouse improvements

This commit is contained in:
isXander
2023-05-14 19:27:07 +01:00
parent 45c20bb2dc
commit f557c1ab05
26 changed files with 496 additions and 228 deletions

View File

@ -72,6 +72,7 @@ dependencies {
"fabric-lifecycle-events-v1", "fabric-lifecycle-events-v1",
"fabric-key-binding-api-v1", "fabric-key-binding-api-v1",
"fabric-registry-sync-v0", "fabric-registry-sync-v0",
"fabric-screen-api-v1",
).forEach { ).forEach {
modImplementation(fabricApi.module(it, libs.versions.fabric.api.get())) modImplementation(fabricApi.module(it, libs.versions.fabric.api.get()))
} }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

4
gradlew vendored
View File

@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac

View File

@ -9,6 +9,7 @@ import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager; import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties; 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.ControllerDeadzoneCalibrationScreen;
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen; import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.ScreenProcessorProvider;
@ -149,7 +150,7 @@ public class Controlify implements ControlifyApi {
LOGGER.info("No controllers found."); LOGGER.info("No controllers found.");
} }
if (getCurrentController().isEmpty() && config().isFirstLaunch()) { if (getCurrentController().isEmpty()) {
this.setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null)); this.setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null));
} else { } else {
// setCurrentController saves config // setCurrentController saves config
@ -183,6 +184,7 @@ public class Controlify implements ControlifyApi {
this.inGameInputHandler = null; this.inGameInputHandler = null;
this.virtualMouseHandler = new VirtualMouseHandler(); this.virtualMouseHandler = new VirtualMouseHandler();
//ContainerButtonGuide.setup();
controllerHIDService = new ControllerHIDService(); controllerHIDService = new ControllerHIDService();
controllerHIDService.start(); controllerHIDService.start();

View File

@ -8,9 +8,10 @@ public final class BindContexts {
public static final BindContext public static final BindContext
INGAME = ctx("ingame"), INGAME = ctx("ingame"),
GUI = ctx("gui"), 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), 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) { private static BindContext ctx(String path, BindContext... parents) {
return new BindContext(Controlify.id(path), Set.of(parents)); return new BindContext(Controlify.id(path), Set.of(parents));

View File

@ -275,25 +275,25 @@ public class ControllerBindings<T extends ControllerState> {
.identifier("controlify", "vmouse_move_up") .identifier("controlify", "vmouse_move_up")
.defaultBind(GamepadBinds.LEFT_STICK_FORWARD) .defaultBind(GamepadBinds.LEFT_STICK_FORWARD)
.category(VMOUSE_CATEGORY) .category(VMOUSE_CATEGORY)
.context(BindContexts.GUI_VMOUSE) .context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
.build()); .build());
register(VMOUSE_MOVE_DOWN = ControllerBindingBuilder.create(controller) register(VMOUSE_MOVE_DOWN = ControllerBindingBuilder.create(controller)
.identifier("controlify", "vmouse_move_down") .identifier("controlify", "vmouse_move_down")
.defaultBind(GamepadBinds.LEFT_STICK_BACKWARD) .defaultBind(GamepadBinds.LEFT_STICK_BACKWARD)
.category(VMOUSE_CATEGORY) .category(VMOUSE_CATEGORY)
.context(BindContexts.GUI_VMOUSE) .context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
.build()); .build());
register(VMOUSE_MOVE_LEFT = ControllerBindingBuilder.create(controller) register(VMOUSE_MOVE_LEFT = ControllerBindingBuilder.create(controller)
.identifier("controlify", "vmouse_move_left") .identifier("controlify", "vmouse_move_left")
.defaultBind(GamepadBinds.LEFT_STICK_LEFT) .defaultBind(GamepadBinds.LEFT_STICK_LEFT)
.category(VMOUSE_CATEGORY) .category(VMOUSE_CATEGORY)
.context(BindContexts.GUI_VMOUSE) .context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
.build()); .build());
register(VMOUSE_MOVE_RIGHT = ControllerBindingBuilder.create(controller) register(VMOUSE_MOVE_RIGHT = ControllerBindingBuilder.create(controller)
.identifier("controlify", "vmouse_move_right") .identifier("controlify", "vmouse_move_right")
.defaultBind(GamepadBinds.LEFT_STICK_RIGHT) .defaultBind(GamepadBinds.LEFT_STICK_RIGHT)
.category(VMOUSE_CATEGORY) .category(VMOUSE_CATEGORY)
.context(BindContexts.GUI_VMOUSE) .context(BindContexts.GUI_VMOUSE_CURSOR_ONLY)
.build()); .build());
register(VMOUSE_LCLICK = ControllerBindingBuilder.create(controller) register(VMOUSE_LCLICK = ControllerBindingBuilder.create(controller)
.identifier("controlify", "vmouse_lclick") .identifier("controlify", "vmouse_lclick")
@ -335,7 +335,7 @@ public class ControllerBindings<T extends ControllerState> {
.identifier("controlify", "vmouse_toggle") .identifier("controlify", "vmouse_toggle")
.defaultBind(GamepadBinds.BACK) .defaultBind(GamepadBinds.BACK)
.category(VMOUSE_CATEGORY) .category(VMOUSE_CATEGORY)
.context(BindContexts.GUI_VMOUSE) .context(BindContexts.GUI_VMOUSE, BindContexts.GUI)
.build()); .build());
register(GUI_NAVI_UP = ControllerBindingBuilder.create(controller) register(GUI_NAVI_UP = ControllerBindingBuilder.create(controller)
.identifier("controlify", "gui_navi_up") .identifier("controlify", "gui_navi_up")

View File

@ -1,4 +1,178 @@
package dev.isxander.controlify.gui.guide; 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<?>;
}
} }

View File

@ -75,8 +75,8 @@ public class InGameButtonGuide implements IngameGuideRegistry {
ControlifyCompat.ifBeginHudBatching(); ControlifyCompat.ifBeginHudBatching();
leftLayout.render(poseStack, tickDelta); leftLayout.renderComponent(poseStack, tickDelta);
rightLayout.render(poseStack, tickDelta); rightLayout.renderComponent(poseStack, tickDelta);
ControlifyCompat.ifEndHudBatching(); ControlifyCompat.ifEndHudBatching();
} }

View File

@ -1,9 +1,6 @@
package dev.isxander.controlify.gui.layout; package dev.isxander.controlify.gui.layout;
import org.joml.Vector2f;
import org.joml.Vector2fc;
import org.joml.Vector2i; import org.joml.Vector2i;
import org.joml.Vector2ic;
public enum AnchorPoint { public enum AnchorPoint {
TOP_LEFT(0, 0), TOP_LEFT(0, 0),
@ -23,7 +20,7 @@ public enum AnchorPoint {
this.anchorY = anchorY; this.anchorY = anchorY;
} }
public Vector2i getAnchorPosition(Vector2ic windowSize) { public Vector2i getAnchorPosition(int w, int h) {
return new Vector2i((int) (windowSize.x() * anchorX), (int) (windowSize.y() * anchorY)); return new Vector2i((int) (w * anchorX), (int) (h * anchorY));
} }
} }

View File

@ -1,12 +1,11 @@
package dev.isxander.controlify.gui.layout; package dev.isxander.controlify.gui.layout;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import org.joml.Vector2i; import net.minecraft.client.gui.components.Renderable;
import org.joml.Vector2ic; import org.joml.Vector2ic;
public class PositionedComponent<T extends RenderComponent> { public class PositionedComponent<T extends RenderComponent> implements Renderable {
private final T component; private final T component;
private int x, y; private int x, y;
@ -21,19 +20,24 @@ public class PositionedComponent<T extends RenderComponent> {
this.offsetY = offsetY; this.offsetY = offsetY;
this.windowAnchor = windowAnchor; this.windowAnchor = windowAnchor;
this.origin = origin; this.origin = origin;
updatePosition();
} }
public void updatePosition() { public void updatePosition() {
Vector2ic windowPosition = windowAnchor.getAnchorPosition(windowSize()); Vector2ic componentSize = component.size();
Vector2ic anchoredPosition = origin.getAnchorPosition(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.x = windowPosition.x() + offsetX - anchoredPosition.x();
this.y = windowPosition.y() + offsetY - anchoredPosition.y(); 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); component.render(stack, x, y, deltaTime);
} }
@ -48,9 +52,4 @@ public class PositionedComponent<T extends RenderComponent> {
public T getComponent() { public T getComponent() {
return component; return component;
} }
private Vector2i windowSize() {
Window window = Minecraft.getInstance().getWindow();
return new Vector2i(window.getGuiScaledWidth(), window.getGuiScaledHeight());
}
} }

View File

@ -111,7 +111,8 @@ public class RowLayoutComponent<T extends RenderComponent> extends AbstractLayou
return this; return this;
} }
public Builder<T> elements(T... elements) { @SafeVarargs
public final Builder<T> elements(T... elements) {
this.elements.addAll(Arrays.asList(elements)); this.elements.addAll(Arrays.asList(elements));
return this; return this;
} }

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
});
}
}

View File

@ -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.ComponentPath;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.FocusNavigationEvent;
import net.minecraft.client.gui.navigation.ScreenDirection; import net.minecraft.client.gui.navigation.ScreenDirection;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker; import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
@Mixin(Screen.class) @Mixin(Screen.class)
public interface ScreenAccessor { public interface ScreenAccessor {
@Invoker @Invoker
@ -17,4 +21,7 @@ public interface ScreenAccessor {
@Invoker @Invoker
void invokeClearFocus(); void invokeClearFocus();
@Accessor
List<Renderable> getRenderables();
} }

View File

@ -1,13 +1,16 @@
package dev.isxander.controlify.screenop; package dev.isxander.controlify.screenop;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode; import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.api.event.ControlifyEvents; 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.mixins.feature.screenop.vanilla.TabNavigationBarAccessor;
import dev.isxander.controlify.sound.ControlifySounds; import dev.isxander.controlify.sound.ControlifySounds;
import dev.isxander.controlify.utils.NavigationHelper; 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.Minecraft;
import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.ComponentPath;
import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.components.events.GuiEventListener;
@ -25,7 +28,7 @@ import java.util.*;
public class ScreenProcessor<T extends Screen> { public class ScreenProcessor<T extends Screen> {
public final T screen; public final T screen;
protected final NavigationHelper navigationHelper = new NavigationHelper(10, 3); 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) { public ScreenProcessor(T screen) {
this.screen = screen; this.screen = screen;
@ -40,12 +43,17 @@ public class ScreenProcessor<T extends Screen> {
if (!handleComponentButtonOverride(controller)) if (!handleComponentButtonOverride(controller))
handleButtons(controller); handleButtons(controller);
} else { } else {
handleScreenVMouse(controller); handleScreenVMouse(controller, Controlify.instance().virtualMouseHandler());
} }
handleTabNavigation(controller); 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) { public void onInputModeChanged(InputMode mode) {
switch (mode) { switch (mode) {
case KEYBOARD_MOUSE -> ((ScreenAccessor) screen).invokeClearFocus(); case KEYBOARD_MOUSE -> ((ScreenAccessor) screen).invokeClearFocus();
@ -115,12 +123,12 @@ public class ScreenProcessor<T extends Screen> {
screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0);
} }
if (controller.bindings().GUI_BACK.justPressed()) { if (controller.bindings().GUI_BACK.justPressed()) {
this.playClackSound(); playClackSound();
screen.onClose(); 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() { protected void setInitialFocus() {
if (screen.getFocused() == null && Controlify.instance().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) { if (screen.getFocused() == null && Controlify.instance().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
var accessor = (ScreenAccessor) screen; var accessor = (ScreenAccessor) screen;
@ -191,8 +203,8 @@ public class ScreenProcessor<T extends Screen> {
} }
} }
public boolean forceVirtualMouse() { public VirtualMouseBehaviour virtualMouseBehaviour() {
return false; return VirtualMouseBehaviour.DEFAULT;
} }
protected Queue<GuiEventListener> getFocusTree() { protected Queue<GuiEventListener> getFocusTree() {
@ -211,7 +223,7 @@ public class ScreenProcessor<T extends Screen> {
return tree; return tree;
} }
protected void playClackSound() { public static void playClackSound() {
minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
} }
} }

View File

@ -1,13 +1,14 @@
package dev.isxander.controlify.screenop; package dev.isxander.controlify.screenop;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.Optional;
public interface ScreenProcessorProvider { public interface ScreenProcessorProvider {
ScreenProcessor<?> screenProcessor(); ScreenProcessor<?> screenProcessor();
static ScreenProcessor<?> provide(Screen screen) { static ScreenProcessor<?> provide(@NotNull Screen screen) {
Optional<ScreenProcessor<?>> optional = REGISTRY.get(screen); Optional<ScreenProcessor<?>> optional = REGISTRY.get(screen);
if (optional.isPresent()) return optional.get(); if (optional.isPresent()) return optional.get();

View File

@ -1,14 +1,34 @@
package dev.isxander.controlify.screenop.compat.vanilla; 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.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.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.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.ClickType; import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.Slot;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
public class AbstractContainerScreenProcessor<T extends AbstractContainerScreen<?>> extends ScreenProcessor<T> { 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 Supplier<Slot> hoveredSlot;
private final ClickSlotFunction clickSlotFunction; private final ClickSlotFunction clickSlotFunction;
@ -19,13 +39,134 @@ public class AbstractContainerScreenProcessor<T extends AbstractContainerScreen<
} }
@Override @Override
protected void handleScreenVMouse(Controller<?, ?> controller) { protected void handleScreenVMouse(Controller<?, ?> controller, VirtualMouseHandler vmouse) {
if (controller.bindings().DROP.justPressed()) { if (controller.bindings().DROP.justPressed()) {
Slot slot = hoveredSlot.get(); Slot slot = hoveredSlot.get();
if (slot != null && slot.hasItem()) { if (slot != null && slot.hasItem()) {
clickSlotFunction.clickSlot(slot, slot.index, 0, ClickType.THROW); 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 @FunctionalInterface

View File

@ -1,8 +1,8 @@
package dev.isxander.controlify.screenop.compat.vanilla; package dev.isxander.controlify.screenop.compat.vanilla;
import dev.isxander.controlify.controller.Controller; 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.mixins.feature.screenop.vanilla.CreativeModeInventoryScreenAccessor;
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.CreativeModeTabs;
@ -15,7 +15,7 @@ public class CreativeModeInventoryScreenProcessor extends AbstractContainerScree
} }
@Override @Override
protected void handleScreenVMouse(Controller<?, ?> controller) { protected void handleScreenVMouse(Controller<?, ?> controller, VirtualMouseHandler vmouse) {
var accessor = (CreativeModeInventoryScreenAccessor) screen; var accessor = (CreativeModeInventoryScreenAccessor) screen;
if (controller.bindings().GUI_NEXT_TAB.justPressed()) { if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
@ -31,6 +31,6 @@ public class CreativeModeInventoryScreenProcessor extends AbstractContainerScree
accessor.invokeSelectTab(tabs.get(newIndex)); accessor.invokeSelectTab(tabs.get(newIndex));
} }
super.handleScreenVMouse(controller); super.handleScreenVMouse(controller, vmouse);
} }
} }

View File

@ -0,0 +1,12 @@
package dev.isxander.controlify.virtualmouse;
public enum VirtualMouseBehaviour {
DEFAULT,
ENABLED,
DISABLED,
CURSOR_ONLY;
public boolean hasCursor() {
return this != DISABLED;
}
}

View File

@ -9,13 +9,14 @@ import dev.isxander.controlify.api.vmousesnapping.ISnapBehaviour;
import dev.isxander.controlify.api.vmousesnapping.SnapPoint; import dev.isxander.controlify.api.vmousesnapping.SnapPoint;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.debug.DebugProperties; import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.screenop.ScreenProcessor;
import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.ScreenProcessorProvider;
import dev.isxander.controlify.api.event.ControlifyEvents; import dev.isxander.controlify.api.event.ControlifyEvents;
import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor; import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor;
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor; import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
import dev.isxander.controlify.utils.ToastUtils;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent; import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
@ -40,7 +41,6 @@ public class VirtualMouseHandler {
private Set<SnapPoint> snapPoints; private Set<SnapPoint> snapPoints;
private SnapPoint lastSnappedPoint; private SnapPoint lastSnappedPoint;
private boolean snapping;
public VirtualMouseHandler() { public VirtualMouseHandler() {
this.minecraft = Minecraft.getInstance(); this.minecraft = Minecraft.getInstance();
@ -77,8 +77,6 @@ public class VirtualMouseHandler {
if (impulseX == 0 && impulseY == 0) { if (impulseX == 0 && impulseY == 0) {
if ((prevImpulseX != 0 || prevImpulseY != 0)) if ((prevImpulseX != 0 || prevImpulseY != 0))
snapToClosestPoint(); snapToClosestPoint();
} else {
snapping = false;
} }
var sensitivity = controller.config().virtualMouseSensitivity; var sensitivity = controller.config().virtualMouseSensitivity;
@ -91,6 +89,17 @@ public class VirtualMouseHandler {
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth()); targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight()); 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(); scrollY += controller.bindings().VMOUSE_SCROLL_UP.state() - controller.bindings().VMOUSE_SCROLL_DOWN.state();
var mouseHandler = (MouseHandlerAccessor) minecraft.mouseHandler; var mouseHandler = (MouseHandlerAccessor) minecraft.mouseHandler;
@ -113,10 +122,6 @@ public class VirtualMouseHandler {
} else if (controller.bindings().VMOUSE_SHIFT_CLICK.justReleased()) { } else if (controller.bindings().VMOUSE_SHIFT_CLICK.justReleased()) {
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_LEFT, GLFW.GLFW_RELEASE, 0); 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() { public void updateMouse() {
@ -164,7 +169,6 @@ public class VirtualMouseHandler {
if (closestSnapPoint != null) { if (closestSnapPoint != null) {
lastSnappedPoint = closestSnapPoint; lastSnappedPoint = closestSnapPoint;
snapping = false;
targetX = currentX = closestSnapPoint.position().x() / scaleFactor.x(); targetX = currentX = closestSnapPoint.position().x() / scaleFactor.x();
targetY = currentY = closestSnapPoint.position().y() / scaleFactor.y(); targetY = currentY = closestSnapPoint.position().y() / scaleFactor.y();
@ -271,15 +275,30 @@ public class VirtualMouseHandler {
public boolean requiresVirtualMouse() { public boolean requiresVirtualMouse() {
var isController = Controlify.instance().currentInputMode() == InputMode.CONTROLLER; var isController = Controlify.instance().currentInputMode() == InputMode.CONTROLLER;
var hasScreen = minecraft.screen != null; 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() { public void toggleVirtualMouse() {
if (minecraft.screen == null) return; 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 screens = Controlify.instance().config().globalSettings().virtualMouseScreens;
var screenClass = minecraft.screen.getClass(); var screenClass = minecraft.screen.getClass();
if (screens.contains(screenClass)) { if (screens.contains(screenClass)) {
@ -287,22 +306,20 @@ public class VirtualMouseHandler {
disableVirtualMouse(); disableVirtualMouse();
Controlify.instance().hideMouse(true, false); Controlify.instance().hideMouse(true, false);
minecraft.getToasts().addToast(SystemToast.multiline( ToastUtils.sendToast(
minecraft,
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
Component.translatable("controlify.toast.vmouse_disabled.title"), Component.translatable("controlify.toast.vmouse_disabled.title"),
Component.translatable("controlify.toast.vmouse_disabled.description") Component.translatable("controlify.toast.vmouse_disabled.description"),
)); false
);
} else { } else {
screens.add(screenClass); screens.add(screenClass);
enableVirtualMouse(); enableVirtualMouse();
minecraft.getToasts().addToast(SystemToast.multiline( ToastUtils.sendToast(
minecraft,
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
Component.translatable("controlify.toast.vmouse_enabled.title"), 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(); Controlify.instance().config().save();
@ -311,4 +328,12 @@ public class VirtualMouseHandler {
public boolean isVirtualMouseEnabled() { public boolean isVirtualMouseEnabled() {
return virtualMouseEnabled; 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);
}
} }

View File

@ -116,6 +116,8 @@
"controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.", "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.title": "Virtual Mouse Disabled",
"controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.", "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.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.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", "controlify.toast.default_controller_connected.title": "Controller Connected",

View File

@ -39,7 +39,7 @@
"feature.guide.ingame.ClientPacketListenerMixin", "feature.guide.ingame.ClientPacketListenerMixin",
"feature.guide.ingame.GuiMixin", "feature.guide.ingame.GuiMixin",
"feature.guide.screen.AbstractButtonMixin", "feature.guide.screen.AbstractButtonMixin",
"feature.guide.screen.AbstractContainerScreenMixin", "feature.guide.screen.AbstractContainerScreenAccessor",
"feature.guide.screen.AbstractWidgetMixin", "feature.guide.screen.AbstractWidgetMixin",
"feature.guide.screen.TabNavigationBarMixin", "feature.guide.screen.TabNavigationBarMixin",
"feature.oofinput.GameRendererMixin", "feature.oofinput.GameRendererMixin",
@ -49,7 +49,9 @@
"feature.rumble.explosion.ClientPacketListenerMixin", "feature.rumble.explosion.ClientPacketListenerMixin",
"feature.rumble.itembreak.LocalPlayerMixin", "feature.rumble.itembreak.LocalPlayerMixin",
"feature.rumble.useitem.LocalPlayerMixin", "feature.rumble.useitem.LocalPlayerMixin",
"feature.screenop.GameRendererMixin",
"feature.screenop.MinecraftMixin", "feature.screenop.MinecraftMixin",
"feature.screenop.ScreenAccessor",
"feature.screenop.ScreenMixin", "feature.screenop.ScreenMixin",
"feature.screenop.vanilla.AbstractButtonMixin", "feature.screenop.vanilla.AbstractButtonMixin",
"feature.screenop.vanilla.AbstractContainerEventHandlerMixin", "feature.screenop.vanilla.AbstractContainerEventHandlerMixin",
@ -64,7 +66,6 @@
"feature.screenop.vanilla.JoinMultiplayerScreenMixin", "feature.screenop.vanilla.JoinMultiplayerScreenMixin",
"feature.screenop.vanilla.LanguageSelectionListEntryMixin", "feature.screenop.vanilla.LanguageSelectionListEntryMixin",
"feature.screenop.vanilla.OptionsSubScreenAccessor", "feature.screenop.vanilla.OptionsSubScreenAccessor",
"feature.screenop.vanilla.ScreenAccessor",
"feature.screenop.vanilla.SelectWorldScreenAccessor", "feature.screenop.vanilla.SelectWorldScreenAccessor",
"feature.screenop.vanilla.SelectWorldScreenMixin", "feature.screenop.vanilla.SelectWorldScreenMixin",
"feature.screenop.vanilla.ServerSelectionListEntryMixin", "feature.screenop.vanilla.ServerSelectionListEntryMixin",

View File

@ -1,6 +1,5 @@
package dev.isxander.controlify.test; package dev.isxander.controlify.test;
import dev.isxander.controlify.controller.Controller;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot; import net.minecraft.client.Screenshot;
@ -69,7 +68,6 @@ public class ClientTestHelper {
public static FakeController createAndUseDummyController() { public static FakeController createAndUseDummyController() {
var controller = new FakeController(); var controller = new FakeController();
Controller.CONTROLLERS.put(controller.uid(), controller);
controller.use(); controller.use();
return controller; return controller;
} }

View File

@ -4,7 +4,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerType; import dev.isxander.controlify.controller.ControllerType;
import dev.isxander.controlify.controller.hid.ControllerHIDService; import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.JoystickConfig; import dev.isxander.controlify.controller.joystick.JoystickConfig;
@ -194,7 +193,6 @@ public class FakeController implements JoystickController<JoystickConfig> {
public void finish() { public void finish() {
Controlify.instance().setCurrentController(null); Controlify.instance().setCurrentController(null);
Controller.CONTROLLERS.remove(uid, this);
} }
@Override @Override