mirror of
https://github.com/chylex/Better-Controls.git
synced 2025-04-11 21:15:43 +02:00
Add core files w/ config serialization, options screen, and keybind registration
This commit is contained in:
parent
999f2bcec0
commit
bee7dee256
logo.afdesign
src/main
java/chylex/bettercontrols
BetterControlsMod.java
config
gui
input
mixin
resources
BIN
logo.afdesign
Normal file
BIN
logo.afdesign
Normal file
Binary file not shown.
11
src/main/java/chylex/bettercontrols/BetterControlsMod.java
Normal file
11
src/main/java/chylex/bettercontrols/BetterControlsMod.java
Normal file
@ -0,0 +1,11 @@
|
||||
package chylex.bettercontrols;
|
||||
import chylex.bettercontrols.config.BetterControlsConfig;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public final class BetterControlsMod implements ClientModInitializer{
|
||||
public static final BetterControlsConfig config = BetterControlsConfig.load(FabricLoader.getInstance().getConfigDir().resolve("BetterControls.json"));
|
||||
|
||||
@Override
|
||||
public void onInitializeClient(){}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package chylex.bettercontrols.config;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class BetterControlsConfig{
|
||||
public static BetterControlsConfig load(final Path path){
|
||||
return ConfigSerializer.deserialize(path).setPath(path);
|
||||
}
|
||||
|
||||
private Path path;
|
||||
|
||||
BetterControlsConfig(){}
|
||||
|
||||
private BetterControlsConfig setPath(final Path path){
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyBinding[] getAllKeyBindings(){
|
||||
return new KeyBinding[0];
|
||||
}
|
||||
|
||||
public void save(){
|
||||
ConfigSerializer.serialize(path, this);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package chylex.bettercontrols.config;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, JsonDeserializer<BetterControlsConfig>{
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private static final Gson gson = new GsonBuilder().registerTypeAdapter(BetterControlsConfig.class, new ConfigSerializer()).setPrettyPrinting().create();
|
||||
|
||||
private ConfigSerializer(){}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(final BetterControlsConfig cfg, final Type typeOfSrc, final JsonSerializationContext context){
|
||||
final JsonObject obj = new JsonObject();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BetterControlsConfig deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException{
|
||||
final BetterControlsConfig cfg = new BetterControlsConfig();
|
||||
final JsonObject obj = json.getAsJsonObject();
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static void serialize(final Path path, final BetterControlsConfig config){
|
||||
try(final JsonWriter writer = gson.newJsonWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8))){
|
||||
gson.getAdapter(BetterControlsConfig.class).write(writer, config);
|
||||
}catch(final IOException e){
|
||||
logger.error("Error saving BetterControls configuration file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
static BetterControlsConfig deserialize(final Path path){
|
||||
try(final JsonReader jsonReader = new JsonReader(Files.newBufferedReader(path, StandardCharsets.UTF_8))){
|
||||
return gson.getAdapter(BetterControlsConfig.class).read(jsonReader);
|
||||
}catch(final FileNotFoundException | NoSuchFileException ignored){
|
||||
}catch(final IOException e){
|
||||
logger.error("Error reading BetterControls configuration file!", e);
|
||||
}
|
||||
|
||||
return new BetterControlsConfig();
|
||||
}
|
||||
}
|
54
src/main/java/chylex/bettercontrols/config/Json.java
Normal file
54
src/main/java/chylex/bettercontrols/config/Json.java
Normal file
@ -0,0 +1,54 @@
|
||||
package chylex.bettercontrols.config;
|
||||
import chylex.bettercontrols.input.KeyBindingWithModifier;
|
||||
import chylex.bettercontrols.input.ModifierKey;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
|
||||
final class Json{
|
||||
private Json(){}
|
||||
|
||||
static void setInt(final JsonObject obj, final String key, final int value){
|
||||
obj.addProperty(key, Integer.valueOf(value));
|
||||
}
|
||||
|
||||
static int getInt(final JsonObject obj, final String key, final int defaultValue){
|
||||
return obj.has(key) ? obj.get(key).getAsInt() : defaultValue;
|
||||
}
|
||||
|
||||
static void setFloat(final JsonObject obj, final String key, final float value){
|
||||
obj.addProperty(key, Float.valueOf(value));
|
||||
}
|
||||
|
||||
static float getFloat(final JsonObject obj, final String key, final float defaultValue){
|
||||
return obj.has(key) ? obj.get(key).getAsFloat() : defaultValue;
|
||||
}
|
||||
|
||||
static void setBool(final JsonObject obj, final String key, final boolean value){
|
||||
obj.addProperty(key, Boolean.valueOf(value));
|
||||
}
|
||||
|
||||
static boolean getBool(final JsonObject obj, final String key, final boolean defaultValue){
|
||||
return obj.has(key) ? obj.get(key).getAsBoolean() : defaultValue;
|
||||
}
|
||||
|
||||
private static final String KEY_SUFFIX = ".Key";
|
||||
private static final String MOD_SUFFIX = ".Mod";
|
||||
|
||||
static void writeKeyBinding(final JsonObject obj, final String key, final KeyBindingWithModifier keyBinding){
|
||||
obj.addProperty(key + KEY_SUFFIX, keyBinding.getBoundKeyTranslationKey());
|
||||
|
||||
if (keyBinding.getModifier() != null){
|
||||
obj.addProperty(key + MOD_SUFFIX, Integer.valueOf(keyBinding.getModifier().id));
|
||||
}
|
||||
}
|
||||
|
||||
static void readKeyBinding(final JsonObject obj, final String key, final KeyBindingWithModifier keyBinding){
|
||||
if (obj.has(key + KEY_SUFFIX)){
|
||||
keyBinding.setBoundKey(InputUtil.fromTranslationKey(obj.get(key + KEY_SUFFIX).getAsString()));
|
||||
}
|
||||
|
||||
if (obj.has(key + MOD_SUFFIX)){
|
||||
keyBinding.setModifier(ModifierKey.getById(obj.get(key + MOD_SUFFIX).getAsInt()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package chylex.bettercontrols.gui;
|
||||
import chylex.bettercontrols.BetterControlsMod;
|
||||
import chylex.bettercontrols.gui.elements.KeyBindingWidget;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.ScreenTexts;
|
||||
import net.minecraft.client.gui.screen.options.GameOptionsScreen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BetterControlsScreen extends GameOptionsScreen{
|
||||
public static final LiteralText TITLE = new LiteralText("Better Controls");
|
||||
|
||||
private static final int BOTTOM_PADDING = 3;
|
||||
private static final int TEXT_PADDING_RIGHT = 4;
|
||||
private static final int TITLE_MARGIN_TOP = 3;
|
||||
private static final int ROW_HEIGHT = 22;
|
||||
|
||||
private OptionListWidget optionsWidget;
|
||||
private KeyBindingWidget editingKeyBinding;
|
||||
private final List<KeyBindingWidget> allKeyBindings = new ArrayList<>();
|
||||
|
||||
public BetterControlsScreen(final Screen parentScreen){
|
||||
super(parentScreen, MinecraftClient.getInstance().options, TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
allKeyBindings.clear();
|
||||
|
||||
final List<Element> elements = new ArrayList<>();
|
||||
int y = 0;
|
||||
|
||||
addButton(new ButtonWidget(width / 2 - 99, height - 29, 200, 20, ScreenTexts.DONE, btn -> client.openScreen(parent)));
|
||||
addChild(optionsWidget = new OptionListWidget(21, height - 32, width, height, elements, y - TITLE_MARGIN_TOP + BOTTOM_PADDING));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(){
|
||||
BetterControlsMod.config.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final MatrixStack matrices, final int mouseX, final int mouseY, final float delta){
|
||||
renderBackground(matrices);
|
||||
optionsWidget.render(matrices, mouseX, mouseY, delta);
|
||||
drawCenteredText(matrices, textRenderer, title, width / 2, 8, (255 << 16) | (255 << 8) | 255);
|
||||
super.render(matrices, mouseX, mouseY, delta);
|
||||
}
|
||||
|
||||
private void startEditingKeyBinding(final KeyBindingWidget widget){
|
||||
if (editingKeyBinding != null){
|
||||
editingKeyBinding.stopEditing();
|
||||
}
|
||||
|
||||
editingKeyBinding = widget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(final double mouseX, final double mouseY, final int button){
|
||||
if (editingKeyBinding != null){
|
||||
editingKeyBinding.bindAndStopEditing(InputUtil.Type.MOUSE.createFromCode(button));
|
||||
onKeyBindingEditingFinished();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(final int keyCode, final int scanCode, final int modifiers){
|
||||
if (editingKeyBinding != null){
|
||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE){
|
||||
editingKeyBinding.bindAndStopEditing(InputUtil.UNKNOWN_KEY);
|
||||
}
|
||||
else{
|
||||
editingKeyBinding.bindAndStopEditing(InputUtil.fromKeyCode(keyCode, scanCode));
|
||||
}
|
||||
|
||||
onKeyBindingEditingFinished();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return super.keyPressed(keyCode, scanCode, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
private void onKeyBindingEditingFinished(){
|
||||
editingKeyBinding = null;
|
||||
KeyBinding.updateKeysByCode();
|
||||
|
||||
for(final KeyBindingWidget widget : allKeyBindings){
|
||||
widget.updateKeyBindingText();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package chylex.bettercontrols.gui;
|
||||
import chylex.bettercontrols.mixin.AccessScreenButtons;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
|
||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.OptionButtonWidget;
|
||||
import net.minecraft.client.options.Option;
|
||||
import java.util.List;
|
||||
|
||||
public final class ControlsScreenPatcher{
|
||||
private ControlsScreenPatcher(){}
|
||||
|
||||
public static void onOpened(final ControlsOptionsScreen screen){
|
||||
final AccessScreenButtons accessor = (AccessScreenButtons)screen;
|
||||
|
||||
final List<? extends Element> children = screen.children();
|
||||
final List<AbstractButtonWidget> buttons = accessor.getButtons();
|
||||
|
||||
final AbstractButtonWidget autoJump = buttons
|
||||
.stream()
|
||||
.filter(it -> it instanceof OptionButtonWidget && ((OptionButtonWidget)it).getOption() == Option.AUTO_JUMP)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
|
||||
if (autoJump != null){
|
||||
children.remove(autoJump);
|
||||
buttons.remove(autoJump);
|
||||
|
||||
accessor.callAddButton(new ButtonWidget(autoJump.x, autoJump.y, autoJump.getWidth(), autoJump.getHeight(), BetterControlsScreen.TITLE.copy().append("..."), btn -> {
|
||||
MinecraftClient.getInstance().openScreen(new BetterControlsScreen(screen));
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
126
src/main/java/chylex/bettercontrols/gui/OptionListWidget.java
Normal file
126
src/main/java/chylex/bettercontrols/gui/OptionListWidget.java
Normal file
@ -0,0 +1,126 @@
|
||||
package chylex.bettercontrols.gui;
|
||||
import chylex.bettercontrols.gui.OptionListWidget.Entry;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Drawable;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||
import net.minecraft.client.gui.widget.ElementListWidget;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class OptionListWidget extends ElementListWidget<Entry>{
|
||||
public static final int ROW_WIDTH = 408;
|
||||
public static final int ROW_PADDING = 2;
|
||||
|
||||
public static final int COL2_W = (ROW_WIDTH / 2) - ROW_PADDING;
|
||||
public static final int COL3_W = (ROW_WIDTH / 3) - ROW_PADDING;
|
||||
public static final int COL4_W = (ROW_WIDTH / 4) - ROW_PADDING;
|
||||
public static final int COL6_W = (ROW_WIDTH / 6) - ROW_PADDING;
|
||||
|
||||
public static int col2(final int column){
|
||||
return (column * ROW_WIDTH) / 2;
|
||||
}
|
||||
|
||||
public static int col3(final int column){
|
||||
return (column * ROW_WIDTH) / 3;
|
||||
}
|
||||
|
||||
public static int col4(final int column){
|
||||
return (column * ROW_WIDTH) / 4;
|
||||
}
|
||||
|
||||
public static int col6(final int column){
|
||||
return (column * ROW_WIDTH) / 6;
|
||||
}
|
||||
|
||||
private static Offset getElementOffset(final Element element){
|
||||
if (element instanceof Widget){
|
||||
return new Offset(((Widget)element).getX(), ((Widget)element).getY());
|
||||
}
|
||||
else if (element instanceof AbstractButtonWidget){
|
||||
return new Offset(((AbstractButtonWidget)element).x, ((AbstractButtonWidget)element).y);
|
||||
}
|
||||
else{
|
||||
return new Offset(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Widget extends Element, Drawable{
|
||||
int getX();
|
||||
int getY();
|
||||
void setX(int x);
|
||||
void setY(int y);
|
||||
}
|
||||
|
||||
private static final class Offset{
|
||||
public final int x;
|
||||
public final int y;
|
||||
|
||||
private Offset(final int x, final int y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public OptionListWidget(final int top, final int bottom, final int width, final int height, final List<Element> widgets, final int innerHeight){
|
||||
super(MinecraftClient.getInstance(), width, height, top, bottom, innerHeight);
|
||||
addEntry(new Entry(widgets));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRowLeft(){
|
||||
return super.getRowLeft() - ROW_PADDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowWidth(){
|
||||
return ROW_WIDTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollbarPositionX(){
|
||||
return (width + ROW_WIDTH) / 2 + 4;
|
||||
}
|
||||
|
||||
protected static final class Entry extends ElementListWidget.Entry<Entry>{
|
||||
private final List<Element> elements;
|
||||
private final Map<Element, Offset> offsets;
|
||||
|
||||
public Entry(final List<Element> elements){
|
||||
this.elements = new ArrayList<>(elements);
|
||||
this.offsets = elements.stream().collect(Collectors.toMap(Function.identity(), OptionListWidget::getElementOffset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Element> children(){
|
||||
return Collections.unmodifiableList(elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final MatrixStack matrices, final int index, final int y, final int x, final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean hovered, final float tickDelta){
|
||||
for(final Element element : elements){
|
||||
final Offset offset = offsets.get(element);
|
||||
|
||||
if (element instanceof AbstractButtonWidget){
|
||||
final AbstractButtonWidget button = (AbstractButtonWidget)element;
|
||||
button.x = x + offset.x;
|
||||
button.y = y + offset.y;
|
||||
}
|
||||
else if (element instanceof Widget){
|
||||
final Widget widget = (Widget)element;
|
||||
widget.setX(x + offset.x);
|
||||
widget.setY(y + offset.y);
|
||||
}
|
||||
|
||||
if (element instanceof Drawable){
|
||||
((Drawable)element).render(matrices, mouseX, mouseY, tickDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
||||
import net.minecraft.client.gui.screen.ScreenTexts;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
|
||||
public final class BooleanValueWidget extends ButtonWidget{
|
||||
private final BooleanConsumer onChanged;
|
||||
private boolean value;
|
||||
|
||||
public BooleanValueWidget(final int x, final int y, final int width, final int height, final boolean currentValue, final BooleanConsumer onChanged){
|
||||
super(x, y, width, height, currentValue ? ScreenTexts.ON : ScreenTexts.OFF, ignore -> {});
|
||||
this.value = currentValue;
|
||||
this.onChanged = onChanged;
|
||||
}
|
||||
|
||||
public BooleanValueWidget(final int x, final int y, final int width, final boolean currentValue, final BooleanConsumer onChanged){
|
||||
this(x, y, width, 20, currentValue, onChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(){
|
||||
super.onPress();
|
||||
value = !value;
|
||||
setMessage(value ? ScreenTexts.ON : ScreenTexts.OFF);
|
||||
onChanged.accept(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CycleButtonWidget<T> extends ButtonWidget{
|
||||
private final List<Option<T>> options;
|
||||
private final Consumer<T> onChanged;
|
||||
private T selectedValue;
|
||||
|
||||
public CycleButtonWidget(final int x, final int y, final int width, final int height, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
|
||||
super(x, y, width, height, Option.find(options, selectedValue).getText(), btn -> {});
|
||||
this.options = options;
|
||||
this.selectedValue = selectedValue;
|
||||
this.onChanged = onChanged;
|
||||
}
|
||||
|
||||
public CycleButtonWidget(final int x, final int y, final int width, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
|
||||
this(x, y, width, 20, options, selectedValue, onChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(){
|
||||
int nextIndex = options.indexOf(Option.find(options, selectedValue)) + 1;
|
||||
|
||||
if (nextIndex >= options.size()){
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
final Option<T> newSelectedOption = options.get(nextIndex);
|
||||
|
||||
selectedValue = newSelectedOption.getValue();
|
||||
onChanged.accept(selectedValue);
|
||||
setMessage(newSelectedOption.getText());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import net.minecraft.client.gui.widget.SliderWidget;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class DiscreteValueSliderWidget<T> extends SliderWidget{
|
||||
private final List<Option<T>> options;
|
||||
private final Consumer<T> onChanged;
|
||||
private T selectedValue;
|
||||
|
||||
public DiscreteValueSliderWidget(final int x, final int y, final int width, final int height, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
|
||||
super(x, y, width, height, Option.find(options, selectedValue).getText(), options.indexOf(Option.find(options, selectedValue)) / (options.size() - 1.0));
|
||||
this.options = options;
|
||||
this.selectedValue = selectedValue;
|
||||
this.onChanged = onChanged;
|
||||
}
|
||||
|
||||
public DiscreteValueSliderWidget(final int x, final int y, final int width, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
|
||||
this(x, y, width, 20, options, selectedValue, onChanged);
|
||||
}
|
||||
|
||||
public Option<T> getSelectedOption(){
|
||||
return options.get(MathHelper.floor(MathHelper.clampedLerp(0.0, options.size() - 1.0, value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateMessage(){
|
||||
setMessage(getSelectedOption().getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyValue(){
|
||||
final T newSelectedValue = getSelectedOption().getValue();
|
||||
|
||||
if (selectedValue != newSelectedValue){
|
||||
selectedValue = newSelectedValue;
|
||||
onChanged.accept(newSelectedValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Formatting;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class KeyBindingWidget extends ButtonWidget{
|
||||
private final KeyBinding binding;
|
||||
private final Text bindingName;
|
||||
|
||||
private final List<AbstractButtonWidget> linkedButtons = new ArrayList<>(1);
|
||||
|
||||
private final Consumer<KeyBindingWidget> onEditingStarted;
|
||||
private boolean isEditing;
|
||||
|
||||
public KeyBindingWidget(final int x, final int y, final int width, final int height, final KeyBinding binding, final Consumer<KeyBindingWidget> onEditingStarted){
|
||||
super(x, y, width, height, LiteralText.EMPTY, btn -> {});
|
||||
this.binding = binding;
|
||||
this.bindingName = new TranslatableText(binding.getTranslationKey());
|
||||
this.onEditingStarted = onEditingStarted;
|
||||
updateKeyBindingText();
|
||||
}
|
||||
|
||||
public KeyBindingWidget(final int x, final int y, final int width, final KeyBinding binding, final Consumer<KeyBindingWidget> onEditingStarted){
|
||||
this(x, y, width, 20, binding, onEditingStarted);
|
||||
}
|
||||
|
||||
public void linkButtonToBoundState(final AbstractButtonWidget button){
|
||||
linkedButtons.add(button);
|
||||
button.active = !binding.isUnbound();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MutableText getNarrationMessage(){
|
||||
return binding.isUnbound() ? new TranslatableText("narrator.controls.unbound", bindingName) : new TranslatableText("narrator.controls.bound", bindingName, super.getNarrationMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(){
|
||||
isEditing = true;
|
||||
onEditingStarted.accept(this);
|
||||
updateKeyBindingText();
|
||||
}
|
||||
|
||||
public void bindAndStopEditing(final InputUtil.Key key){
|
||||
binding.setBoundKey(key);
|
||||
stopEditing();
|
||||
|
||||
for(final AbstractButtonWidget button : linkedButtons){
|
||||
button.active = !binding.isUnbound();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopEditing(){
|
||||
isEditing = false;
|
||||
updateKeyBindingText();
|
||||
}
|
||||
|
||||
public void updateKeyBindingText(){
|
||||
boolean hasConflict = false;
|
||||
|
||||
if (!binding.isUnbound()){
|
||||
for(final KeyBinding other : MinecraftClient.getInstance().options.keysAll){
|
||||
if (binding != other && binding.equals(other)){
|
||||
hasConflict = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditing){
|
||||
setMessage((new LiteralText("> ")).append(binding.getBoundKeyLocalizedText().shallowCopy().formatted(Formatting.YELLOW)).append(" <").formatted(Formatting.YELLOW));
|
||||
}
|
||||
else if (hasConflict){
|
||||
setMessage(binding.getBoundKeyLocalizedText().shallowCopy().formatted(Formatting.RED));
|
||||
}
|
||||
else{
|
||||
setMessage(binding.isUnbound() ? Text.of("(No Binding)") : binding.getBoundKeyLocalizedText());
|
||||
}
|
||||
}
|
||||
}
|
26
src/main/java/chylex/bettercontrols/gui/elements/Option.java
Normal file
26
src/main/java/chylex/bettercontrols/gui/elements/Option.java
Normal file
@ -0,0 +1,26 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import net.minecraft.text.Text;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Option<T>{
|
||||
private final T value;
|
||||
private final Text text;
|
||||
|
||||
public Option(final T value, final Text text){
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public T getValue(){
|
||||
return value;
|
||||
}
|
||||
|
||||
public Text getText(){
|
||||
return text;
|
||||
}
|
||||
|
||||
public static <T> Option<T> find(final List<Option<T>> options, final T value){
|
||||
return options.stream().filter(it -> Objects.equals(it.value, value)).findFirst().orElseGet(() -> options.get(0));
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package chylex.bettercontrols.gui.elements;
|
||||
import chylex.bettercontrols.gui.OptionListWidget.Widget;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.OrderedText;
|
||||
import net.minecraft.text.Text;
|
||||
import java.util.List;
|
||||
|
||||
public final class TextWidget extends DrawableHelper implements Widget{
|
||||
public static final int LEFT = 0;
|
||||
public static final int CENTER = 1;
|
||||
|
||||
private final Text text;
|
||||
private int x;
|
||||
private int y;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final int align;
|
||||
|
||||
public TextWidget(final int x, final int y, final int width, final int height, final Text text, final int align){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.text = text;
|
||||
this.align = align;
|
||||
}
|
||||
|
||||
public TextWidget(final int x, final int y, final int width, final Text text, final int align){
|
||||
this(x, y, width, 20, text, align);
|
||||
}
|
||||
|
||||
public TextWidget(final int x, final int y, final int width, final Text text){
|
||||
this(x, y, width, 20, text, LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX(){
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY(){
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(final int x){
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(final int y){
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final MatrixStack matrices, final int mouseX, final int mouseY, final float delta){
|
||||
final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
|
||||
final List<OrderedText> lines = textRenderer.wrapLines(text, width);
|
||||
final int lineHeight = textRenderer.fontHeight + 1;
|
||||
|
||||
final int finalX = align == CENTER ? x + (width / 2) - (lines.stream().mapToInt(textRenderer::getWidth).max().orElse(0) / 2) : x;
|
||||
final int finalY = y + (height / 2) - (lineHeight * lines.size() / 2) + 1;
|
||||
|
||||
for(int i = 0; i < lines.size(); i++){
|
||||
final OrderedText line = lines.get(i);
|
||||
textRenderer.drawWithShadow(matrices, line, finalX, finalY + (i * lineHeight), (255 << 16) | (255 << 8) | 255);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package chylex.bettercontrols.input;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil.Type;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class KeyBindingWithModifier extends KeyBinding{
|
||||
public static final String CATEGORY = "key.categories.bettercontrols";
|
||||
|
||||
@Nullable
|
||||
private ModifierKey modifier = null;
|
||||
|
||||
public KeyBindingWithModifier(final String translationKey){
|
||||
super(translationKey, Type.KEYSYM, -1, CATEGORY);
|
||||
}
|
||||
|
||||
public void setModifier(final @Nullable ModifierKey modifier){
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
public @Nullable ModifierKey getModifier(){
|
||||
return modifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPressed(){
|
||||
return super.isPressed() && (modifier == null || modifier.isPressed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasPressed(){
|
||||
return super.wasPressed() && (modifier == null || modifier.isPressed());
|
||||
}
|
||||
}
|
42
src/main/java/chylex/bettercontrols/input/ModifierKey.java
Normal file
42
src/main/java/chylex/bettercontrols/input/ModifierKey.java
Normal file
@ -0,0 +1,42 @@
|
||||
package chylex.bettercontrols.input;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
|
||||
public enum ModifierKey{
|
||||
CONTROL(0){
|
||||
@Override
|
||||
public boolean isPressed(){
|
||||
return Screen.hasControlDown();
|
||||
}
|
||||
},
|
||||
|
||||
SHIFT(1){
|
||||
@Override
|
||||
public boolean isPressed(){
|
||||
return Screen.hasShiftDown();
|
||||
}
|
||||
},
|
||||
|
||||
ALT(2){
|
||||
@Override
|
||||
public boolean isPressed(){
|
||||
return Screen.hasAltDown();
|
||||
}
|
||||
};
|
||||
|
||||
public final int id;
|
||||
|
||||
ModifierKey(final int id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public abstract boolean isPressed();
|
||||
|
||||
public static ModifierKey getById(final int id){
|
||||
switch(id){
|
||||
case 0: return CONTROL;
|
||||
case 1: return SHIFT;
|
||||
case 2: return ALT;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package chylex.bettercontrols.mixin;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(KeyBinding.class)
|
||||
public interface AccessKeyBindingFields{
|
||||
@Accessor
|
||||
static Map<String, Integer> getCategoryOrderMap(){
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package chylex.bettercontrols.mixin;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface AccessScreenButtons{
|
||||
@Accessor
|
||||
List<AbstractButtonWidget> getButtons();
|
||||
|
||||
@Invoker
|
||||
<T extends AbstractButtonWidget> T callAddButton(T button);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package chylex.bettercontrols.mixin;
|
||||
import chylex.bettercontrols.BetterControlsMod;
|
||||
import chylex.bettercontrols.input.KeyBindingWithModifier;
|
||||
import net.minecraft.client.options.GameOptions;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(GameOptions.class)
|
||||
public abstract class HookLoadGameOptions{
|
||||
private boolean hasLoaded = false;
|
||||
|
||||
@Mutable
|
||||
@Final
|
||||
@Shadow
|
||||
public KeyBinding[] keysAll;
|
||||
|
||||
@Inject(method = "load()V", at = @At("HEAD"))
|
||||
private void load(final CallbackInfo info){
|
||||
if (hasLoaded){
|
||||
return;
|
||||
}
|
||||
|
||||
hasLoaded = true;
|
||||
keysAll = ArrayUtils.addAll(keysAll, BetterControlsMod.config.getAllKeyBindings());
|
||||
AccessKeyBindingFields.getCategoryOrderMap().put(KeyBindingWithModifier.CATEGORY, Integer.valueOf(Integer.MAX_VALUE));
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package chylex.bettercontrols.mixin;
|
||||
import chylex.bettercontrols.gui.ControlsScreenPatcher;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
|
||||
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(MinecraftClient.class)
|
||||
public abstract class HookOpenScreen{
|
||||
@Inject(method = "openScreen(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("TAIL"))
|
||||
private void openScreen(final Screen ignore, final CallbackInfo ci){
|
||||
final MinecraftClient mc = MinecraftClient.getInstance();
|
||||
final Screen screen = mc.currentScreen;
|
||||
|
||||
if (screen != null && screen.getClass() == ControlsOptionsScreen.class && !Screen.hasAltDown()){
|
||||
ControlsScreenPatcher.onOpened((ControlsOptionsScreen)screen);
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/main/resources/assets/bettercontrols/icon.png
Normal file
BIN
src/main/resources/assets/bettercontrols/icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 21 KiB |
3
src/main/resources/assets/bettercontrols/lang/en_us.json
Normal file
3
src/main/resources/assets/bettercontrols/lang/en_us.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"key.categories.bettercontrols": "Better Controls"
|
||||
}
|
36
src/main/resources/fabric.mod.json
Normal file
36
src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "bettercontrols",
|
||||
"version": "${version}",
|
||||
|
||||
"name": "Better Controls",
|
||||
"description": "Adds many powerful key bindings and options to control your movement.\nThe features complement vanilla mechanics without giving unfair advantages, so server use should be fine.\nTo configure the mod, open Options - Controls - Better Controls. If you want to see the vanilla Controls screen, hold Alt while opening it.",
|
||||
"icon": "assets/bettercontrols/icon.png",
|
||||
"license": "MPL-2.0",
|
||||
|
||||
"authors": [ "chylex" ],
|
||||
"contact": {
|
||||
"homepage": "",
|
||||
"sources": "https://github.com/chylex/Better-Controls",
|
||||
"issues": "https://github.com/chylex/Better-Controls/issues"
|
||||
},
|
||||
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"client": [ "chylex.bettercontrols.BetterControlsMod" ]
|
||||
},
|
||||
|
||||
"mixins": [{
|
||||
"config": "mixins.json",
|
||||
"environment": "client"
|
||||
}],
|
||||
|
||||
"depends": {
|
||||
"fabricloader": ">=0.7.4",
|
||||
"minecraft": "1.16.x"
|
||||
},
|
||||
|
||||
"custom": {
|
||||
"modmenu:clientsideOnly": true
|
||||
}
|
||||
}
|
15
src/main/resources/mixins.json
Normal file
15
src/main/resources/mixins.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "chylex.bettercontrols.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"AccessKeyBindingFields",
|
||||
"AccessScreenButtons",
|
||||
"HookLoadGameOptions",
|
||||
"HookOpenScreen"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user