Skip to content

Commit

Permalink
config: movement speed sliders (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
hashalite authored Apr 1, 2024
2 parents 26c2b64 + 4fd2ce0 commit c205a67
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and Freecam's versioning is based on [Semantic Versioning](https://semver.org/sp

### Changed

- Movement speed options now use sliders instead of text fields ([#190](https://github.com/MinecraftFreecam/Freecam/pull/190)).

### Removed

### Fixed
Expand Down
7 changes: 6 additions & 1 deletion common/src/main/java/net/xolt/freecam/config/ModConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.EnumHandler.EnumDisplayOption;
import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer;
import me.shedaniel.clothconfig2.gui.entries.SelectionListEntry;
import net.xolt.freecam.config.gui.AutoConfigExtensions;
import net.xolt.freecam.config.gui.BoundedContinuous;
import net.xolt.freecam.config.gui.VariantTooltip;
import net.xolt.freecam.variant.api.BuildVariant;

@Config(name = "freecam")
Expand All @@ -17,7 +20,7 @@ public class ModConfig implements ConfigData {

public static void init() {
AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new);
ConfigExtensions.init(AutoConfig.getGuiRegistry(ModConfig.class));
AutoConfigExtensions.apply(ModConfig.class);
INSTANCE = AutoConfig.getConfigHolder(ModConfig.class).getConfig();
}

Expand All @@ -30,9 +33,11 @@ public static class MovementConfig {
public FlightMode flightMode = FlightMode.DEFAULT;

@ConfigEntry.Gui.Tooltip
@BoundedContinuous(max = 10)
public double horizontalSpeed = 1.0;

@ConfigEntry.Gui.Tooltip
@BoundedContinuous(max = 10)
public double verticalSpeed = 1.0;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.AutoConfig;
import me.shedaniel.autoconfig.ConfigData;
import me.shedaniel.autoconfig.gui.DefaultGuiProviders;
import me.shedaniel.autoconfig.gui.DefaultGuiTransformers;
import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.network.chat.Component;

/**
* Extensions and modifications to AutoConfig.
*
* @see DefaultGuiProviders
* @see DefaultGuiTransformers
*/
public class AutoConfigExtensions {
static final Component RESET_TEXT = Component.translatable("text.cloth-config.reset_value");
static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();

private AutoConfigExtensions() {}

public static void apply(Class<? extends ConfigData> configClass) {
GuiRegistry registry = AutoConfig.getGuiRegistry(configClass);
VariantTooltipImpl.apply(registry);
BoundedContinuousImpl.apply(registry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.annotation.ConfigEntry;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A continuous version of {@link ConfigEntry.BoundedDiscrete}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BoundedContinuous {

/**
* The number of decimal places the slider will round to.
*/
int precision() default 2;

double min() default 0;
double max();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.autoconfig.util.Utils;
import net.minecraft.network.chat.Component;

import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static net.xolt.freecam.config.gui.AutoConfigExtensions.RESET_TEXT;

class BoundedContinuousImpl {

private BoundedContinuousImpl() {}

static void apply(GuiRegistry registry) {

registry.registerAnnotationProvider(
(i18n, field, config, defaults, guiProvider) -> {
Consumer<Double> save = newValue -> Utils.setUnsafely(field, config, newValue);
Supplier<Double> defaultValue = () -> Utils.getUnsafely(field, defaults);
double value = Utils.getUnsafely(field, config, defaultValue.get());
BoundedContinuous bounds = field.getAnnotation(BoundedContinuous.class);
return Collections.singletonList(new DoubleSliderEntry(Component.translatable(i18n), bounds.precision(), bounds.min(), bounds.max(), value, RESET_TEXT, defaultValue, save));
},
field -> field.getType() == Double.TYPE || field.getType() == Double.class,
BoundedContinuous.class
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package net.xolt.freecam.config.gui;

import com.google.common.util.concurrent.AtomicDouble;
import com.mojang.blaze3d.platform.Window;
import me.shedaniel.clothconfig2.gui.entries.TooltipListEntry;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractSliderButton;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static net.xolt.freecam.Freecam.MC;

/**
* {@link IntegerSliderEntry} ported from {@code int} to {@code double}.
*/
class DoubleSliderEntry extends TooltipListEntry<Double> {
private final Slider sliderWidget;
private final Button resetButton;
private final AtomicDouble value;
private final double original;
private final int precision;
private final double minimum;
private final double maximum;
private final Supplier<Double> defaultValue;
private final List<AbstractWidget> widgets;

DoubleSliderEntry(Component fieldName, int precision, double minimum, double maximum, double value, Component resetText, Supplier<Double> defaultValue, @Nullable Consumer<Double> save) {
//noinspection deprecation,UnstableApiUsage
super(fieldName, null);
this.value = new AtomicDouble(value);
this.original = value;
this.defaultValue = defaultValue;
this.maximum = maximum;
this.minimum = minimum;
this.precision = precision;
this.saveCallback = save;
this.sliderWidget = new Slider(0, 0, 152, 20, (this.value.get() - minimum) / (maximum - minimum));
this.sliderWidget.updateMessage();
this.resetButton = Button.builder(resetText, widget -> this.setValue(this.defaultValue.get()))
.width(MC.font.width(resetText) + 6)
.build();
this.widgets = List.of(this.sliderWidget, this.resetButton);
}

@Override
public Double getValue() {
return value.get();
}

public void setValue(double value) {
double clamped = Mth.clamp(value, minimum, maximum);
this.value.set(clamped);
sliderWidget.setValue((clamped - minimum) / (maximum - minimum));
sliderWidget.updateMessage();
}

@Override
public boolean isEdited() {
return super.isEdited() || getValue() != original;
}

@Override
public Optional<Double> getDefaultValue() {
return Optional.ofNullable(defaultValue).map(Supplier::get);
}

@Override
public @NotNull List<? extends GuiEventListener> children() {
return widgets;
}

@Override
public List<? extends NarratableEntry> narratables() {
return widgets;
}

@Override
public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) {
super.render(graphics, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isHovered, delta);
Window window = MC.getWindow();
resetButton.active = isEditable() && getDefaultValue().isPresent() && defaultValue.get() != value.get();
resetButton.setY(y);
sliderWidget.active = isEditable();
sliderWidget.setY(y);

Component name = getDisplayedFieldName();
if (MC.font.isBidirectional()) {
graphics.drawString(MC.font, name.getVisualOrderText(), window.getGuiScaledWidth() - x - MC.font.width(name), y + 6, getPreferredTextColor());
resetButton.setX(x);
sliderWidget.setX(x + resetButton.getWidth() + 1);
} else {
graphics.drawString(MC.font, name.getVisualOrderText(), x, y + 6, getPreferredTextColor());
resetButton.setX(x + entryWidth - resetButton.getWidth());
sliderWidget.setX(x + entryWidth - 150);
}

sliderWidget.setWidth(150 - resetButton.getWidth() - 2);
resetButton.render(graphics, mouseX, mouseY, delta);
sliderWidget.render(graphics, mouseX, mouseY, delta);
}

private final class Slider extends AbstractSliderButton {
private Slider(int x, int y, int width, int height, double value) {
super(x, y, width, height, Component.empty(), value);
}

@Override
public void updateMessage() {
NumberFormat fmt = DecimalFormat.getInstance();
fmt.setMinimumIntegerDigits(1);
fmt.setMinimumFractionDigits(precision);
fmt.setMaximumFractionDigits(precision);
setMessage(Component.literal("Value: " + fmt.format(DoubleSliderEntry.this.value.get())));
}

@Override
protected void applyValue() {
double rounded = BigDecimal.valueOf(DoubleSliderEntry.this.minimum + (DoubleSliderEntry.this.maximum - DoubleSliderEntry.this.minimum) * this.value)
.setScale(DoubleSliderEntry.this.precision, RoundingMode.HALF_UP)
.doubleValue();
DoubleSliderEntry.this.value.set(rounded);
}

@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
return DoubleSliderEntry.this.isEditable() && super.keyPressed(keyCode, scanCode, modifiers);
}

@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
return DoubleSliderEntry.this.isEditable() && super.mouseDragged(mouseX, mouseY, button, dragX, dragY);
}

public void setValue(double value) {
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.xolt.freecam.config;
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.annotation.ConfigEntry;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package net.xolt.freecam.config;
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.annotation.ConfigEntry;
import me.shedaniel.autoconfig.gui.DefaultGuiProviders;
import me.shedaniel.autoconfig.gui.DefaultGuiTransformers;
import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.gui.entries.TextListEntry;
Expand All @@ -20,17 +18,11 @@
import java.util.function.Supplier;
import java.util.stream.IntStream;

/**
* Extensions and modifications to AutoConfig.
*
* @see DefaultGuiProviders
* @see DefaultGuiTransformers
*/
public class ConfigExtensions {
class VariantTooltipImpl {

private ConfigExtensions() {}
private VariantTooltipImpl() {}

public static void init(GuiRegistry registry) {
static void apply(GuiRegistry registry) {

registry.registerAnnotationTransformer(
(guis, i18n, field, config, defaults, guiProvider) -> {
Expand Down

0 comments on commit c205a67

Please sign in to comment.