forked from Clones/Controlify
virtual mouse + singleplayer screen compat + 22w05a
This commit is contained in:
@ -7,10 +7,10 @@ github_release = "2.+"
|
|||||||
machete = "1.+"
|
machete = "1.+"
|
||||||
grgit = "5.0.+"
|
grgit = "5.0.+"
|
||||||
|
|
||||||
minecraft = "23w04a"
|
minecraft = "23w05a"
|
||||||
quilt_mappings = "10"
|
quilt_mappings = "1"
|
||||||
fabric_loader = "0.14.13"
|
fabric_loader = "0.14.13"
|
||||||
fabric_api = "0.73.1+1.19.4"
|
fabric_api = "0.73.3+1.19.4"
|
||||||
mixin_extras = "0.2.0-beta.1"
|
mixin_extras = "0.2.0-beta.1"
|
||||||
yet_another_config_lib = "2.2.0+update.1.19.4-SNAPSHOT"
|
yet_another_config_lib = "2.2.0+update.1.19.4-SNAPSHOT"
|
||||||
mod_menu = "6.0.0-beta.1"
|
mod_menu = "6.0.0-beta.1"
|
||||||
|
@ -7,8 +7,12 @@ import dev.isxander.controlify.controller.Controller;
|
|||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import dev.isxander.controlify.event.ControlifyEvents;
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
import dev.isxander.controlify.ingame.InGameInputHandler;
|
import dev.isxander.controlify.ingame.InGameInputHandler;
|
||||||
|
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||||
|
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@ -18,20 +22,24 @@ public class Controlify {
|
|||||||
|
|
||||||
private Controller currentController;
|
private Controller currentController;
|
||||||
private InGameInputHandler inGameInputHandler;
|
private InGameInputHandler inGameInputHandler;
|
||||||
|
private VirtualMouseHandler virtualMouseHandler;
|
||||||
private InputMode currentInputMode;
|
private InputMode currentInputMode;
|
||||||
|
|
||||||
|
private final ControlifyConfig config = new ControlifyConfig();
|
||||||
|
|
||||||
public void onInitializeInput() {
|
public void onInitializeInput() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
// find already connected controllers
|
// find already connected controllers
|
||||||
for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; i++) {
|
for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; i++) {
|
||||||
if (GLFW.glfwJoystickPresent(i)) {
|
if (GLFW.glfwJoystickPresent(i)) {
|
||||||
setCurrentController(Controller.byId(i));
|
setCurrentController(Controller.byId(i));
|
||||||
LOGGER.info("Controller found: " + currentController.name());
|
LOGGER.info("Controller found: " + currentController.name());
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load after initial controller discovery
|
// load after initial controller discovery
|
||||||
ControlifyConfig.load();
|
config().load();
|
||||||
|
|
||||||
// listen for new controllers
|
// listen for new controllers
|
||||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||||
@ -40,16 +48,32 @@ public class Controlify {
|
|||||||
LOGGER.info("Controller connected: " + currentController.name());
|
LOGGER.info("Controller connected: " + currentController.name());
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||||
|
|
||||||
ControlifyConfig.load(); // load config again if a configuration already exists for this controller
|
config().load(); // load config again if a configuration already exists for this controller
|
||||||
ControlifyConfig.save(); // save config if it doesn't exist
|
config().save(); // save config if it doesn't exist
|
||||||
|
|
||||||
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
|
minecraft,
|
||||||
|
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||||
|
Component.translatable("controlify.toast.controller_connected.title"),
|
||||||
|
Component.translatable("controlify.toast.controller_connected.description")
|
||||||
|
));
|
||||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||||
var controller = Controller.CONTROLLERS.remove(jid);
|
var controller = Controller.CONTROLLERS.remove(jid);
|
||||||
setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null));
|
setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null));
|
||||||
LOGGER.info("Controller disconnected: " + controller.name());
|
LOGGER.info("Controller disconnected: " + controller.name());
|
||||||
this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||||
|
|
||||||
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
|
minecraft,
|
||||||
|
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||||
|
Component.translatable("controlify.toast.controller_disconnected.title"),
|
||||||
|
Component.translatable("controlify.toast.controller_disconnected.description", controller.name())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.virtualMouseHandler = new VirtualMouseHandler();
|
||||||
|
|
||||||
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,39 +93,74 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (client.screen != null) {
|
if (client.screen != null) {
|
||||||
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
if (!this.virtualMouseHandler().isVirtualMouseEnabled())
|
||||||
|
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
||||||
} else {
|
} else {
|
||||||
this.getInGameInputHandler().inputTick();
|
this.inGameInputHandler().inputTick();
|
||||||
}
|
}
|
||||||
|
this.virtualMouseHandler().handleControllerInput(currentController);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Controller getCurrentController() {
|
public ControlifyConfig config() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Controller currentController() {
|
||||||
return currentController;
|
return currentController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentController(Controller controller) {
|
public void setCurrentController(Controller controller) {
|
||||||
if (this.currentController == controller) return;
|
if (this.currentController == controller) return;
|
||||||
|
|
||||||
this.currentController = controller;
|
this.currentController = controller;
|
||||||
this.inGameInputHandler = new InGameInputHandler(controller);
|
|
||||||
|
this.inGameInputHandler = new InGameInputHandler(this.currentController != null ? controller : Controller.DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InGameInputHandler getInGameInputHandler() {
|
public InGameInputHandler inGameInputHandler() {
|
||||||
return inGameInputHandler;
|
return inGameInputHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputMode getCurrentInputMode() {
|
public VirtualMouseHandler virtualMouseHandler() {
|
||||||
|
return virtualMouseHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputMode currentInputMode() {
|
||||||
return currentInputMode;
|
return currentInputMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentInputMode(InputMode currentInputMode) {
|
public void setCurrentInputMode(InputMode currentInputMode) {
|
||||||
if (this.currentInputMode == currentInputMode) return;
|
if (this.currentInputMode == currentInputMode) return;
|
||||||
|
|
||||||
this.currentInputMode = currentInputMode;
|
this.currentInputMode = currentInputMode;
|
||||||
|
|
||||||
|
var minecraft = Minecraft.getInstance();
|
||||||
|
hideMouse(currentInputMode == InputMode.CONTROLLER);
|
||||||
|
if (minecraft.screen != null) {
|
||||||
|
ScreenProcessorProvider.provide(minecraft.screen).onInputModeChanged(currentInputMode);
|
||||||
|
}
|
||||||
|
|
||||||
ControlifyEvents.INPUT_MODE_CHANGED.invoker().onInputModeChanged(currentInputMode);
|
ControlifyEvents.INPUT_MODE_CHANGED.invoker().onInputModeChanged(currentInputMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Controlify getInstance() {
|
public void hideMouse(boolean hide) {
|
||||||
|
var minecraft = Minecraft.getInstance();
|
||||||
|
GLFW.glfwSetInputMode(
|
||||||
|
minecraft.getWindow().getWindow(),
|
||||||
|
GLFW.GLFW_CURSOR,
|
||||||
|
hide
|
||||||
|
? GLFW.GLFW_CURSOR_HIDDEN
|
||||||
|
: GLFW.GLFW_CURSOR_NORMAL
|
||||||
|
);
|
||||||
|
if (minecraft.screen != null) {
|
||||||
|
var mouseHandlerAccessor = (MouseHandlerAccessor) minecraft.mouseHandler;
|
||||||
|
if (hide && !virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||||
|
// stop mouse hovering over last element before hiding cursor but don't actually move it
|
||||||
|
// so when the user switches back to mouse it will be in the same place
|
||||||
|
mouseHandlerAccessor.invokeOnMove(minecraft.getWindow().getWindow(), 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Controlify instance() {
|
||||||
if (instance == null) instance = new Controlify();
|
if (instance == null) instance = new Controlify();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ public class ControllerBindings {
|
|||||||
INVENTORY,
|
INVENTORY,
|
||||||
CHANGE_PERSPECTIVE,
|
CHANGE_PERSPECTIVE,
|
||||||
OPEN_CHAT,
|
OPEN_CHAT,
|
||||||
GUI_PRESS, GUI_BACK;
|
GUI_PRESS, GUI_BACK,
|
||||||
|
VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_MCLICK, VMOUSE_ESCAPE, VMOUSE_TOGGLE;
|
||||||
|
|
||||||
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
||||||
|
|
||||||
@ -44,7 +45,11 @@ public class ControllerBindings {
|
|||||||
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat));
|
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat));
|
||||||
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press"), null));
|
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press"), null));
|
||||||
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back"), null));
|
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back"), null));
|
||||||
|
register(VMOUSE_LCLICK = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick"), null));
|
||||||
|
register(VMOUSE_RCLICK = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick"), null));
|
||||||
|
register(VMOUSE_MCLICK = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_mclick"), null));
|
||||||
|
register(VMOUSE_ESCAPE = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape"), null));
|
||||||
|
register(VMOUSE_TOGGLE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "vmouse_toggle"), null));
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
||||||
|
|
||||||
@ -82,7 +87,7 @@ public class ControllerBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void imitateVanillaClick(Controller controller) {
|
private void imitateVanillaClick(Controller controller) {
|
||||||
if (Controlify.getInstance().getCurrentInputMode() != InputMode.CONTROLLER)
|
if (Controlify.instance().currentInputMode() != InputMode.CONTROLLER)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (var binding : registry().values()) {
|
for (var binding : registry().values()) {
|
||||||
|
@ -2,12 +2,13 @@ package dev.isxander.controlify.compatibility.screen;
|
|||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.InputMode;
|
import dev.isxander.controlify.InputMode;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.CustomFocus;
|
import dev.isxander.controlify.compatibility.screen.component.CustomFocus;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
import dev.isxander.controlify.mixins.compat.screen.vanilla.ScreenAccessor;
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.ScreenAccessor;
|
||||||
import net.minecraft.client.gui.ComponentPath;
|
import net.minecraft.client.gui.ComponentPath;
|
||||||
import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
|
|
||||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||||
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;
|
||||||
@ -15,16 +16,14 @@ import net.minecraft.client.gui.screens.Screen;
|
|||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
|
|
||||||
public class ScreenProcessor {
|
public class ScreenProcessor<T extends Screen> {
|
||||||
private static final int REPEAT_DELAY = 3;
|
public final T screen;
|
||||||
|
|
||||||
public final Screen screen;
|
|
||||||
private int lastMoved = 0;
|
private int lastMoved = 0;
|
||||||
|
|
||||||
public ScreenProcessor(Screen screen) {
|
public ScreenProcessor(T screen) {
|
||||||
this.screen = screen;
|
this.screen = screen;
|
||||||
|
ControlifyEvents.VIRTUAL_MOUSE_TOGGLED.register(this::onVirtualMouseToggled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onControllerUpdate(Controller controller) {
|
public void onControllerUpdate(Controller controller) {
|
||||||
@ -32,6 +31,17 @@ public class ScreenProcessor {
|
|||||||
handleButtons(controller);
|
handleButtons(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onInputModeChanged(InputMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case KEYBOARD_MOUSE -> ((ScreenAccessor) screen).invokeClearFocus();
|
||||||
|
case CONTROLLER -> {
|
||||||
|
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||||
|
setInitialFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void handleComponentNavigation(Controller controller) {
|
protected void handleComponentNavigation(Controller controller) {
|
||||||
var focusTree = getFocusTree();
|
var focusTree = getFocusTree();
|
||||||
while (!focusTree.isEmpty()) {
|
while (!focusTree.isEmpty()) {
|
||||||
@ -42,7 +52,7 @@ public class ScreenProcessor {
|
|||||||
|
|
||||||
var accessor = (ScreenAccessor) screen;
|
var accessor = (ScreenAccessor) screen;
|
||||||
|
|
||||||
boolean repeatEventAvailable = ++lastMoved >= REPEAT_DELAY;
|
boolean repeatEventAvailable = ++lastMoved >= controller.config().screenRepeatNavigationDelay;
|
||||||
|
|
||||||
var axes = controller.state().axes();
|
var axes = controller.state().axes();
|
||||||
var prevAxes = controller.prevState().axes();
|
var prevAxes = controller.prevState().axes();
|
||||||
@ -93,8 +103,19 @@ public class ScreenProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onWidgetRebuild() {
|
public void onWidgetRebuild() {
|
||||||
// initial focus
|
setInitialFocus();
|
||||||
if (screen.getFocused() == null && Controlify.getInstance().getCurrentInputMode() == InputMode.CONTROLLER) {
|
}
|
||||||
|
|
||||||
|
public void onVirtualMouseToggled(boolean enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
((ScreenAccessor) screen).invokeClearFocus();
|
||||||
|
} else {
|
||||||
|
setInitialFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setInitialFocus() {
|
||||||
|
if (screen.getFocused() == null && Controlify.instance().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||||
var accessor = (ScreenAccessor) screen;
|
var accessor = (ScreenAccessor) screen;
|
||||||
ComponentPath path = screen.nextFocusPath(accessor.invokeCreateArrowEvent(ScreenDirection.DOWN));
|
ComponentPath path = screen.nextFocusPath(accessor.invokeCreateArrowEvent(ScreenDirection.DOWN));
|
||||||
if (path != null)
|
if (path != null)
|
||||||
@ -102,6 +123,10 @@ public class ScreenProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean forceVirtualMouse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected Queue<GuiEventListener> getFocusTree() {
|
protected Queue<GuiEventListener> getFocusTree() {
|
||||||
if (screen.getFocused() == null) return new ArrayDeque<>();
|
if (screen.getFocused() == null) return new ArrayDeque<>();
|
||||||
|
|
||||||
@ -110,7 +135,9 @@ public class ScreenProcessor {
|
|||||||
tree.add(focused);
|
tree.add(focused);
|
||||||
while (focused instanceof CustomFocus customFocus) {
|
while (focused instanceof CustomFocus customFocus) {
|
||||||
focused = customFocus.getCustomFocus();
|
focused = customFocus.getCustomFocus();
|
||||||
tree.addFirst(focused);
|
|
||||||
|
if (focused != null)
|
||||||
|
tree.addFirst(focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
|
@ -3,9 +3,9 @@ package dev.isxander.controlify.compatibility.screen;
|
|||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
|
||||||
public interface ScreenProcessorProvider {
|
public interface ScreenProcessorProvider {
|
||||||
ScreenProcessor screenProcessor();
|
ScreenProcessor<?> screenProcessor();
|
||||||
|
|
||||||
static ScreenProcessor provide(Screen screen) {
|
static ScreenProcessor<?> provide(Screen screen) {
|
||||||
return ((ScreenProcessorProvider) screen).screenProcessor();
|
return ((ScreenProcessorProvider) screen).screenProcessor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,14 @@ import net.minecraft.client.gui.components.events.GuiEventListener;
|
|||||||
public interface ComponentProcessor {
|
public interface ComponentProcessor {
|
||||||
ComponentProcessor EMPTY = new ComponentProcessor(){};
|
ComponentProcessor EMPTY = new ComponentProcessor(){};
|
||||||
|
|
||||||
default boolean overrideControllerNavigation(ScreenProcessor screen, Controller controller) {
|
default boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
default boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onNavigateTo(ScreenProcessor screen, Controller controller) {
|
default void onNavigateTo(ScreenProcessor<?> screen, Controller controller) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.SelectWorldScreenAccessor;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
|
|
||||||
|
public class SelectWorldScreenProcessor extends ScreenProcessor<SelectWorldScreen> {
|
||||||
|
public SelectWorldScreenProcessor(SelectWorldScreen screen) {
|
||||||
|
super(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleButtons(Controller controller) {
|
||||||
|
if (screen.getFocused() != null && screen.getFocused() instanceof Button) {
|
||||||
|
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||||
|
screen.setFocused(((SelectWorldScreenAccessor) screen).getList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handleButtons(controller);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.compatibility.screen.component;
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
@ -24,7 +25,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerNavigation(ScreenProcessor screen, Controller controller) {
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
||||||
if (!this.canChangeValueGetter.get()) return false;
|
if (!this.canChangeValueGetter.get()) return false;
|
||||||
|
|
||||||
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
||||||
@ -51,7 +52,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||||
if (!this.canChangeValueGetter.get()) return false;
|
if (!this.canChangeValueGetter.get()) return false;
|
||||||
|
|
||||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||||
@ -63,7 +64,8 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNavigateTo(ScreenProcessor screen, Controller controller) {
|
public void onNavigateTo(ScreenProcessor<?> screen, Controller controller) {
|
||||||
|
System.out.println("navigated!");
|
||||||
this.canChangeValueSetter.accept(false);
|
this.canChangeValueSetter.accept(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.SelectWorldScreenAccessor;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
|
|
||||||
|
public class WorldListEntryComponentProcessor implements ComponentProcessor {
|
||||||
|
@Override
|
||||||
|
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
||||||
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
|
var selectWorldScreen = (SelectWorldScreen) screen.screen;
|
||||||
|
selectWorldScreen.setFocused(((SelectWorldScreenAccessor) selectWorldScreen).getSelectButton());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.config;
|
package dev.isxander.controlify.config;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerConfig;
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
@ -19,20 +20,23 @@ public class ControlifyConfig {
|
|||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
private static JsonObject config = new JsonObject();
|
private JsonObject controllerData = new JsonObject();
|
||||||
|
private GlobalSettings globalSettings = new GlobalSettings();
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
Controlify.LOGGER.info("Saving Controlify config...");
|
||||||
|
|
||||||
public static void save() {
|
|
||||||
try {
|
try {
|
||||||
generateConfig();
|
|
||||||
|
|
||||||
Files.deleteIfExists(CONFIG_PATH);
|
Files.deleteIfExists(CONFIG_PATH);
|
||||||
Files.writeString(CONFIG_PATH, GSON.toJson(config), StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING);
|
Files.writeString(CONFIG_PATH, GSON.toJson(generateConfig()), StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException("Failed to save config!", e);
|
throw new IllegalStateException("Failed to save config!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load() {
|
public void load() {
|
||||||
|
Controlify.LOGGER.info("Loading Controlify config...");
|
||||||
|
|
||||||
if (!Files.exists(CONFIG_PATH)) {
|
if (!Files.exists(CONFIG_PATH)) {
|
||||||
save();
|
save();
|
||||||
return;
|
return;
|
||||||
@ -45,19 +49,26 @@ public class ControlifyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateConfig() {
|
private JsonObject generateConfig() {
|
||||||
JsonObject configCopy = config.deepCopy(); // we use the old config, so we don't lose disconnected controller data
|
JsonObject config = new JsonObject();
|
||||||
|
|
||||||
|
JsonObject newControllerData = controllerData.deepCopy(); // we use the old config, so we don't lose disconnected controller data
|
||||||
|
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
// `add` replaces if already existing
|
// `add` replaces if already existing
|
||||||
// TODO: find a better way to identify controllers, GUID will report the same for multiple controllers of the same model
|
// TODO: find a better way to identify controllers, GUID will report the same for multiple controllers of the same model
|
||||||
configCopy.add(controller.guid(), generateControllerConfig(controller));
|
newControllerData.add(controller.guid(), generateControllerConfig(controller));
|
||||||
}
|
}
|
||||||
|
|
||||||
config = configCopy;
|
controllerData = newControllerData;
|
||||||
|
config.add("controllers", controllerData);
|
||||||
|
|
||||||
|
config.add("global", GSON.toJsonTree(globalSettings));
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonObject generateControllerConfig(Controller controller) {
|
private JsonObject generateControllerConfig(Controller controller) {
|
||||||
JsonObject object = new JsonObject();
|
JsonObject object = new JsonObject();
|
||||||
|
|
||||||
object.add("config", GSON.toJsonTree(controller.config()));
|
object.add("config", GSON.toJsonTree(controller.config()));
|
||||||
@ -66,17 +77,27 @@ public class ControlifyConfig {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyConfig(JsonObject object) {
|
private void applyConfig(JsonObject object) {
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
globalSettings = GSON.fromJson(object.getAsJsonObject("global"), GlobalSettings.class);
|
||||||
var settings = object.getAsJsonObject(controller.guid());
|
if (globalSettings == null) globalSettings = new GlobalSettings();
|
||||||
if (settings != null) {
|
|
||||||
applyControllerConfig(controller, settings);
|
JsonObject controllers = object.getAsJsonObject("controllers");
|
||||||
|
if (controllers != null) {
|
||||||
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
|
var settings = controllers.getAsJsonObject(controller.guid());
|
||||||
|
if (settings != null) {
|
||||||
|
applyControllerConfig(controller, settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyControllerConfig(Controller controller, JsonObject object) {
|
private void applyControllerConfig(Controller controller, JsonObject object) {
|
||||||
controller.config().overwrite(GSON.fromJson(object.getAsJsonObject("config"), ControllerConfig.class));
|
controller.setConfig(GSON.fromJson(object.getAsJsonObject("config"), ControllerConfig.class));
|
||||||
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GlobalSettings globalSettings() {
|
||||||
|
return globalSettings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package dev.isxander.controlify.config;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GlobalSettings {
|
||||||
|
public static final GlobalSettings DEFAULT = new GlobalSettings();
|
||||||
|
|
||||||
|
public List<String> virtualMouseScreens = Lists.newArrayList(
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
@ -1,23 +1,46 @@
|
|||||||
package dev.isxander.controlify.config.gui;
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.bindings.Bind;
|
import dev.isxander.controlify.bindings.Bind;
|
||||||
import dev.isxander.controlify.config.ControlifyConfig;
|
import dev.isxander.controlify.config.GlobalSettings;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerConfig;
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
import dev.isxander.yacl.api.ConfigCategory;
|
import dev.isxander.yacl.api.*;
|
||||||
import dev.isxander.yacl.api.Option;
|
import dev.isxander.yacl.gui.controllers.cycling.CyclingListController;
|
||||||
import dev.isxander.yacl.api.OptionGroup;
|
|
||||||
import dev.isxander.yacl.api.YetAnotherConfigLib;
|
|
||||||
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
|
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
|
||||||
|
import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController;
|
||||||
|
import dev.isxander.yacl.gui.controllers.string.StringController;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.locale.Language;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public class YACLHelper {
|
public class YACLHelper {
|
||||||
public static Screen generateConfigScreen(Screen parent) {
|
public static Screen generateConfigScreen(Screen parent) {
|
||||||
|
var controlify = Controlify.instance();
|
||||||
|
|
||||||
var yacl = YetAnotherConfigLib.createBuilder()
|
var yacl = YetAnotherConfigLib.createBuilder()
|
||||||
.title(Component.literal("Controlify"))
|
.title(Component.literal("Controlify"))
|
||||||
.save(ControlifyConfig::save);
|
.save(() -> controlify.config().save());
|
||||||
|
|
||||||
|
var globalCategory = ConfigCategory.createBuilder()
|
||||||
|
.name(Component.translatable("controlify.gui.category.global"))
|
||||||
|
.option(Option.createBuilder(Controller.class)
|
||||||
|
.name(Component.translatable("controlify.gui.current_controller"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.current_controller.tooltip"))
|
||||||
|
.binding(Controlify.instance().currentController(), () -> Controlify.instance().currentController(), v -> Controlify.instance().setCurrentController(v))
|
||||||
|
.controller(opt -> new CyclingListController<>(opt, Controller.CONTROLLERS.values().stream().filter(Controller::connected).toList(), c -> Component.literal(c.name())))
|
||||||
|
.instant(true)
|
||||||
|
.build())
|
||||||
|
.option(ListOption.createBuilder(String.class)
|
||||||
|
.name(Component.translatable("controlify.gui.vmouse_screens"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.vmouse_screens.tooltip"))
|
||||||
|
.binding(GlobalSettings.DEFAULT.virtualMouseScreens, () -> controlify.config().globalSettings().virtualMouseScreens, v -> controlify.config().globalSettings().virtualMouseScreens = v)
|
||||||
|
.controller(StringController::new)
|
||||||
|
.initial(Language.getInstance().getOrDefault("controlify.gui.vmouse_screens.placeholder"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
yacl.category(globalCategory.build());
|
||||||
|
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
var category = ConfigCategory.createBuilder();
|
var category = ConfigCategory.createBuilder();
|
||||||
@ -42,19 +65,25 @@ public class YACLHelper {
|
|||||||
.binding(def.verticalLookSensitivity, () -> config.verticalLookSensitivity, v -> config.verticalLookSensitivity = v)
|
.binding(def.verticalLookSensitivity, () -> config.verticalLookSensitivity, v -> config.verticalLookSensitivity = v)
|
||||||
.controller(opt -> new FloatSliderController(opt, 0.1f, 2f, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0.1f, 2f, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build())
|
||||||
|
.option(Option.createBuilder(int.class)
|
||||||
|
.name(Component.translatable("controlify.gui.screen_repeat_navi_delay"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.screen_repeat_navi_delay.tooltip"))
|
||||||
|
.binding(def.screenRepeatNavigationDelay, () -> config.screenRepeatNavigationDelay, v -> config.screenRepeatNavigationDelay = v)
|
||||||
|
.controller(opt -> new IntegerSliderController(opt, 1, 20, 1, v -> Component.translatable("controlify.gui.format.ticks", v)))
|
||||||
|
.build())
|
||||||
.option(Option.createBuilder(float.class)
|
.option(Option.createBuilder(float.class)
|
||||||
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
||||||
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
||||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
.binding(def.leftStickDeadzone, () -> config.leftStickDeadzone, v -> config.leftStickDeadzone = v)
|
.binding(def.leftStickDeadzone, () -> config.leftStickDeadzone, v -> config.leftStickDeadzone = v)
|
||||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.02f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build())
|
||||||
.option(Option.createBuilder(float.class)
|
.option(Option.createBuilder(float.class)
|
||||||
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
||||||
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
||||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
.binding(def.rightStickDeadzone, () -> config.rightStickDeadzone, v -> config.rightStickDeadzone = v)
|
.binding(def.rightStickDeadzone, () -> config.rightStickDeadzone, v -> config.rightStickDeadzone = v)
|
||||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.02f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build())
|
||||||
.option(Option.createBuilder(float.class)
|
.option(Option.createBuilder(float.class)
|
||||||
.name(Component.translatable("controlify.gui.left_trigger_threshold"))
|
.name(Component.translatable("controlify.gui.left_trigger_threshold"))
|
||||||
|
@ -11,6 +11,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public final class Controller {
|
public final class Controller {
|
||||||
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||||
|
public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false);
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
private final String guid;
|
private final String guid;
|
||||||
@ -21,7 +22,7 @@ public final class Controller {
|
|||||||
private ControllerState prevState = ControllerState.EMPTY;
|
private ControllerState prevState = ControllerState.EMPTY;
|
||||||
|
|
||||||
private final ControllerBindings bindings = new ControllerBindings(this);
|
private final ControllerBindings bindings = new ControllerBindings(this);
|
||||||
private final ControllerConfig config = new ControllerConfig();
|
private ControllerConfig config = new ControllerConfig();
|
||||||
|
|
||||||
public Controller(int id, String guid, String name, boolean gamepad) {
|
public Controller(int id, String guid, String name, boolean gamepad) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -81,6 +82,8 @@ public final class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String name() {
|
public String name() {
|
||||||
|
if (config().customName != null)
|
||||||
|
return config().customName;
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +95,10 @@ public final class Controller {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setConfig(ControllerConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == this) return true;
|
if (obj == this) return true;
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
package dev.isxander.controlify.controller;
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
import dev.isxander.controlify.config.ControlifyConfig;
|
|
||||||
|
|
||||||
public class ControllerConfig {
|
public class ControllerConfig {
|
||||||
public static final ControllerConfig DEFAULT = new ControllerConfig();
|
public static final ControllerConfig DEFAULT = new ControllerConfig();
|
||||||
|
|
||||||
public float horizontalLookSensitivity = 1f;
|
public float horizontalLookSensitivity = 1f;
|
||||||
public float verticalLookSensitivity = 0.9f;
|
public float verticalLookSensitivity = 0.9f;
|
||||||
|
|
||||||
public float leftStickDeadzone = 0.2f;
|
public float leftStickDeadzone = 0.1f;
|
||||||
public float rightStickDeadzone = 0.2f;
|
public float rightStickDeadzone = 0.1f;
|
||||||
|
|
||||||
// not sure if triggers need deadzones
|
// not sure if triggers need deadzones
|
||||||
public float leftTriggerDeadzone = 0.0f;
|
public float leftTriggerDeadzone = 0.0f;
|
||||||
@ -18,21 +16,7 @@ public class ControllerConfig {
|
|||||||
public float leftTriggerActivationThreshold = 0.5f;
|
public float leftTriggerActivationThreshold = 0.5f;
|
||||||
public float rightTriggerActivationThreshold = 0.5f;
|
public float rightTriggerActivationThreshold = 0.5f;
|
||||||
|
|
||||||
|
public int screenRepeatNavigationDelay = 4;
|
||||||
|
|
||||||
public String customName = null;
|
public String customName = null;
|
||||||
|
|
||||||
public void notifyChanged() {
|
|
||||||
ControlifyConfig.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void overwrite(ControllerConfig from) {
|
|
||||||
this.horizontalLookSensitivity = from.horizontalLookSensitivity;
|
|
||||||
this.verticalLookSensitivity = from.verticalLookSensitivity;
|
|
||||||
this.leftStickDeadzone = from.leftStickDeadzone;
|
|
||||||
this.rightStickDeadzone = from.rightStickDeadzone;
|
|
||||||
this.leftTriggerDeadzone = from.leftTriggerDeadzone;
|
|
||||||
this.rightTriggerDeadzone = from.rightTriggerDeadzone;
|
|
||||||
this.leftTriggerActivationThreshold = from.leftTriggerActivationThreshold;
|
|
||||||
this.rightTriggerActivationThreshold = from.rightTriggerActivationThreshold;
|
|
||||||
this.customName = from.customName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,12 @@ public class ControlifyEvents {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public static final Event<VirtualMouseToggled> VIRTUAL_MOUSE_TOGGLED = EventFactory.createArrayBacked(VirtualMouseToggled.class, callbacks -> enabled -> {
|
||||||
|
for (VirtualMouseToggled callback : callbacks) {
|
||||||
|
callback.onVirtualMouseToggled(enabled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InputModeChanged {
|
public interface InputModeChanged {
|
||||||
void onInputModeChanged(InputMode mode);
|
void onInputModeChanged(InputMode mode);
|
||||||
@ -39,4 +45,9 @@ public class ControlifyEvents {
|
|||||||
public interface ControllerBindRegistry {
|
public interface ControllerBindRegistry {
|
||||||
void onRegisterControllerBinds(ControllerBindings bindings, Controller controller);
|
void onRegisterControllerBinds(ControllerBindings bindings, Controller controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface VirtualMouseToggled {
|
||||||
|
void onVirtualMouseToggled(boolean enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,6 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||||||
public class AbstractSelectionListMixin {
|
public class AbstractSelectionListMixin {
|
||||||
@ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/InputType;isKeyboard()Z"))
|
@ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/InputType;isKeyboard()Z"))
|
||||||
private boolean shouldEnsureEntryVisible(boolean keyboard) {
|
private boolean shouldEnsureEntryVisible(boolean keyboard) {
|
||||||
return keyboard || Controlify.getInstance().getCurrentInputMode() == InputMode.CONTROLLER;
|
return keyboard || Controlify.instance().currentInputMode() == InputMode.CONTROLLER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
|||||||
|
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.SliderComponentProcessor;
|
import dev.isxander.controlify.compatibility.vanilla.SliderComponentProcessor;
|
||||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
@ -14,4 +14,7 @@ public interface ScreenAccessor {
|
|||||||
|
|
||||||
@Invoker
|
@Invoker
|
||||||
void invokeChangeFocus(ComponentPath path);
|
void invokeChangeFocus(ComponentPath path);
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
void invokeClearFocus();
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
@Mixin(Screen.class)
|
@Mixin(Screen.class)
|
||||||
public class ScreenMixin implements ScreenProcessorProvider {
|
public class ScreenMixin implements ScreenProcessorProvider {
|
||||||
@Unique
|
@Unique
|
||||||
private final ScreenProcessor controlify$processor = new ScreenProcessor((Screen) (Object) this);
|
private final ScreenProcessor<Screen> controlify$processor = new ScreenProcessor<>((Screen) (Object) this);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScreenProcessor screenProcessor() {
|
public ScreenProcessor<Screen> screenProcessor() {
|
||||||
return controlify$processor;
|
return controlify$processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.WorldSelectionList;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(SelectWorldScreen.class)
|
||||||
|
public interface SelectWorldScreenAccessor {
|
||||||
|
@Accessor
|
||||||
|
Button getSelectButton();
|
||||||
|
|
||||||
|
@Accessor
|
||||||
|
WorldSelectionList getList();
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.SelectWorldScreenProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
@Mixin(SelectWorldScreen.class)
|
||||||
|
public class SelectWorldScreenMixin implements ScreenProcessorProvider {
|
||||||
|
private final SelectWorldScreenProcessor controlify$processor = new SelectWorldScreenProcessor((SelectWorldScreen) (Object) this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScreenProcessor<?> screenProcessor() {
|
||||||
|
return controlify$processor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.WorldListEntryComponentProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.WorldSelectionList;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
@Mixin(WorldSelectionList.WorldListEntry.class)
|
||||||
|
public class WorldSelectionListEntryMixin implements ComponentProcessorProvider {
|
||||||
|
private final WorldListEntryComponentProcessor controlify$processor = new WorldListEntryComponentProcessor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentProcessor componentProcessor() {
|
||||||
|
return controlify$processor;
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,10 @@
|
|||||||
package dev.isxander.controlify.mixins.core;
|
package dev.isxander.controlify.mixins.core;
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.InputMode;
|
import dev.isxander.controlify.InputMode;
|
||||||
import dev.isxander.controlify.ingame.ControllerPlayerMovement;
|
import dev.isxander.controlify.ingame.ControllerPlayerMovement;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.Options;
|
|
||||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||||
import net.minecraft.client.player.Input;
|
|
||||||
import net.minecraft.client.player.KeyboardInput;
|
|
||||||
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
|
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
@ -29,7 +23,7 @@ public class ClientPacketListenerMixin {
|
|||||||
|
|
||||||
@Inject(method = "handleLogin", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;input:Lnet/minecraft/client/player/Input;", opcode = Opcodes.ASTORE, shift = At.Shift.AFTER))
|
@Inject(method = "handleLogin", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;input:Lnet/minecraft/client/player/Input;", opcode = Opcodes.ASTORE, shift = At.Shift.AFTER))
|
||||||
private void useControllerInput(ClientboundLoginPacket packet, CallbackInfo ci) {
|
private void useControllerInput(ClientboundLoginPacket packet, CallbackInfo ci) {
|
||||||
if (Controlify.getInstance().getCurrentInputMode() == InputMode.CONTROLLER && minecraft.player != null)
|
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER && minecraft.player != null)
|
||||||
minecraft.player.input = new ControllerPlayerMovement(Controlify.getInstance().getCurrentController());
|
minecraft.player.input = new ControllerPlayerMovement(Controlify.instance().currentController());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,22 @@ package dev.isxander.controlify.mixins.core;
|
|||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.InputMode;
|
import dev.isxander.controlify.InputMode;
|
||||||
import net.minecraft.client.KeyboardHandler;
|
import net.minecraft.client.KeyboardHandler;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(KeyboardHandler.class)
|
@Mixin(KeyboardHandler.class)
|
||||||
public class KeyboardHandlerMixin {
|
public class KeyboardHandlerMixin {
|
||||||
@Inject(method = "keyPress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setLastInputType(Lnet/minecraft/client/InputType;)V"))
|
@Shadow @Final private Minecraft minecraft;
|
||||||
private void onKeyboardInput(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) {
|
|
||||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
// m_unngxkoe is lambda for GLFW keypress hook - do it outside of the `keyPress` method due to fake inputs
|
||||||
|
@Inject(method = "m_unngxkoe", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;keyPress(JIIII)V"))
|
||||||
|
private void onKeyboardInput(long window, int i, int j, int k, int m, CallbackInfo ci) {
|
||||||
|
if (window == minecraft.getWindow().getWindow())
|
||||||
|
Controlify.instance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
public class MinecraftMixin {
|
public class MinecraftMixin {
|
||||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
||||||
private void onInputInitialized(CallbackInfo ci) {
|
private void onInputInitialized(CallbackInfo ci) {
|
||||||
Controlify.getInstance().onInitializeInput();
|
Controlify.instance().onInitializeInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;turnPlayer()V"))
|
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;turnPlayer()V"))
|
||||||
private void doPlayerLook(boolean tick, CallbackInfo ci) {
|
private void doPlayerLook(boolean tick, CallbackInfo ci) {
|
||||||
Controlify.getInstance().getInGameInputHandler().processPlayerLook();
|
Controlify.instance().inGameInputHandler().processPlayerLook();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,37 @@ package dev.isxander.controlify.mixins.core;
|
|||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.InputMode;
|
import dev.isxander.controlify.InputMode;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.MouseHandler;
|
import net.minecraft.client.MouseHandler;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(MouseHandler.class)
|
@Mixin(MouseHandler.class)
|
||||||
public class MouseHandlerMixin {
|
public class MouseHandlerMixin {
|
||||||
@Inject(method = "onPress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setLastInputType(Lnet/minecraft/client/InputType;)V"))
|
@Shadow @Final private Minecraft minecraft;
|
||||||
|
|
||||||
|
// m_sljgmtqm is lambda for GLFW mouse click hook - do it outside of the `onPress` method due to fake inputs
|
||||||
|
@Inject(method = "m_sljgmtqm", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onPress(JIII)V"))
|
||||||
private void onMouseClickInput(long window, int button, int action, int modifiers, CallbackInfo ci) {
|
private void onMouseClickInput(long window, int button, int action, int modifiers, CallbackInfo ci) {
|
||||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
if (window == minecraft.getWindow().getWindow())
|
||||||
|
Controlify.instance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "onMove", at = @At("RETURN"))
|
// m_swhlgdws is lambda for GLFW mouse move hook - do it outside of the `onMove` method due to fake inputs
|
||||||
|
@Inject(method = "m_swhlgdws", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onMove(JDD)V"))
|
||||||
private void onMouseMoveInput(long window, double x, double y, CallbackInfo ci) {
|
private void onMouseMoveInput(long window, double x, double y, CallbackInfo ci) {
|
||||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
if (window == minecraft.getWindow().getWindow())
|
||||||
|
Controlify.instance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getOverlay()Lnet/minecraft/client/gui/screens/Overlay;"))
|
// m_qoshpwkl is lambda for GLFW mouse scroll hook - do it outside of the `onScroll` method due to fake inputs
|
||||||
|
@Inject(method = "m_qoshpwkl", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onScroll(JDD)V"))
|
||||||
private void onMouseScrollInput(long window, double scrollDeltaX, double scrollDeltaY, CallbackInfo ci) {
|
private void onMouseScrollInput(long window, double scrollDeltaX, double scrollDeltaY, CallbackInfo ci) {
|
||||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
if (window == minecraft.getWindow().getWindow())
|
||||||
|
Controlify.instance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package dev.isxander.controlify.mixins.feature.virtualmouse;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
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 {
|
||||||
|
@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) {
|
||||||
|
Controlify.instance().virtualMouseHandler().renderVirtualMouse(poseStack);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package dev.isxander.controlify.mixins.feature.virtualmouse;
|
||||||
|
|
||||||
|
import net.minecraft.client.KeyboardHandler;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(KeyboardHandler.class)
|
||||||
|
public interface KeyboardHandlerAccessor {
|
||||||
|
@Invoker
|
||||||
|
void invokeKeyPress(long window, int key, int scancode, int action, int modifiers);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package dev.isxander.controlify.mixins.feature.virtualmouse;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
public class MinecraftMixin {
|
||||||
|
@Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;updateTitle()V"))
|
||||||
|
private void onScreenChanged(Screen screen, CallbackInfo ci) {
|
||||||
|
Controlify.instance().virtualMouseHandler().onScreenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;turnPlayer()V"))
|
||||||
|
private void onUpdateMouse(boolean tick, CallbackInfo ci) {
|
||||||
|
Controlify.instance().virtualMouseHandler().updateMouse();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package dev.isxander.controlify.mixins.feature.virtualmouse;
|
||||||
|
|
||||||
|
import net.minecraft.client.MouseHandler;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(MouseHandler.class)
|
||||||
|
public interface MouseHandlerAccessor {
|
||||||
|
@Invoker
|
||||||
|
void invokeOnMove(long window, double x, double y);
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
void invokeOnPress(long window, int button, int action, int modifiers);
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
void invokeOnScroll(long window, double scrollDeltaX, double scrollDeltaY);
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
package dev.isxander.controlify.virtualmouse;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import dev.isxander.controlify.InputMode;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
|
import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor;
|
||||||
|
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||||
|
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;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
public class VirtualMouseHandler {
|
||||||
|
private static final ResourceLocation CURSOR_TEXTURE = new ResourceLocation("controlify", "textures/gui/virtual_mouse.png");
|
||||||
|
|
||||||
|
private double targetX, targetY;
|
||||||
|
private double currentX, currentY;
|
||||||
|
private final Minecraft minecraft;
|
||||||
|
private boolean virtualMouseEnabled;
|
||||||
|
|
||||||
|
public VirtualMouseHandler() {
|
||||||
|
this.minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
|
ControlifyEvents.INPUT_MODE_CHANGED.register(this::onInputModeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleControllerInput(Controller controller) {
|
||||||
|
if (controller.bindings().VMOUSE_TOGGLE.justPressed()) {
|
||||||
|
toggleVirtualMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!virtualMouseEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftStickX = controller.state().axes().leftStickX();
|
||||||
|
var leftStickY = controller.state().axes().leftStickY();
|
||||||
|
|
||||||
|
// quadratic function to make small movements smaller
|
||||||
|
// abs to keep sign
|
||||||
|
targetX += leftStickX * Mth.abs(leftStickX) * 20f;
|
||||||
|
targetY += leftStickY * Mth.abs(leftStickY) * 20f;
|
||||||
|
|
||||||
|
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
|
||||||
|
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight());
|
||||||
|
|
||||||
|
var mouseHandler = (MouseHandlerAccessor) minecraft.mouseHandler;
|
||||||
|
var keyboardHandler = (KeyboardHandlerAccessor) minecraft.keyboardHandler;
|
||||||
|
|
||||||
|
if (controller.bindings().VMOUSE_LCLICK.justPressed()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_LEFT, GLFW.GLFW_PRESS, 0);
|
||||||
|
} else if (controller.bindings().VMOUSE_LCLICK.justReleased()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_LEFT, GLFW.GLFW_RELEASE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.bindings().VMOUSE_RCLICK.justPressed()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_RIGHT, GLFW.GLFW_PRESS, 0);
|
||||||
|
} else if (controller.bindings().VMOUSE_RCLICK.justReleased()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_RIGHT, GLFW.GLFW_RELEASE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.bindings().VMOUSE_MCLICK.justPressed()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_MIDDLE, GLFW.GLFW_PRESS, 0);
|
||||||
|
} else if (controller.bindings().VMOUSE_MCLICK.justReleased()) {
|
||||||
|
mouseHandler.invokeOnPress(minecraft.getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_MIDDLE, GLFW.GLFW_RELEASE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.bindings().VMOUSE_ESCAPE.justPressed()) {
|
||||||
|
keyboardHandler.invokeKeyPress(minecraft.getWindow().getWindow(), GLFW.GLFW_KEY_ESCAPE, 0, GLFW.GLFW_PRESS, 0);
|
||||||
|
} else if (controller.bindings().VMOUSE_ESCAPE.justReleased()) {
|
||||||
|
keyboardHandler.invokeKeyPress(minecraft.getWindow().getWindow(), GLFW.GLFW_KEY_ESCAPE, 0, GLFW.GLFW_RELEASE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: scrolling with right stick
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMouse() {
|
||||||
|
if (!virtualMouseEnabled) return;
|
||||||
|
if (targetX == currentX && targetY == currentY) return; // don't need to needlessly update mouse position
|
||||||
|
|
||||||
|
currentX = Mth.lerp(minecraft.getDeltaFrameTime(), currentX, targetX);
|
||||||
|
currentY = Mth.lerp(minecraft.getDeltaFrameTime(), currentY, targetY);
|
||||||
|
|
||||||
|
((MouseHandlerAccessor) minecraft.mouseHandler).invokeOnMove(minecraft.getWindow().getWindow(), currentX, currentY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onScreenChanged() {
|
||||||
|
if (minecraft.screen != null) {
|
||||||
|
if (requiresVirtualMouse()) {
|
||||||
|
enableVirtualMouse();
|
||||||
|
} else {
|
||||||
|
disableVirtualMouse();
|
||||||
|
}
|
||||||
|
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER)
|
||||||
|
GLFW.glfwSetInputMode(minecraft.getWindow().getWindow(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_HIDDEN);
|
||||||
|
} else if (virtualMouseEnabled) {
|
||||||
|
disableVirtualMouse();
|
||||||
|
minecraft.mouseHandler.grabMouse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInputModeChanged(InputMode mode) {
|
||||||
|
if (mode == InputMode.CONTROLLER) {
|
||||||
|
if (requiresVirtualMouse()) {
|
||||||
|
enableVirtualMouse();
|
||||||
|
}
|
||||||
|
} else if (virtualMouseEnabled) {
|
||||||
|
disableVirtualMouse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderVirtualMouse(PoseStack matrices) {
|
||||||
|
if (!virtualMouseEnabled) return;
|
||||||
|
|
||||||
|
RenderSystem.setShaderTexture(0, CURSOR_TEXTURE);
|
||||||
|
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
|
||||||
|
RenderSystem.enableBlend();
|
||||||
|
|
||||||
|
var scaledX = currentX * (double)this.minecraft.getWindow().getGuiScaledWidth() / (double)this.minecraft.getWindow().getScreenWidth();
|
||||||
|
var scaledY = currentY * (double)this.minecraft.getWindow().getGuiScaledHeight() / (double)this.minecraft.getWindow().getScreenHeight();
|
||||||
|
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.translate(scaledX, scaledY, 0);
|
||||||
|
matrices.scale(0.5f, 0.5f, 0.5f);
|
||||||
|
|
||||||
|
GuiComponent.blit(matrices, -16, -16, 0, 0, 32, 32, 32, 32);
|
||||||
|
|
||||||
|
matrices.popPose();
|
||||||
|
|
||||||
|
RenderSystem.disableBlend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableVirtualMouse() {
|
||||||
|
if (virtualMouseEnabled) return;
|
||||||
|
|
||||||
|
setMousePosition();
|
||||||
|
GLFW.glfwSetInputMode(minecraft.getWindow().getWindow(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||||
|
virtualMouseEnabled = true;
|
||||||
|
|
||||||
|
if (minecraft.mouseHandler.xpos() == 0 && minecraft.mouseHandler.ypos() == 0) {
|
||||||
|
targetX = currentX = minecraft.getWindow().getScreenWidth() / 2f;
|
||||||
|
targetY = currentY = minecraft.getWindow().getScreenHeight() / 2f;
|
||||||
|
} else {
|
||||||
|
targetX = currentX = minecraft.mouseHandler.xpos();
|
||||||
|
targetY = currentY = minecraft.mouseHandler.ypos();
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlifyEvents.VIRTUAL_MOUSE_TOGGLED.invoker().onVirtualMouseToggled(true);
|
||||||
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
|
minecraft,
|
||||||
|
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||||
|
Component.translatable("controlify.toast.vmouse_enabled.title"),
|
||||||
|
Component.translatable("controlify.toast.vmouse_enabled.description")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableVirtualMouse() {
|
||||||
|
if (!virtualMouseEnabled) return;
|
||||||
|
|
||||||
|
GLFW.glfwSetInputMode(minecraft.getWindow().getWindow(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
||||||
|
setMousePosition();
|
||||||
|
virtualMouseEnabled = false;
|
||||||
|
targetX = currentX = minecraft.mouseHandler.xpos();
|
||||||
|
targetY = currentY = minecraft.mouseHandler.ypos();
|
||||||
|
|
||||||
|
ControlifyEvents.VIRTUAL_MOUSE_TOGGLED.invoker().onVirtualMouseToggled(false);
|
||||||
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
|
minecraft,
|
||||||
|
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||||
|
Component.translatable("controlify.toast.vmouse_disabled.title"),
|
||||||
|
Component.translatable("controlify.toast.vmouse_disabled.description")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMousePosition() {
|
||||||
|
GLFW.glfwSetCursorPos(
|
||||||
|
minecraft.getWindow().getWindow(),
|
||||||
|
targetX,
|
||||||
|
targetY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requiresVirtualMouse() {
|
||||||
|
return Controlify.instance().currentInputMode() == InputMode.CONTROLLER
|
||||||
|
&& minecraft.screen != null
|
||||||
|
&& (ScreenProcessorProvider.provide(minecraft.screen).forceVirtualMouse()
|
||||||
|
|| Controlify.instance().config().globalSettings().virtualMouseScreens.contains(minecraft.screen.getClass().getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleVirtualMouse() {
|
||||||
|
if (minecraft.screen == null) return;
|
||||||
|
|
||||||
|
var screens = Controlify.instance().config().globalSettings().virtualMouseScreens;
|
||||||
|
var screenName = minecraft.screen.getClass().getName();
|
||||||
|
if (screens.contains(screenName)) {
|
||||||
|
screens.remove(screenName);
|
||||||
|
disableVirtualMouse();
|
||||||
|
Controlify.instance().hideMouse(true);
|
||||||
|
} else {
|
||||||
|
screens.add(screenName);
|
||||||
|
enableVirtualMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
Controlify.instance().config().save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVirtualMouseEnabled() {
|
||||||
|
return virtualMouseEnabled;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,19 @@
|
|||||||
{
|
{
|
||||||
|
"controlify.gui.category.global": "Global",
|
||||||
|
"controlify.gui.current_controller": "Current Controller",
|
||||||
|
"controlify.gui.current_controller.tooltip": "In Controlify's infancy, only one controller can be used at a time, this selects which one you want to use.",
|
||||||
|
"controlify.gui.vmouse_screens": "Virtual Mouse Screens",
|
||||||
|
"controlify.gui.vmouse_screens.tooltip": "A list of Screen class names that require virtual mouse to operate, this is usually due to the screen not being compatible with controller input.",
|
||||||
|
"controlify.gui.vmouse_screens.placeholder": "Screen class name here...",
|
||||||
|
|
||||||
"controlify.gui.group.config": "Config",
|
"controlify.gui.group.config": "Config",
|
||||||
"controlify.gui.group.config.tooltip": "Adjust the controller configuration.",
|
"controlify.gui.group.config.tooltip": "Adjust the controller configuration.",
|
||||||
"controlify.gui.horizontal_look_sensitivity": "Horizontal Look Sensitivity",
|
"controlify.gui.horizontal_look_sensitivity": "Horizontal Look Sensitivity",
|
||||||
"controlify.gui.horizontal_look_sensitivity.tooltip": "How fast the camera moves horizontally when looking around.",
|
"controlify.gui.horizontal_look_sensitivity.tooltip": "How fast the camera moves horizontally when looking around.",
|
||||||
"controlify.gui.vertical_look_sensitivity": "Vertical Look Sensitivity",
|
"controlify.gui.vertical_look_sensitivity": "Vertical Look Sensitivity",
|
||||||
"controlify.gui.vertical_look_sensitivity.tooltip": "How fast the camera moves vertically when looking around.",
|
"controlify.gui.vertical_look_sensitivity.tooltip": "How fast the camera moves vertically when looking around.",
|
||||||
|
"controlify.gui.screen_repeat_navi_delay": "Screen Repeat Navigation Delay",
|
||||||
|
"controlify.gui.screen_repeat_navi_delay.tooltip": "How the delay is for navigation to start repeating if you hold the stick.",
|
||||||
"controlify.gui.left_stick_deadzone": "Left Stick Deadzone",
|
"controlify.gui.left_stick_deadzone": "Left Stick Deadzone",
|
||||||
"controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.",
|
"controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.",
|
||||||
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
||||||
@ -19,6 +28,17 @@
|
|||||||
"controlify.gui.group.controls.tooltip": "Adjust the controller controls.",
|
"controlify.gui.group.controls.tooltip": "Adjust the controller controls.",
|
||||||
"controlify.gui.bind_input_awaiting": "Press any button",
|
"controlify.gui.bind_input_awaiting": "Press any button",
|
||||||
|
|
||||||
|
"controlify.gui.format.ticks": "%s ticks",
|
||||||
|
|
||||||
|
"controlify.toast.vmouse_enabled.title": "Virtual Mouse Enabled",
|
||||||
|
"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.controller_connected.title": "Controller Connected",
|
||||||
|
"controlify.toast.controller_connected.description": "A new controller has just been connected. You can switch to it in Controlify settings.",
|
||||||
|
"controlify.toast.controller_disconnected.title": "Controller Disconnected",
|
||||||
|
"controlify.toast.controller_disconnected.description": "'%s' was disconnected.",
|
||||||
|
|
||||||
"controlify.binding.controlify.jump": "Jump",
|
"controlify.binding.controlify.jump": "Jump",
|
||||||
"controlify.binding.controlify.sneak": "Sneak",
|
"controlify.binding.controlify.sneak": "Sneak",
|
||||||
"controlify.binding.controlify.attack": "Attack",
|
"controlify.binding.controlify.attack": "Attack",
|
||||||
@ -32,5 +52,9 @@
|
|||||||
"controlify.binding.controlify.open_chat": "Open Chat",
|
"controlify.binding.controlify.open_chat": "Open Chat",
|
||||||
"controlify.binding.controlify.gui_press": "GUI Press",
|
"controlify.binding.controlify.gui_press": "GUI Press",
|
||||||
"controlify.binding.controlify.gui_back": "GUI Back",
|
"controlify.binding.controlify.gui_back": "GUI Back",
|
||||||
"controlify.binding.controlify.drop": "Drop Item"
|
"controlify.binding.controlify.drop": "Drop Item",
|
||||||
|
"controlify.binding.controlify.vmouse_lclick": "Virtual Mouse LClick",
|
||||||
|
"controlify.binding.controlify.vmouse_rclick": "Virtual Mouse RClick",
|
||||||
|
"controlify.binding.controlify.vmouse_mclick": "Virtual Mouse MClick",
|
||||||
|
"controlify.binding.controlify.vmouse_escape": "Virtual Mouse Key Escape"
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 861 B After Width: | Height: | Size: 861 B |
@ -12,10 +12,17 @@
|
|||||||
"compat.screen.vanilla.ContainerObjectSelectionListEntryMixin",
|
"compat.screen.vanilla.ContainerObjectSelectionListEntryMixin",
|
||||||
"compat.screen.vanilla.ScreenAccessor",
|
"compat.screen.vanilla.ScreenAccessor",
|
||||||
"compat.screen.vanilla.ScreenMixin",
|
"compat.screen.vanilla.ScreenMixin",
|
||||||
|
"compat.screen.vanilla.SelectWorldScreenAccessor",
|
||||||
|
"compat.screen.vanilla.SelectWorldScreenMixin",
|
||||||
|
"compat.screen.vanilla.WorldSelectionListEntryMixin",
|
||||||
"core.ClientPacketListenerMixin",
|
"core.ClientPacketListenerMixin",
|
||||||
"core.KeyboardHandlerMixin",
|
"core.KeyboardHandlerMixin",
|
||||||
"core.MinecraftMixin",
|
"core.MinecraftMixin",
|
||||||
"core.MouseHandlerMixin",
|
"core.MouseHandlerMixin",
|
||||||
"feature.bind.KeyMappingAccessor"
|
"feature.bind.KeyMappingAccessor",
|
||||||
|
"feature.virtualmouse.GameRendererMixin",
|
||||||
|
"feature.virtualmouse.KeyboardHandlerAccessor",
|
||||||
|
"feature.virtualmouse.MinecraftMixin",
|
||||||
|
"feature.virtualmouse.MouseHandlerAccessor"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user