forked from Clones/Controlify
compound joysticks, button guide in screens, improve API
This commit is contained in:
@ -0,0 +1,158 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CompoundJoystickController implements JoystickController<JoystickConfig> {
|
||||
private final String uid;
|
||||
private final List<Integer> joysticks;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final ControllerType compoundType;
|
||||
private final ControllerBindings<JoystickState> bindings;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
private JoystickConfig config;
|
||||
private final JoystickConfig defaultConfig;
|
||||
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
|
||||
public CompoundJoystickController(List<Integer> joystickIds, String uid, ControllerType compoundType) {
|
||||
this.joysticks = ImmutableList.copyOf(joystickIds);
|
||||
this.uid = uid;
|
||||
this.compoundType = compoundType;
|
||||
|
||||
this.axisCount = joystickIds.stream().mapToInt(this::getAxisCountForJoystick).sum();
|
||||
this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum();
|
||||
this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum();
|
||||
|
||||
this.mapping = RPJoystickMapping.fromType(type());
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
|
||||
this.bindings = new ControllerBindings<>(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String uid() {
|
||||
return this.uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControllerBindings<JoystickState> bindings() {
|
||||
return this.bindings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return this.prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
this.prevState = this.state;
|
||||
|
||||
var states = this.joysticks.stream().map(joystick -> JoystickState.fromJoystick(this, joystick)).toList();
|
||||
this.state = JoystickState.merged(mapping(), states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
this.state = JoystickState.empty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickConfig config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickConfig defaultConfig() {
|
||||
return this.defaultConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetConfig() {
|
||||
this.config = new JoystickConfig(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
JoystickConfig newConfig = gson.fromJson(json, JoystickConfig.class);
|
||||
if (newConfig != null) {
|
||||
this.config = newConfig;
|
||||
} else {
|
||||
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
this.config = defaultConfig();
|
||||
}
|
||||
this.config.setup(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControllerType type() {
|
||||
return this.compoundType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return type().friendlyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickMapping mapping() {
|
||||
return this.mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int axisCount() {
|
||||
return this.axisCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int buttonCount() {
|
||||
return this.buttonCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hatCount() {
|
||||
return this.hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeUsed() {
|
||||
return JoystickController.super.canBeUsed()
|
||||
&& joysticks.stream().allMatch(GLFW::glfwJoystickPresent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int joystickId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getAxisCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickAxes(joystick).capacity();
|
||||
}
|
||||
|
||||
private int getButtonCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickButtons(joystick).capacity();
|
||||
}
|
||||
|
||||
private int getHatCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickHats(joystick).capacity();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record CompoundJoystickInfo(Collection<String> joystickUids, String friendlyName) {
|
||||
public ControllerType type() {
|
||||
return new ControllerType(friendlyName, createUID(joystickUids));
|
||||
}
|
||||
|
||||
public boolean canBeUsed() {
|
||||
List<Controller<?, ?>> joysticks = Controller.CONTROLLERS.values().stream().filter(c -> joystickUids.contains(c.uid())).toList();
|
||||
if (joysticks.size() != joystickUids().size()) {
|
||||
return false; // not all controllers are connected
|
||||
}
|
||||
if (joysticks.stream().anyMatch(c -> !c.canBeUsed())) {
|
||||
return false; // not all controllers can be used
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return Controller.CONTROLLERS.containsKey(createUID(joystickUids));
|
||||
}
|
||||
|
||||
public Optional<CompoundJoystickController> attemptCreate() {
|
||||
if (!canBeUsed()) return Optional.empty();
|
||||
|
||||
List<Integer> joystickIDs = Controller.CONTROLLERS.values().stream()
|
||||
.filter(c -> joystickUids.contains(c.uid()))
|
||||
.map(Controller::joystickId)
|
||||
.toList();
|
||||
|
||||
ControllerType type = type();
|
||||
return Optional.of(new CompoundJoystickController(joystickIDs, type.identifier(), type));
|
||||
}
|
||||
|
||||
public static String createUID(Collection<String> joystickUIDs) {
|
||||
return "compound-" + String.join("_", joystickUIDs);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.ControllerConfig;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -8,9 +9,10 @@ import java.util.Map;
|
||||
public class JoystickConfig extends ControllerConfig {
|
||||
private Map<String, Float> deadzones;
|
||||
|
||||
private transient JoystickController controller;
|
||||
private transient JoystickController<?> controller;
|
||||
|
||||
public JoystickConfig(JoystickController controller) {
|
||||
public JoystickConfig(JoystickController<?> controller) {
|
||||
Validate.notNull(controller);
|
||||
setup(controller);
|
||||
}
|
||||
|
||||
@ -30,7 +32,7 @@ public class JoystickConfig extends ControllerConfig {
|
||||
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
|
||||
}
|
||||
|
||||
void setup(JoystickController controller) {
|
||||
void setup(JoystickController<?> controller) {
|
||||
this.controller = controller;
|
||||
if (this.deadzones == null) {
|
||||
deadzones = new HashMap<>();
|
||||
|
@ -1,74 +1,20 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickConfig;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
public interface JoystickController<T extends JoystickConfig> extends Controller<JoystickState, T> {
|
||||
JoystickMapping mapping();
|
||||
|
||||
public class JoystickController extends AbstractController<JoystickState, JoystickConfig> {
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public JoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type()));
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
}
|
||||
int axisCount();
|
||||
int buttonCount();
|
||||
int hatCount();
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
prevState = state;
|
||||
state = JoystickState.fromJoystick(this, joystickId);
|
||||
}
|
||||
|
||||
public JoystickMapping mapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeUsed() {
|
||||
default boolean canBeUsed() {
|
||||
return !(mapping() instanceof UnmappedJoystickMapping);
|
||||
}
|
||||
|
||||
public int axisCount() {
|
||||
return axisCount;
|
||||
}
|
||||
|
||||
public int buttonCount() {
|
||||
return buttonCount;
|
||||
}
|
||||
|
||||
public int hatCount() {
|
||||
return hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
super.setConfig(gson, json);
|
||||
this.config.setup(this);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import org.lwjgl.glfw.GLFW;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@ -58,7 +59,17 @@ public class JoystickState implements ControllerState {
|
||||
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
||||
}
|
||||
|
||||
public static JoystickState fromJoystick(JoystickController joystick, int joystickId) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoystickState{" +
|
||||
"axes=" + axes +
|
||||
", rawAxes=" + rawAxes +
|
||||
", buttons=" + buttons +
|
||||
", hats=" + hats +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static JoystickState fromJoystick(JoystickController<?> joystick, int joystickId) {
|
||||
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId);
|
||||
List<Float> axes = new ArrayList<>();
|
||||
List<Float> rawAxes = new ArrayList<>();
|
||||
@ -107,6 +118,40 @@ public class JoystickState implements ControllerState {
|
||||
return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats);
|
||||
}
|
||||
|
||||
public static JoystickState empty(JoystickController<?> joystick) {
|
||||
var axes = new ArrayList<Float>();
|
||||
var buttons = new ArrayList<Boolean>();
|
||||
var hats = new ArrayList<HatState>();
|
||||
|
||||
for (int i = 0; i < joystick.axisCount(); i++) {
|
||||
axes.add(joystick.mapping().axis(i).restingValue());
|
||||
}
|
||||
for (int i = 0; i < joystick.buttonCount(); i++) {
|
||||
buttons.add(false);
|
||||
}
|
||||
for (int i = 0; i < joystick.hatCount(); i++) {
|
||||
hats.add(HatState.CENTERED);
|
||||
}
|
||||
|
||||
return new JoystickState(joystick.mapping(), axes, axes, buttons, hats);
|
||||
}
|
||||
|
||||
public static JoystickState merged(JoystickMapping mapping, Collection<JoystickState> states) {
|
||||
var axes = new ArrayList<Float>();
|
||||
var rawAxes = new ArrayList<Float>();
|
||||
var buttons = new ArrayList<Boolean>();
|
||||
var hats = new ArrayList<HatState>();
|
||||
|
||||
for (var state : states) {
|
||||
axes.addAll(state.axes);
|
||||
rawAxes.addAll(state.rawAxes);
|
||||
buttons.addAll(state.buttons);
|
||||
hats.addAll(state.hats);
|
||||
}
|
||||
|
||||
return new JoystickState(mapping, axes, rawAxes, buttons, hats);
|
||||
}
|
||||
|
||||
public enum HatState implements NameableEnum {
|
||||
CENTERED,
|
||||
UP,
|
||||
|
@ -0,0 +1,83 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SingleJoystickController extends AbstractController<JoystickState, JoystickConfig> implements JoystickController<JoystickConfig> {
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type()));
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
prevState = state;
|
||||
state = JoystickState.fromJoystick(this, joystickId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
this.state = JoystickState.empty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickMapping mapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int axisCount() {
|
||||
return axisCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int buttonCount() {
|
||||
return buttonCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hatCount() {
|
||||
return hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int joystickId() {
|
||||
return joystickId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
super.setConfig(gson, json);
|
||||
this.config.setup(this);
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ public interface JoystickMapping {
|
||||
|
||||
boolean isAxisResting(float value);
|
||||
|
||||
float restingValue();
|
||||
|
||||
String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
}
|
||||
|
||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restState, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||
@Override
|
||||
public float modifyAxis(float value) {
|
||||
if (inpRange() == null || outRange() == null)
|
||||
@ -122,7 +122,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
|
||||
@Override
|
||||
public boolean isAxisResting(float value) {
|
||||
return value == restState();
|
||||
return value == restingValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,7 +45,12 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
|
||||
@Override
|
||||
public boolean isAxisResting(float value) {
|
||||
return value == 0;
|
||||
return value == restingValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float restingValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user