diff --git a/CHANGELOG.md b/CHANGELOG.md index 06451eb90..f32798583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/common/src/main/java/net/xolt/freecam/config/ModConfig.java b/common/src/main/java/net/xolt/freecam/config/ModConfig.java index 8b00c2d82..6fd2a93d3 100644 --- a/common/src/main/java/net/xolt/freecam/config/ModConfig.java +++ b/common/src/main/java/net/xolt/freecam/config/ModConfig.java @@ -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") @@ -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(); } @@ -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; } diff --git a/common/src/main/java/net/xolt/freecam/config/gui/AutoConfigExtensions.java b/common/src/main/java/net/xolt/freecam/config/gui/AutoConfigExtensions.java new file mode 100644 index 000000000..2c30f6b39 --- /dev/null +++ b/common/src/main/java/net/xolt/freecam/config/gui/AutoConfigExtensions.java @@ -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 configClass) { + GuiRegistry registry = AutoConfig.getGuiRegistry(configClass); + VariantTooltipImpl.apply(registry); + BoundedContinuousImpl.apply(registry); + } +} diff --git a/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuous.java b/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuous.java new file mode 100644 index 000000000..df20cea1e --- /dev/null +++ b/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuous.java @@ -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(); +} diff --git a/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuousImpl.java b/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuousImpl.java new file mode 100644 index 000000000..2cf5629a2 --- /dev/null +++ b/common/src/main/java/net/xolt/freecam/config/gui/BoundedContinuousImpl.java @@ -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 save = newValue -> Utils.setUnsafely(field, config, newValue); + Supplier 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 + ); + } +} diff --git a/common/src/main/java/net/xolt/freecam/config/gui/DoubleSliderEntry.java b/common/src/main/java/net/xolt/freecam/config/gui/DoubleSliderEntry.java new file mode 100644 index 000000000..5fdbbddba --- /dev/null +++ b/common/src/main/java/net/xolt/freecam/config/gui/DoubleSliderEntry.java @@ -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 { + 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 defaultValue; + private final List widgets; + + DoubleSliderEntry(Component fieldName, int precision, double minimum, double maximum, double value, Component resetText, Supplier defaultValue, @Nullable Consumer 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 getDefaultValue() { + return Optional.ofNullable(defaultValue).map(Supplier::get); + } + + @Override + public @NotNull List children() { + return widgets; + } + + @Override + public List 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; + } + } +} diff --git a/common/src/main/java/net/xolt/freecam/config/VariantTooltip.java b/common/src/main/java/net/xolt/freecam/config/gui/VariantTooltip.java similarity index 95% rename from common/src/main/java/net/xolt/freecam/config/VariantTooltip.java rename to common/src/main/java/net/xolt/freecam/config/gui/VariantTooltip.java index 7ba4128dc..690557a6f 100644 --- a/common/src/main/java/net/xolt/freecam/config/VariantTooltip.java +++ b/common/src/main/java/net/xolt/freecam/config/gui/VariantTooltip.java @@ -1,4 +1,4 @@ -package net.xolt.freecam.config; +package net.xolt.freecam.config.gui; import me.shedaniel.autoconfig.annotation.ConfigEntry; diff --git a/common/src/main/java/net/xolt/freecam/config/ConfigExtensions.java b/common/src/main/java/net/xolt/freecam/config/gui/VariantTooltipImpl.java similarity index 93% rename from common/src/main/java/net/xolt/freecam/config/ConfigExtensions.java rename to common/src/main/java/net/xolt/freecam/config/gui/VariantTooltipImpl.java index 210827002..c5c25c91b 100644 --- a/common/src/main/java/net/xolt/freecam/config/ConfigExtensions.java +++ b/common/src/main/java/net/xolt/freecam/config/gui/VariantTooltipImpl.java @@ -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; @@ -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) -> {