From 5e6c45c2b27bf9b13733cc27256f98b1cd88d071 Mon Sep 17 00:00:00 2001 From: Riqqqque Date: Tue, 5 May 2026 22:57:07 -0600 Subject: [PATCH 1/6] Add numeric framerate limit input --- .../config/structure/IntegerOption.java | 6 + .../client/gui/SodiumConfigBuilder.java | 7 +- .../client/gui/options/FramerateLimit.java | 15 ++ .../control/ControlValueFormatterImpls.java | 3 +- .../control/IntegerTextBoxControl.java | 255 ++++++++++++++++++ .../gui/FramerateLimitOptionMixin.java | 56 ++++ .../main/resources/sodium-common.mixins.json | 1 + 7 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java index e81d289cf7..48123f4bc2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java @@ -7,7 +7,9 @@ import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.api.config.option.SteppedValidator; import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.caffeinemc.mods.sodium.client.gui.options.control.Control; +import net.caffeinemc.mods.sodium.client.gui.options.control.IntegerTextBoxControl; import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; @@ -59,6 +61,10 @@ Integer validateValue(Integer value) { @Override Control createControl() { + if (FramerateLimit.OPTION_ID.equals(this.id)) { + return new IntegerTextBoxControl(this); + } + return new SliderControl(this); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index fc3847f377..01944f383b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -20,6 +20,7 @@ import net.caffeinemc.mods.sodium.client.config.structure.Config; import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; +import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatterImpls; import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; @@ -310,8 +311,8 @@ private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { .setName(Component.translatable("options.framerateLimit")) .setTooltip(Component.translatable("sodium.options.fps_limit.tooltip")) .setValueFormatter(ControlValueFormatterImpls.fpsLimit()) - .setRange(10, 260, 10) - .setDefaultValue(60) + .setRange(FramerateLimit.MIN, FramerateLimit.MAX, 1) + .setDefaultValue(FramerateLimit.SODIUM_DEFAULT) .setBinding(this.vanillaOpts.framerateLimit()::set, this.vanillaOpts.framerateLimit()::get) ) ); @@ -727,4 +728,4 @@ private OptionPageBuilder buildAdvancedPage(ConfigBuilder builder) { return advancedPage; } -} \ No newline at end of file +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java new file mode 100644 index 0000000000..98c8605624 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.sodium.client.gui.options; + +import net.minecraft.resources.Identifier; + +public final class FramerateLimit { + public static final Identifier OPTION_ID = Identifier.parse("sodium:general.framerate_limit"); + + public static final int MIN = 10; + public static final int MAX = 1_000_000; + public static final int SODIUM_DEFAULT = 60; + public static final int VANILLA_DEFAULT = 120; + + private FramerateLimit() { + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java index d03ff70643..92c81441c8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.platform.Monitor; import net.caffeinemc.mods.sodium.api.config.option.ControlValueFormatter; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; @@ -32,7 +33,7 @@ public static ControlValueFormatter resolution() { } public static ControlValueFormatter fpsLimit() { - return (v) -> (v == 260) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v); + return (v) -> (v == FramerateLimit.MAX) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v); } public static ControlValueFormatter brightness() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java new file mode 100644 index 0000000000..11614e96f6 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java @@ -0,0 +1,255 @@ +package net.caffeinemc.mods.sodium.client.gui.options.control; + +import com.mojang.blaze3d.platform.cursor.CursorTypes; +import net.caffeinemc.mods.sodium.client.config.structure.IntegerOption; +import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; +import net.caffeinemc.mods.sodium.client.gui.ColorTheme; +import net.caffeinemc.mods.sodium.client.gui.Colors; +import net.caffeinemc.mods.sodium.client.gui.Layout; +import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.util.Mth; + +public class IntegerTextBoxControl implements Control { + private final IntegerOption option; + + public IntegerTextBoxControl(IntegerOption option) { + this.option = option; + } + + @Override + public ControlElement createElement(Screen screen, AbstractOptionList list, Dim2i dim, ColorTheme theme) { + return new IntegerTextBoxControlElement(list, this.option, dim, theme); + } + + @Override + public StatefulOption getOption() { + return this.option; + } + + @Override + public int getMaxWidth() { + return Layout.SLIDER_WIDTH; + } + + static class IntegerTextBoxControlElement extends StatefulControlElement { + private static final int TEXT_BOX_WIDTH = Layout.SLIDER_WIDTH; + private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 4; + + private final IntegerOption option; + private final EditBox textBox; + + private boolean updatingText; + + public IntegerTextBoxControlElement(AbstractOptionList list, IntegerOption option, Dim2i dim, ColorTheme theme) { + super(list, dim, theme); + + this.option = option; + + this.textBox = new EditBox( + this.font, + 0, + 0, + TEXT_BOX_WIDTH - (Layout.INNER_MARGIN * 2), + TEXT_BOX_HEIGHT, + option.getName()); + this.textBox.setBordered(false); + this.textBox.setMaxLength(String.valueOf(option.getSteppedValidator().max()).length()); + this.textBox.setResponder(this::setValueFromText); + this.syncTextToOption(); + } + + @Override + public IntegerOption getOption() { + return this.option; + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + super.extractRenderState(graphics, mouseX, mouseY, delta); + + if (!this.option.showControl() || this.isResetOverlayActive()) { + return; + } + + this.textBox.setEditable(this.option.isEnabled()); + this.textBox.setTextColor(this.option.isEnabled() ? Colors.FOREGROUND : Colors.FOREGROUND_DISABLED); + this.textBox.setTextColorUneditable(Colors.FOREGROUND_DISABLED); + + if (!this.textBox.isFocused()) { + this.syncTextToOption(); + } + + this.updateTextBoxPosition(); + + int x = this.getTextBoxX(); + int y = this.getTextBoxY(); + int borderColor = (this.isFocused() || this.isMouseOverTextBox(mouseX, mouseY)) ? this.theme.themeLighter : Colors.BACKGROUND_LIGHT; + + this.drawRect(graphics, x, y, x + TEXT_BOX_WIDTH, y + TEXT_BOX_HEIGHT, Colors.BACKGROUND_MEDIUM); + this.drawBorder(graphics, x, y, x + TEXT_BOX_WIDTH, y + TEXT_BOX_HEIGHT, borderColor); + this.textBox.extractRenderState(graphics, mouseX, mouseY, delta); + + if (this.isMouseOverTextBox(mouseX, mouseY)) { + graphics.requestCursor(CursorTypes.IBEAM); + } + } + + @Override + public int getContentWidth() { + return TEXT_BOX_WIDTH; + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (super.mouseClicked(event, doubleClick)) { + this.setFocused(false); + this.syncTextToOption(); + return true; + } + + if (this.isResetOverlayActive() || !this.option.isEnabled() || !this.option.showControl()) { + return false; + } + + this.updateTextBoxPosition(); + + if (event.button() == 0 && this.isMouseOverTextBox(event.x(), event.y())) { + this.focused = true; + this.textBox.setFocused(true); + return this.textBox.mouseClicked(event, doubleClick); + } + + return false; + } + + @Override + public boolean keyPressed(KeyEvent event) { + if (!this.isFocused()) { + return false; + } + + if (event.isEscape() || event.isConfirmation()) { + this.setFocused(false); + return true; + } + + return this.textBox.keyPressed(event); + } + + @Override + public boolean charTyped(CharacterEvent event) { + if (!this.isFocused() || !isDigit(event.codepoint())) { + return false; + } + + return this.textBox.charTyped(event); + } + + @Override + public void setFocused(boolean focused) { + super.setFocused(focused); + + if (focused && this.isFocused()) { + this.textBox.setFocused(true); + } else if (!focused) { + this.commitText(); + this.textBox.setFocused(false); + } + } + + private int getTextBoxX() { + return this.getLimitX() - TEXT_BOX_WIDTH - Layout.OPTION_TEXT_SIDE_PADDING; + } + + private int getTextBoxY() { + return this.getCenterY() - (TEXT_BOX_HEIGHT / 2); + } + + private void updateTextBoxPosition() { + this.textBox.setX(this.getTextBoxX() + Layout.INNER_MARGIN); + this.textBox.setY(this.getTextBoxY() + ((TEXT_BOX_HEIGHT - this.font.lineHeight) / 2)); + } + + private boolean isMouseOverTextBox(double mouseX, double mouseY) { + int x = this.getTextBoxX(); + int y = this.getTextBoxY(); + return mouseX >= x && mouseX < x + TEXT_BOX_WIDTH && mouseY >= y && mouseY < y + TEXT_BOX_HEIGHT; + } + + private void setValueFromText(String text) { + if (this.updatingText) { + return; + } + + String sanitized = sanitize(text); + if (!sanitized.equals(text)) { + this.setText(sanitized); + return; + } + + if (sanitized.isEmpty()) { + return; + } + + this.option.modifyValue(this.getClampedValue(sanitized)); + } + + private void commitText() { + String text = this.textBox.getValue(); + if (text.isEmpty()) { + this.syncTextToOption(); + return; + } + + int value = this.getClampedValue(text); + this.option.modifyValue(value); + this.setText(String.valueOf(value)); + } + + private int getClampedValue(String text) { + var range = this.option.getSteppedValidator(); + int value; + + try { + value = Integer.parseInt(text); + } catch (NumberFormatException ignored) { + value = range.max(); + } + + return Mth.clamp(value, range.min(), range.max()); + } + + private void syncTextToOption() { + this.setText(String.valueOf(this.option.getValidatedValue())); + } + + private void setText(String text) { + this.updatingText = true; + this.textBox.setValue(text); + this.updatingText = false; + } + + private static String sanitize(String text) { + var builder = new StringBuilder(text.length()); + + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c >= '0' && c <= '9') { + builder.append(c); + } + } + + return builder.toString(); + } + + private static boolean isDigit(int codepoint) { + return codepoint >= '0' && codepoint <= '9'; + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java new file mode 100644 index 0000000000..7b2c118780 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java @@ -0,0 +1,56 @@ +package net.caffeinemc.mods.sodium.mixin.features.gui; + +import com.mojang.serialization.Codec; +import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; +import net.minecraft.client.Minecraft; +import net.minecraft.client.OptionInstance; +import net.minecraft.client.Options; +import net.minecraft.network.chat.Component; +import org.objectweb.asm.Opcodes; +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(Options.class) +public class FramerateLimitOptionMixin { + @Mutable + @Shadow + @Final + private OptionInstance framerateLimit; + + @Inject( + method = "", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/Options;framerateLimit:Lnet/minecraft/client/OptionInstance;", + opcode = Opcodes.PUTFIELD, + shift = At.Shift.AFTER + ) + ) + private void replaceFramerateLimitOption(Minecraft minecraft, java.io.File optionsFile, CallbackInfo ci) { + this.framerateLimit = new OptionInstance<>( + "options.framerateLimit", + OptionInstance.noTooltip(), + FramerateLimitOptionMixin::formatFramerateLimit, + new OptionInstance.IntRange(FramerateLimit.MIN, FramerateLimit.MAX), + Codec.intRange(FramerateLimit.MIN, FramerateLimit.MAX), + FramerateLimit.VANILLA_DEFAULT, + FramerateLimitOptionMixin::setFramerateLimit); + } + + private static Component formatFramerateLimit(Component caption, Integer value) { + if (value == FramerateLimit.MAX) { + return Options.genericValueLabel(caption, Component.translatable("options.framerateLimit.max")); + } + + return Options.genericValueLabel(caption, Component.translatable("options.framerate", value)); + } + + private static void setFramerateLimit(Integer value) { + Minecraft.getInstance().getFramerateLimitTracker().setFramerateLimit(value); + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 628c405f92..c350466c5c 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -46,6 +46,7 @@ "core.world.map.ClientLevelMixin", "core.world.map.ClientPacketListenerMixin", "features.gui.OptionsAccessor", + "features.gui.FramerateLimitOptionMixin", "features.gui.hooks.console.GameRendererMixin", "features.gui.hooks.debug.DebugEntryMemoryMixin", "features.gui.hooks.debug.DebugScreenEntriesAccessor", From fe7c7312bcce658fe65810fd8d8e64041a094c8e Mon Sep 17 00:00:00 2001 From: Riqqqque Date: Wed, 6 May 2026 21:58:13 -0600 Subject: [PATCH 2/6] Expose integer text box controls --- .../config/option/IntegerOptionControl.java | 9 +++++++++ .../config/structure/IntegerOptionBuilder.java | 10 +++++++++- .../builder/IntegerOptionBuilderImpl.java | 15 +++++++++++++++ .../client/config/structure/IntegerOption.java | 18 ++++++++++++------ .../sodium/client/gui/SodiumConfigBuilder.java | 12 ++++++++---- .../client/gui/options/FramerateLimit.java | 15 --------------- .../control/ControlValueFormatterImpls.java | 5 ++--- .../options/control/IntegerTextBoxControl.java | 14 ++++---------- .../gui/FramerateLimitOptionMixin.java | 13 ++++++++----- 9 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java new file mode 100644 index 0000000000..dc02def6db --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +/** + * The control style used by an integer option. + */ +public enum IntegerOptionControl { + SLIDER, + TEXT_BOX +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java index 313ee6f079..f3a91e022e 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -103,6 +103,14 @@ public interface IntegerOptionBuilder extends StatefulOptionBuilder { */ IntegerOptionBuilder setValidatorProvider(Function provider, Identifier... dependencies); + /** + * Sets the control style for this integer option. + * + * @param control The control style to use. + * @return The current builder instance. + */ + IntegerOptionBuilder setControl(IntegerOptionControl control); + /** * Sets the value formatter for this integer option. * @@ -110,4 +118,4 @@ public interface IntegerOptionBuilder extends StatefulOptionBuilder { * @return The current builder instance. */ IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter); -} \ No newline at end of file +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index 4822924fae..c4f5625b3d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -19,6 +19,7 @@ class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { private DependentValue validatorProvider; + private IntegerOptionControl control; private ControlValueFormatter valueFormatter; IntegerOptionBuilderImpl(Identifier id) { @@ -51,6 +52,7 @@ IntegerOption build() { this.getBinding(), this.getApplyHook(), this.getValidatorProvider(), + this.getControl(), this.getValueFormatter()); } @@ -70,6 +72,11 @@ DependentValue getValidatorProvider() { return getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); } + IntegerOptionControl getControl() { + var control = getFirstNotNull(this.control, IntegerOption::getControlType); + return control != null ? control : IntegerOptionControl.SLIDER; + } + ControlValueFormatter getValueFormatter() { return getFirstNotNull(this.valueFormatter, IntegerOption::getValueFormatter); } @@ -193,6 +200,14 @@ public IntegerOptionBuilder setValidatorProvider(Function { private final DependentValue validator; + private final IntegerOptionControl control; private final ControlValueFormatter valueFormatter; public IntegerOption( @@ -37,10 +38,12 @@ public IntegerOption( OptionBinding binding, Consumer applyHook, DependentValue validator, + IntegerOptionControl control, ControlValueFormatter valueFormatter ) { super(id, dependencies, name, enabled, storage, tooltipProvider, impact, flags, defaultValue, controlHiddenWhenDisabled, binding, applyHook); this.validator = validator; + this.control = control; this.valueFormatter = valueFormatter; } @@ -61,11 +64,10 @@ Integer validateValue(Integer value) { @Override Control createControl() { - if (FramerateLimit.OPTION_ID.equals(this.id)) { - return new IntegerTextBoxControl(this); - } - - return new SliderControl(this); + return switch (this.control) { + case SLIDER -> new SliderControl(this); + case TEXT_BOX -> new IntegerTextBoxControl(this); + }; } public SteppedValidator getSteppedValidator() { @@ -80,6 +82,10 @@ public DependentValue getValidatorProvider() { return this.validator; } + public IntegerOptionControl getControlType() { + return this.control; + } + public ControlValueFormatter getValueFormatter() { return this.valueFormatter; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index 01944f383b..4fe6c32f40 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -10,6 +10,7 @@ import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; import net.caffeinemc.mods.sodium.api.config.ConfigState; import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.IntegerOptionControl; import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.api.config.option.Range; @@ -20,7 +21,6 @@ import net.caffeinemc.mods.sodium.client.config.structure.Config; import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; -import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatterImpls; import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; @@ -48,6 +48,9 @@ public class SodiumConfigBuilder implements ConfigEntryPoint { private static final Identifier SODIUM_ICON = Identifier.fromNamespaceAndPath("sodium", "textures/gui/config-icon.png"); private static final SodiumOptions DEFAULTS = SodiumOptions.defaults(); + private static final int FRAMERATE_LIMIT_MIN = 10; + private static final int FRAMERATE_LIMIT_MAX = 1_000_000; + private static final int FRAMERATE_LIMIT_DEFAULT = 60; private final Options vanillaOpts; private final StorageEventHandler vanillaStorage; @@ -310,9 +313,10 @@ private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { .setStorageHandler(this.vanillaStorage) .setName(Component.translatable("options.framerateLimit")) .setTooltip(Component.translatable("sodium.options.fps_limit.tooltip")) - .setValueFormatter(ControlValueFormatterImpls.fpsLimit()) - .setRange(FramerateLimit.MIN, FramerateLimit.MAX, 1) - .setDefaultValue(FramerateLimit.SODIUM_DEFAULT) + .setValueFormatter(ControlValueFormatterImpls.fpsLimit(FRAMERATE_LIMIT_MAX)) + .setRange(FRAMERATE_LIMIT_MIN, FRAMERATE_LIMIT_MAX, 1) + .setDefaultValue(FRAMERATE_LIMIT_DEFAULT) + .setControl(IntegerOptionControl.TEXT_BOX) .setBinding(this.vanillaOpts.framerateLimit()::set, this.vanillaOpts.framerateLimit()::get) ) ); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java deleted file mode 100644 index 98c8605624..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/FramerateLimit.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gui.options; - -import net.minecraft.resources.Identifier; - -public final class FramerateLimit { - public static final Identifier OPTION_ID = Identifier.parse("sodium:general.framerate_limit"); - - public static final int MIN = 10; - public static final int MAX = 1_000_000; - public static final int SODIUM_DEFAULT = 60; - public static final int VANILLA_DEFAULT = 120; - - private FramerateLimit() { - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java index 92c81441c8..471e2cc422 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java @@ -3,7 +3,6 @@ import com.mojang.blaze3d.platform.Monitor; import net.caffeinemc.mods.sodium.api.config.option.ControlValueFormatter; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; @@ -32,8 +31,8 @@ public static ControlValueFormatter resolution() { }; } - public static ControlValueFormatter fpsLimit() { - return (v) -> (v == FramerateLimit.MAX) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v); + public static ControlValueFormatter fpsLimit(int maximum) { + return (v) -> (v == maximum) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v); } public static ControlValueFormatter brightness() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java index 11614e96f6..947ded9b50 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java @@ -15,6 +15,8 @@ import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.util.Mth; +import java.util.regex.Pattern; + public class IntegerTextBoxControl implements Control { private final IntegerOption option; @@ -38,6 +40,7 @@ public int getMaxWidth() { } static class IntegerTextBoxControlElement extends StatefulControlElement { + private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("[^0-9]"); private static final int TEXT_BOX_WIDTH = Layout.SLIDER_WIDTH; private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 4; @@ -236,16 +239,7 @@ private void setText(String text) { } private static String sanitize(String text) { - var builder = new StringBuilder(text.length()); - - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - if (c >= '0' && c <= '9') { - builder.append(c); - } - } - - return builder.toString(); + return NON_DIGIT_PATTERN.matcher(text).replaceAll(""); } private static boolean isDigit(int codepoint) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java index 7b2c118780..51353bba8b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.mixin.features.gui; import com.mojang.serialization.Codec; -import net.caffeinemc.mods.sodium.client.gui.options.FramerateLimit; import net.minecraft.client.Minecraft; import net.minecraft.client.OptionInstance; import net.minecraft.client.Options; @@ -17,6 +16,10 @@ @Mixin(Options.class) public class FramerateLimitOptionMixin { + private static final int FRAMERATE_LIMIT_MIN = 10; + private static final int FRAMERATE_LIMIT_MAX = 1_000_000; + private static final int FRAMERATE_LIMIT_DEFAULT = 120; + @Mutable @Shadow @Final @@ -36,14 +39,14 @@ private void replaceFramerateLimitOption(Minecraft minecraft, java.io.File optio "options.framerateLimit", OptionInstance.noTooltip(), FramerateLimitOptionMixin::formatFramerateLimit, - new OptionInstance.IntRange(FramerateLimit.MIN, FramerateLimit.MAX), - Codec.intRange(FramerateLimit.MIN, FramerateLimit.MAX), - FramerateLimit.VANILLA_DEFAULT, + new OptionInstance.IntRange(FRAMERATE_LIMIT_MIN, FRAMERATE_LIMIT_MAX), + Codec.intRange(FRAMERATE_LIMIT_MIN, FRAMERATE_LIMIT_MAX), + FRAMERATE_LIMIT_DEFAULT, FramerateLimitOptionMixin::setFramerateLimit); } private static Component formatFramerateLimit(Component caption, Integer value) { - if (value == FramerateLimit.MAX) { + if (value == FRAMERATE_LIMIT_MAX) { return Options.genericValueLabel(caption, Component.translatable("options.framerateLimit.max")); } From 84b7c2fe04d75f696558790f60bbbeccad7457bc Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 7 May 2026 17:33:51 +0200 Subject: [PATCH 3/6] Clean up some things --- ...rol.java => IntegerOptionControlStyle.java} | 2 +- .../config/structure/IntegerOptionBuilder.java | 2 +- .../builder/IntegerOptionBuilderImpl.java | 14 ++++++-------- .../client/config/structure/IntegerOption.java | 18 +++++++----------- .../sodium/client/gui/SodiumConfigBuilder.java | 11 +++++------ .../gui/FramerateLimitOptionMixin.java | 6 ++---- 6 files changed, 22 insertions(+), 31 deletions(-) rename common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/{IntegerOptionControl.java => IntegerOptionControlStyle.java} (77%) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControlStyle.java similarity index 77% rename from common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java rename to common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControlStyle.java index dc02def6db..67aac8e825 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControl.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/IntegerOptionControlStyle.java @@ -3,7 +3,7 @@ /** * The control style used by an integer option. */ -public enum IntegerOptionControl { +public enum IntegerOptionControlStyle { SLIDER, TEXT_BOX } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java index f3a91e022e..7a042cfc42 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -109,7 +109,7 @@ public interface IntegerOptionBuilder extends StatefulOptionBuilder { * @param control The control style to use. * @return The current builder instance. */ - IntegerOptionBuilder setControl(IntegerOptionControl control); + IntegerOptionBuilder setControlStyle(IntegerOptionControlStyle control); /** * Sets the value formatter for this integer option. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index c4f5625b3d..d9d091181f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -19,7 +19,7 @@ class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { private DependentValue validatorProvider; - private IntegerOptionControl control; + private IntegerOptionControlStyle control = IntegerOptionControlStyle.SLIDER; private ControlValueFormatter valueFormatter; IntegerOptionBuilderImpl(Identifier id) { @@ -32,6 +32,7 @@ void validateData() { Validate.notNull(this.getValidatorProvider(), "Validator provider must be set"); Validate.notNull(this.getValueFormatter(), "Value formatter must be set"); + Validate.notNull(this.getControlStyle(), "Control style must be set"); } @Override @@ -52,7 +53,7 @@ IntegerOption build() { this.getBinding(), this.getApplyHook(), this.getValidatorProvider(), - this.getControl(), + this.getControlStyle(), this.getValueFormatter()); } @@ -72,9 +73,8 @@ DependentValue getValidatorProvider() { return getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); } - IntegerOptionControl getControl() { - var control = getFirstNotNull(this.control, IntegerOption::getControlType); - return control != null ? control : IntegerOptionControl.SLIDER; + IntegerOptionControlStyle getControlStyle() { + return getFirstNotNull(this.control, IntegerOption::getControlStyle); } ControlValueFormatter getValueFormatter() { @@ -201,9 +201,7 @@ public IntegerOptionBuilder setValidatorProvider(Function { private final DependentValue validator; - private final IntegerOptionControl control; + private final IntegerOptionControlStyle controlStyle; private final ControlValueFormatter valueFormatter; public IntegerOption( @@ -38,12 +34,12 @@ public IntegerOption( OptionBinding binding, Consumer applyHook, DependentValue validator, - IntegerOptionControl control, + IntegerOptionControlStyle controlStyle, ControlValueFormatter valueFormatter ) { super(id, dependencies, name, enabled, storage, tooltipProvider, impact, flags, defaultValue, controlHiddenWhenDisabled, binding, applyHook); this.validator = validator; - this.control = control; + this.controlStyle = controlStyle; this.valueFormatter = valueFormatter; } @@ -64,7 +60,7 @@ Integer validateValue(Integer value) { @Override Control createControl() { - return switch (this.control) { + return switch (this.controlStyle) { case SLIDER -> new SliderControl(this); case TEXT_BOX -> new IntegerTextBoxControl(this); }; @@ -82,8 +78,8 @@ public DependentValue getValidatorProvider() { return this.validator; } - public IntegerOptionControl getControlType() { - return this.control; + public IntegerOptionControlStyle getControlStyle() { + return this.controlStyle; } public ControlValueFormatter getValueFormatter() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index 4fe6c32f40..12c41a06d4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -10,7 +10,7 @@ import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; import net.caffeinemc.mods.sodium.api.config.ConfigState; import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; -import net.caffeinemc.mods.sodium.api.config.option.IntegerOptionControl; +import net.caffeinemc.mods.sodium.api.config.option.IntegerOptionControlStyle; import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.api.config.option.Range; @@ -48,9 +48,9 @@ public class SodiumConfigBuilder implements ConfigEntryPoint { private static final Identifier SODIUM_ICON = Identifier.fromNamespaceAndPath("sodium", "textures/gui/config-icon.png"); private static final SodiumOptions DEFAULTS = SodiumOptions.defaults(); - private static final int FRAMERATE_LIMIT_MIN = 10; - private static final int FRAMERATE_LIMIT_MAX = 1_000_000; - private static final int FRAMERATE_LIMIT_DEFAULT = 60; + public static final int FRAMERATE_LIMIT_MIN = 10; + public static final int FRAMERATE_LIMIT_MAX = 1_000_000; + public static final int FRAMERATE_LIMIT_DEFAULT = 60; private final Options vanillaOpts; private final StorageEventHandler vanillaStorage; @@ -316,7 +316,7 @@ private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { .setValueFormatter(ControlValueFormatterImpls.fpsLimit(FRAMERATE_LIMIT_MAX)) .setRange(FRAMERATE_LIMIT_MIN, FRAMERATE_LIMIT_MAX, 1) .setDefaultValue(FRAMERATE_LIMIT_DEFAULT) - .setControl(IntegerOptionControl.TEXT_BOX) + .setControlStyle(IntegerOptionControlStyle.TEXT_BOX) .setBinding(this.vanillaOpts.framerateLimit()::set, this.vanillaOpts.framerateLimit()::get) ) ); @@ -731,5 +731,4 @@ private OptionPageBuilder buildAdvancedPage(ConfigBuilder builder) { ); return advancedPage; } - } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java index 51353bba8b..76c8fb341f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/FramerateLimitOptionMixin.java @@ -14,12 +14,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import static net.caffeinemc.mods.sodium.client.gui.SodiumConfigBuilder.*; + @Mixin(Options.class) public class FramerateLimitOptionMixin { - private static final int FRAMERATE_LIMIT_MIN = 10; - private static final int FRAMERATE_LIMIT_MAX = 1_000_000; - private static final int FRAMERATE_LIMIT_DEFAULT = 120; - @Mutable @Shadow @Final From 1c1f3b120c02e2e47cd93c004d89cbb32755a0dd Mon Sep 17 00:00:00 2001 From: Riqqqque Date: Fri, 8 May 2026 14:27:46 -0600 Subject: [PATCH 4/6] Fix integer text box focus --- .../options/control/IntegerTextBoxControl.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java index 947ded9b50..7a604ca5a1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java @@ -42,7 +42,7 @@ public int getMaxWidth() { static class IntegerTextBoxControlElement extends StatefulControlElement { private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("[^0-9]"); private static final int TEXT_BOX_WIDTH = Layout.SLIDER_WIDTH; - private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 4; + private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 6; private final IntegerOption option; private final EditBox textBox; @@ -123,9 +123,9 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { this.updateTextBoxPosition(); if (event.button() == 0 && this.isMouseOverTextBox(event.x(), event.y())) { - this.focused = true; - this.textBox.setFocused(true); - return this.textBox.mouseClicked(event, doubleClick); + this.setFocused(true); + this.textBox.mouseClicked(event, doubleClick); + return true; } return false; @@ -156,12 +156,12 @@ public boolean charTyped(CharacterEvent event) { @Override public void setFocused(boolean focused) { - super.setFocused(focused); - - if (focused && this.isFocused()) { + if (focused) { + this.focused = true; this.textBox.setFocused(true); - } else if (!focused) { + } else { this.commitText(); + this.focused = false; this.textBox.setFocused(false); } } @@ -176,7 +176,7 @@ private int getTextBoxY() { private void updateTextBoxPosition() { this.textBox.setX(this.getTextBoxX() + Layout.INNER_MARGIN); - this.textBox.setY(this.getTextBoxY() + ((TEXT_BOX_HEIGHT - this.font.lineHeight) / 2)); + this.textBox.setY(this.getTextBoxY() + ((TEXT_BOX_HEIGHT - this.font.lineHeight + 1) / 2)); } private boolean isMouseOverTextBox(double mouseX, double mouseY) { From d6cb97bf3cf701e90fb110a8d9c38c060a904b64 Mon Sep 17 00:00:00 2001 From: Riqqqque Date: Fri, 8 May 2026 14:56:52 -0600 Subject: [PATCH 5/6] Fix integer textbox focus routing --- .../control/IntegerTextBoxControl.java | 77 +++++++++++++++++-- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java index 7a604ca5a1..12d21b9502 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java @@ -7,14 +7,21 @@ import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.CharacterEvent; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.util.Mth; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import java.util.List; import java.util.regex.Pattern; public class IntegerTextBoxControl implements Control { @@ -39,7 +46,7 @@ public int getMaxWidth() { return Layout.SLIDER_WIDTH; } - static class IntegerTextBoxControlElement extends StatefulControlElement { + static class IntegerTextBoxControlElement extends StatefulControlElement implements ContainerEventHandler { private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("[^0-9]"); private static final int TEXT_BOX_WIDTH = Layout.SLIDER_WIDTH; private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 6; @@ -47,6 +54,8 @@ static class IntegerTextBoxControlElement extends StatefulControlElement { private final IntegerOption option; private final EditBox textBox; + private @Nullable GuiEventListener focusedElement; + private boolean dragging; private boolean updatingText; public IntegerTextBoxControlElement(AbstractOptionList list, IntegerOption option, Dim2i dim, ColorTheme theme) { @@ -76,7 +85,7 @@ public IntegerOption getOption() { public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { super.extractRenderState(graphics, mouseX, mouseY, delta); - if (!this.option.showControl() || this.isResetOverlayActive()) { + if (!this.option.showControl() || (this.isResetOverlayActive() && this.getFocused() != this.textBox)) { return; } @@ -133,7 +142,7 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { @Override public boolean keyPressed(KeyEvent event) { - if (!this.isFocused()) { + if (this.getFocused() != this.textBox) { return false; } @@ -147,23 +156,75 @@ public boolean keyPressed(KeyEvent event) { @Override public boolean charTyped(CharacterEvent event) { - if (!this.isFocused() || !isDigit(event.codepoint())) { + if (this.getFocused() != this.textBox || !isDigit(event.codepoint())) { return false; } return this.textBox.charTyped(event); } + @Override + public @Nullable ComponentPath nextFocusPath(FocusNavigationEvent event) { + if (!this.option.isEnabled()) { + return null; + } + + if (!this.option.showControl()) { + return super.nextFocusPath(event); + } + + return ContainerEventHandler.super.nextFocusPath(event); + } + @Override public void setFocused(boolean focused) { if (focused) { - this.focused = true; - this.textBox.setFocused(true); + this.setFocused(this.textBox); } else { + this.setFocused((GuiEventListener) null); + } + } + + @Override + public @NonNull List children() { + return List.of(this.textBox); + } + + @Override + public @Nullable GuiEventListener getFocused() { + return this.focusedElement; + } + + @Override + public void setFocused(@Nullable GuiEventListener guiEventListener) { + if (this.focusedElement == guiEventListener) { + return; + } + + if (guiEventListener == null && this.focusedElement == this.textBox) { this.commitText(); - this.focused = false; - this.textBox.setFocused(false); } + + if (this.focusedElement != null) { + this.focusedElement.setFocused(false); + } + + this.focusedElement = guiEventListener; + this.focused = guiEventListener != null; + + if (this.focusedElement != null) { + this.focusedElement.setFocused(true); + } + } + + @Override + public boolean isDragging() { + return this.dragging; + } + + @Override + public void setDragging(boolean dragging) { + this.dragging = dragging; } private int getTextBoxX() { From 9af86f157150c7aa10cccfed57c945fa056ee272 Mon Sep 17 00:00:00 2001 From: Riqqqque Date: Fri, 8 May 2026 21:39:31 -0600 Subject: [PATCH 6/6] Clean up integer textbox focus handling --- .../gui/options/control/ControlElement.java | 14 ++++- .../control/IntegerTextBoxControl.java | 60 ++----------------- 2 files changed, 16 insertions(+), 58 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java index 08a9ff3ea8..fbf3d1fa0b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java @@ -4,7 +4,7 @@ import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.Layout; -import net.caffeinemc.mods.sodium.client.gui.widgets.AbstractWidget; +import net.caffeinemc.mods.sodium.client.gui.widgets.AbstractParentWidget; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.ComponentPath; @@ -15,7 +15,7 @@ import net.minecraft.network.chat.Style; import org.jspecify.annotations.Nullable; -public abstract class ControlElement extends AbstractWidget { +public abstract class ControlElement extends AbstractParentWidget { protected final AbstractOptionList list; protected final ColorTheme theme; @@ -83,6 +83,16 @@ public int getY() { if (!this.getOption().isEnabled()) { return null; } + + if (this.children().isEmpty()) { + return ComponentPath.leaf(this); + } + return super.nextFocusPath(event); } + + @Override + public boolean isFocused() { + return super.isFocused() || this.getFocused() != null; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java index 12d21b9502..557a775854 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/IntegerTextBoxControl.java @@ -7,21 +7,16 @@ import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.util.Dim2i; -import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.CharacterEvent; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.util.Mth; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import java.util.List; import java.util.regex.Pattern; public class IntegerTextBoxControl implements Control { @@ -46,7 +41,7 @@ public int getMaxWidth() { return Layout.SLIDER_WIDTH; } - static class IntegerTextBoxControlElement extends StatefulControlElement implements ContainerEventHandler { + static class IntegerTextBoxControlElement extends StatefulControlElement { private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("[^0-9]"); private static final int TEXT_BOX_WIDTH = Layout.SLIDER_WIDTH; private static final int TEXT_BOX_HEIGHT = Layout.BUTTON_SHORT - 6; @@ -54,8 +49,6 @@ static class IntegerTextBoxControlElement extends StatefulControlElement impleme private final IntegerOption option; private final EditBox textBox; - private @Nullable GuiEventListener focusedElement; - private boolean dragging; private boolean updatingText; public IntegerTextBoxControlElement(AbstractOptionList list, IntegerOption option, Dim2i dim, ColorTheme theme) { @@ -74,6 +67,7 @@ public IntegerTextBoxControlElement(AbstractOptionList list, IntegerOption optio this.textBox.setMaxLength(String.valueOf(option.getSteppedValidator().max()).length()); this.textBox.setResponder(this::setValueFromText); this.syncTextToOption(); + this.addChild(this.textBox); } @Override @@ -163,19 +157,6 @@ public boolean charTyped(CharacterEvent event) { return this.textBox.charTyped(event); } - @Override - public @Nullable ComponentPath nextFocusPath(FocusNavigationEvent event) { - if (!this.option.isEnabled()) { - return null; - } - - if (!this.option.showControl()) { - return super.nextFocusPath(event); - } - - return ContainerEventHandler.super.nextFocusPath(event); - } - @Override public void setFocused(boolean focused) { if (focused) { @@ -185,46 +166,13 @@ public void setFocused(boolean focused) { } } - @Override - public @NonNull List children() { - return List.of(this.textBox); - } - - @Override - public @Nullable GuiEventListener getFocused() { - return this.focusedElement; - } - @Override public void setFocused(@Nullable GuiEventListener guiEventListener) { - if (this.focusedElement == guiEventListener) { - return; - } - - if (guiEventListener == null && this.focusedElement == this.textBox) { + if (guiEventListener == null && this.getFocused() == this.textBox) { this.commitText(); } - if (this.focusedElement != null) { - this.focusedElement.setFocused(false); - } - - this.focusedElement = guiEventListener; - this.focused = guiEventListener != null; - - if (this.focusedElement != null) { - this.focusedElement.setFocused(true); - } - } - - @Override - public boolean isDragging() { - return this.dragging; - } - - @Override - public void setDragging(boolean dragging) { - this.dragging = dragging; + super.setFocused(guiEventListener); } private int getTextBoxX() {