From 3a86e9d2ba89466f7f9ef51c29eb5879bcb8a98e Mon Sep 17 00:00:00 2001 From: isXander Date: Thu, 17 Nov 2022 17:18:19 +0000 Subject: 22w46a --- .../java/dev/isxander/yacl/test/GuiTest.java | 417 ++++++++++++++++++++ .../dev/isxander/yacl/test/ModMenuIntegration.java | 437 --------------------- .../yacl/test/mixins/TitleScreenMixin.java | 26 ++ 3 files changed, 443 insertions(+), 437 deletions(-) create mode 100644 src/testmod/java/dev/isxander/yacl/test/GuiTest.java delete mode 100644 src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java create mode 100644 src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java (limited to 'src/testmod/java/dev/isxander') diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java new file mode 100644 index 0000000..449a081 --- /dev/null +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -0,0 +1,417 @@ +package dev.isxander.yacl.test; + +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.gui.RequireRestartScreen; +import dev.isxander.yacl.gui.controllers.*; +import dev.isxander.yacl.gui.controllers.cycling.EnumController; +import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; +import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; +import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; +import dev.isxander.yacl.gui.controllers.slider.LongSliderController; +import dev.isxander.yacl.gui.controllers.string.StringController; +import dev.isxander.yacl.test.config.ConfigData; +import dev.isxander.yacl.test.config.Entrypoint; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.option.GraphicsMode; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.Text; + +import java.awt.*; + +public class GuiTest { + public static Screen getModConfigScreenFactory(Screen parent) { + return Entrypoint.getConfig().buildConfig((config, builder) -> builder + .title(Text.of("Test Suites")) + .category(ConfigCategory.createBuilder() + .name(Text.of("Suites")) + .option(ButtonOption.createBuilder() + .name(Text.of("Full Test Suite")) + .controller(ActionController::new) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getFullTestSuite(screen))) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Basic Wiki Suite")) + .controller(ActionController::new) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiBasic(screen))) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Group Wiki Suite")) + .controller(ActionController::new) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiGroups(screen))) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Unavailable Test Suite")) + .controller(ActionController::new) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getDisabledTest(screen))) + .build()) + .build()) + ) + .generateScreen(parent); + } + + private static Screen getFullTestSuite(Screen parent) { + return Entrypoint.getConfig().buildConfig((config, builder) -> builder + .title(Text.of("Test GUI")) + .category(ConfigCategory.createBuilder() + .name(Text.of("Control Examples")) + .tooltip(Text.of("Example Category Description")) + .group(OptionGroup.createBuilder() + .name(Text.of("Boolean Controllers")) + .tooltip(Text.of("Test!")) + .collapsed(true) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Boolean Toggle")) + .tooltip(value -> Text.of("A simple toggle button that contains the value '" + value + "'")) + .binding( + config.getDefaults().booleanToggle, + () -> config.getConfig().booleanToggle, + (value) -> config.getConfig().booleanToggle = value + ) + .controller(BooleanController::new) + .flag(OptionFlag.GAME_RESTART) + .available(false) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Custom Boolean Toggle")) + .tooltip(Text.of("You can customize these controllers like this!")) + .binding( + config.getDefaults().customBooleanToggle, + () -> config.getConfig().customBooleanToggle, + (value) -> config.getConfig().customBooleanToggle = value + ) + .controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true)) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Tick Box")) + .tooltip(Text.of("There are even alternate methods of displaying the same data type!")) + .binding( + config.getDefaults().tickbox, + () -> config.getConfig().tickbox, + (value) -> config.getConfig().tickbox = value + ) + .controller(TickBoxController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Slider Controllers")) + .option(Option.createBuilder(int.class) + .name(Text.of("Int Slider that is cut off because the slider")) + .instant(true) + .binding( + config.getDefaults().intSlider, + () -> config.getConfig().intSlider, + value -> config.getConfig().intSlider = value + + ) + .controller(opt -> new IntegerSliderController(opt, 0, 3, 1)) + .build()) + .option(Option.createBuilder(double.class) + .name(Text.of("Double Slider")) + .binding( + config.getDefaults().doubleSlider, + () -> config.getConfig().doubleSlider, + (value) -> config.getConfig().doubleSlider = value + ) + .controller(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) + .build()) + .option(Option.createBuilder(float.class) + .name(Text.of("Float Slider")) + .binding( + config.getDefaults().floatSlider, + () -> config.getConfig().floatSlider, + (value) -> config.getConfig().floatSlider = value + ) + .controller(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) + .build()) + .option(Option.createBuilder(long.class) + .name(Text.of("Long Slider")) + .binding( + config.getDefaults().longSlider, + () -> config.getConfig().longSlider, + (value) -> config.getConfig().longSlider = value + ) + .controller(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Input Field Controllers")) + .option(Option.createBuilder(String.class) + .name(Text.of("Text Option")) + .binding( + config.getDefaults().textField, + () -> config.getConfig().textField, + value -> config.getConfig().textField = value + ) + .controller(StringController::new) + .build()) + .option(Option.createBuilder(Color.class) + .name(Text.of("Color Option")) + .binding( + config.getDefaults().colorOption, + () -> config.getConfig().colorOption, + value -> config.getConfig().colorOption = value + ) + .controller(ColorController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Enum Controllers")) + .option(Option.createBuilder(ConfigData.Alphabet.class) + .name(Text.of("Enum Cycler")) + .binding( + config.getDefaults().enumOption, + () -> config.getConfig().enumOption, + (value) -> config.getConfig().enumOption = value + ) + .controller(EnumController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Options that aren't really options")) + .option(ButtonOption.createBuilder() + .name(Text.of("Button \"Option\"")) + .action((screen, opt) -> SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Button Pressed"), Text.of("Button option was invoked!"))) + .controller(ActionController::new) + .build()) + .option(Option.createBuilder(Text.class) + .binding(Binding.immutable(Text.empty() + .append(Text.literal("a").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("a"))))) + .append(Text.literal("b").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("b"))))) + .append(Text.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("c"))))) + .append(Text.literal("e").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("e"))))) + .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev"))) + )) + .controller(LabelController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Minecraft Bindings")) + .tooltip(Text.of("YACL can also bind Minecraft options!")) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Minecraft AutoJump")) + .tooltip(Text.of("You can even bind minecraft options!")) + .binding(Binding.minecraft(MinecraftClient.getInstance().options.getAutoJump())) + .controller(TickBoxController::new) + .build()) + .option(Option.createBuilder(GraphicsMode.class) + .name(Text.of("Minecraft Graphics Mode")) + .binding(Binding.minecraft(MinecraftClient.getInstance().options.getGraphicsMode())) + .controller(EnumController::new) + .build()) + .build()) + .build()) + .category(PlaceholderCategory.createBuilder() + .name(Text.of("Placeholder Category")) + .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen)) + .build()) + .category(ConfigCategory.createBuilder() + .name(Text.of("Group Test")) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Root Test")) + .binding( + config.getDefaults().groupTestRoot, + () -> config.getConfig().groupTestRoot, + value -> config.getConfig().groupTestRoot = value + ) + .controller(TickBoxController::new) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("First Group")) + .option(Option.createBuilder(boolean.class) + .name(Text.of("First Group Test 1")) + .binding( + config.getDefaults().groupTestFirstGroup, + () -> config.getConfig().groupTestFirstGroup, + value -> config.getConfig().groupTestFirstGroup = value + ) + .controller(TickBoxController::new) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("First Group Test 2")) + .binding( + config.getDefaults().groupTestFirstGroup2, + () -> config.getConfig().groupTestFirstGroup2, + value -> config.getConfig().groupTestFirstGroup2 = value + ) + .controller(TickBoxController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Text.empty()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Second Group Test")) + .binding( + config.getDefaults().groupTestSecondGroup, + () -> config.getConfig().groupTestSecondGroup, + value -> config.getConfig().groupTestSecondGroup = value + ) + .controller(TickBoxController::new) + .build()) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(Text.of("Scroll Test")) + .option(Option.createBuilder(int.class) + .name(Text.of("Int Slider that is cut off because the slider")) + .binding( + config.getDefaults().scrollingSlider, + () -> config.getConfig().scrollingSlider, + (value) -> config.getConfig().scrollingSlider = value + ) + .controller(opt -> new IntegerSliderController(opt, 0, 10, 1)) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.of("Option")) + .action((screen, opt) -> {}) + .controller(ActionController::new) + .build()) + .build()) + .save(() -> { + MinecraftClient.getInstance().options.write(); + config.save(); + }) + ) + .generateScreen(parent); + } + + private static Screen getDisabledTest(Screen parent) { + return Entrypoint.getConfig().buildConfig((config, builder) -> builder + .title(Text.empty()) + .category(ConfigCategory.createBuilder() + .name(Text.of("Disabled Test")) + .option(Option.createBuilder(int.class) + .name(Text.of("Slider")) + .binding(Binding.immutable(0)) + .controller(opt -> new IntegerSliderController(opt, 0, 5, 1)) + .available(false) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Tick Box")) + .binding(Binding.immutable(true)) + .controller(TickBoxController::new) + .available(false) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Tick Box (Enabled)")) + .binding(Binding.immutable(true)) + .controller(TickBoxController::new) + .build()) + .option(Option.createBuilder(String.class) + .name(Text.of("Text Field")) + .binding(Binding.immutable("hi")) + .controller(StringController::new) + .available(false) + .build()) + .build()) + ) + .generateScreen(parent); + } + + private static Screen getWikiBasic(Screen parent) { + return Entrypoint.getConfig().buildConfig((config, builder) -> builder + .title(Text.of("Mod Name")) + .category(ConfigCategory.createBuilder() + .name(Text.of("My Category")) + .tooltip(Text.of("This displays when you hover over a category button")) // optional + .option(Option.createBuilder(boolean.class) + .name(Text.of("My Boolean Option")) + .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional + .binding( + config.getDefaults().booleanToggle, // default + () -> config.getConfig().booleanToggle, // getter + newValue -> config.getConfig().booleanToggle = newValue // setter + ) + .controller(BooleanController::new) + .build()) + .build()) + ) + .generateScreen(parent); + } + + private static Screen getWikiGroups(Screen parent) { + return Entrypoint.getConfig().buildConfig((config, builder) -> builder + .title(Text.of("Mod Name")) + .category(ConfigCategory.createBuilder() + .name(Text.of("My Category")) + .tooltip(Text.of("This displays when you hover over a category button")) // optional + .group(OptionGroup.createBuilder() + .name(Text.of("Option Group")) + .option(Option.createBuilder(boolean.class) + .name(Text.of("My Boolean Option")) + .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional + .binding( + config.getDefaults().booleanToggle, // default + () -> config.getConfig().booleanToggle, // getter + newValue -> config.getConfig().booleanToggle = newValue // setter + ) + .controller(BooleanController::new) + .build()) + .build()) + .build()) + ) + .generateScreen(parent); + } +} diff --git a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java deleted file mode 100644 index 596a0bf..0000000 --- a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java +++ /dev/null @@ -1,437 +0,0 @@ -package dev.isxander.yacl.test; - -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.gui.RequireRestartScreen; -import dev.isxander.yacl.gui.controllers.*; -import dev.isxander.yacl.gui.controllers.cycling.EnumController; -import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; -import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; -import dev.isxander.yacl.gui.controllers.slider.LongSliderController; -import dev.isxander.yacl.gui.controllers.string.StringController; -import dev.isxander.yacl.test.config.ConfigData; -import dev.isxander.yacl.test.config.Entrypoint; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.option.GraphicsMode; -import net.minecraft.client.toast.SystemToast; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.Text; - -import java.awt.*; - -public class ModMenuIntegration implements ModMenuApi { - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return (parent) -> Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.of("Test Suites")) - .category(ConfigCategory.createBuilder() - .name(Text.of("Suites")) - .option(ButtonOption.createBuilder() - .name(Text.of("Full Test Suite")) - .controller(ActionController::new) - .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getFullTestSuite(screen))) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Basic Wiki Suite")) - .controller(ActionController::new) - .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiBasic(screen))) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Group Wiki Suite")) - .controller(ActionController::new) - .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiGroups(screen))) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Unavailable Test Suite")) - .controller(ActionController::new) - .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getDisabledTest(screen))) - .build()) - .build()) - ) - .generateScreen(parent); - } - - private Screen getFullTestSuite(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.of("Test GUI")) - .category(ConfigCategory.createBuilder() - .name(Text.of("Control Examples")) - .tooltip(Text.of("Example Category Description")) - .group(OptionGroup.createBuilder() - .name(Text.of("Boolean Controllers")) - .tooltip(Text.of("Test!")) - .collapsed(true) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Boolean Toggle")) - .tooltip(value -> Text.of("A simple toggle button that contains the value '" + value + "'")) - .binding( - config.getDefaults().booleanToggle, - () -> config.getConfig().booleanToggle, - (value) -> config.getConfig().booleanToggle = value - ) - .controller(BooleanController::new) - .flag(OptionFlag.GAME_RESTART) - .available(false) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Custom Boolean Toggle")) - .tooltip(Text.of("You can customize these controllers like this!")) - .binding( - config.getDefaults().customBooleanToggle, - () -> config.getConfig().customBooleanToggle, - (value) -> config.getConfig().customBooleanToggle = value - ) - .controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true)) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Tick Box")) - .tooltip(Text.of("There are even alternate methods of displaying the same data type!")) - .binding( - config.getDefaults().tickbox, - () -> config.getConfig().tickbox, - (value) -> config.getConfig().tickbox = value - ) - .controller(TickBoxController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("Slider Controllers")) - .option(Option.createBuilder(int.class) - .name(Text.of("Int Slider that is cut off because the slider")) - .instant(true) - .binding( - config.getDefaults().intSlider, - () -> config.getConfig().intSlider, - value -> config.getConfig().intSlider = value - - ) - .controller(opt -> new IntegerSliderController(opt, 0, 3, 1)) - .build()) - .option(Option.createBuilder(double.class) - .name(Text.of("Double Slider")) - .binding( - config.getDefaults().doubleSlider, - () -> config.getConfig().doubleSlider, - (value) -> config.getConfig().doubleSlider = value - ) - .controller(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) - .build()) - .option(Option.createBuilder(float.class) - .name(Text.of("Float Slider")) - .binding( - config.getDefaults().floatSlider, - () -> config.getConfig().floatSlider, - (value) -> config.getConfig().floatSlider = value - ) - .controller(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) - .build()) - .option(Option.createBuilder(long.class) - .name(Text.of("Long Slider")) - .binding( - config.getDefaults().longSlider, - () -> config.getConfig().longSlider, - (value) -> config.getConfig().longSlider = value - ) - .controller(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("Input Field Controllers")) - .option(Option.createBuilder(String.class) - .name(Text.of("Text Option")) - .binding( - config.getDefaults().textField, - () -> config.getConfig().textField, - value -> config.getConfig().textField = value - ) - .controller(StringController::new) - .build()) - .option(Option.createBuilder(Color.class) - .name(Text.of("Color Option")) - .binding( - config.getDefaults().colorOption, - () -> config.getConfig().colorOption, - value -> config.getConfig().colorOption = value - ) - .controller(ColorController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("Enum Controllers")) - .option(Option.createBuilder(ConfigData.Alphabet.class) - .name(Text.of("Enum Cycler")) - .binding( - config.getDefaults().enumOption, - () -> config.getConfig().enumOption, - (value) -> config.getConfig().enumOption = value - ) - .controller(EnumController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("Options that aren't really options")) - .option(ButtonOption.createBuilder() - .name(Text.of("Button \"Option\"")) - .action((screen, opt) -> SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Button Pressed"), Text.of("Button option was invoked!"))) - .controller(ActionController::new) - .build()) - .option(Option.createBuilder(Text.class) - .binding(Binding.immutable(Text.empty() - .append(Text.literal("a").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("a"))))) - .append(Text.literal("b").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("b"))))) - .append(Text.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("c"))))) - .append(Text.literal("e").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("e"))))) - .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev"))) - )) - .controller(LabelController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("Minecraft Bindings")) - .tooltip(Text.of("YACL can also bind Minecraft options!")) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Minecraft AutoJump")) - .tooltip(Text.of("You can even bind minecraft options!")) - .binding(Binding.minecraft(MinecraftClient.getInstance().options.getAutoJump())) - .controller(TickBoxController::new) - .build()) - .option(Option.createBuilder(GraphicsMode.class) - .name(Text.of("Minecraft Graphics Mode")) - .binding(Binding.minecraft(MinecraftClient.getInstance().options.getGraphicsMode())) - .controller(EnumController::new) - .build()) - .build()) - .build()) - .category(PlaceholderCategory.createBuilder() - .name(Text.of("Placeholder Category")) - .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen)) - .build()) - .category(ConfigCategory.createBuilder() - .name(Text.of("Group Test")) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Root Test")) - .binding( - config.getDefaults().groupTestRoot, - () -> config.getConfig().groupTestRoot, - value -> config.getConfig().groupTestRoot = value - ) - .controller(TickBoxController::new) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.of("First Group")) - .option(Option.createBuilder(boolean.class) - .name(Text.of("First Group Test 1")) - .binding( - config.getDefaults().groupTestFirstGroup, - () -> config.getConfig().groupTestFirstGroup, - value -> config.getConfig().groupTestFirstGroup = value - ) - .controller(TickBoxController::new) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("First Group Test 2")) - .binding( - config.getDefaults().groupTestFirstGroup2, - () -> config.getConfig().groupTestFirstGroup2, - value -> config.getConfig().groupTestFirstGroup2 = value - ) - .controller(TickBoxController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Text.empty()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Second Group Test")) - .binding( - config.getDefaults().groupTestSecondGroup, - () -> config.getConfig().groupTestSecondGroup, - value -> config.getConfig().groupTestSecondGroup = value - ) - .controller(TickBoxController::new) - .build()) - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(Text.of("Scroll Test")) - .option(Option.createBuilder(int.class) - .name(Text.of("Int Slider that is cut off because the slider")) - .binding( - config.getDefaults().scrollingSlider, - () -> config.getConfig().scrollingSlider, - (value) -> config.getConfig().scrollingSlider = value - ) - .controller(opt -> new IntegerSliderController(opt, 0, 10, 1)) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .option(ButtonOption.createBuilder() - .name(Text.of("Option")) - .action((screen, opt) -> {}) - .controller(ActionController::new) - .build()) - .build()) - .save(() -> { - MinecraftClient.getInstance().options.write(); - config.save(); - }) - ) - .generateScreen(parent); - } - - private Screen getDisabledTest(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.empty()) - .category(ConfigCategory.createBuilder() - .name(Text.of("Disabled Test")) - .option(Option.createBuilder(int.class) - .name(Text.of("Slider")) - .binding(Binding.immutable(0)) - .controller(opt -> new IntegerSliderController(opt, 0, 5, 1)) - .available(false) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Tick Box")) - .binding(Binding.immutable(true)) - .controller(TickBoxController::new) - .available(false) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Tick Box (Enabled)")) - .binding(Binding.immutable(true)) - .controller(TickBoxController::new) - .build()) - .option(Option.createBuilder(String.class) - .name(Text.of("Text Field")) - .binding(Binding.immutable("hi")) - .controller(StringController::new) - .available(false) - .build()) - .build()) - ) - .generateScreen(parent); - } - - private Screen getWikiBasic(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.of("Mod Name")) - .category(ConfigCategory.createBuilder() - .name(Text.of("My Category")) - .tooltip(Text.of("This displays when you hover over a category button")) // optional - .option(Option.createBuilder(boolean.class) - .name(Text.of("My Boolean Option")) - .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional - .binding( - config.getDefaults().booleanToggle, // default - () -> config.getConfig().booleanToggle, // getter - newValue -> config.getConfig().booleanToggle = newValue // setter - ) - .controller(BooleanController::new) - .build()) - .build()) - ) - .generateScreen(parent); - } - - private Screen getWikiGroups(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.of("Mod Name")) - .category(ConfigCategory.createBuilder() - .name(Text.of("My Category")) - .tooltip(Text.of("This displays when you hover over a category button")) // optional - .group(OptionGroup.createBuilder() - .name(Text.of("Option Group")) - .option(Option.createBuilder(boolean.class) - .name(Text.of("My Boolean Option")) - .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional - .binding( - config.getDefaults().booleanToggle, // default - () -> config.getConfig().booleanToggle, // getter - newValue -> config.getConfig().booleanToggle = newValue // setter - ) - .controller(BooleanController::new) - .build()) - .build()) - .build()) - ) - .generateScreen(parent); - } - - private ConfigScreenFactory getWikiButton() { - return (parent) -> Entrypoint.getConfig().buildConfig((config, builder) -> builder - .title(Text.of("Mod Name")) - .category(ConfigCategory.createBuilder() - .name(Text.of("My Category")) - .tooltip(Text.of("This displays when you hover over a category button")) // optional - .option(ButtonOption.createBuilder() - .name(Text.of("Pressable Button")) - .tooltip(Text.of("This is so easy!")) // optional - .action(screen -> {}) - .controller(ActionController::new) - .build()) - .build()) - ) - .generateScreen(parent); - } -} diff --git a/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java b/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java new file mode 100644 index 0000000..b32a392 --- /dev/null +++ b/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java @@ -0,0 +1,26 @@ +package dev.isxander.yacl.test.mixins; + +import dev.isxander.yacl.test.GuiTest; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +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(TitleScreen.class) +public abstract class TitleScreenMixin extends Screen { + protected TitleScreenMixin(Text title) { + super(title); + } + + @Inject(method = "init", at = @At("RETURN")) + private void injectTestButton(CallbackInfo ci) { + addDrawableChild(ButtonWidget.createBuilder(Text.of("YACL"), button -> client.setScreen(GuiTest.getModConfigScreenFactory(client.currentScreen))) + .setPosition(0, 0) + .setWidth(50) + .build()); + } +} -- cgit From 204b1dcd421e3fee5e3a7fdeb2fe0582ae5987a9 Mon Sep 17 00:00:00 2001 From: isXander Date: Tue, 22 Nov 2022 21:43:25 +0000 Subject: bump to pre1 --- build.gradle.kts | 2 +- gradle.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- src/testmod/java/dev/isxander/yacl/test/config/Entrypoint.java | 3 +-- .../java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java | 6 +++--- 5 files changed, 7 insertions(+), 8 deletions(-) (limited to 'src/testmod/java/dev/isxander') diff --git a/build.gradle.kts b/build.gradle.kts index eea4cdb..491f972 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { mappings("net.fabricmc:yarn:$minecraftVersion+build.+:v2") modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") - modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.66.2+1.19.3")) + modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.67.1+1.19.3")) "testmodImplementation"(sourceSets.main.get().output) } diff --git a/gradle.properties b/gradle.properties index a7543f6..841f08b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx3G -minecraftVersion=22w46a +minecraftVersion=1.19.3-pre1 fabricLoaderVersion=0.14.10 modId=yet-another-config-lib diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 37b150e..234a281 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -20,7 +20,7 @@ }, "depends": { "fabricloader": ">=0.14.0", - "minecraft": "~1.19.3-alpha.22.46.a", + "minecraft": "~1.19.3-beta.1", "java": ">=17", "fabric-resource-loader-v0": "*" }, diff --git a/src/testmod/java/dev/isxander/yacl/test/config/Entrypoint.java b/src/testmod/java/dev/isxander/yacl/test/config/Entrypoint.java index 39baffa..c71f592 100644 --- a/src/testmod/java/dev/isxander/yacl/test/config/Entrypoint.java +++ b/src/testmod/java/dev/isxander/yacl/test/config/Entrypoint.java @@ -1,13 +1,12 @@ package dev.isxander.yacl.test.config; -import com.google.gson.Gson; import dev.isxander.yacl.config.ConfigInstance; import dev.isxander.yacl.config.GsonConfigInstance; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.loader.api.FabricLoader; public class Entrypoint implements ClientModInitializer { - private static GsonConfigInstance config; + private static ConfigInstance config; @Override public void onInitializeClient() { diff --git a/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java b/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java index b32a392..2635eaf 100644 --- a/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java +++ b/src/testmod/java/dev/isxander/yacl/test/mixins/TitleScreenMixin.java @@ -18,9 +18,9 @@ public abstract class TitleScreenMixin extends Screen { @Inject(method = "init", at = @At("RETURN")) private void injectTestButton(CallbackInfo ci) { - addDrawableChild(ButtonWidget.createBuilder(Text.of("YACL"), button -> client.setScreen(GuiTest.getModConfigScreenFactory(client.currentScreen))) - .setPosition(0, 0) - .setWidth(50) + addDrawableChild(ButtonWidget.builder(Text.of("YACL"), button -> client.setScreen(GuiTest.getModConfigScreenFactory(client.currentScreen))) + .position(0, 0) + .width(50) .build()); } } -- cgit From c26d3a89caae20f1a91898202d91de8dc90da5ad Mon Sep 17 00:00:00 2001 From: isXander Date: Fri, 25 Nov 2022 16:08:07 +0000 Subject: pre2 mapping changes + bug fix Fix tick box name text length limiting --- build.gradle.kts | 2 +- changelogs/2.0.0.md | 1 + gradle.properties | 2 +- src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java | 2 +- src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java | 2 +- .../java/dev/isxander/yacl/gui/controllers/TickBoxController.java | 5 +++++ src/testmod/java/dev/isxander/yacl/test/GuiTest.java | 2 +- 7 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src/testmod/java/dev/isxander') diff --git a/build.gradle.kts b/build.gradle.kts index 491f972..9436d08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { mappings("net.fabricmc:yarn:$minecraftVersion+build.+:v2") modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") - modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.67.1+1.19.3")) + modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.67.2+1.19.3")) "testmodImplementation"(sourceSets.main.get().output) } diff --git a/changelogs/2.0.0.md b/changelogs/2.0.0.md index 832c84c..2c8cd6e 100644 --- a/changelogs/2.0.0.md +++ b/changelogs/2.0.0.md @@ -1 +1,2 @@ - Update to 1.19.3 +- Fix tick box name text length limiting diff --git a/gradle.properties b/gradle.properties index 841f08b..e39781b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx3G -minecraftVersion=1.19.3-pre1 +minecraftVersion=1.19.3-pre2 fabricLoaderVersion=0.14.10 modId=yet-another-config-lib diff --git a/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java index a4d6304..4823428 100644 --- a/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java @@ -1,7 +1,7 @@ package dev.isxander.yacl.gui; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Tooltip; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; diff --git a/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java b/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java index 197a162..9f153f6 100644 --- a/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java @@ -2,7 +2,7 @@ package dev.isxander.yacl.gui; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.screen.Tooltip; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.OrderedText; diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java index 193f5a6..340983d 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java @@ -93,6 +93,11 @@ public class TickBoxController implements Controller { return 10; } + @Override + protected int getUnhoveredControlWidth() { + return 10; + } + public void toggleSetting() { control.option().requestSet(!control.option().pendingValue()); playDownSound(); diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java index 449a081..d50164d 100644 --- a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -85,7 +85,7 @@ public class GuiTest { .controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true)) .build()) .option(Option.createBuilder(boolean.class) - .name(Text.of("Tick Box")) + .name(Text.of("Tick Box aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) .tooltip(Text.of("There are even alternate methods of displaying the same data type!")) .binding( config.getDefaults().tickbox, -- cgit From d163b9128d760e53e34fd6c08dbf782fa3d50c51 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 27 Nov 2022 18:17:36 +0000 Subject: split sourcesets --- build.gradle.kts | 15 +- src/client/java/dev/isxander/yacl/api/Binding.java | 64 +++ .../java/dev/isxander/yacl/api/ButtonOption.java | 123 ++++++ .../java/dev/isxander/yacl/api/ConfigCategory.java | 157 +++++++ .../java/dev/isxander/yacl/api/Controller.java | 28 ++ .../java/dev/isxander/yacl/api/NameableEnum.java | 10 + src/client/java/dev/isxander/yacl/api/Option.java | 336 +++++++++++++++ .../java/dev/isxander/yacl/api/OptionFlag.java | 27 ++ .../java/dev/isxander/yacl/api/OptionGroup.java | 141 +++++++ .../dev/isxander/yacl/api/PlaceholderCategory.java | 94 +++++ .../dev/isxander/yacl/api/YetAnotherConfigLib.java | 136 ++++++ .../dev/isxander/yacl/api/utils/Dimension.java | 33 ++ .../isxander/yacl/api/utils/MutableDimension.java | 11 + .../dev/isxander/yacl/api/utils/OptionUtils.java | 37 ++ .../java/dev/isxander/yacl/gui/AbstractWidget.java | 108 +++++ .../dev/isxander/yacl/gui/CategoryListWidget.java | 96 +++++ .../java/dev/isxander/yacl/gui/CategoryWidget.java | 31 ++ .../isxander/yacl/gui/LowProfileButtonWidget.java | 29 ++ .../dev/isxander/yacl/gui/OptionListWidget.java | 470 +++++++++++++++++++++ .../isxander/yacl/gui/RequireRestartScreen.java | 16 + .../dev/isxander/yacl/gui/SearchFieldWidget.java | 62 +++ .../isxander/yacl/gui/TextScaledButtonWidget.java | 44 ++ .../dev/isxander/yacl/gui/TooltipButtonWidget.java | 30 ++ .../java/dev/isxander/yacl/gui/YACLScreen.java | 269 ++++++++++++ .../yacl/gui/controllers/ActionController.java | 120 ++++++ .../yacl/gui/controllers/BooleanController.java | 156 +++++++ .../yacl/gui/controllers/ColorController.java | 203 +++++++++ .../yacl/gui/controllers/ControllerWidget.java | 165 ++++++++ .../yacl/gui/controllers/LabelController.java | 144 +++++++ .../yacl/gui/controllers/TickBoxController.java | 120 ++++++ .../cycling/CyclingControllerElement.java | 60 +++ .../controllers/cycling/CyclingListController.java | 79 ++++ .../gui/controllers/cycling/EnumController.java | 60 +++ .../controllers/cycling/ICyclingController.java | 38 ++ .../yacl/gui/controllers/package-info.java | 12 + .../controllers/slider/DoubleSliderController.java | 114 +++++ .../controllers/slider/FloatSliderController.java | 114 +++++ .../gui/controllers/slider/ISliderController.java | 54 +++ .../slider/IntegerSliderController.java | 111 +++++ .../controllers/slider/LongSliderController.java | 111 +++++ .../slider/SliderControllerElement.java | 160 +++++++ .../yacl/gui/controllers/slider/package-info.java | 10 + .../gui/controllers/string/IStringController.java | 32 ++ .../gui/controllers/string/StringController.java | 45 ++ .../string/StringControllerElement.java | 283 +++++++++++++ .../dev/isxander/yacl/impl/ButtonOptionImpl.java | 142 +++++++ .../dev/isxander/yacl/impl/ConfigCategoryImpl.java | 10 + .../dev/isxander/yacl/impl/GenericBindingImpl.java | 35 ++ .../dev/isxander/yacl/impl/OptionGroupImpl.java | 10 + .../java/dev/isxander/yacl/impl/OptionImpl.java | 144 +++++++ .../yacl/impl/PlaceholderCategoryImpl.java | 19 + .../yacl/impl/YetAnotherConfigLibImpl.java | 19 + .../yacl/impl/utils/DimensionIntegerImpl.java | 115 +++++ .../isxander/yacl/impl/utils/YACLConstants.java | 8 + .../yacl/mixin/client/SimpleOptionAccessor.java | 11 + .../yet-another-config-lib.client.mixins.json | 11 + src/main/java/dev/isxander/yacl/api/Binding.java | 64 --- .../java/dev/isxander/yacl/api/ButtonOption.java | 123 ------ .../java/dev/isxander/yacl/api/ConfigCategory.java | 158 ------- .../java/dev/isxander/yacl/api/Controller.java | 28 -- .../java/dev/isxander/yacl/api/NameableEnum.java | 10 - src/main/java/dev/isxander/yacl/api/Option.java | 336 --------------- .../java/dev/isxander/yacl/api/OptionFlag.java | 27 -- .../java/dev/isxander/yacl/api/OptionGroup.java | 141 ------- .../dev/isxander/yacl/api/PlaceholderCategory.java | 94 ----- .../dev/isxander/yacl/api/YetAnotherConfigLib.java | 136 ------ .../dev/isxander/yacl/api/utils/Dimension.java | 33 -- .../isxander/yacl/api/utils/MutableDimension.java | 11 - .../dev/isxander/yacl/api/utils/OptionUtils.java | 37 -- .../dev/isxander/yacl/config/ConfigInstance.java | 7 - .../java/dev/isxander/yacl/gui/AbstractWidget.java | 108 ----- .../dev/isxander/yacl/gui/CategoryListWidget.java | 96 ----- .../java/dev/isxander/yacl/gui/CategoryWidget.java | 31 -- .../isxander/yacl/gui/LowProfileButtonWidget.java | 29 -- .../dev/isxander/yacl/gui/OptionListWidget.java | 470 --------------------- .../isxander/yacl/gui/RequireRestartScreen.java | 16 - .../dev/isxander/yacl/gui/SearchFieldWidget.java | 62 --- .../isxander/yacl/gui/TextScaledButtonWidget.java | 44 -- .../dev/isxander/yacl/gui/TooltipButtonWidget.java | 30 -- .../java/dev/isxander/yacl/gui/YACLScreen.java | 269 ------------ .../yacl/gui/controllers/ActionController.java | 120 ------ .../yacl/gui/controllers/BooleanController.java | 156 ------- .../yacl/gui/controllers/ColorController.java | 203 --------- .../yacl/gui/controllers/ControllerWidget.java | 165 -------- .../yacl/gui/controllers/EnumController.java | 35 -- .../yacl/gui/controllers/LabelController.java | 144 ------- .../yacl/gui/controllers/TickBoxController.java | 120 ------ .../cycling/CyclingControllerElement.java | 60 --- .../controllers/cycling/CyclingListController.java | 79 ---- .../gui/controllers/cycling/EnumController.java | 60 --- .../controllers/cycling/ICyclingController.java | 38 -- .../yacl/gui/controllers/package-info.java | 12 - .../controllers/slider/DoubleSliderController.java | 114 ----- .../controllers/slider/FloatSliderController.java | 114 ----- .../gui/controllers/slider/ISliderController.java | 54 --- .../slider/IntegerSliderController.java | 111 ----- .../controllers/slider/LongSliderController.java | 111 ----- .../slider/SliderControllerElement.java | 160 ------- .../yacl/gui/controllers/slider/package-info.java | 10 - .../gui/controllers/string/IStringController.java | 32 -- .../gui/controllers/string/StringController.java | 45 -- .../string/StringControllerElement.java | 283 ------------- .../dev/isxander/yacl/impl/ButtonOptionImpl.java | 142 ------- .../dev/isxander/yacl/impl/ConfigCategoryImpl.java | 10 - .../dev/isxander/yacl/impl/GenericBindingImpl.java | 35 -- .../dev/isxander/yacl/impl/OptionGroupImpl.java | 10 - .../java/dev/isxander/yacl/impl/OptionImpl.java | 145 ------- .../yacl/impl/PlaceholderCategoryImpl.java | 19 - .../yacl/impl/YetAnotherConfigLibImpl.java | 19 - .../yacl/impl/utils/DimensionIntegerImpl.java | 115 ----- .../isxander/yacl/impl/utils/YACLConstants.java | 8 - .../isxander/yacl/mixin/SimpleOptionAccessor.java | 11 - src/main/resources/fabric.mod.json | 5 +- .../resources/yet-another-config-lib.mixins.json | 11 - .../java/dev/isxander/yacl/test/GuiTest.java | 1 - 115 files changed, 5085 insertions(+), 5114 deletions(-) create mode 100644 src/client/java/dev/isxander/yacl/api/Binding.java create mode 100644 src/client/java/dev/isxander/yacl/api/ButtonOption.java create mode 100644 src/client/java/dev/isxander/yacl/api/ConfigCategory.java create mode 100644 src/client/java/dev/isxander/yacl/api/Controller.java create mode 100644 src/client/java/dev/isxander/yacl/api/NameableEnum.java create mode 100644 src/client/java/dev/isxander/yacl/api/Option.java create mode 100644 src/client/java/dev/isxander/yacl/api/OptionFlag.java create mode 100644 src/client/java/dev/isxander/yacl/api/OptionGroup.java create mode 100644 src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java create mode 100644 src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java create mode 100644 src/client/java/dev/isxander/yacl/api/utils/Dimension.java create mode 100644 src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java create mode 100644 src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java create mode 100644 src/client/java/dev/isxander/yacl/gui/AbstractWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/CategoryWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/OptionListWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/RequireRestartScreen.java create mode 100644 src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/TooltipButtonWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/YACLScreen.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/package-info.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java create mode 100644 src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/OptionImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java create mode 100644 src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java create mode 100644 src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java create mode 100644 src/client/resources/yet-another-config-lib.client.mixins.json delete mode 100644 src/main/java/dev/isxander/yacl/api/Binding.java delete mode 100644 src/main/java/dev/isxander/yacl/api/ButtonOption.java delete mode 100644 src/main/java/dev/isxander/yacl/api/ConfigCategory.java delete mode 100644 src/main/java/dev/isxander/yacl/api/Controller.java delete mode 100644 src/main/java/dev/isxander/yacl/api/NameableEnum.java delete mode 100644 src/main/java/dev/isxander/yacl/api/Option.java delete mode 100644 src/main/java/dev/isxander/yacl/api/OptionFlag.java delete mode 100644 src/main/java/dev/isxander/yacl/api/OptionGroup.java delete mode 100644 src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java delete mode 100644 src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java delete mode 100644 src/main/java/dev/isxander/yacl/api/utils/Dimension.java delete mode 100644 src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java delete mode 100644 src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/AbstractWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/CategoryWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/OptionListWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/YACLScreen.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/package-info.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/slider/package-info.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java delete mode 100644 src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/OptionImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java delete mode 100644 src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java delete mode 100644 src/main/java/dev/isxander/yacl/mixin/SimpleOptionAccessor.java delete mode 100644 src/main/resources/yet-another-config-lib.mixins.json (limited to 'src/testmod/java/dev/isxander') diff --git a/build.gradle.kts b/build.gradle.kts index 9436d08..b30a011 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,9 +21,22 @@ version = "2.0.0" if (ciRun) version = "$version+${grgit.branch.current().name}-SNAPSHOT" +loom { + splitEnvironmentSourceSets() + + mods { + register("yet-another-config-lib") { + sourceSet(sourceSets["main"]) + sourceSet(sourceSets["client"]) + } + } +} + val testmod by sourceSets.registering { compileClasspath += sourceSets.main.get().compileClasspath runtimeClasspath += sourceSets.main.get().runtimeClasspath + compileClasspath += sourceSets["client"].compileClasspath + runtimeClasspath += sourceSets["client"].runtimeClasspath } loom { @@ -54,7 +67,7 @@ dependencies { mappings("net.fabricmc:yarn:$minecraftVersion+build.+:v2") modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") - modImplementation(fabricApi.module("fabric-resource-loader-v0", "0.67.2+1.19.3")) + "modClientImplementation"(fabricApi.module("fabric-resource-loader-v0", "0.67.2+1.19.3")) "testmodImplementation"(sourceSets.main.get().output) } diff --git a/src/client/java/dev/isxander/yacl/api/Binding.java b/src/client/java/dev/isxander/yacl/api/Binding.java new file mode 100644 index 0000000..91158d3 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/Binding.java @@ -0,0 +1,64 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.impl.GenericBindingImpl; +import dev.isxander.yacl.mixin.client.SimpleOptionAccessor; +import net.minecraft.client.option.SimpleOption; +import org.apache.commons.lang3.Validate; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Controls modifying the bound option. + * Provides the default value, a setter and a getter. + */ +public interface Binding { + void setValue(T value); + + T getValue(); + + T defaultValue(); + + /** + * Creates a generic binding. + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @param setter should set the option to the supplied value + */ + static Binding generic(T def, Supplier getter, Consumer setter) { + Validate.notNull(def, "`def` must not be null"); + Validate.notNull(getter, "`getter` must not be null"); + Validate.notNull(setter, "`setter` must not be null"); + + return new GenericBindingImpl<>(def, getter, setter); + } + + /** + * Creates a {@link Binding} for Minecraft's {@link SimpleOption} + */ + static Binding minecraft(SimpleOption minecraftOption) { + Validate.notNull(minecraftOption, "`minecraftOption` must not be null"); + + return new GenericBindingImpl<>( + ((SimpleOptionAccessor) (Object) minecraftOption).getDefaultValue(), + minecraftOption::getValue, + minecraftOption::setValue + ); + } + + /** + * Creates an immutable binding that has no default and cannot be modified. + * + * @param value the value for the binding + */ + static Binding immutable(T value) { + Validate.notNull(value, "`value` must not be null"); + + return new GenericBindingImpl<>( + value, + () -> value, + changed -> {} + ); + } +} diff --git a/src/client/java/dev/isxander/yacl/api/ButtonOption.java b/src/client/java/dev/isxander/yacl/api/ButtonOption.java new file mode 100644 index 0000000..1124a9a --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/ButtonOption.java @@ -0,0 +1,123 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.ButtonOptionImpl; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +public interface ButtonOption extends Option> { + /** + * Action to be executed upon button press + */ + BiConsumer action(); + + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name; + private final List tooltipLines = new ArrayList<>(); + private boolean available = true; + private Function>> controlGetter; + private BiConsumer action; + + private Builder() { + + } + + /** + * Sets the name to be used by the option. + * + * @see Option#name() + */ + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + /** + * Sets the tooltip to be used by the option. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Option.Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notNull(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + public Builder action(@NotNull BiConsumer action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = action; + return this; + } + + /** + * Action to be executed upon button press + * + * @see ButtonOption#action() + */ + @Deprecated + public Builder action(@NotNull Consumer action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = (screen, button) -> action.accept(screen); + return this; + } + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + public Builder available(boolean available) { + this.available = available; + return this; + } + + /** + * Sets the controller for the option. + * This is how you interact and change the options. + * + * @see dev.isxander.yacl.gui.controllers + */ + public Builder controller(@NotNull Function>> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + public ButtonOption build() { + Validate.notNull(name, "`name` must not be null when building `Option`"); + Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); + Validate.notNull(action, "`action` must not be null when building `Option`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ButtonOptionImpl(name, concatenatedTooltip, action, available, controlGetter); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/ConfigCategory.java b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java new file mode 100644 index 0000000..e9755dd --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java @@ -0,0 +1,157 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.impl.ConfigCategoryImpl; +import dev.isxander.yacl.impl.OptionGroupImpl; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Separates {@link Option}s or {@link OptionGroup}s into multiple distinct sections. + * Served to a user as a button in the left column, + * upon pressing, the options list is filled with options contained within this category. + */ +public interface ConfigCategory { + /** + * Name of category, displayed as a button on the left column. + */ + @NotNull Text name(); + + /** + * Gets every {@link OptionGroup} in this category. + */ + @NotNull ImmutableList groups(); + + /** + * Tooltip (or description) of the category. + * Rendered on hover. + */ + @NotNull Text tooltip(); + + /** + * Creates a builder to construct a {@link ConfigCategory} + */ + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name; + + private final List> rootOptions = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + private final List tooltipLines = new ArrayList<>(); + + private Builder() { + + } + + /** + * Sets name of the category + * + * @see ConfigCategory#name() + */ + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + /** + * Adds an option to the root group of the category. + * To add to another group, use {@link Builder#group(OptionGroup)}. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see ConfigCategory#groups() + * @see OptionGroup#isRoot() + */ + public Builder option(@NotNull Option option) { + Validate.notNull(option, "`option` must not be null"); + + this.rootOptions.add(option); + return this; + } + + /** + * Adds multiple options to the root group of the category. + * To add to another group, use {@link Builder#groups(Collection)}. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see ConfigCategory#groups() + * @see OptionGroup#isRoot() + */ + public Builder options(@NotNull Collection> options) { + Validate.notNull(options, "`options` must not be null"); + + this.rootOptions.addAll(options); + return this; + } + + /** + * Adds an option group. + * To add an option to the root group, use {@link Builder#option(Option)} + * To construct a group, use {@link OptionGroup#createBuilder()} + */ + public Builder group(@NotNull OptionGroup group) { + Validate.notNull(group, "`group` must not be null"); + + this.groups.add(group); + return this; + } + + /** + * Adds multiple option groups. + * To add multiple options to the root group, use {@link Builder#options(Collection)} + * To construct a group, use {@link OptionGroup#createBuilder()} + */ + public Builder groups(@NotNull Collection groups) { + Validate.notEmpty(groups, "`groups` must not be empty"); + + this.groups.addAll(groups); + return this; + } + + /** + * Sets the tooltip to be used by the category. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + public ConfigCategory build() { + Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); + + List combinedGroups = new ArrayList<>(); + combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), false, true)); + combinedGroups.addAll(groups); + + Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/Controller.java b/src/client/java/dev/isxander/yacl/api/Controller.java new file mode 100644 index 0000000..7bf7e7f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/Controller.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.text.Text; + +/** + * Provides a widget to control the option. + */ +public interface Controller { + /** + * Gets the dedicated {@link Option} for this controller + */ + Option option(); + + /** + * Gets the formatted value based on {@link Option#pendingValue()} + */ + Text formatValue(); + + /** + * Provides a widget to display + * + * @param screen parent screen + */ + AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension); +} diff --git a/src/client/java/dev/isxander/yacl/api/NameableEnum.java b/src/client/java/dev/isxander/yacl/api/NameableEnum.java new file mode 100644 index 0000000..793b230 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/NameableEnum.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl.api; + +import net.minecraft.text.Text; + +/** + * Used for the default value formatter of {@link dev.isxander.yacl.gui.controllers.cycling.EnumController} + */ +public interface NameableEnum { + Text getDisplayName(); +} diff --git a/src/client/java/dev/isxander/yacl/api/Option.java b/src/client/java/dev/isxander/yacl/api/Option.java new file mode 100644 index 0000000..772c816 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/Option.java @@ -0,0 +1,336 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.impl.OptionImpl; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public interface Option { + /** + * Name of the option + */ + @NotNull Text name(); + + /** + * Tooltip (or description) of the option. + * Rendered on hover. + */ + @NotNull Text tooltip(); + + /** + * Widget provider for a type of option. + * + * @see dev.isxander.yacl.gui.controllers + */ + @NotNull Controller controller(); + + /** + * Binding for the option. + * Controls setting, getting and default value. + * + * @see Binding + */ + @NotNull Binding binding(); + + /** + * If the option can be configured + */ + boolean available(); + + /** + * Sets if the option can be configured after being built + * + * @see Option#available() + */ + void setAvailable(boolean available); + + /** + * Class of the option type. + * Used by some controllers. + */ + @NotNull Class typeClass(); + + /** + * Tasks that needs to be executed upon applying changes. + */ + @NotNull ImmutableSet flags(); + + /** + * Checks if the pending value is not equal to the current set value + */ + boolean changed(); + + /** + * If true, modifying this option recommends a restart. + */ + @Deprecated + boolean requiresRestart(); + + /** + * Value in the GUI, ready to set the actual bound value or be undone. + */ + @NotNull T pendingValue(); + + /** + * Sets the pending value + */ + void requestSet(T value); + + /** + * Applies the pending value to the bound value. + * Cannot be undone. + * + * @return if there were changes to apply {@link Option#changed()} + */ + boolean applyValue(); + + /** + * Sets the pending value to the bound value. + */ + void forgetPendingValue(); + + /** + * Sets the pending value to the default bound value. + */ + void requestSetDefault(); + + /** + * Checks if the current pending value is equal to its default value + */ + boolean isPendingValueDefault(); + + /** + * Adds a listener for when the pending value changes + */ + void addListener(BiConsumer, T> changedListener); + + /** + * Creates a builder to construct an {@link Option} + * + * @param type of the option's value + * @param typeClass used to capture the type + */ + static Builder createBuilder(Class typeClass) { + return new Builder<>(typeClass); + } + + class Builder { + private Text name = Text.literal("Name not specified!").formatted(Formatting.RED); + + private final List> tooltipGetters = new ArrayList<>(); + + private Function, Controller> controlGetter; + + private Binding binding; + + private boolean available = true; + + private boolean instant = false; + + private final Set flags = new HashSet<>(); + + private final Class typeClass; + + private final List, T>> listeners = new ArrayList<>(); + + private Builder(Class typeClass) { + this.typeClass = typeClass; + } + + /** + * Sets the name to be used by the option. + * + * @see Option#name() + */ + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + /** + * Sets the tooltip to be used by the option. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltipGetter function to get tooltip depending on value {@link Builder#build()}. + */ + @SafeVarargs + public final Builder tooltip(@NotNull Function... tooltipGetter) { + Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); + + this.tooltipGetters.addAll(List.of(tooltipGetter)); + return this; + } + + /** + * Sets the tooltip to be used by the option. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notNull(tooltips, "`tooltips` cannot be empty"); + + this.tooltipGetters.addAll(Stream.of(tooltips).map(text -> (Function) t -> text).toList()); + return this; + } + + /** + * Sets the controller for the option. + * This is how you interact and change the options. + * + * @see dev.isxander.yacl.gui.controllers + */ + public Builder controller(@NotNull Function, Controller> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + /** + * Sets the binding for the option. + * Used for default, getter and setter. + * + * @see Binding + */ + public Builder binding(@NotNull Binding binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + /** + * Sets the binding for the option. + * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @param setter should set the option to the supplied value + * @see Binding + */ + public Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { + Validate.notNull(def, "`def` must not be null"); + Validate.notNull(getter, "`getter` must not be null"); + Validate.notNull(setter, "`setter` must not be null"); + + this.binding = Binding.generic(def, getter, setter); + return this; + } + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + public Builder available(boolean available) { + this.available = available; + return this; + } + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + public Builder flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + public Builder flags(@NotNull Collection flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + /** + * Instantly invokes the binder's setter when modified in the GUI. + * Prevents the user from undoing the change + *

+ * Does not support {@link Option#flags()}! + */ + public Builder instant(boolean instant) { + this.instant = instant; + return this; + } + + /** + * Adds a listener to the option. Invoked upon changing the pending value. + * + * @see Option#addListener(BiConsumer) + */ + public Builder listener(@NotNull BiConsumer, T> listener) { + this.listeners.add(listener); + return this; + } + + /** + * Adds multiple listeners to the option. Invoked upon changing the pending value. + * + * @see Option#addListener(BiConsumer) + */ + public Builder listeners(@NotNull Collection, T>> listeners) { + this.listeners.addAll(listeners); + return this; + } + + /** + * Dictates whether the option should require a restart. + * {@link Option#requiresRestart()} + */ + @Deprecated + public Builder requiresRestart(boolean requiresRestart) { + if (requiresRestart) flag(OptionFlag.GAME_RESTART); + else flags.remove(OptionFlag.GAME_RESTART); + + return this; + } + + public Option build() { + Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); + Validate.notNull(binding, "`binding` must not be null when building `Option`"); + Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); + + Function concatenatedTooltipGetter = value -> { + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Function line : tooltipGetters) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line.apply(value)); + } + + return concatenatedTooltip; + }; + + if (instant) { + listeners.add((opt, pendingValue) -> opt.applyValue()); + } + + return new OptionImpl<>(name, concatenatedTooltipGetter, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass, listeners); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/OptionFlag.java b/src/client/java/dev/isxander/yacl/api/OptionFlag.java new file mode 100644 index 0000000..203a674 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/OptionFlag.java @@ -0,0 +1,27 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.gui.RequireRestartScreen; +import net.minecraft.client.MinecraftClient; + +import java.util.function.Consumer; + +/** + * Code that is executed upon certain options being applied. + * Each flag is executed only once per save, no matter the amount of options with the flag. + */ +@FunctionalInterface +public interface OptionFlag extends Consumer { + /** + * Warns the user that a game restart is required for the changes to take effect + */ + OptionFlag GAME_RESTART = client -> client.setScreen(new RequireRestartScreen(client.currentScreen)); + + /** + * Reloads chunks upon applying (F3+A) + */ + OptionFlag RELOAD_CHUNKS = client -> client.worldRenderer.reload(); + + OptionFlag WORLD_RENDER_UPDATE = client -> client.worldRenderer.scheduleTerrainUpdate(); + + OptionFlag ASSET_RELOAD = MinecraftClient::reloadResourcesConcurrently; +} diff --git a/src/client/java/dev/isxander/yacl/api/OptionGroup.java b/src/client/java/dev/isxander/yacl/api/OptionGroup.java new file mode 100644 index 0000000..3364bdf --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/OptionGroup.java @@ -0,0 +1,141 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.impl.OptionGroupImpl; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Serves as a separator between multiple chunks of options + * that may be too similar or too few to be placed in a separate {@link ConfigCategory}. + * Or maybe you just want your config to feel less dense. + */ +public interface OptionGroup { + /** + * Name of the option group, displayed as a separator in the option lists. + * Can be empty. + */ + Text name(); + + /** + * Tooltip displayed on hover. + */ + Text tooltip(); + + /** + * List of all options in the group + */ + @NotNull ImmutableList> options(); + + /** + * Dictates if the group should be collapsed by default. + */ + boolean collapsed(); + + /** + * Always false when using the {@link Builder} + * used to not render the separator if true + */ + boolean isRoot(); + + /** + * Creates a builder to construct a {@link OptionGroup} + */ + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name = Text.empty(); + private final List tooltipLines = new ArrayList<>(); + private final List> options = new ArrayList<>(); + private boolean collapsed = false; + + private Builder() { + + } + + /** + * Sets name of the group, can be {@link Text#empty()} to just separate options, like sodium. + * + * @see OptionGroup#name() + */ + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + /** + * Sets the tooltip to be used by the option group. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + /** + * Adds an option to group. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see OptionGroup#options() + */ + public Builder option(@NotNull Option option) { + Validate.notNull(option, "`option` must not be null"); + + this.options.add(option); + return this; + } + + /** + * Adds multiple options to group. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see OptionGroup#options() + */ + public Builder options(@NotNull Collection> options) { + Validate.notEmpty(options, "`options` must not be empty"); + + this.options.addAll(options); + return this; + } + + /** + * Dictates if the group should be collapsed by default + * + * @see OptionGroup#collapsed() + */ + public Builder collapsed(boolean collapsible) { + this.collapsed = collapsible; + return this; + } + + public OptionGroup build() { + Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java b/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java new file mode 100644 index 0000000..de7441c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java @@ -0,0 +1,94 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.PlaceholderCategoryImpl; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +/** + * A placeholder category that actually just opens another screen, + * instead of displaying options + */ +public interface PlaceholderCategory extends ConfigCategory { + /** + * Function to create a screen to open upon changing to this category + */ + BiFunction screen(); + + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name; + + private final List tooltipLines = new ArrayList<>(); + + private BiFunction screenFunction; + + private Builder() { + + } + + /** + * Sets name of the category + * + * @see ConfigCategory#name() + */ + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + /** + * Sets the tooltip to be used by the category. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + /** + * Screen to open upon selecting this category + * + * @see PlaceholderCategory#screen() + */ + public Builder screen(@NotNull BiFunction screenFunction) { + Validate.notNull(screenFunction, "`screenFunction` cannot be null"); + + this.screenFunction = screenFunction; + return this; + } + + public PlaceholderCategory build() { + Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java new file mode 100644 index 0000000..a69ae4e --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java @@ -0,0 +1,136 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.YetAnotherConfigLibImpl; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +/** + * Main class of the mod. + * Contains all data and used to provide a {@link Screen} + */ +public interface YetAnotherConfigLib { + /** + * Title of the GUI. Only used for Minecraft narration. + */ + Text title(); + + /** + * Gets all config categories. + */ + ImmutableList categories(); + + /** + * Ran when changes are saved. Can be used to save config to a file etc. + */ + Runnable saveFunction(); + + /** + * Ran every time the YACL screen initialises. Can be paired with FAPI to add custom widgets. + */ + Consumer initConsumer(); + + /** + * Generates a Screen to display based on this instance. + * + * @param parent parent screen to open once closed + */ + Screen generateScreen(@Nullable Screen parent); + + /** + * Creates a builder to construct YACL + */ + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text title; + private final List categories = new ArrayList<>(); + private Runnable saveFunction = () -> {}; + private Consumer initConsumer = screen -> {}; + + private Builder() { + + } + + /** + * Sets title of GUI for Minecraft narration + * + * @see YetAnotherConfigLib#title() + */ + public Builder title(@NotNull Text title) { + Validate.notNull(title, "`title` cannot be null"); + + this.title = title; + return this; + } + + /** + * Adds a new category. + * To create a category you need to use {@link ConfigCategory#createBuilder()} + * + * @see YetAnotherConfigLib#categories() + */ + public Builder category(@NotNull ConfigCategory category) { + Validate.notNull(category, "`category` cannot be null"); + + this.categories.add(category); + return this; + } + + /** + * Adds multiple categories at once. + * To create a category you need to use {@link ConfigCategory#createBuilder()} + * + * @see YetAnotherConfigLib#categories() + */ + public Builder categories(@NotNull Collection categories) { + Validate.notNull(categories, "`categories` cannot be null"); + + this.categories.addAll(categories); + return this; + } + + /** + * Used to define a save function for when user clicks the Save Changes button + * + * @see YetAnotherConfigLib#saveFunction() + */ + public Builder save(@NotNull Runnable saveFunction) { + Validate.notNull(saveFunction, "`saveFunction` cannot be null"); + + this.saveFunction = saveFunction; + return this; + } + + /** + * Defines a consumer that is accepted every time the YACL screen initialises + * + * @see YetAnotherConfigLib#initConsumer() + */ + public Builder screenInit(@NotNull Consumer initConsumer) { + Validate.notNull(initConsumer, "`initConsumer` cannot be null"); + + this.initConsumer = initConsumer; + return this; + } + + public YetAnotherConfigLib build() { + Validate.notNull(title, "`title must not be null to build `YetAnotherConfigLib`"); + Validate.notEmpty(categories, "`categories` must not be empty to build `YetAnotherConfigLib`"); + Validate.isTrue(!categories.stream().allMatch(category -> category instanceof PlaceholderCategory), "At least one regular category is required to build `YetAnotherConfigLib`"); + + return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/utils/Dimension.java b/src/client/java/dev/isxander/yacl/api/utils/Dimension.java new file mode 100644 index 0000000..0de0a58 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/utils/Dimension.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl.api.utils; + +import dev.isxander.yacl.impl.utils.DimensionIntegerImpl; + +public interface Dimension { + T x(); + T y(); + + T width(); + T height(); + + T xLimit(); + T yLimit(); + + T centerX(); + T centerY(); + + boolean isPointInside(T x, T y); + + MutableDimension clone(); + + Dimension withX(T x); + Dimension withY(T y); + Dimension withWidth(T width); + Dimension withHeight(T height); + + Dimension moved(T x, T y); + Dimension expanded(T width, T height); + + static MutableDimension ofInt(int x, int y, int width, int height) { + return new DimensionIntegerImpl(x, y, width, height); + } +} diff --git a/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java b/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java new file mode 100644 index 0000000..eff0186 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl.api.utils; + +public interface MutableDimension extends Dimension { + MutableDimension setX(T x); + MutableDimension setY(T y); + MutableDimension setWidth(T width); + MutableDimension setHeight(T height); + + MutableDimension move(T x, T y); + MutableDimension expand(T width, T height); +} diff --git a/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java b/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java new file mode 100644 index 0000000..ab46b5b --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java @@ -0,0 +1,37 @@ +package dev.isxander.yacl.api.utils; + +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.YetAnotherConfigLib; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class OptionUtils { + /** + * Consumes all options, ignoring groups and categories. + * When consumer returns true, this function stops iterating. + */ + public static void consumeOptions(YetAnotherConfigLib yacl, Function, Boolean> consumer) { + for (ConfigCategory category : yacl.categories()) { + for (OptionGroup group : category.groups()) { + for (Option option : group.options()) { + if (consumer.apply(option)) return; + } + } + } + } + + /** + * Consumes all options, ignoring groups and categories. + * + * @see OptionUtils#consumeOptions(YetAnotherConfigLib, Function) + */ + public static void forEachOptions(YetAnotherConfigLib yacl, Consumer> consumer) { + consumeOptions(yacl, (opt) -> { + consumer.accept(opt); + return false; + }); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java b/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java new file mode 100644 index 0000000..ffb4dec --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java @@ -0,0 +1,108 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.isxander.yacl.api.utils.Dimension; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.sound.SoundEvents; + +import java.awt.Color; + +public abstract class AbstractWidget implements Element, Drawable, Selectable { + protected final MinecraftClient client = MinecraftClient.getInstance(); + protected final TextRenderer textRenderer = client.textRenderer; + protected final int inactiveColor = 0xFFA0A0A0; + + private Dimension dim; + + public AbstractWidget(Dimension dim) { + this.dim = dim; + } + + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + public boolean canReset() { + return false; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + if (dim == null) return false; + return this.dim.isPointInside((int) mouseX, (int) mouseY); + } + + public void setDimension(Dimension dim) { + this.dim = dim; + } + + public Dimension getDimension() { + return dim; + } + + @Override + public SelectionType getType() { + return SelectionType.NONE; + } + + public void unfocus() { + + } + + public boolean matchesSearch(String query) { + return true; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + + } + + protected void drawButtonRect(MatrixStack matrices, int x1, int y1, int x2, int y2, boolean hovered, boolean enabled) { + if (x1 > x2) { + int xx1 = x1; + x1 = x2; + x2 = xx1; + } + if (y1 > y2) { + int yy1 = y1; + y1 = y2; + y2 = yy1; + } + int width = x2 - x1; + int height = y2 - y1; + + RenderSystem.setShader(GameRenderer::getPositionTexProgram); + RenderSystem.setShaderTexture(0, ClickableWidget.WIDGETS_TEXTURE); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + int i = !enabled ? 0 : hovered ? 2 : 1; + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + DrawableHelper.drawTexture(matrices, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); + DrawableHelper.drawTexture(matrices, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); + } + + protected int multiplyColor(int hex, float amount) { + Color color = new Color(hex, true); + + return new Color(Math.max((int)(color.getRed() *amount), 0), + Math.max((int)(color.getGreen()*amount), 0), + Math.max((int)(color.getBlue() *amount), 0), + color.getAlpha()).getRGB(); + } + + public void playDownSound() { + MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java new file mode 100644 index 0000000..46a9fdf --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java @@ -0,0 +1,96 @@ +package dev.isxander.yacl.gui; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.systems.RenderSystem; +import dev.isxander.yacl.api.ConfigCategory; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; + +import java.util.List; + +public class CategoryListWidget extends ElementListWidget { + private final YACLScreen yaclScreen; + + public CategoryListWidget(MinecraftClient client, YACLScreen yaclScreen, int screenWidth, int screenHeight) { + super(client, screenWidth / 3, yaclScreen.searchFieldWidget.getY() - 5, 0, yaclScreen.searchFieldWidget.getY() - 5, 21); + this.yaclScreen = yaclScreen; + setRenderBackground(false); + setRenderHorizontalShadows(false); + + for (ConfigCategory category : yaclScreen.config.categories()) { + addEntry(new CategoryEntry(category)); + } + } + + @Override + protected void renderList(MatrixStack matrices, int mouseX, int mouseY, float delta) { + double d = this.client.getWindow().getScaleFactor(); + RenderSystem.enableScissor(0, (int)((yaclScreen.height - bottom) * d), (int)(width * d), (int)(height * d)); + super.renderList(matrices, mouseX, mouseY, delta); + RenderSystem.disableScissor(); + } + + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + for (CategoryEntry entry : children()) { + entry.postRender(matrices, mouseX, mouseY, delta); + } + } + + @Override + public int getRowWidth() { + return Math.min(width - width / 10, 396); + } + + @Override + public int getRowLeft() { + return super.getRowLeft() - 2; + } + + @Override + protected int getScrollbarPositionX() { + return width - 2; + } + + public class CategoryEntry extends Entry { + private final CategoryWidget categoryButton; + public final int categoryIndex; + + public CategoryEntry(ConfigCategory category) { + this.categoryIndex = yaclScreen.config.categories().indexOf(category); + categoryButton = new CategoryWidget( + yaclScreen, + category, + categoryIndex, + getRowLeft(), 0, + getRowWidth(), 20 + ); + } + + @Override + public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + if (mouseY > bottom) { + mouseY = -20; + } + + categoryButton.setY(y); + categoryButton.render(matrices, mouseX, mouseY, tickDelta); + } + + private void postRender(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) { + categoryButton.renderHoveredTooltip(matrices); + } + + @Override + public List children() { + return ImmutableList.of(categoryButton); + } + + @Override + public List selectableChildren() { + return ImmutableList.of(categoryButton); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java b/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java new file mode 100644 index 0000000..3c5d8d2 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java @@ -0,0 +1,31 @@ +package dev.isxander.yacl.gui; + +import dev.isxander.yacl.api.ConfigCategory; +import net.minecraft.client.sound.SoundManager; + +public class CategoryWidget extends TooltipButtonWidget { + private final int categoryIndex; + + public CategoryWidget(YACLScreen screen, ConfigCategory category, int categoryIndex, int x, int y, int width, int height) { + super(screen, x, y, width, height, category.name(), category.tooltip(), btn -> { + screen.searchFieldWidget.setText(""); + screen.changeCategory(categoryIndex); + }); + this.categoryIndex = categoryIndex; + } + + private boolean isCurrentCategory() { + return ((YACLScreen) screen).getCurrentCategoryIdx() == categoryIndex; + } + + @Override + protected int getYImage(boolean hovered) { + return super.getYImage(hovered || isCurrentCategory()); + } + + @Override + public void playDownSound(SoundManager soundManager) { + if (!isCurrentCategory()) + super.playDownSound(soundManager); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java new file mode 100644 index 0000000..9fa01a7 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java @@ -0,0 +1,29 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; + +public class LowProfileButtonWidget extends ButtonWidget { + public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION_SUPPLIER); + } + + public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress, Tooltip tooltip) { + this(x, y, width, height, message, onPress); + setTooltip(tooltip); + } + + @Override + public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (!isHovered()) { + int j = this.active ? 0xFFFFFF : 0xA0A0A0; + drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, this.getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, j | MathHelper.ceil(this.alpha * 255.0F) << 24); + } else { + super.renderButton(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java new file mode 100644 index 0000000..eed3aff --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -0,0 +1,470 @@ +package dev.isxander.yacl.gui; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.utils.Dimension; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.narration.NarrationPart; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Supplier; + +public class OptionListWidget extends ElementListWidget { + private final YACLScreen yaclScreen; + private boolean singleCategory = false; + + private ImmutableList viewableChildren; + + private double smoothScrollAmount = getScrollAmount(); + private boolean returnSmoothAmount = false; + + public OptionListWidget(YACLScreen screen, MinecraftClient client, int width, int height) { + super(client, width / 3 * 2, height, 0, height, 22); + this.yaclScreen = screen; + left = width - this.width; + right = width; + + refreshOptions(); + } + + public void refreshOptions() { + clearEntries(); + + List categories = new ArrayList<>(); + if (yaclScreen.getCurrentCategoryIdx() == -1) { + categories.addAll(yaclScreen.config.categories()); + } else { + categories.add(yaclScreen.config.categories().get(yaclScreen.getCurrentCategoryIdx())); + } + singleCategory = categories.size() == 1; + + for (ConfigCategory category : categories) { + for (OptionGroup group : category.groups()) { + Supplier viewableSupplier; + GroupSeparatorEntry groupSeparatorEntry = null; + if (!group.isRoot()) { + groupSeparatorEntry = new GroupSeparatorEntry(group, yaclScreen); + viewableSupplier = groupSeparatorEntry::isExpanded; + addEntry(groupSeparatorEntry); + } else { + viewableSupplier = () -> true; + } + + List optionEntries = new ArrayList<>(); + for (Option option : group.options()) { + OptionEntry entry = new OptionEntry(option, category, group, option.controller().provideWidget(yaclScreen, Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20)), viewableSupplier); + addEntry(entry); + optionEntries.add(entry); + } + + if (groupSeparatorEntry != null) { + groupSeparatorEntry.setOptionEntries(optionEntries); + } + } + } + + recacheViewableChildren(); + setScrollAmount(0); + } + + public void expandAllGroups() { + for (Entry entry : super.children()) { + if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { + groupSeparatorEntry.setExpanded(true); + } + } + } + + /* + below code is licensed from cloth-config under LGPL3 + modified to inherit vanilla's EntryListWidget and use yarn mappings + */ + + @Nullable + @Override + protected Entry getEntryAtPosition(double x, double y) { + int listMiddleX = this.left + this.width / 2; + int minX = listMiddleX - this.getRowWidth() / 2; + int maxX = listMiddleX + this.getRowWidth() / 2; + int currentY = MathHelper.floor(y - (double) this.top) - this.headerHeight + (int) this.getScrollAmount() - 4; + int itemY = 0; + int itemIndex = -1; + for (int i = 0; i < children().size(); i++) { + Entry item = children().get(i); + itemY += item.getItemHeight(); + if (itemY > currentY) { + itemIndex = i; + break; + } + } + return x < (double) this.getScrollbarPositionX() && x >= minX && y <= maxX && itemIndex >= 0 && currentY >= 0 && itemIndex < this.getEntryCount() ? this.children().get(itemIndex) : null; + } + + @Override + protected int getMaxPosition() { + return children().stream().map(Entry::getItemHeight).reduce(0, Integer::sum) + headerHeight; + } + + @Override + protected void centerScrollOn(Entry entry) { + double d = (this.bottom - this.top) / -2d; + for (int i = 0; i < this.children().indexOf(entry) && i < this.getEntryCount(); i++) + d += children().get(i).getItemHeight(); + this.setScrollAmount(d); + } + + @Override + protected int getRowTop(int index) { + int integer = top + 4 - (int) this.getScrollAmount() + headerHeight; + for (int i = 0; i < children().size() && i < index; i++) + integer += children().get(i).getItemHeight(); + return integer; + } + + @Override + protected void renderList(MatrixStack matrices, int mouseX, int mouseY, float delta) { + int left = this.getRowLeft(); + int right = this.getRowWidth(); + int count = this.getEntryCount(); + + for(int i = 0; i < count; ++i) { + Entry entry = children().get(i); + int top = this.getRowTop(i); + int bottom = top + entry.getItemHeight(); + int entryHeight = entry.getItemHeight() - 4; + if (bottom >= this.top && top <= this.bottom) { + this.renderEntry(matrices, mouseX, mouseY, delta, i, left, top, right, entryHeight); + } + } + } + + /* END cloth config code */ + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + smoothScrollAmount = MathHelper.lerp(MinecraftClient.getInstance().getLastFrameDuration() * 0.5, smoothScrollAmount, getScrollAmount()); + returnSmoothAmount = true; + super.render(matrices, mouseX, mouseY, delta); + returnSmoothAmount = false; + } + + /** + * awful code to only use smooth scroll state when rendering, + * not other code that needs target scroll amount + */ + @Override + public double getScrollAmount() { + if (returnSmoothAmount) + return smoothScrollAmount; + + return super.getScrollAmount(); + } + + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + for (Entry entry : children()) { + entry.postRender(matrices, mouseX, mouseY, delta); + } + } + + @Override + public int getRowWidth() { + return Math.min(396, (int)(width / 1.3f)); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (Entry child : children()) { + if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry) + optionEntry.widget.unfocus(); + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + for (Entry child : children()) { + if (child.mouseScrolled(mouseX, mouseY, amount)) + return true; + } + + this.setScrollAmount(this.getScrollAmount() - amount * 20 /* * (double) (getMaxScroll() / getEntryCount()) / 2.0D */); + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + for (Entry child : children()) { + if (child.keyPressed(keyCode, scanCode, modifiers)) + return true; + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + for (Entry child : children()) { + if (child.charTyped(chr, modifiers)) + return true; + } + + return super.charTyped(chr, modifiers); + } + + @Override + protected int getScrollbarPositionX() { + return left + width - (int)(width * 0.05f); + } + + @Override + protected void renderBackground(MatrixStack matrices) { + setRenderBackground(client.world == null); + if (client.world != null) + fill(matrices, left, top, right, bottom, 0x6B000000); + } + + public void recacheViewableChildren() { + this.viewableChildren = ImmutableList.copyOf(super.children().stream().filter(Entry::isViewable).toList()); + + // update y positions before they need to be rendered are rendered + int i = 0; + for (Entry entry : viewableChildren) { + if (entry instanceof OptionEntry optionEntry) + optionEntry.widget.setDimension(optionEntry.widget.getDimension().withY(getRowTop(i))); + i++; + } + } + + @Override + public List children() { + return viewableChildren; + } + + public abstract class Entry extends ElementListWidget.Entry { + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + public boolean isViewable() { + return true; + } + + public int getItemHeight() { + return 22; + } + + protected boolean isHovered() { + return Objects.equals(getHoveredEntry(), this); + } + } + + public class OptionEntry extends Entry { + public final Option option; + public final ConfigCategory category; + public final OptionGroup group; + + public final AbstractWidget widget; + private final Supplier viewableSupplier; + + private final TextScaledButtonWidget resetButton; + + private final String categoryName; + private final String groupName; + + private OptionEntry(Option option, ConfigCategory category, OptionGroup group, AbstractWidget widget, Supplier viewableSupplier) { + this.option = option; + this.category = category; + this.group = group; + this.widget = widget; + this.viewableSupplier = viewableSupplier; + this.categoryName = category.name().getString().toLowerCase(); + this.groupName = group.name().getString().toLowerCase(); + if (this.widget.canReset()) { + this.widget.setDimension(this.widget.getDimension().expanded(-21, 0)); + this.resetButton = new TextScaledButtonWidget(widget.getDimension().xLimit() + 1, -50, 20, 20, 2f, Text.of("\u21BB"), button -> { + option.requestSetDefault(); + }); + option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); + this.resetButton.active = !option.isPendingValueDefault() && option.available(); + } else { + this.resetButton = null; + } + } + + @Override + public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + widget.setDimension(widget.getDimension().withY(y)); + + widget.render(matrices, mouseX, mouseY, tickDelta); + + if (resetButton != null) { + resetButton.setY(y); + resetButton.render(matrices, mouseX, mouseY, tickDelta); + } + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + widget.postRender(matrices, mouseX, mouseY, delta); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + return widget.mouseScrolled(mouseX, mouseY, amount); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + return widget.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + return widget.charTyped(chr, modifiers); + } + + @Override + public boolean isViewable() { + String query = yaclScreen.searchFieldWidget.getText(); + return viewableSupplier.get() + && (yaclScreen.searchFieldWidget.isEmpty() + || (!singleCategory && categoryName.contains(query)) + || groupName.contains(query) + || widget.matchesSearch(query)); + } + + @Override + public int getItemHeight() { + return widget.getDimension().height() + 2; + } + + @Override + public List selectableChildren() { + if (resetButton == null) + return ImmutableList.of(widget); + + return ImmutableList.of(widget, resetButton); + } + + @Override + public List children() { + if (resetButton == null) + return ImmutableList.of(widget); + + return ImmutableList.of(widget, resetButton); + } + } + + public class GroupSeparatorEntry extends Entry { + private final OptionGroup group; + private final MultilineText wrappedName; + private final MultilineText wrappedTooltip; + + private final LowProfileButtonWidget expandMinimizeButton; + + private final Screen screen; + private final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + private boolean groupExpanded; + + private List optionEntries; + + private int y; + + private GroupSeparatorEntry(OptionGroup group, Screen screen) { + this.group = group; + this.screen = screen; + this.wrappedName = MultilineText.create(textRenderer, group.name(), getRowWidth() - 45); + this.wrappedTooltip = MultilineText.create(textRenderer, group.tooltip(), screen.width / 3 * 2 - 10); + this.groupExpanded = !group.collapsed(); + this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Text.empty(), btn -> { + setExpanded(!isExpanded()); + recacheViewableChildren(); + }); + updateExpandMinimizeText(); + } + + @Override + public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + this.y = y; + + expandMinimizeButton.setX(x); + expandMinimizeButton.setY(y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2); + expandMinimizeButton.render(matrices, mouseX, mouseY, tickDelta); + + wrappedName.drawCenterWithShadow(matrices, x + entryWidth / 2, y + getYPadding()); + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (isHovered() && !expandMinimizeButton.isMouseOver(mouseX, mouseY)) { + YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getRowLeft() + getRowWidth() / 2, y - 3, y + getItemHeight() + 3, screen.width, screen.height); + } + } + + public boolean isExpanded() { + return groupExpanded; + } + + public void setExpanded(boolean expanded) { + this.groupExpanded = expanded; + updateExpandMinimizeText(); + } + + private void updateExpandMinimizeText() { + expandMinimizeButton.setMessage(Text.of(isExpanded() ? "\u25BC" : "\u25B6")); + } + + public void setOptionEntries(List optionEntries) { + this.optionEntries = optionEntries; + } + + @Override + public boolean isViewable() { + return yaclScreen.searchFieldWidget.isEmpty() || optionEntries.stream().anyMatch(OptionEntry::isViewable); + } + + @Override + public int getItemHeight() { + return Math.max(wrappedName.count(), 1) * textRenderer.fontHeight + getYPadding() * 2; + } + + private int getYPadding() { + return 6; + } + + @Override + public List selectableChildren() { + return ImmutableList.of(new Selectable() { + @Override + public Selectable.SelectionType getType() { + return Selectable.SelectionType.HOVERED; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.TITLE, group.name()); + } + }); + } + + @Override + public List children() { + return ImmutableList.of(expandMinimizeButton); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/RequireRestartScreen.java b/src/client/java/dev/isxander/yacl/gui/RequireRestartScreen.java new file mode 100644 index 0000000..3c46738 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/RequireRestartScreen.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class RequireRestartScreen extends ConfirmScreen { + public RequireRestartScreen(Screen parent) { + super(option -> { + if (option) MinecraftClient.getInstance().scheduleStop(); + else MinecraftClient.getInstance().setScreen(parent); + }, Text.translatable("yacl.restart.title").formatted(Formatting.RED, Formatting.BOLD), Text.translatable("yacl.restart.message"), Text.translatable("yacl.restart.yes"), Text.translatable("yacl.restart.no")); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java b/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java new file mode 100644 index 0000000..5b7c9dc --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java @@ -0,0 +1,62 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +public class SearchFieldWidget extends TextFieldWidget { + private Text emptyText; + private final YACLScreen yaclScreen; + private final TextRenderer textRenderer; + + private boolean isEmpty = true; + + public SearchFieldWidget(YACLScreen yaclScreen, TextRenderer textRenderer, int x, int y, int width, int height, Text text, Text emptyText) { + super(textRenderer, x, y, width, height, text); + setChangedListener(string -> update()); + setTextPredicate(string -> !string.endsWith(" ") && !string.startsWith(" ")); + this.yaclScreen = yaclScreen; + this.textRenderer = textRenderer; + this.emptyText = emptyText; + } + + @Override + public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { + super.renderButton(matrices, mouseX, mouseY, delta); + if (isVisible() && isEmpty()) { + textRenderer.drawWithShadow(matrices, emptyText, getX() + 4, this.getY() + (this.height - 8) / 2f, 0x707070); + } + } + + private void update() { + boolean wasEmpty = isEmpty; + isEmpty = getText().isEmpty(); + + if (isEmpty && wasEmpty) + return; + + if (!isEmpty && yaclScreen.getCurrentCategoryIdx() != -1) + yaclScreen.changeCategory(-1); + if (isEmpty && yaclScreen.getCurrentCategoryIdx() == -1) + yaclScreen.changeCategory(0); + + yaclScreen.optionList.expandAllGroups(); + yaclScreen.optionList.recacheViewableChildren(); + + yaclScreen.optionList.setScrollAmount(0); + yaclScreen.categoryList.setScrollAmount(0); + } + + public boolean isEmpty() { + return isEmpty; + } + + public Text getEmptyText() { + return emptyText; + } + + public void setEmptyText(Text emptyText) { + this.emptyText = emptyText; + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java b/src/client/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java new file mode 100644 index 0000000..2d0a99c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java @@ -0,0 +1,44 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.OrderedText; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; + +public class TextScaledButtonWidget extends ButtonWidget { + public float textScale; + + public TextScaledButtonWidget(int x, int y, int width, int height, float textScale, Text message, PressAction onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION_SUPPLIER); + this.textScale = textScale; + } + + public TextScaledButtonWidget(int x, int y, int width, int height, float textScale, Text message, PressAction onPress, Tooltip tooltip) { + this(x, y, width, height, textScale, message, onPress); + setTooltip(tooltip); + } + + @Override + public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { + // prevents super from rendering text + Text message = getMessage(); + setMessage(Text.empty()); + + super.renderButton(matrices, mouseX, mouseY, delta); + + setMessage(message); + int j = this.active ? 16777215 : 10526880; + OrderedText orderedText = getMessage().asOrderedText(); + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + matrices.push(); + matrices.translate(((this.getX() + this.width / 2f) - textRenderer.getWidth(orderedText) * textScale / 2), (float)this.getY() + (this.height - 8 * textScale) / 2f / textScale, 0); + matrices.scale(textScale, textScale, 1); + textRenderer.drawWithShadow(matrices, orderedText, 0, 0, j | MathHelper.ceil(this.alpha * 255.0F) << 24); + matrices.pop(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/TooltipButtonWidget.java b/src/client/java/dev/isxander/yacl/gui/TooltipButtonWidget.java new file mode 100644 index 0000000..b034f4b --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/TooltipButtonWidget.java @@ -0,0 +1,30 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +public class TooltipButtonWidget extends ButtonWidget { + + protected final Screen screen; + protected MultilineText wrappedDescription; + + public TooltipButtonWidget(Screen screen, int x, int y, int width, int height, Text message, Text tooltip, PressAction onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION_SUPPLIER); + this.screen = screen; + setTooltip(tooltip); + } + + public void renderHoveredTooltip(MatrixStack matrices) { + if (isHovered()) { + YACLScreen.renderMultilineTooltip(matrices, MinecraftClient.getInstance().textRenderer, wrappedDescription, getX() + width / 2, getY() - 4, getY() + height + 4, screen.width, screen.height); + } + } + + public void setTooltip(Text tooltip) { + wrappedDescription = MultilineText.create(MinecraftClient.getInstance().textRenderer, tooltip, screen.width / 3 - 5); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/YACLScreen.java b/src/client/java/dev/isxander/yacl/gui/YACLScreen.java new file mode 100644 index 0000000..e36c8e8 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/YACLScreen.java @@ -0,0 +1,269 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; +import dev.isxander.yacl.api.utils.OptionUtils; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.render.*; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.joml.Matrix4f; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +public class YACLScreen extends Screen { + public final YetAnotherConfigLib config; + private int currentCategoryIdx; + + private final Screen parent; + + public OptionListWidget optionList; + public CategoryListWidget categoryList; + public TooltipButtonWidget finishedSaveButton, cancelResetButton, undoButton; + public SearchFieldWidget searchFieldWidget; + + public Text saveButtonMessage, saveButtonTooltipMessage; + private int saveButtonMessageTime; + + + public YACLScreen(YetAnotherConfigLib config, Screen parent) { + super(config.title()); + this.config = config; + this.parent = parent; + this.currentCategoryIdx = 0; + } + + @Override + protected void init() { + int columnWidth = width / 3; + int padding = columnWidth / 20; + columnWidth = Math.min(columnWidth, 400); + int paddedWidth = columnWidth - padding * 2; + + MutableDimension actionDim = Dimension.ofInt(width / 3 / 2, height - padding - 20, paddedWidth, 20); + finishedSaveButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), Text.empty(), (btn) -> { + saveButtonMessage = null; + + if (pendingChanges()) { + Set flags = new HashSet<>(); + OptionUtils.forEachOptions(config, option -> { + if (option.applyValue()) { + flags.addAll(option.flags()); + } + }); + OptionUtils.forEachOptions(config, option -> { + if (option.changed()) { + option.forgetPendingValue(); + } + }); + config.saveFunction().run(); + + flags.forEach(flag -> flag.accept(client)); + } else close(); + }); + actionDim.expand(-actionDim.width() / 2 - 2, 0).move(-actionDim.width() / 2 - 2, -22); + cancelResetButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), Text.empty(), (btn) -> { + if (pendingChanges()) { + OptionUtils.forEachOptions(config, Option::forgetPendingValue); + close(); + } else { + OptionUtils.forEachOptions(config, Option::requestSetDefault); + } + + }); + actionDim.move(actionDim.width() + 4, 0); + undoButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.translatable("yacl.gui.undo"), Text.translatable("yacl.gui.undo.tooltip"), (btn) -> { + OptionUtils.forEachOptions(config, Option::forgetPendingValue); + }); + + searchFieldWidget = new SearchFieldWidget(this, textRenderer, width / 3 / 2 - paddedWidth / 2 + 1, undoButton.getY() - 22, paddedWidth - 2, 18, Text.translatable("gui.recipebook.search_hint"), Text.translatable("gui.recipebook.search_hint")); + + categoryList = new CategoryListWidget(client, this, width, height); + addSelectableChild(categoryList); + + updateActionAvailability(); + addDrawableChild(searchFieldWidget); + addDrawableChild(cancelResetButton); + addDrawableChild(undoButton); + addDrawableChild(finishedSaveButton); + + optionList = new OptionListWidget(this, client, width, height); + addSelectableChild(optionList); + + config.initConsumer().accept(this); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + renderBackground(matrices); + + super.render(matrices, mouseX, mouseY, delta); + categoryList.render(matrices, mouseX, mouseY, delta); + searchFieldWidget.render(matrices, mouseX, mouseY, delta); + optionList.render(matrices, mouseX, mouseY, delta); + + categoryList.postRender(matrices, mouseX, mouseY, delta); + optionList.postRender(matrices, mouseX, mouseY, delta); + + for (Element child : children()) { + if (child instanceof TooltipButtonWidget tooltipButtonWidget) { + tooltipButtonWidget.renderHoveredTooltip(matrices); + } + } + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (optionList.keyPressed(keyCode, scanCode, modifiers)) { + return true; + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (optionList.charTyped(chr, modifiers)) { + return true; + } + + return super.charTyped(chr, modifiers); + } + + public void changeCategory(int idx) { + if (idx == currentCategoryIdx) + return; + + if (idx != -1 && config.categories().get(idx) instanceof PlaceholderCategory placeholderCategory) { + client.setScreen(placeholderCategory.screen().apply(client, this)); + } else { + currentCategoryIdx = idx; + optionList.refreshOptions(); + } + } + + public int getCurrentCategoryIdx() { + return currentCategoryIdx; + } + + private void updateActionAvailability() { + boolean pendingChanges = pendingChanges(); + + undoButton.active = pendingChanges; + finishedSaveButton.setMessage(pendingChanges ? Text.translatable("yacl.gui.save") : Text.translatable("gui.done")); + finishedSaveButton.setTooltip(pendingChanges ? Text.translatable("yacl.gui.save.tooltip") : Text.translatable("yacl.gui.finished.tooltip")); + cancelResetButton.setMessage(pendingChanges ? Text.translatable("gui.cancel") : Text.translatable("controls.reset")); + cancelResetButton.setTooltip(pendingChanges ? Text.translatable("yacl.gui.cancel.tooltip") : Text.translatable("yacl.gui.reset.tooltip")); + } + + @Override + public void tick() { + searchFieldWidget.tick(); + + updateActionAvailability(); + + if (saveButtonMessage != null) { + if (saveButtonMessageTime > 140) { + saveButtonMessage = null; + saveButtonTooltipMessage = null; + saveButtonMessageTime = 0; + } else { + saveButtonMessageTime++; + finishedSaveButton.setMessage(saveButtonMessage); + if (saveButtonTooltipMessage != null) { + finishedSaveButton.setTooltip(saveButtonTooltipMessage); + } + } + } + } + + private void setSaveButtonMessage(Text message, Text tooltip) { + saveButtonMessage = message; + saveButtonTooltipMessage = tooltip; + saveButtonMessageTime = 0; + } + + private boolean pendingChanges() { + AtomicBoolean pendingChanges = new AtomicBoolean(false); + OptionUtils.consumeOptions(config, (option) -> { + if (option.changed()) { + pendingChanges.set(true); + return true; + } + return false; + }); + + return pendingChanges.get(); + } + + @Override + public boolean shouldCloseOnEsc() { + if (pendingChanges()) { + setSaveButtonMessage(Text.translatable("yacl.gui.save_before_exit").formatted(Formatting.RED), Text.translatable("yacl.gui.save_before_exit.tooltip")); + return false; + } + return true; + } + + @Override + public void close() { + client.setScreen(parent); + } + + public static void renderMultilineTooltip(MatrixStack matrices, TextRenderer textRenderer, MultilineText text, int centerX, int yAbove, int yBelow, int screenWidth, int screenHeight) { + if (text.count() > 0) { + int maxWidth = text.getMaxWidth(); + int lineHeight = textRenderer.fontHeight + 1; + int height = text.count() * lineHeight - 1; + + int belowY = yBelow + 12; + int aboveY = yAbove - height + 12; + int maxBelow = screenHeight - (belowY + height); + int minAbove = aboveY - height; + int y = belowY; + if (maxBelow < -8) + y = maxBelow > minAbove ? belowY : aboveY; + + int x = Math.max(centerX - text.getMaxWidth() / 2 - 12, -6); + + int drawX = x + 12; + int drawY = y - 12; + + matrices.push(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + Matrix4f matrix4f = matrices.peek().getPositionMatrix(); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 4, drawX + maxWidth + 3, drawY - 3, 400, -267386864, -267386864); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY + height + 3, drawX + maxWidth + 3, drawY + height + 4, 400, -267386864, -267386864); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3, drawX + maxWidth + 3, drawY + height + 3, 400, -267386864, -267386864); + fillGradient(matrix4f, bufferBuilder, drawX - 4, drawY - 3, drawX - 3, drawY + height + 3, 400, -267386864, -267386864); + fillGradient(matrix4f, bufferBuilder, drawX + maxWidth + 3, drawY - 3, drawX + maxWidth + 4, drawY + height + 3, 400, -267386864, -267386864); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3 + 1, drawX - 3 + 1, drawY + height + 3 - 1, 400, 1347420415, 1344798847); + fillGradient(matrix4f, bufferBuilder, drawX + maxWidth + 2, drawY - 3 + 1, drawX + maxWidth + 3, drawY + height + 3 - 1, 400, 1347420415, 1344798847); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3, drawX + maxWidth + 3, drawY - 3 + 1, 400, 1347420415, 1347420415); + fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY + height + 2, drawX + maxWidth + 3, drawY + height + 3, 400, 1344798847, 1344798847); + RenderSystem.enableDepthTest(); + RenderSystem.disableTexture(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + RenderSystem.disableBlend(); + RenderSystem.enableTexture(); + matrices.translate(0.0, 0.0, 400.0); + + text.drawWithShadow(matrices, drawX, drawY, lineHeight, -1); + + matrices.pop(); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java new file mode 100644 index 0000000..b8e2cd1 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java @@ -0,0 +1,120 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.ButtonOption; +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +import java.util.function.BiConsumer; + +/** + * Simple controller that simply runs the button action on press + * and renders a {@link} Text on the right. + */ +public class ActionController implements Controller> { + public static final Text DEFAULT_TEXT = Text.translatable("yacl.control.action.execute"); + + private final ButtonOption option; + private final Text text; + + /** + * Constructs an action controller + * with the default formatter of {@link ActionController#DEFAULT_TEXT} + * + * @param option bound option + */ + public ActionController(ButtonOption option) { + this(option, DEFAULT_TEXT); + } + + /** + * Constructs an action controller + * + * @param option bound option + * @param text text to display + */ + public ActionController(ButtonOption option, Text text) { + this.option = option; + this.text = text; + + } + + /** + * {@inheritDoc} + */ + @Override + public ButtonOption option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return text; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ActionControllerElement(this, screen, widgetDimension); + } + + public static class ActionControllerElement extends ControllerWidget { + private final String buttonString; + + public ActionControllerElement(ActionController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + buttonString = control.formatValue().getString().toLowerCase(); + } + + public void executeAction() { + playDownSound(); + control.option().action().accept(screen, control.option()); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY) && isAvailable()) { + executeAction(); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + executeAction(); + return true; + } + + return false; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + @Override + public boolean canReset() { + return false; + } + + @Override + public boolean matchesSearch(String query) { + return super.matchesSearch(query) || buttonString.contains(query); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java b/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java new file mode 100644 index 0000000..7037ff5 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java @@ -0,0 +1,156 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; + +import java.util.function.Function; + +/** + * This controller renders a simple formatted {@link Text} + */ +public class BooleanController implements Controller { + + public static final Function ON_OFF_FORMATTER = (state) -> + state + ? Text.translatable("options.on") + : Text.translatable("options.off"); + + public static final Function TRUE_FALSE_FORMATTER = (state) -> + state + ? Text.translatable("yacl.control.boolean.true") + : Text.translatable("yacl.control.boolean.false"); + + public static final Function YES_NO_FORMATTER = (state) -> + state + ? Text.translatable("gui.yes") + : Text.translatable("gui.no"); + + private final Option option; + private final Function valueFormatter; + private final boolean coloured; + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + */ + public BooleanController(Option option) { + this(option, ON_OFF_FORMATTER, false); + } + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option option, boolean coloured) { + this(option, ON_OFF_FORMATTER, coloured); + } + + /** + * Constructs a tickbox controller + * + * @param option bound option + * @param valueFormatter format value into any {@link Text} + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option option, Function valueFormatter, boolean coloured) { + this.option = option; + this.valueFormatter = valueFormatter; + this.coloured = coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * Value format is green or red depending on the state + */ + public boolean coloured() { + return coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new BooleanControllerElement(this, screen, widgetDimension); + } + + public static class BooleanControllerElement extends ControllerWidget { + public BooleanControllerElement(BooleanController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || !isAvailable()) + return false; + + toggleSetting(); + return true; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + protected Text getValueText() { + if (control.coloured()) { + return super.getValueText().copy().formatted(control.option().pendingValue() ? Formatting.GREEN : Formatting.RED); + } + + return super.getValueText(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + toggleSetting(); + return true; + } + + return false; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java new file mode 100644 index 0000000..a68475d --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java @@ -0,0 +1,203 @@ +package dev.isxander.yacl.gui.controllers; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.string.IStringController; +import dev.isxander.yacl.gui.controllers.string.StringControllerElement; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.awt.Color; +import java.util.List; + +/** + * A color controller that uses a hex color field as input. + */ +public class ColorController implements IStringController { + private final Option option; + private final boolean allowAlpha; + + /** + * Constructs a color controller with {@link ColorController#allowAlpha()} defaulting to false + * + * @param option bound option + */ + public ColorController(Option option) { + this(option, false); + } + + /** + * Constructs a color controller + * + * @param option bound option + * @param allowAlpha allows the color input to accept alpha values + */ + public ColorController(Option option, boolean allowAlpha) { + this.option = option; + this.allowAlpha = allowAlpha; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + public boolean allowAlpha() { + return allowAlpha; + } + + @Override + public String getString() { + return formatValue().getString(); + } + + @Override + public Text formatValue() { + MutableText text = Text.literal("#"); + text.append(Text.literal(toHex(option().pendingValue().getRed())).formatted(Formatting.RED)); + text.append(Text.literal(toHex(option().pendingValue().getGreen())).formatted(Formatting.GREEN)); + text.append(Text.literal(toHex(option().pendingValue().getBlue())).formatted(Formatting.BLUE)); + if (allowAlpha()) text.append(toHex(option().pendingValue().getAlpha())); + return text; + } + + private String toHex(int value) { + String hex = Integer.toString(value, 16).toUpperCase(); + if (hex.length() == 1) + hex = "0" + hex; + return hex; + } + + @Override + public void setFromString(String value) { + if (value.startsWith("#")) + value = value.substring(1); + + int red = Integer.parseInt(value.substring(0, 2), 16); + int green = Integer.parseInt(value.substring(2, 4), 16); + int blue = Integer.parseInt(value.substring(4, 6), 16); + + if (allowAlpha()) { + int alpha = Integer.parseInt(value.substring(6, 8), 16); + option().requestSet(new Color(red, green, blue, alpha)); + } else { + option().requestSet(new Color(red, green, blue)); + } + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ColorControllerElement(this, screen, widgetDimension); + } + + public static class ColorControllerElement extends StringControllerElement { + private final ColorController colorController; + + protected MutableDimension colorPreviewDim; + + private final List allowedChars; + + public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + this.colorController = control; + this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); + } + + @Override + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (isHovered()) { + colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); + super.drawValueText(matrices, mouseX, mouseY, delta); + } + + DrawableHelper.fill(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); + drawOutline(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); + } + + @Override + public void write(String string) { + for (char chr : string.toCharArray()) { + if (!allowedChars.contains(Character.toLowerCase(chr))) { + return; + } + } + + if (caretPos == 0) + return; + + string = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); + + inputField.replace(caretPos, caretPos + string.length(), string); + caretPos += string.length(); + setSelectionLength(); + + updateControl(); + } + + @Override + protected void doBackspace() { + if (caretPos > 1) { + inputField.setCharAt(caretPos - 1, '0'); + caretPos--; + updateControl(); + } + } + + @Override + protected void doDelete() { + + } + + @Override + protected boolean canUseShortcuts() { + return false; + } + + protected void setSelectionLength() { + selectionLength = caretPos < inputField.length() && caretPos > 0 ? 1 : 0; + } + + @Override + protected int getDefaultCarotPos() { + return colorController.allowAlpha() ? 3 : 1; + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + + int previewSize = (dim.height() - getYPadding() * 2) / 2; + colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (super.keyPressed(keyCode, scanCode, modifiers)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } + return false; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java new file mode 100644 index 0000000..c7f9e97 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java @@ -0,0 +1,165 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public abstract class ControllerWidget> extends AbstractWidget { + protected final T control; + protected MultilineText wrappedTooltip; + protected final YACLScreen screen; + + protected boolean focused = false; + protected boolean hovered = false; + + protected final Text modifiedOptionName; + protected final String optionNameString; + + public ControllerWidget(T control, YACLScreen screen, Dimension dim) { + super(dim); + this.control = control; + this.screen = screen; + control.option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + this.modifiedOptionName = control.option().name().copy().formatted(Formatting.ITALIC); + this.optionNameString = control.option().name().getString().toLowerCase(); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + hovered = isMouseOver(mouseX, mouseY); + + Text name = control.option().changed() ? modifiedOptionName : control.option().name(); + String nameString = name.getString(); + + boolean firstIter = true; + while (textRenderer.getWidth(nameString) > getDimension().width() - getControlWidth() - getXPadding() - 7) { + nameString = nameString.substring(0, Math.max(nameString.length() - (firstIter ? 2 : 5), 0)).trim(); + nameString += "..."; + + firstIter = false; + } + + Text shortenedName = Text.literal(nameString).fillStyle(name.getStyle()); + + drawButtonRect(matrices, getDimension().x(), getDimension().y(), getDimension().xLimit(), getDimension().yLimit(), isHovered(), isAvailable()); + matrices.push(); + matrices.translate(getDimension().x() + getXPadding(), getTextY(), 0); + textRenderer.drawWithShadow(matrices, shortenedName, 0, 0, getValueColor()); + matrices.pop(); + + drawValueText(matrices, mouseX, mouseY, delta); + if (isHovered()) { + drawHoveredControl(matrices, mouseX, mouseY, delta); + } + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (hovered) { + YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); + } + } + + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + Text valueText = getValueText(); + matrices.push(); + matrices.translate(getDimension().xLimit() - textRenderer.getWidth(valueText) - getXPadding(), getTextY(), 0); + textRenderer.drawWithShadow(matrices, valueText, 0, 0, getValueColor()); + matrices.pop(); + } + + private void updateTooltip() { + this.wrappedTooltip = MultilineText.create(textRenderer, control.option().tooltip(), screen.width / 3 * 2 - 10); + } + + protected int getControlWidth() { + return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); + } + + public boolean isHovered() { + return isAvailable() && (hovered || focused); + } + + protected abstract int getHoveredControlWidth(); + + protected int getUnhoveredControlWidth() { + return textRenderer.getWidth(getValueText()); + } + + protected int getXPadding() { + return 5; + } + + protected int getYPadding() { + return 2; + } + + protected Text getValueText() { + return control.formatValue(); + } + + protected boolean isAvailable() { + return control.option().available(); + } + + protected int getValueColor() { + return isAvailable() ? -1 : inactiveColor; + } + + @Override + public boolean canReset() { + return true; + } + + protected void drawOutline(MatrixStack matrices, int x1, int y1, int x2, int y2, int width, int color) { + DrawableHelper.fill(matrices, x1, y1, x2, y1 + width, color); + DrawableHelper.fill(matrices, x2, y1, x2 - width, y2, color); + DrawableHelper.fill(matrices, x1, y2, x2, y2 - width, color); + DrawableHelper.fill(matrices, x1, y1, x1 + width, y2, color); + } + + protected float getTextY() { + return getDimension().y() + getDimension().height() / 2f - textRenderer.fontHeight / 2f; + } + + @Override + public boolean changeFocus(boolean lookForwards) { + if (!isAvailable()) + return false; + + this.focused = !this.focused; + return this.focused; + } + + @Override + public void unfocus() { + this.focused = false; + } + + @Override + public boolean matchesSearch(String query) { + return optionNameString.contains(query.toLowerCase()); + } + + @Override + public SelectionType getType() { + return focused ? SelectionType.FOCUSED : isHovered() ? SelectionType.HOVERED : SelectionType.NONE; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java b/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java new file mode 100644 index 0000000..8369680 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java @@ -0,0 +1,144 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.text.*; + +import java.util.List; + +/** + * Simply renders some text as a label. + */ +public class LabelController implements Controller { + private final Option option; + /** + * Constructs a label controller + * + * @param option bound option + */ + public LabelController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + @Override + public Text formatValue() { + return option().pendingValue(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new LabelControllerElement(screen, widgetDimension); + } + + public class LabelControllerElement extends AbstractWidget { + private List wrappedText; + protected MultilineText wrappedTooltip; + + protected final YACLScreen screen; + + public LabelControllerElement(YACLScreen screen, Dimension dim) { + super(dim); + this.screen = screen; + option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + updateText(); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + updateText(); + + float y = getDimension().y(); + for (OrderedText text : wrappedText) { + textRenderer.drawWithShadow(matrices, text, getDimension().x(), y + getYPadding(), option().available() ? -1 : 0xFFA0A0A0); + y += textRenderer.fontHeight; + } + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (isMouseOver(mouseX, mouseY)) { + YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); + + Style style = getStyle(mouseX, mouseY); + if (style != null && style.getHoverEvent() != null) { + HoverEvent hoverEvent = style.getHoverEvent(); + HoverEvent.ItemStackContent itemStackContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ITEM); + if (itemStackContent != null) { + ItemStack stack = itemStackContent.asStack(); + screen.renderTooltip(matrices, screen.getTooltipFromItem(stack), stack.getTooltipData(), mouseX, mouseY); + } else { + HoverEvent.EntityContent entityContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ENTITY); + if (entityContent != null) { + if (this.client.options.advancedItemTooltips) { + screen.renderTooltip(matrices, entityContent.asTooltip(), mouseX, mouseY); + } + } else { + Text text = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT); + if (text != null) { + MultilineText multilineText = MultilineText.create(textRenderer, text, getDimension().width()); + YACLScreen.renderMultilineTooltip(matrices, textRenderer, multilineText, getDimension().centerX(), getDimension().y(), getDimension().yLimit(), screen.width, screen.height); + } + } + } + } + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY)) + return false; + + Style style = getStyle((int) mouseX, (int) mouseY); + return screen.handleTextClick(style); + } + + protected Style getStyle(int mouseX, int mouseY) { + if (!getDimension().isPointInside(mouseX, mouseY)) + return null; + + int x = mouseX - getDimension().x(); + int y = mouseY - getDimension().y() - getYPadding(); + int line = y / textRenderer.fontHeight; + + if (x < 0 || x > getDimension().xLimit()) return null; + if (y < 0 || y > getDimension().yLimit()) return null; + if (line < 0 || line >= wrappedText.size()) return null; + + return textRenderer.getTextHandler().getStyleAt(wrappedText.get(line), x); + } + + private int getYPadding() { + return 3; + } + + private void updateText() { + wrappedText = textRenderer.wrapLines(formatValue(), getDimension().width()); + setDimension(getDimension().withHeight(wrappedText.size() * textRenderer.fontHeight + getYPadding() * 2)); + } + + private void updateTooltip() { + this.wrappedTooltip = MultilineText.create(textRenderer, option().tooltip(), screen.width / 3 * 2 - 10); + } + + @Override + public boolean matchesSearch(String query) { + return formatValue().getString().toLowerCase().contains(query.toLowerCase()); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java new file mode 100644 index 0000000..340983d --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java @@ -0,0 +1,120 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +/** + * This controller renders a tickbox + */ +public class TickBoxController implements Controller { + private final Option option; + + /** + * Constructs a tickbox controller + * + * @param option bound option + */ + public TickBoxController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return Text.empty(); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new TickBoxControllerElement(this, screen, widgetDimension); + } + + public static class TickBoxControllerElement extends ControllerWidget { + public TickBoxControllerElement(TickBoxController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + int outlineSize = 10; + int outlineX1 = getDimension().xLimit() - getXPadding() - outlineSize; + int outlineY1 = getDimension().centerY() - outlineSize / 2; + int outlineX2 = getDimension().xLimit() - getXPadding(); + int outlineY2 = getDimension().centerY() + outlineSize / 2; + + int color = getValueColor(); + int shadowColor = multiplyColor(color, 0.25f); + + drawOutline(matrices, outlineX1 + 1, outlineY1 + 1, outlineX2 + 1, outlineY2 + 1, 1, shadowColor); + drawOutline(matrices, outlineX1, outlineY1, outlineX2, outlineY2, 1, color); + if (control.option().pendingValue()) { + DrawableHelper.fill(matrices, outlineX1 + 3, outlineY1 + 3, outlineX2 - 1, outlineY2 - 1, shadowColor); + DrawableHelper.fill(matrices, outlineX1 + 2, outlineY1 + 2, outlineX2 - 2, outlineY2 - 2, color); + } + } + + @Override + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (!isHovered()) + drawHoveredControl(matrices, mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || !isAvailable()) + return false; + + toggleSetting(); + return true; + } + + @Override + protected int getHoveredControlWidth() { + return 10; + } + + @Override + protected int getUnhoveredControlWidth() { + return 10; + } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + toggleSetting(); + return true; + } + + return false; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java new file mode 100644 index 0000000..ab0a9c3 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java @@ -0,0 +1,60 @@ +package dev.isxander.yacl.gui.controllers.cycling; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.ControllerWidget; +import net.minecraft.client.gui.screen.Screen; +import org.lwjgl.glfw.GLFW; + +public class CyclingControllerElement extends ControllerWidget> { + + public CyclingControllerElement(ICyclingController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + public void cycleValue(int increment) { + int targetIdx = control.getPendingValue() + increment; + if (targetIdx >= control.getCycleLength()) { + targetIdx -= control.getCycleLength(); + } else if (targetIdx < 0) { + targetIdx += control.getCycleLength(); + } + control.setPendingValue(targetIdx); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || (button != 0 && button != 1) || !isAvailable()) + return false; + + playDownSound(); + cycleValue(button == 1 || Screen.hasShiftDown() || Screen.hasControlDown() ? -1 : 1); + + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) + return false; + + switch (keyCode) { + case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> + cycleValue(-1); + case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> + cycleValue(1); + case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_SPACE, GLFW.GLFW_KEY_KP_ENTER -> + cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); + default -> { + return false; + } + } + + return true; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java new file mode 100644 index 0000000..3b14066 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java @@ -0,0 +1,79 @@ +package dev.isxander.yacl.gui.controllers.cycling; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; + +import java.util.function.Function; + +/** + * A controller where once clicked, cycles through elements + * in the provided list. + */ +public class CyclingListController implements ICyclingController { + private final Option option; + private final Function valueFormatter; + private final ImmutableList values; + + /** + * Constructs a {@link CyclingListController}, with a default + * value formatter of {@link Object#toString()}. + * @param option option of which to bind the controller to + * @param values the values to cycle through + */ + public CyclingListController(Option option, Iterable values) { + this(option, values, value -> Text.of(value.toString())); + } + + /** + * Constructs a {@link CyclingListController} + * @param option option of which to bind the controller to + * @param values the values to cycle through + * @param valueFormatter function of how to convert each value to a string to display + */ + public CyclingListController(Option option, Iterable values, Function valueFormatter) { + this.option = option; + this.valueFormatter = valueFormatter; + this.values = ImmutableList.copyOf(values); + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(int ordinal) { + option().requestSet(values.get(ordinal)); + } + + /** + * {@inheritDoc} + */ + @Override + public int getPendingValue() { + return values.indexOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public int getCycleLength() { + return values.size(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java new file mode 100644 index 0000000..bc9f46d --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java @@ -0,0 +1,60 @@ +package dev.isxander.yacl.gui.controllers.cycling; + +import dev.isxander.yacl.api.NameableEnum; +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import net.minecraft.util.TranslatableOption; + +import java.util.Arrays; +import java.util.function.Function; + +/** + * Simple controller type that displays the enum on the right. + *

+ * Cycles forward with left click, cycles backward with right click or when shift is held + * + * @param enum type + */ +public class EnumController> extends CyclingListController { + public static > Function getDefaultFormatter() { + return value -> { + if (value instanceof NameableEnum nameableEnum) + return nameableEnum.getDisplayName(); + if (value instanceof TranslatableOption translatableOption) + return translatableOption.getText(); + return Text.of(value.toString()); + }; + } + + /** + * Constructs a cycling enum controller with a default value formatter and all values being available. + * The default value formatter first searches if the + * enum is a {@link NameableEnum} or {@link TranslatableOption} else, just uses {@link Enum#toString()} + * + * @param option bound option + */ + public EnumController(Option option) { + this(option, getDefaultFormatter()); + } + + /** + * Constructs a cycling enum controller with all values being available. + * + * @param option bound option + * @param valueFormatter format the enum into any {@link Text} + */ + public EnumController(Option option, Function valueFormatter) { + this(option, valueFormatter, option.typeClass().getEnumConstants()); + } + + /** + * Constructs a cycling enum controller. + * + * @param option bound option + * @param valueFormatter format the enum into any {@link Text} + * @param availableValues all enum constants that can be cycled through + */ + public EnumController(Option option, Function valueFormatter, T[] availableValues) { + super(option, Arrays.asList(availableValues), valueFormatter); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java new file mode 100644 index 0000000..081b572 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java @@ -0,0 +1,38 @@ +package dev.isxander.yacl.gui.controllers.cycling; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; + +/** + * This interface simply generifies setting and getting of + * the pending value, using an ordinal so elements can cycle through + * without knowing the content. + */ +public interface ICyclingController extends Controller { + /** + * Sets the pending value to whatever corresponds to the ordinal + * @param ordinal index of element to set + */ + void setPendingValue(int ordinal); + + /** + * Gets the pending ordinal that corresponds to the actual value + * @return ordinal + */ + int getPendingValue(); + + /** + * Allows the element when it should wrap-around back to zeroth ordinal + */ + int getCycleLength(); + + /** + * {@inheritDoc} + */ + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new CyclingControllerElement(this, screen, widgetDimension); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/package-info.java b/src/client/java/dev/isxander/yacl/gui/controllers/package-info.java new file mode 100644 index 0000000..12ce86b --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/package-info.java @@ -0,0 +1,12 @@ +/** + * This package contains all {@link dev.isxander.yacl.api.Controller} implementations + * + *

    + *
  • For numbers: {@link dev.isxander.yacl.gui.controllers.slider}
  • + *
  • For booleans: {@link dev.isxander.yacl.gui.controllers.TickBoxController}
  • + *
  • For lists/enums: {@link dev.isxander.yacl.gui.controllers.cycling}
  • + *
  • For strings: {@link dev.isxander.yacl.gui.controllers.string.StringController}
  • + *
  • For {@link dev.isxander.yacl.api.ButtonOption}: {@link dev.isxander.yacl.gui.controllers.ActionController}
  • + *
+ */ +package dev.isxander.yacl.gui.controllers; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java new file mode 100644 index 0000000..b530e8c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java @@ -0,0 +1,114 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for doubles. + */ +public class DoubleSliderController implements ISliderController { + /** + * Formats doubles to two decimal places + */ + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value)); + + private final Option option; + + private final double min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for doubles + * using the default value formatter {@link DoubleSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public DoubleSliderController(Option option, double min, double max, double interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for doubles. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public DoubleSliderController(Option option, double min, double max, double interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet(value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java new file mode 100644 index 0000000..d7c203e --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java @@ -0,0 +1,114 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for floats. + */ +public class FloatSliderController implements ISliderController { + /** + * Formats floats to one decimal place + */ + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value)); + + private final Option option; + + private final float min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for floats + * using the default value formatter {@link FloatSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public FloatSliderController(Option option, float min, float max, float interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for floats. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public FloatSliderController(Option option, float min, float max, float interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((float) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java new file mode 100644 index 0000000..aa3c18f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; + +/** + * Simple custom slider implementation that shifts the current value across when shown. + *

+ * For simplicity, {@link SliderControllerElement} works in doubles so each + * {@link ISliderController} must cast to double. This is to get around re-writing the element for every type. + */ +public interface ISliderController extends Controller { + /** + * Gets the minimum value for the slider + */ + double min(); + + /** + * Gets the maximum value for the slider + */ + double max(); + + /** + * Gets the interval (or step size) for the slider. + */ + double interval(); + + /** + * Gets the range of the slider. + */ + default double range() { + return max() - min(); + } + + /** + * Sets the {@link dev.isxander.yacl.api.Option}'s pending value + */ + void setPendingValue(double value); + + /** + * Gets the {@link dev.isxander.yacl.api.Option}'s pending value + */ + double pendingValue(); + + /** + * {@inheritDoc} + */ + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new SliderControllerElement(this, screen, widgetDimension, min(), max(), interval()); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java new file mode 100644 index 0000000..a8bca7c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for integers. + */ +public class IntegerSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + + private final Option option; + + private final int min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for integers + * using the default value formatter {@link IntegerSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public IntegerSliderController(Option option, int min, int max, int interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for integers. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public IntegerSliderController(Option option, int min, int max, int interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((int) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java new file mode 100644 index 0000000..50559d5 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for longs. + */ +public class LongSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + + private final Option option; + + private final long min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for longs + * using the default value formatter {@link LongSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public LongSliderController(Option option, long min, long max, long interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for longs. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public LongSliderController(Option option, long min, long max, long interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((long) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java new file mode 100644 index 0000000..913cc00 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java @@ -0,0 +1,160 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.ControllerWidget; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.glfw.GLFW; + +public class SliderControllerElement extends ControllerWidget> { + private final double min, max, interval; + + private float interpolation; + + private Dimension sliderBounds; + + private boolean mouseDown = false; + + public SliderControllerElement(ISliderController option, YACLScreen screen, Dimension dim, double min, double max, double interval) { + super(option, screen, dim); + this.min = min; + this.max = max; + this.interval = interval; + setDimension(dim); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + super.render(matrices, mouseX, mouseY, delta); + + calculateInterpolation(); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + // track + DrawableHelper.fill(matrices, sliderBounds.x(), sliderBounds.centerY() - 1, sliderBounds.xLimit(), sliderBounds.centerY(), -1); + // track shadow + DrawableHelper.fill(matrices, sliderBounds.x() + 1, sliderBounds.centerY(), sliderBounds.xLimit() + 1, sliderBounds.centerY() + 1, 0xFF404040); + + // thumb shadow + DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2 + 1, sliderBounds.y() + 1, getThumbX() + getThumbWidth() / 2 + 1, sliderBounds.yLimit() + 1, 0xFF404040); + // thumb + DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2, sliderBounds.y(), getThumbX() + getThumbWidth() / 2, sliderBounds.yLimit(), -1); + } + + @Override + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + matrices.push(); + if (isHovered()) + matrices.translate(-(sliderBounds.width() + 6 + getThumbWidth() / 2f), 0, 0); + super.drawValueText(matrices, mouseX, mouseY, delta); + matrices.pop(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isAvailable() || button != 0 || !sliderBounds.isPointInside((int) mouseX, (int) mouseY)) + return false; + + mouseDown = true; + + setValueFromMouse(mouseX); + return true; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (!isAvailable() || button != 0 || !mouseDown) + return false; + + setValueFromMouse(mouseX); + return true; + } + + public void incrementValue(double amount) { + control.setPendingValue(MathHelper.clamp(control.pendingValue() + interval * amount, min, max)); + calculateInterpolation(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + if (!isAvailable() || (!isMouseOver(mouseX, mouseY)) || (!Screen.hasShiftDown() && !Screen.hasControlDown())) + return false; + + incrementValue(amount); + return true; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (isAvailable() && mouseDown) + playDownSound(); + mouseDown = false; + + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) + return false; + + switch (keyCode) { + case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> incrementValue(-1); + case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); + default -> { + return false; + } + } + + return true; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + return super.isMouseOver(mouseX, mouseY) || mouseDown; + } + + protected void setValueFromMouse(double mouseX) { + double value = (mouseX - sliderBounds.x()) / sliderBounds.width() * control.range(); + control.setPendingValue(roundToInterval(value)); + calculateInterpolation(); + } + + protected double roundToInterval(double value) { + return MathHelper.clamp(min + (interval * Math.round(value / interval)), min, max); // extremely imprecise, requires clamping + } + + @Override + protected int getHoveredControlWidth() { + return sliderBounds.width() + getUnhoveredControlWidth() + 6 + getThumbWidth() / 2; + } + + protected void calculateInterpolation() { + interpolation = MathHelper.clamp((float) ((control.pendingValue() - control.min()) * 1 / control.range()), 0f, 1f); + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + sliderBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - getThumbWidth() / 2 - dim.width() / 3, dim.centerY() - 5, dim.width() / 3, 10); + } + + protected int getThumbX() { + return (int) (sliderBounds.x() + sliderBounds.width() * interpolation); + } + + protected int getThumbWidth() { + return 4; + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (super.isMouseOver(mouseX, mouseY)) + super.postRender(matrices, mouseX, mouseY, delta); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java new file mode 100644 index 0000000..bff0d57 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains implementations of sliders for different number types + *

    + *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.slider.DoubleSliderController}
  • + *
  • For floats: {@link dev.isxander.yacl.gui.controllers.slider.FloatSliderController}
  • + *
  • For integers: {@link dev.isxander.yacl.gui.controllers.slider.IntegerSliderController}
  • + *
  • For longs: {@link dev.isxander.yacl.gui.controllers.slider.LongSliderController}
  • + *
+ */ +package dev.isxander.yacl.gui.controllers.slider; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java new file mode 100644 index 0000000..41843b8 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl.gui.controllers.string; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; + +/** + * A controller that can be any type but can input and output a string. + */ +public interface IStringController extends Controller { + /** + * Gets the option's pending value as a string. + * + * @see Option#pendingValue() + */ + String getString(); + + /** + * Sets the option's pending value from a string. + * + * @see Option#requestSet(Object) + */ + void setFromString(String value); + + /** + * {@inheritDoc} + */ + @Override + default Text formatValue() { + return Text.of(getString()); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java new file mode 100644 index 0000000..0caaa93 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java @@ -0,0 +1,45 @@ +package dev.isxander.yacl.gui.controllers.string; + +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; + +/** + * A custom text field implementation for strings. + */ +public class StringController implements IStringController { + private final Option option; + + /** + * Constructs a string controller + * + * @param option bound option + */ + public StringController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + @Override + public String getString() { + return option().pendingValue(); + } + + @Override + public void setFromString(String value) { + option().requestSet(value); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java new file mode 100644 index 0000000..0c3b7c9 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java @@ -0,0 +1,283 @@ +package dev.isxander.yacl.gui.controllers.string; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.ControllerWidget; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; + +public class StringControllerElement extends ControllerWidget> { + protected StringBuilder inputField; + protected Dimension inputFieldBounds; + protected boolean inputFieldFocused; + + protected int caretPos; + protected int selectionLength; + + protected float ticks; + + private final Text emptyText; + + public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + inputField = new StringBuilder(control.getString()); + inputFieldFocused = false; + selectionLength = 0; + emptyText = Text.literal("Click to type...").formatted(Formatting.GRAY); + setDimension(dim); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + ticks += delta; + + DrawableHelper.fill(matrices, inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1); + DrawableHelper.fill(matrices, inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040); + + if (inputFieldFocused || focused) { + int caretX = inputFieldBounds.x() + textRenderer.getWidth(control.getString().substring(0, caretPos)) - 1; + if (inputField.isEmpty()) + caretX += inputFieldBounds.width() / 2; + + if (ticks % 20 <= 10) { + DrawableHelper.fill(matrices, caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1); + } + + if (selectionLength != 0) { + int selectionX = inputFieldBounds.x() + textRenderer.getWidth(control.getString().substring(0, caretPos + selectionLength)); + DrawableHelper.fill(matrices, caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF); + } + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isAvailable() && inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) { + if (!inputFieldFocused) { + inputFieldFocused = true; + caretPos = getDefaultCarotPos(); + } else { + int textWidth = (int) mouseX - inputFieldBounds.x(); + caretPos = textRenderer.trimToWidth(control.getString(), textWidth).length(); + selectionLength = 0; + } + return true; + } else { + inputFieldFocused = false; + } + + return false; + } + + protected int getDefaultCarotPos() { + return inputField.length(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!inputFieldFocused) + return false; + + switch (keyCode) { + case GLFW.GLFW_KEY_ESCAPE -> { + inputFieldFocused = false; + return true; + } + case GLFW.GLFW_KEY_LEFT -> { + if (Screen.hasShiftDown()) { + if (Screen.hasControlDown()) { + int spaceChar = findSpaceIndex(true); + selectionLength += caretPos - spaceChar; + caretPos = spaceChar; + } else if (caretPos > 0) { + caretPos--; + selectionLength += 1; + } + } else { + if (caretPos > 0) + caretPos--; + selectionLength = 0; + } + + return true; + } + case GLFW.GLFW_KEY_RIGHT -> { + if (Screen.hasShiftDown()) { + if (Screen.hasControlDown()) { + int spaceChar = findSpaceIndex(false); + selectionLength -= spaceChar - caretPos; + caretPos = spaceChar; + } else if (caretPos < inputField.length()) { + caretPos++; + selectionLength -= 1; + } + } else { + if (caretPos < inputField.length()) + caretPos++; + selectionLength = 0; + } + + return true; + } + case GLFW.GLFW_KEY_BACKSPACE -> { + doBackspace(); + return true; + } + case GLFW.GLFW_KEY_DELETE -> { + doDelete(); + return true; + } + } + + if (canUseShortcuts()) { + if (Screen.isPaste(keyCode)) { + this.write(client.keyboard.getClipboard()); + return true; + } else if (Screen.isCopy(keyCode) && selectionLength != 0) { + client.keyboard.setClipboard(getSelection()); + return true; + } else if (Screen.isCut(keyCode) && selectionLength != 0) { + client.keyboard.setClipboard(getSelection()); + this.write(""); + return true; + } else if (Screen.isSelectAll(keyCode)) { + caretPos = inputField.length(); + selectionLength = -caretPos; + return true; + } + } + + return false; + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (!inputFieldFocused) + return false; + + write(Character.toString(chr)); + + return true; + } + + protected boolean canUseShortcuts() { + return true; + } + + protected void doBackspace() { + if (selectionLength != 0) { + write(""); + } else if (caretPos > 0) { + inputField.deleteCharAt(caretPos - 1); + caretPos--; + updateControl(); + } + } + + protected void doDelete() { + if (caretPos < inputField.length()) { + inputField.deleteCharAt(caretPos); + updateControl(); + } + } + + public void write(String string) { + if (selectionLength == 0) { + string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString())); + + inputField.insert(caretPos, string); + caretPos += string.length(); + } else { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()) + textRenderer.getWidth(inputField.substring(start, end))); + + inputField.replace(start, end, string); + caretPos = start + string.length(); + selectionLength = 0; + } + updateControl(); + } + + public int getMaxLength() { + return getDimension().width() / 8 * 7; + } + + public int getSelectionStart() { + return Math.min(caretPos, caretPos + selectionLength); + } + + public int getSelectionEnd() { + return Math.max(caretPos, caretPos + selectionLength); + } + + protected String getSelection() { + return inputField.substring(getSelectionStart(), getSelectionEnd()); + } + + protected int findSpaceIndex(boolean reverse) { + int i; + int fromIndex = caretPos; + if (reverse) { + if (caretPos > 0) + fromIndex -= 1; + i = this.inputField.lastIndexOf(" ", fromIndex); + + if (i == -1) i = 0; + } else { + if (caretPos < inputField.length()) + fromIndex += 1; + i = this.inputField.indexOf(" ", fromIndex); + + if (i == -1) i = inputField.length(); + } + + return i; + } + + @Override + public boolean changeFocus(boolean lookForwards) { + return inputFieldFocused = super.changeFocus(lookForwards); + } + + @Override + public void unfocus() { + super.unfocus(); + inputFieldFocused = false; + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + + int width = Math.max(6, textRenderer.getWidth(getValueText())); + inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.fontHeight / 2, width, textRenderer.fontHeight); + } + + @Override + public boolean isHovered() { + return super.isHovered() || inputFieldFocused; + } + + protected void updateControl() { + control.setFromString(inputField.toString()); + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + @Override + protected Text getValueText() { + if (!inputFieldFocused && inputField.isEmpty()) + return emptyText; + + return super.getValueText(); + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java new file mode 100644 index 0000000..dcb9c7a --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java @@ -0,0 +1,142 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ButtonOptionImpl implements ButtonOption { + private final Text name; + private final Text tooltip; + private final BiConsumer action; + private boolean available; + private final Controller> controller; + private final Binding> binding; + + public ButtonOptionImpl( + @NotNull Text name, + @Nullable Text tooltip, + @NotNull BiConsumer action, + boolean available, + @NotNull Function>> controlGetter + ) { + this.name = name; + this.tooltip = tooltip; + this.action = action; + this.available = available; + this.controller = controlGetter.apply(this); + this.binding = new EmptyBinderImpl(); + } + + @Override + public @NotNull Text name() { + return name; + } + + @Override + public @NotNull Text tooltip() { + return tooltip; + } + + @Override + public BiConsumer action() { + return action; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull Controller> controller() { + return controller; + } + + @Override + public @NotNull Binding> binding() { + return binding; + } + + @Override + public @NotNull Class> typeClass() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull ImmutableSet flags() { + return ImmutableSet.of(); + } + + @Override + public boolean requiresRestart() { + return false; + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull BiConsumer pendingValue() { + throw new UnsupportedOperationException(); + } + + @Override + public void requestSet(BiConsumer value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean applyValue() { + return false; + } + + @Override + public void forgetPendingValue() { + + } + + @Override + public void requestSetDefault() { + + } + + @Override + public boolean isPendingValueDefault() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(BiConsumer>, BiConsumer> changedListener) { + + } + + private static class EmptyBinderImpl implements Binding> { + @Override + public void setValue(BiConsumer value) { + + } + + @Override + public BiConsumer getValue() { + throw new UnsupportedOperationException(); + } + + @Override + public BiConsumer defaultValue() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java new file mode 100644 index 0000000..971fecf --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.OptionGroup; +import net.minecraft.text.Text; + +public record ConfigCategoryImpl(Text name, ImmutableList groups, Text tooltip) implements ConfigCategory { + +} diff --git a/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java b/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java new file mode 100644 index 0000000..1867bb6 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl.impl; + +import dev.isxander.yacl.api.Binding; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class GenericBindingImpl implements Binding { + private final T def; + private final Supplier getter; + private final Consumer setter; + + public GenericBindingImpl(T def, Supplier getter, Consumer setting) { + this.def = def; + this.getter = getter; + this.setter = setting; + } + + + @Override + public void setValue(T value) { + setter.accept(value); + } + + @Override + public T getValue() { + return getter.get(); + } + + @Override + public T defaultValue() { + return def; + } + +} diff --git a/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java new file mode 100644 index 0000000..58bc96b --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +public record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList> options, boolean collapsed, boolean isRoot) implements OptionGroup { +} diff --git a/src/client/java/dev/isxander/yacl/impl/OptionImpl.java b/src/client/java/dev/isxander/yacl/impl/OptionImpl.java new file mode 100644 index 0000000..90158c7 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/OptionImpl.java @@ -0,0 +1,144 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.Binding; +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionFlag; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class OptionImpl implements Option { + private final Text name; + private Text tooltip; + private final Controller controller; + private final Binding binding; + private boolean available; + + private final ImmutableSet flags; + + private final Class typeClass; + + private T pendingValue; + + private final List, T>> listeners; + + public OptionImpl( + @NotNull Text name, + @Nullable Function tooltipGetter, + @NotNull Function, Controller> controlGetter, + @NotNull Binding binding, + boolean available, + ImmutableSet flags, + @NotNull Class typeClass, + @NotNull Collection, T>> listeners + ) { + this.name = name; + this.binding = binding; + this.available = available; + this.flags = flags; + this.typeClass = typeClass; + this.listeners = new ArrayList<>(listeners); + this.controller = controlGetter.apply(this); + + addListener((opt, pending) -> tooltip = tooltipGetter.apply(pending)); + requestSet(binding().getValue()); + } + + @Override + public @NotNull Text name() { + return name; + } + + @Override + public @NotNull Text tooltip() { + return tooltip; + } + + @Override + public @NotNull Controller controller() { + return controller; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull Class typeClass() { + return typeClass; + } + + @Override + public @NotNull ImmutableSet flags() { + return flags; + } + + @Override + public boolean requiresRestart() { + return flags.contains(OptionFlag.GAME_RESTART); + } + + @Override + public boolean changed() { + return !binding().getValue().equals(pendingValue); + } + + @Override + public @NotNull T pendingValue() { + return pendingValue; + } + + @Override + public void requestSet(T value) { + pendingValue = value; + listeners.forEach(listener -> listener.accept(this, pendingValue)); + } + + @Override + public boolean applyValue() { + if (changed()) { + binding().setValue(pendingValue); + return true; + } + return false; + } + + @Override + public void forgetPendingValue() { + requestSet(binding().getValue()); + } + + @Override + public void requestSetDefault() { + requestSet(binding().defaultValue()); + } + + @Override + public boolean isPendingValueDefault() { + return binding().defaultValue().equals(pendingValue()); + } + + @Override + public void addListener(BiConsumer, T> changedListener) { + this.listeners.add(changedListener); + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java new file mode 100644 index 0000000..a5180ad --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.PlaceholderCategory; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiFunction; + +public record PlaceholderCategoryImpl(Text name, BiFunction screen, Text tooltip) implements PlaceholderCategory { + @Override + public @NotNull ImmutableList groups() { + return ImmutableList.of(); + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java b/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java new file mode 100644 index 0000000..eb23eac --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.YetAnotherConfigLib; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.utils.YACLConstants; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +import java.util.function.Consumer; + +public record YetAnotherConfigLibImpl(Text title, ImmutableList categories, Runnable saveFunction, Consumer initConsumer) implements YetAnotherConfigLib { + @Override + public Screen generateScreen(Screen parent) { + YACLConstants.LOGGER.info("Generating YACL screen"); + return new YACLScreen(this, parent); + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java new file mode 100644 index 0000000..6c7508d --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java @@ -0,0 +1,115 @@ +package dev.isxander.yacl.impl.utils; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; + +public class DimensionIntegerImpl implements MutableDimension { + private int x, y; + private int width, height; + + public DimensionIntegerImpl(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public Integer x() { + return x; + } + + @Override + public Integer y() { + return y; + } + + @Override + public Integer width() { + return width; + } + + @Override + public Integer height() { + return height; + } + + @Override + public Integer xLimit() { + return x + width; + } + + @Override + public Integer yLimit() { + return y + height; + } + + @Override + public Integer centerX() { + return x + width / 2; + } + + @Override + public Integer centerY() { + return y + height / 2; + } + + @Override + public boolean isPointInside(Integer x, Integer y) { + return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); + } + + @Override + public MutableDimension clone() { + return new DimensionIntegerImpl(x, y, width, height); + } + + @Override public MutableDimension setX(Integer x) { this.x = x; return this; } + @Override public MutableDimension setY(Integer y) { this.y = y; return this; } + @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } + @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } + + @Override + public Dimension withX(Integer x) { + return clone().setX(x); + } + + @Override + public Dimension withY(Integer y) { + return clone().setY(y); + } + + @Override + public Dimension withWidth(Integer width) { + return clone().setWidth(width); + } + + @Override + public Dimension withHeight(Integer height) { + return clone().setHeight(height); + } + + @Override + public MutableDimension move(Integer x, Integer y) { + this.x += x; + this.y += y; + return this; + } + + @Override + public MutableDimension expand(Integer width, Integer height) { + this.width += width; + this.height += height; + return this; + } + + @Override + public Dimension moved(Integer x, Integer y) { + return clone().move(x, y); + } + + @Override + public Dimension expanded(Integer width, Integer height) { + return clone().expand(width, height); + } +} diff --git a/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java new file mode 100644 index 0000000..3d382d4 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java @@ -0,0 +1,8 @@ +package dev.isxander.yacl.impl.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YACLConstants { + public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); +} diff --git a/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java b/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java new file mode 100644 index 0000000..a48c808 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl.mixin.client; + +import net.minecraft.client.option.SimpleOption; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SimpleOption.class) +public interface SimpleOptionAccessor { + @Accessor + T getDefaultValue(); +} diff --git a/src/client/resources/yet-another-config-lib.client.mixins.json b/src/client/resources/yet-another-config-lib.client.mixins.json new file mode 100644 index 0000000..2367a67 --- /dev/null +++ b/src/client/resources/yet-another-config-lib.client.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "dev.isxander.yacl.mixin.client", + "compatibilityLevel": "JAVA_17", + "injectors": { + "defaultRequire": 1 + }, + "client": [ + "SimpleOptionAccessor" + ] +} diff --git a/src/main/java/dev/isxander/yacl/api/Binding.java b/src/main/java/dev/isxander/yacl/api/Binding.java deleted file mode 100644 index 395beb2..0000000 --- a/src/main/java/dev/isxander/yacl/api/Binding.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.impl.GenericBindingImpl; -import dev.isxander.yacl.mixin.SimpleOptionAccessor; -import net.minecraft.client.option.SimpleOption; -import org.apache.commons.lang3.Validate; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * Controls modifying the bound option. - * Provides the default value, a setter and a getter. - */ -public interface Binding { - void setValue(T value); - - T getValue(); - - T defaultValue(); - - /** - * Creates a generic binding. - * - * @param def default value of the option, used to reset - * @param getter should return the current value of the option - * @param setter should set the option to the supplied value - */ - static Binding generic(T def, Supplier getter, Consumer setter) { - Validate.notNull(def, "`def` must not be null"); - Validate.notNull(getter, "`getter` must not be null"); - Validate.notNull(setter, "`setter` must not be null"); - - return new GenericBindingImpl<>(def, getter, setter); - } - - /** - * Creates a {@link Binding} for Minecraft's {@link SimpleOption} - */ - static Binding minecraft(SimpleOption minecraftOption) { - Validate.notNull(minecraftOption, "`minecraftOption` must not be null"); - - return new GenericBindingImpl<>( - ((SimpleOptionAccessor) (Object) minecraftOption).getDefaultValue(), - minecraftOption::getValue, - minecraftOption::setValue - ); - } - - /** - * Creates an immutable binding that has no default and cannot be modified. - * - * @param value the value for the binding - */ - static Binding immutable(T value) { - Validate.notNull(value, "`value` must not be null"); - - return new GenericBindingImpl<>( - value, - () -> value, - changed -> {} - ); - } -} diff --git a/src/main/java/dev/isxander/yacl/api/ButtonOption.java b/src/main/java/dev/isxander/yacl/api/ButtonOption.java deleted file mode 100644 index 1124a9a..0000000 --- a/src/main/java/dev/isxander/yacl/api/ButtonOption.java +++ /dev/null @@ -1,123 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.ButtonOptionImpl; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; - -public interface ButtonOption extends Option> { - /** - * Action to be executed upon button press - */ - BiConsumer action(); - - static Builder createBuilder() { - return new Builder(); - } - - class Builder { - private Text name; - private final List tooltipLines = new ArrayList<>(); - private boolean available = true; - private Function>> controlGetter; - private BiConsumer action; - - private Builder() { - - } - - /** - * Sets the name to be used by the option. - * - * @see Option#name() - */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - /** - * Sets the tooltip to be used by the option. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Option.Builder#build()}. - */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notNull(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - public Builder action(@NotNull BiConsumer action) { - Validate.notNull(action, "`action` cannot be null"); - - this.action = action; - return this; - } - - /** - * Action to be executed upon button press - * - * @see ButtonOption#action() - */ - @Deprecated - public Builder action(@NotNull Consumer action) { - Validate.notNull(action, "`action` cannot be null"); - - this.action = (screen, button) -> action.accept(screen); - return this; - } - - /** - * Sets if the option can be configured - * - * @see Option#available() - */ - public Builder available(boolean available) { - this.available = available; - return this; - } - - /** - * Sets the controller for the option. - * This is how you interact and change the options. - * - * @see dev.isxander.yacl.gui.controllers - */ - public Builder controller(@NotNull Function>> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controlGetter = control; - return this; - } - - public ButtonOption build() { - Validate.notNull(name, "`name` must not be null when building `Option`"); - Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); - Validate.notNull(action, "`action` must not be null when building `Option`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new ButtonOptionImpl(name, concatenatedTooltip, action, available, controlGetter); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java deleted file mode 100644 index 27c3e95..0000000 --- a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java +++ /dev/null @@ -1,158 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.impl.ConfigCategoryImpl; -import dev.isxander.yacl.impl.OptionGroupImpl; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; - -/** - * Separates {@link Option}s or {@link OptionGroup}s into multiple distinct sections. - * Served to a user as a button in the left column, - * upon pressing, the options list is filled with options contained within this category. - */ -public interface ConfigCategory { - /** - * Name of category, displayed as a button on the left column. - */ - @NotNull Text name(); - - /** - * Gets every {@link OptionGroup} in this category. - */ - @NotNull ImmutableList groups(); - - /** - * Tooltip (or description) of the category. - * Rendered on hover. - */ - @NotNull Text tooltip(); - - /** - * Creates a builder to construct a {@link ConfigCategory} - */ - static Builder createBuilder() { - return new Builder(); - } - - class Builder { - private Text name; - - private final List> rootOptions = new ArrayList<>(); - private final List groups = new ArrayList<>(); - - private final List tooltipLines = new ArrayList<>(); - - private Builder() { - - } - - /** - * Sets name of the category - * - * @see ConfigCategory#name() - */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - /** - * Adds an option to the root group of the category. - * To add to another group, use {@link Builder#group(OptionGroup)}. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see ConfigCategory#groups() - * @see OptionGroup#isRoot() - */ - public Builder option(@NotNull Option option) { - Validate.notNull(option, "`option` must not be null"); - - this.rootOptions.add(option); - return this; - } - - /** - * Adds multiple options to the root group of the category. - * To add to another group, use {@link Builder#groups(Collection)}. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see ConfigCategory#groups() - * @see OptionGroup#isRoot() - */ - public Builder options(@NotNull Collection> options) { - Validate.notNull(options, "`options` must not be null"); - - this.rootOptions.addAll(options); - return this; - } - - /** - * Adds an option group. - * To add an option to the root group, use {@link Builder#option(Option)} - * To construct a group, use {@link OptionGroup#createBuilder()} - */ - public Builder group(@NotNull OptionGroup group) { - Validate.notNull(group, "`group` must not be null"); - - this.groups.add(group); - return this; - } - - /** - * Adds multiple option groups. - * To add multiple options to the root group, use {@link Builder#options(Collection)} - * To construct a group, use {@link OptionGroup#createBuilder()} - */ - public Builder groups(@NotNull Collection groups) { - Validate.notEmpty(groups, "`groups` must not be empty"); - - this.groups.addAll(groups); - return this; - } - - /** - * Sets the tooltip to be used by the category. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. - */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - public ConfigCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - List combinedGroups = new ArrayList<>(); - combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), false, true)); - combinedGroups.addAll(groups); - - Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/Controller.java b/src/main/java/dev/isxander/yacl/api/Controller.java deleted file mode 100644 index 7bf7e7f..0000000 --- a/src/main/java/dev/isxander/yacl/api/Controller.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.text.Text; - -/** - * Provides a widget to control the option. - */ -public interface Controller { - /** - * Gets the dedicated {@link Option} for this controller - */ - Option option(); - - /** - * Gets the formatted value based on {@link Option#pendingValue()} - */ - Text formatValue(); - - /** - * Provides a widget to display - * - * @param screen parent screen - */ - AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension); -} diff --git a/src/main/java/dev/isxander/yacl/api/NameableEnum.java b/src/main/java/dev/isxander/yacl/api/NameableEnum.java deleted file mode 100644 index 793b230..0000000 --- a/src/main/java/dev/isxander/yacl/api/NameableEnum.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api; - -import net.minecraft.text.Text; - -/** - * Used for the default value formatter of {@link dev.isxander.yacl.gui.controllers.cycling.EnumController} - */ -public interface NameableEnum { - Text getDisplayName(); -} diff --git a/src/main/java/dev/isxander/yacl/api/Option.java b/src/main/java/dev/isxander/yacl/api/Option.java deleted file mode 100644 index 772c816..0000000 --- a/src/main/java/dev/isxander/yacl/api/Option.java +++ /dev/null @@ -1,336 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.impl.OptionImpl; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -public interface Option { - /** - * Name of the option - */ - @NotNull Text name(); - - /** - * Tooltip (or description) of the option. - * Rendered on hover. - */ - @NotNull Text tooltip(); - - /** - * Widget provider for a type of option. - * - * @see dev.isxander.yacl.gui.controllers - */ - @NotNull Controller controller(); - - /** - * Binding for the option. - * Controls setting, getting and default value. - * - * @see Binding - */ - @NotNull Binding binding(); - - /** - * If the option can be configured - */ - boolean available(); - - /** - * Sets if the option can be configured after being built - * - * @see Option#available() - */ - void setAvailable(boolean available); - - /** - * Class of the option type. - * Used by some controllers. - */ - @NotNull Class typeClass(); - - /** - * Tasks that needs to be executed upon applying changes. - */ - @NotNull ImmutableSet flags(); - - /** - * Checks if the pending value is not equal to the current set value - */ - boolean changed(); - - /** - * If true, modifying this option recommends a restart. - */ - @Deprecated - boolean requiresRestart(); - - /** - * Value in the GUI, ready to set the actual bound value or be undone. - */ - @NotNull T pendingValue(); - - /** - * Sets the pending value - */ - void requestSet(T value); - - /** - * Applies the pending value to the bound value. - * Cannot be undone. - * - * @return if there were changes to apply {@link Option#changed()} - */ - boolean applyValue(); - - /** - * Sets the pending value to the bound value. - */ - void forgetPendingValue(); - - /** - * Sets the pending value to the default bound value. - */ - void requestSetDefault(); - - /** - * Checks if the current pending value is equal to its default value - */ - boolean isPendingValueDefault(); - - /** - * Adds a listener for when the pending value changes - */ - void addListener(BiConsumer, T> changedListener); - - /** - * Creates a builder to construct an {@link Option} - * - * @param type of the option's value - * @param typeClass used to capture the type - */ - static Builder createBuilder(Class typeClass) { - return new Builder<>(typeClass); - } - - class Builder { - private Text name = Text.literal("Name not specified!").formatted(Formatting.RED); - - private final List> tooltipGetters = new ArrayList<>(); - - private Function, Controller> controlGetter; - - private Binding binding; - - private boolean available = true; - - private boolean instant = false; - - private final Set flags = new HashSet<>(); - - private final Class typeClass; - - private final List, T>> listeners = new ArrayList<>(); - - private Builder(Class typeClass) { - this.typeClass = typeClass; - } - - /** - * Sets the name to be used by the option. - * - * @see Option#name() - */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - /** - * Sets the tooltip to be used by the option. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltipGetter function to get tooltip depending on value {@link Builder#build()}. - */ - @SafeVarargs - public final Builder tooltip(@NotNull Function... tooltipGetter) { - Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); - - this.tooltipGetters.addAll(List.of(tooltipGetter)); - return this; - } - - /** - * Sets the tooltip to be used by the option. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. - */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notNull(tooltips, "`tooltips` cannot be empty"); - - this.tooltipGetters.addAll(Stream.of(tooltips).map(text -> (Function) t -> text).toList()); - return this; - } - - /** - * Sets the controller for the option. - * This is how you interact and change the options. - * - * @see dev.isxander.yacl.gui.controllers - */ - public Builder controller(@NotNull Function, Controller> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controlGetter = control; - return this; - } - - /** - * Sets the binding for the option. - * Used for default, getter and setter. - * - * @see Binding - */ - public Builder binding(@NotNull Binding binding) { - Validate.notNull(binding, "`binding` cannot be null"); - - this.binding = binding; - return this; - } - - /** - * Sets the binding for the option. - * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} - * - * @param def default value of the option, used to reset - * @param getter should return the current value of the option - * @param setter should set the option to the supplied value - * @see Binding - */ - public Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { - Validate.notNull(def, "`def` must not be null"); - Validate.notNull(getter, "`getter` must not be null"); - Validate.notNull(setter, "`setter` must not be null"); - - this.binding = Binding.generic(def, getter, setter); - return this; - } - - /** - * Sets if the option can be configured - * - * @see Option#available() - */ - public Builder available(boolean available) { - this.available = available; - return this; - } - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - public Builder flag(@NotNull OptionFlag... flag) { - Validate.notNull(flag, "`flag` must not be null"); - - this.flags.addAll(Arrays.asList(flag)); - return this; - } - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - public Builder flags(@NotNull Collection flags) { - Validate.notNull(flags, "`flags` must not be null"); - - this.flags.addAll(flags); - return this; - } - - /** - * Instantly invokes the binder's setter when modified in the GUI. - * Prevents the user from undoing the change - *

- * Does not support {@link Option#flags()}! - */ - public Builder instant(boolean instant) { - this.instant = instant; - return this; - } - - /** - * Adds a listener to the option. Invoked upon changing the pending value. - * - * @see Option#addListener(BiConsumer) - */ - public Builder listener(@NotNull BiConsumer, T> listener) { - this.listeners.add(listener); - return this; - } - - /** - * Adds multiple listeners to the option. Invoked upon changing the pending value. - * - * @see Option#addListener(BiConsumer) - */ - public Builder listeners(@NotNull Collection, T>> listeners) { - this.listeners.addAll(listeners); - return this; - } - - /** - * Dictates whether the option should require a restart. - * {@link Option#requiresRestart()} - */ - @Deprecated - public Builder requiresRestart(boolean requiresRestart) { - if (requiresRestart) flag(OptionFlag.GAME_RESTART); - else flags.remove(OptionFlag.GAME_RESTART); - - return this; - } - - public Option build() { - Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); - Validate.notNull(binding, "`binding` must not be null when building `Option`"); - Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); - - Function concatenatedTooltipGetter = value -> { - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Function line : tooltipGetters) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line.apply(value)); - } - - return concatenatedTooltip; - }; - - if (instant) { - listeners.add((opt, pendingValue) -> opt.applyValue()); - } - - return new OptionImpl<>(name, concatenatedTooltipGetter, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass, listeners); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/OptionFlag.java b/src/main/java/dev/isxander/yacl/api/OptionFlag.java deleted file mode 100644 index 203a674..0000000 --- a/src/main/java/dev/isxander/yacl/api/OptionFlag.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.RequireRestartScreen; -import net.minecraft.client.MinecraftClient; - -import java.util.function.Consumer; - -/** - * Code that is executed upon certain options being applied. - * Each flag is executed only once per save, no matter the amount of options with the flag. - */ -@FunctionalInterface -public interface OptionFlag extends Consumer { - /** - * Warns the user that a game restart is required for the changes to take effect - */ - OptionFlag GAME_RESTART = client -> client.setScreen(new RequireRestartScreen(client.currentScreen)); - - /** - * Reloads chunks upon applying (F3+A) - */ - OptionFlag RELOAD_CHUNKS = client -> client.worldRenderer.reload(); - - OptionFlag WORLD_RENDER_UPDATE = client -> client.worldRenderer.scheduleTerrainUpdate(); - - OptionFlag ASSET_RELOAD = MinecraftClient::reloadResourcesConcurrently; -} diff --git a/src/main/java/dev/isxander/yacl/api/OptionGroup.java b/src/main/java/dev/isxander/yacl/api/OptionGroup.java deleted file mode 100644 index 3364bdf..0000000 --- a/src/main/java/dev/isxander/yacl/api/OptionGroup.java +++ /dev/null @@ -1,141 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.impl.OptionGroupImpl; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Serves as a separator between multiple chunks of options - * that may be too similar or too few to be placed in a separate {@link ConfigCategory}. - * Or maybe you just want your config to feel less dense. - */ -public interface OptionGroup { - /** - * Name of the option group, displayed as a separator in the option lists. - * Can be empty. - */ - Text name(); - - /** - * Tooltip displayed on hover. - */ - Text tooltip(); - - /** - * List of all options in the group - */ - @NotNull ImmutableList> options(); - - /** - * Dictates if the group should be collapsed by default. - */ - boolean collapsed(); - - /** - * Always false when using the {@link Builder} - * used to not render the separator if true - */ - boolean isRoot(); - - /** - * Creates a builder to construct a {@link OptionGroup} - */ - static Builder createBuilder() { - return new Builder(); - } - - class Builder { - private Text name = Text.empty(); - private final List tooltipLines = new ArrayList<>(); - private final List> options = new ArrayList<>(); - private boolean collapsed = false; - - private Builder() { - - } - - /** - * Sets name of the group, can be {@link Text#empty()} to just separate options, like sodium. - * - * @see OptionGroup#name() - */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` must not be null"); - - this.name = name; - return this; - } - - /** - * Sets the tooltip to be used by the option group. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. - */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - /** - * Adds an option to group. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see OptionGroup#options() - */ - public Builder option(@NotNull Option option) { - Validate.notNull(option, "`option` must not be null"); - - this.options.add(option); - return this; - } - - /** - * Adds multiple options to group. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see OptionGroup#options() - */ - public Builder options(@NotNull Collection> options) { - Validate.notEmpty(options, "`options` must not be empty"); - - this.options.addAll(options); - return this; - } - - /** - * Dictates if the group should be collapsed by default - * - * @see OptionGroup#collapsed() - */ - public Builder collapsed(boolean collapsible) { - this.collapsed = collapsible; - return this; - } - - public OptionGroup build() { - Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java b/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java deleted file mode 100644 index de7441c..0000000 --- a/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java +++ /dev/null @@ -1,94 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.PlaceholderCategoryImpl; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiFunction; - -/** - * A placeholder category that actually just opens another screen, - * instead of displaying options - */ -public interface PlaceholderCategory extends ConfigCategory { - /** - * Function to create a screen to open upon changing to this category - */ - BiFunction screen(); - - static Builder createBuilder() { - return new Builder(); - } - - class Builder { - private Text name; - - private final List tooltipLines = new ArrayList<>(); - - private BiFunction screenFunction; - - private Builder() { - - } - - /** - * Sets name of the category - * - * @see ConfigCategory#name() - */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - /** - * Sets the tooltip to be used by the category. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. - */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - /** - * Screen to open upon selecting this category - * - * @see PlaceholderCategory#screen() - */ - public Builder screen(@NotNull BiFunction screenFunction) { - Validate.notNull(screenFunction, "`screenFunction` cannot be null"); - - this.screenFunction = screenFunction; - return this; - } - - public PlaceholderCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java deleted file mode 100644 index a69ae4e..0000000 --- a/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java +++ /dev/null @@ -1,136 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.YetAnotherConfigLibImpl; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Consumer; - -/** - * Main class of the mod. - * Contains all data and used to provide a {@link Screen} - */ -public interface YetAnotherConfigLib { - /** - * Title of the GUI. Only used for Minecraft narration. - */ - Text title(); - - /** - * Gets all config categories. - */ - ImmutableList categories(); - - /** - * Ran when changes are saved. Can be used to save config to a file etc. - */ - Runnable saveFunction(); - - /** - * Ran every time the YACL screen initialises. Can be paired with FAPI to add custom widgets. - */ - Consumer initConsumer(); - - /** - * Generates a Screen to display based on this instance. - * - * @param parent parent screen to open once closed - */ - Screen generateScreen(@Nullable Screen parent); - - /** - * Creates a builder to construct YACL - */ - static Builder createBuilder() { - return new Builder(); - } - - class Builder { - private Text title; - private final List categories = new ArrayList<>(); - private Runnable saveFunction = () -> {}; - private Consumer initConsumer = screen -> {}; - - private Builder() { - - } - - /** - * Sets title of GUI for Minecraft narration - * - * @see YetAnotherConfigLib#title() - */ - public Builder title(@NotNull Text title) { - Validate.notNull(title, "`title` cannot be null"); - - this.title = title; - return this; - } - - /** - * Adds a new category. - * To create a category you need to use {@link ConfigCategory#createBuilder()} - * - * @see YetAnotherConfigLib#categories() - */ - public Builder category(@NotNull ConfigCategory category) { - Validate.notNull(category, "`category` cannot be null"); - - this.categories.add(category); - return this; - } - - /** - * Adds multiple categories at once. - * To create a category you need to use {@link ConfigCategory#createBuilder()} - * - * @see YetAnotherConfigLib#categories() - */ - public Builder categories(@NotNull Collection categories) { - Validate.notNull(categories, "`categories` cannot be null"); - - this.categories.addAll(categories); - return this; - } - - /** - * Used to define a save function for when user clicks the Save Changes button - * - * @see YetAnotherConfigLib#saveFunction() - */ - public Builder save(@NotNull Runnable saveFunction) { - Validate.notNull(saveFunction, "`saveFunction` cannot be null"); - - this.saveFunction = saveFunction; - return this; - } - - /** - * Defines a consumer that is accepted every time the YACL screen initialises - * - * @see YetAnotherConfigLib#initConsumer() - */ - public Builder screenInit(@NotNull Consumer initConsumer) { - Validate.notNull(initConsumer, "`initConsumer` cannot be null"); - - this.initConsumer = initConsumer; - return this; - } - - public YetAnotherConfigLib build() { - Validate.notNull(title, "`title must not be null to build `YetAnotherConfigLib`"); - Validate.notEmpty(categories, "`categories` must not be empty to build `YetAnotherConfigLib`"); - Validate.isTrue(!categories.stream().allMatch(category -> category instanceof PlaceholderCategory), "At least one regular category is required to build `YetAnotherConfigLib`"); - - return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/api/utils/Dimension.java b/src/main/java/dev/isxander/yacl/api/utils/Dimension.java deleted file mode 100644 index 0de0a58..0000000 --- a/src/main/java/dev/isxander/yacl/api/utils/Dimension.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.isxander.yacl.api.utils; - -import dev.isxander.yacl.impl.utils.DimensionIntegerImpl; - -public interface Dimension { - T x(); - T y(); - - T width(); - T height(); - - T xLimit(); - T yLimit(); - - T centerX(); - T centerY(); - - boolean isPointInside(T x, T y); - - MutableDimension clone(); - - Dimension withX(T x); - Dimension withY(T y); - Dimension withWidth(T width); - Dimension withHeight(T height); - - Dimension moved(T x, T y); - Dimension expanded(T width, T height); - - static MutableDimension ofInt(int x, int y, int width, int height) { - return new DimensionIntegerImpl(x, y, width, height); - } -} diff --git a/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java b/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java deleted file mode 100644 index eff0186..0000000 --- a/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.api.utils; - -public interface MutableDimension extends Dimension { - MutableDimension setX(T x); - MutableDimension setY(T y); - MutableDimension setWidth(T width); - MutableDimension setHeight(T height); - - MutableDimension move(T x, T y); - MutableDimension expand(T width, T height); -} diff --git a/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java b/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java deleted file mode 100644 index ab46b5b..0000000 --- a/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.isxander.yacl.api.utils; - -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionGroup; -import dev.isxander.yacl.api.YetAnotherConfigLib; - -import java.util.function.Consumer; -import java.util.function.Function; - -public class OptionUtils { - /** - * Consumes all options, ignoring groups and categories. - * When consumer returns true, this function stops iterating. - */ - public static void consumeOptions(YetAnotherConfigLib yacl, Function, Boolean> consumer) { - for (ConfigCategory category : yacl.categories()) { - for (OptionGroup group : category.groups()) { - for (Option option : group.options()) { - if (consumer.apply(option)) return; - } - } - } - } - - /** - * Consumes all options, ignoring groups and categories. - * - * @see OptionUtils#consumeOptions(YetAnotherConfigLib, Function) - */ - public static void forEachOptions(YetAnotherConfigLib yacl, Consumer> consumer) { - consumeOptions(yacl, (opt) -> { - consumer.accept(opt); - return false; - }); - } -} diff --git a/src/main/java/dev/isxander/yacl/config/ConfigInstance.java b/src/main/java/dev/isxander/yacl/config/ConfigInstance.java index a7c013c..8795eb1 100644 --- a/src/main/java/dev/isxander/yacl/config/ConfigInstance.java +++ b/src/main/java/dev/isxander/yacl/config/ConfigInstance.java @@ -1,9 +1,6 @@ package dev.isxander.yacl.config; -import dev.isxander.yacl.api.YetAnotherConfigLib; - import java.lang.reflect.InvocationTargetException; -import java.util.function.BiFunction; /** * Responsible for handing the actual config data type. @@ -46,10 +43,6 @@ public abstract class ConfigInstance { return this.configClass; } - public YetAnotherConfigLib buildConfig(BiFunction, YetAnotherConfigLib.Builder, YetAnotherConfigLib.Builder> builder) { - return builder.apply(this, YetAnotherConfigLib.createBuilder().save(this::save)).build(); - } - public abstract void save(); public abstract void load(); } diff --git a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java b/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java deleted file mode 100644 index bede0ae..0000000 --- a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import dev.isxander.yacl.api.utils.Dimension; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.Drawable; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.Selectable; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.widget.ClickableWidget; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.client.sound.PositionedSoundInstance; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.sound.SoundEvents; - -import java.awt.*; - -public abstract class AbstractWidget implements Element, Drawable, Selectable { - protected final MinecraftClient client = MinecraftClient.getInstance(); - protected final TextRenderer textRenderer = client.textRenderer; - protected final int inactiveColor = 0xFFA0A0A0; - - private Dimension dim; - - public AbstractWidget(Dimension dim) { - this.dim = dim; - } - - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - - } - - public boolean canReset() { - return false; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - if (dim == null) return false; - return this.dim.isPointInside((int) mouseX, (int) mouseY); - } - - public void setDimension(Dimension dim) { - this.dim = dim; - } - - public Dimension getDimension() { - return dim; - } - - @Override - public SelectionType getType() { - return SelectionType.NONE; - } - - public void unfocus() { - - } - - public boolean matchesSearch(String query) { - return true; - } - - @Override - public void appendNarrations(NarrationMessageBuilder builder) { - - } - - protected void drawButtonRect(MatrixStack matrices, int x1, int y1, int x2, int y2, boolean hovered, boolean enabled) { - if (x1 > x2) { - int xx1 = x1; - x1 = x2; - x2 = xx1; - } - if (y1 > y2) { - int yy1 = y1; - y1 = y2; - y2 = yy1; - } - int width = x2 - x1; - int height = y2 - y1; - - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderTexture(0, ClickableWidget.WIDGETS_TEXTURE); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - int i = !enabled ? 0 : hovered ? 2 : 1; - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - DrawableHelper.drawTexture(matrices, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); - DrawableHelper.drawTexture(matrices, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); - } - - protected int multiplyColor(int hex, float amount) { - Color color = new Color(hex, true); - - return new Color(Math.max((int)(color.getRed() *amount), 0), - Math.max((int)(color.getGreen()*amount), 0), - Math.max((int)(color.getBlue() *amount), 0), - color.getAlpha()).getRGB(); - } - - public void playDownSound() { - MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java b/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java deleted file mode 100644 index 46a9fdf..0000000 --- a/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java +++ /dev/null @@ -1,96 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.google.common.collect.ImmutableList; -import com.mojang.blaze3d.systems.RenderSystem; -import dev.isxander.yacl.api.ConfigCategory; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.Selectable; -import net.minecraft.client.gui.widget.ElementListWidget; -import net.minecraft.client.util.math.MatrixStack; - -import java.util.List; - -public class CategoryListWidget extends ElementListWidget { - private final YACLScreen yaclScreen; - - public CategoryListWidget(MinecraftClient client, YACLScreen yaclScreen, int screenWidth, int screenHeight) { - super(client, screenWidth / 3, yaclScreen.searchFieldWidget.getY() - 5, 0, yaclScreen.searchFieldWidget.getY() - 5, 21); - this.yaclScreen = yaclScreen; - setRenderBackground(false); - setRenderHorizontalShadows(false); - - for (ConfigCategory category : yaclScreen.config.categories()) { - addEntry(new CategoryEntry(category)); - } - } - - @Override - protected void renderList(MatrixStack matrices, int mouseX, int mouseY, float delta) { - double d = this.client.getWindow().getScaleFactor(); - RenderSystem.enableScissor(0, (int)((yaclScreen.height - bottom) * d), (int)(width * d), (int)(height * d)); - super.renderList(matrices, mouseX, mouseY, delta); - RenderSystem.disableScissor(); - } - - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - for (CategoryEntry entry : children()) { - entry.postRender(matrices, mouseX, mouseY, delta); - } - } - - @Override - public int getRowWidth() { - return Math.min(width - width / 10, 396); - } - - @Override - public int getRowLeft() { - return super.getRowLeft() - 2; - } - - @Override - protected int getScrollbarPositionX() { - return width - 2; - } - - public class CategoryEntry extends Entry { - private final CategoryWidget categoryButton; - public final int categoryIndex; - - public CategoryEntry(ConfigCategory category) { - this.categoryIndex = yaclScreen.config.categories().indexOf(category); - categoryButton = new CategoryWidget( - yaclScreen, - category, - categoryIndex, - getRowLeft(), 0, - getRowWidth(), 20 - ); - } - - @Override - public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - if (mouseY > bottom) { - mouseY = -20; - } - - categoryButton.setY(y); - categoryButton.render(matrices, mouseX, mouseY, tickDelta); - } - - private void postRender(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) { - categoryButton.renderHoveredTooltip(matrices); - } - - @Override - public List children() { - return ImmutableList.of(categoryButton); - } - - @Override - public List selectableChildren() { - return ImmutableList.of(categoryButton); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java b/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java deleted file mode 100644 index 3c5d8d2..0000000 --- a/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.isxander.yacl.gui; - -import dev.isxander.yacl.api.ConfigCategory; -import net.minecraft.client.sound.SoundManager; - -public class CategoryWidget extends TooltipButtonWidget { - private final int categoryIndex; - - public CategoryWidget(YACLScreen screen, ConfigCategory category, int categoryIndex, int x, int y, int width, int height) { - super(screen, x, y, width, height, category.name(), category.tooltip(), btn -> { - screen.searchFieldWidget.setText(""); - screen.changeCategory(categoryIndex); - }); - this.categoryIndex = categoryIndex; - } - - private boolean isCurrentCategory() { - return ((YACLScreen) screen).getCurrentCategoryIdx() == categoryIndex; - } - - @Override - protected int getYImage(boolean hovered) { - return super.getYImage(hovered || isCurrentCategory()); - } - - @Override - public void playDownSound(SoundManager soundManager) { - if (!isCurrentCategory()) - super.playDownSound(soundManager); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java deleted file mode 100644 index 4823428..0000000 --- a/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; - -public class LowProfileButtonWidget extends ButtonWidget { - public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress) { - super(x, y, width, height, message, onPress, ButtonWidget.DEFAULT_NARRATION_SUPPLIER); - } - - public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress, Tooltip tooltip) { - this(x, y, width, height, message, onPress); - setTooltip(tooltip); - } - - @Override - public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (!isHovered()) { - int j = this.active ? 0xFFFFFF : 0xA0A0A0; - drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, this.getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, j | MathHelper.ceil(this.alpha * 255.0F) << 24); - } else { - super.renderButton(matrices, mouseX, mouseY, delta); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java deleted file mode 100644 index eed3aff..0000000 --- a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java +++ /dev/null @@ -1,470 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionGroup; -import dev.isxander.yacl.api.utils.Dimension; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.Selectable; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.screen.narration.NarrationPart; -import net.minecraft.client.gui.widget.ElementListWidget; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.function.Supplier; - -public class OptionListWidget extends ElementListWidget { - private final YACLScreen yaclScreen; - private boolean singleCategory = false; - - private ImmutableList viewableChildren; - - private double smoothScrollAmount = getScrollAmount(); - private boolean returnSmoothAmount = false; - - public OptionListWidget(YACLScreen screen, MinecraftClient client, int width, int height) { - super(client, width / 3 * 2, height, 0, height, 22); - this.yaclScreen = screen; - left = width - this.width; - right = width; - - refreshOptions(); - } - - public void refreshOptions() { - clearEntries(); - - List categories = new ArrayList<>(); - if (yaclScreen.getCurrentCategoryIdx() == -1) { - categories.addAll(yaclScreen.config.categories()); - } else { - categories.add(yaclScreen.config.categories().get(yaclScreen.getCurrentCategoryIdx())); - } - singleCategory = categories.size() == 1; - - for (ConfigCategory category : categories) { - for (OptionGroup group : category.groups()) { - Supplier viewableSupplier; - GroupSeparatorEntry groupSeparatorEntry = null; - if (!group.isRoot()) { - groupSeparatorEntry = new GroupSeparatorEntry(group, yaclScreen); - viewableSupplier = groupSeparatorEntry::isExpanded; - addEntry(groupSeparatorEntry); - } else { - viewableSupplier = () -> true; - } - - List optionEntries = new ArrayList<>(); - for (Option option : group.options()) { - OptionEntry entry = new OptionEntry(option, category, group, option.controller().provideWidget(yaclScreen, Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20)), viewableSupplier); - addEntry(entry); - optionEntries.add(entry); - } - - if (groupSeparatorEntry != null) { - groupSeparatorEntry.setOptionEntries(optionEntries); - } - } - } - - recacheViewableChildren(); - setScrollAmount(0); - } - - public void expandAllGroups() { - for (Entry entry : super.children()) { - if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { - groupSeparatorEntry.setExpanded(true); - } - } - } - - /* - below code is licensed from cloth-config under LGPL3 - modified to inherit vanilla's EntryListWidget and use yarn mappings - */ - - @Nullable - @Override - protected Entry getEntryAtPosition(double x, double y) { - int listMiddleX = this.left + this.width / 2; - int minX = listMiddleX - this.getRowWidth() / 2; - int maxX = listMiddleX + this.getRowWidth() / 2; - int currentY = MathHelper.floor(y - (double) this.top) - this.headerHeight + (int) this.getScrollAmount() - 4; - int itemY = 0; - int itemIndex = -1; - for (int i = 0; i < children().size(); i++) { - Entry item = children().get(i); - itemY += item.getItemHeight(); - if (itemY > currentY) { - itemIndex = i; - break; - } - } - return x < (double) this.getScrollbarPositionX() && x >= minX && y <= maxX && itemIndex >= 0 && currentY >= 0 && itemIndex < this.getEntryCount() ? this.children().get(itemIndex) : null; - } - - @Override - protected int getMaxPosition() { - return children().stream().map(Entry::getItemHeight).reduce(0, Integer::sum) + headerHeight; - } - - @Override - protected void centerScrollOn(Entry entry) { - double d = (this.bottom - this.top) / -2d; - for (int i = 0; i < this.children().indexOf(entry) && i < this.getEntryCount(); i++) - d += children().get(i).getItemHeight(); - this.setScrollAmount(d); - } - - @Override - protected int getRowTop(int index) { - int integer = top + 4 - (int) this.getScrollAmount() + headerHeight; - for (int i = 0; i < children().size() && i < index; i++) - integer += children().get(i).getItemHeight(); - return integer; - } - - @Override - protected void renderList(MatrixStack matrices, int mouseX, int mouseY, float delta) { - int left = this.getRowLeft(); - int right = this.getRowWidth(); - int count = this.getEntryCount(); - - for(int i = 0; i < count; ++i) { - Entry entry = children().get(i); - int top = this.getRowTop(i); - int bottom = top + entry.getItemHeight(); - int entryHeight = entry.getItemHeight() - 4; - if (bottom >= this.top && top <= this.bottom) { - this.renderEntry(matrices, mouseX, mouseY, delta, i, left, top, right, entryHeight); - } - } - } - - /* END cloth config code */ - - @Override - public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { - smoothScrollAmount = MathHelper.lerp(MinecraftClient.getInstance().getLastFrameDuration() * 0.5, smoothScrollAmount, getScrollAmount()); - returnSmoothAmount = true; - super.render(matrices, mouseX, mouseY, delta); - returnSmoothAmount = false; - } - - /** - * awful code to only use smooth scroll state when rendering, - * not other code that needs target scroll amount - */ - @Override - public double getScrollAmount() { - if (returnSmoothAmount) - return smoothScrollAmount; - - return super.getScrollAmount(); - } - - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - for (Entry entry : children()) { - entry.postRender(matrices, mouseX, mouseY, delta); - } - } - - @Override - public int getRowWidth() { - return Math.min(396, (int)(width / 1.3f)); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - for (Entry child : children()) { - if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry) - optionEntry.widget.unfocus(); - } - - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - for (Entry child : children()) { - if (child.mouseScrolled(mouseX, mouseY, amount)) - return true; - } - - this.setScrollAmount(this.getScrollAmount() - amount * 20 /* * (double) (getMaxScroll() / getEntryCount()) / 2.0D */); - return true; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - for (Entry child : children()) { - if (child.keyPressed(keyCode, scanCode, modifiers)) - return true; - } - - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - for (Entry child : children()) { - if (child.charTyped(chr, modifiers)) - return true; - } - - return super.charTyped(chr, modifiers); - } - - @Override - protected int getScrollbarPositionX() { - return left + width - (int)(width * 0.05f); - } - - @Override - protected void renderBackground(MatrixStack matrices) { - setRenderBackground(client.world == null); - if (client.world != null) - fill(matrices, left, top, right, bottom, 0x6B000000); - } - - public void recacheViewableChildren() { - this.viewableChildren = ImmutableList.copyOf(super.children().stream().filter(Entry::isViewable).toList()); - - // update y positions before they need to be rendered are rendered - int i = 0; - for (Entry entry : viewableChildren) { - if (entry instanceof OptionEntry optionEntry) - optionEntry.widget.setDimension(optionEntry.widget.getDimension().withY(getRowTop(i))); - i++; - } - } - - @Override - public List children() { - return viewableChildren; - } - - public abstract class Entry extends ElementListWidget.Entry { - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - - } - - public boolean isViewable() { - return true; - } - - public int getItemHeight() { - return 22; - } - - protected boolean isHovered() { - return Objects.equals(getHoveredEntry(), this); - } - } - - public class OptionEntry extends Entry { - public final Option option; - public final ConfigCategory category; - public final OptionGroup group; - - public final AbstractWidget widget; - private final Supplier viewableSupplier; - - private final TextScaledButtonWidget resetButton; - - private final String categoryName; - private final String groupName; - - private OptionEntry(Option option, ConfigCategory category, OptionGroup group, AbstractWidget widget, Supplier viewableSupplier) { - this.option = option; - this.category = category; - this.group = group; - this.widget = widget; - this.viewableSupplier = viewableSupplier; - this.categoryName = category.name().getString().toLowerCase(); - this.groupName = group.name().getString().toLowerCase(); - if (this.widget.canReset()) { - this.widget.setDimension(this.widget.getDimension().expanded(-21, 0)); - this.resetButton = new TextScaledButtonWidget(widget.getDimension().xLimit() + 1, -50, 20, 20, 2f, Text.of("\u21BB"), button -> { - option.requestSetDefault(); - }); - option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); - this.resetButton.active = !option.isPendingValueDefault() && option.available(); - } else { - this.resetButton = null; - } - } - - @Override - public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - widget.setDimension(widget.getDimension().withY(y)); - - widget.render(matrices, mouseX, mouseY, tickDelta); - - if (resetButton != null) { - resetButton.setY(y); - resetButton.render(matrices, mouseX, mouseY, tickDelta); - } - } - - @Override - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - widget.postRender(matrices, mouseX, mouseY, delta); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - return widget.mouseScrolled(mouseX, mouseY, amount); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return widget.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - return widget.charTyped(chr, modifiers); - } - - @Override - public boolean isViewable() { - String query = yaclScreen.searchFieldWidget.getText(); - return viewableSupplier.get() - && (yaclScreen.searchFieldWidget.isEmpty() - || (!singleCategory && categoryName.contains(query)) - || groupName.contains(query) - || widget.matchesSearch(query)); - } - - @Override - public int getItemHeight() { - return widget.getDimension().height() + 2; - } - - @Override - public List selectableChildren() { - if (resetButton == null) - return ImmutableList.of(widget); - - return ImmutableList.of(widget, resetButton); - } - - @Override - public List children() { - if (resetButton == null) - return ImmutableList.of(widget); - - return ImmutableList.of(widget, resetButton); - } - } - - public class GroupSeparatorEntry extends Entry { - private final OptionGroup group; - private final MultilineText wrappedName; - private final MultilineText wrappedTooltip; - - private final LowProfileButtonWidget expandMinimizeButton; - - private final Screen screen; - private final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - - private boolean groupExpanded; - - private List optionEntries; - - private int y; - - private GroupSeparatorEntry(OptionGroup group, Screen screen) { - this.group = group; - this.screen = screen; - this.wrappedName = MultilineText.create(textRenderer, group.name(), getRowWidth() - 45); - this.wrappedTooltip = MultilineText.create(textRenderer, group.tooltip(), screen.width / 3 * 2 - 10); - this.groupExpanded = !group.collapsed(); - this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Text.empty(), btn -> { - setExpanded(!isExpanded()); - recacheViewableChildren(); - }); - updateExpandMinimizeText(); - } - - @Override - public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - this.y = y; - - expandMinimizeButton.setX(x); - expandMinimizeButton.setY(y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2); - expandMinimizeButton.render(matrices, mouseX, mouseY, tickDelta); - - wrappedName.drawCenterWithShadow(matrices, x + entryWidth / 2, y + getYPadding()); - } - - @Override - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (isHovered() && !expandMinimizeButton.isMouseOver(mouseX, mouseY)) { - YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getRowLeft() + getRowWidth() / 2, y - 3, y + getItemHeight() + 3, screen.width, screen.height); - } - } - - public boolean isExpanded() { - return groupExpanded; - } - - public void setExpanded(boolean expanded) { - this.groupExpanded = expanded; - updateExpandMinimizeText(); - } - - private void updateExpandMinimizeText() { - expandMinimizeButton.setMessage(Text.of(isExpanded() ? "\u25BC" : "\u25B6")); - } - - public void setOptionEntries(List optionEntries) { - this.optionEntries = optionEntries; - } - - @Override - public boolean isViewable() { - return yaclScreen.searchFieldWidget.isEmpty() || optionEntries.stream().anyMatch(OptionEntry::isViewable); - } - - @Override - public int getItemHeight() { - return Math.max(wrappedName.count(), 1) * textRenderer.fontHeight + getYPadding() * 2; - } - - private int getYPadding() { - return 6; - } - - @Override - public List selectableChildren() { - return ImmutableList.of(new Selectable() { - @Override - public Selectable.SelectionType getType() { - return Selectable.SelectionType.HOVERED; - } - - @Override - public void appendNarrations(NarrationMessageBuilder builder) { - builder.put(NarrationPart.TITLE, group.name()); - } - }); - } - - @Override - public List children() { - return ImmutableList.of(expandMinimizeButton); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java b/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java deleted file mode 100644 index 3c46738..0000000 --- a/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; - -public class RequireRestartScreen extends ConfirmScreen { - public RequireRestartScreen(Screen parent) { - super(option -> { - if (option) MinecraftClient.getInstance().scheduleStop(); - else MinecraftClient.getInstance().setScreen(parent); - }, Text.translatable("yacl.restart.title").formatted(Formatting.RED, Formatting.BOLD), Text.translatable("yacl.restart.message"), Text.translatable("yacl.restart.yes"), Text.translatable("yacl.restart.no")); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java b/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java deleted file mode 100644 index 5b7c9dc..0000000 --- a/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; - -public class SearchFieldWidget extends TextFieldWidget { - private Text emptyText; - private final YACLScreen yaclScreen; - private final TextRenderer textRenderer; - - private boolean isEmpty = true; - - public SearchFieldWidget(YACLScreen yaclScreen, TextRenderer textRenderer, int x, int y, int width, int height, Text text, Text emptyText) { - super(textRenderer, x, y, width, height, text); - setChangedListener(string -> update()); - setTextPredicate(string -> !string.endsWith(" ") && !string.startsWith(" ")); - this.yaclScreen = yaclScreen; - this.textRenderer = textRenderer; - this.emptyText = emptyText; - } - - @Override - public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { - super.renderButton(matrices, mouseX, mouseY, delta); - if (isVisible() && isEmpty()) { - textRenderer.drawWithShadow(matrices, emptyText, getX() + 4, this.getY() + (this.height - 8) / 2f, 0x707070); - } - } - - private void update() { - boolean wasEmpty = isEmpty; - isEmpty = getText().isEmpty(); - - if (isEmpty && wasEmpty) - return; - - if (!isEmpty && yaclScreen.getCurrentCategoryIdx() != -1) - yaclScreen.changeCategory(-1); - if (isEmpty && yaclScreen.getCurrentCategoryIdx() == -1) - yaclScreen.changeCategory(0); - - yaclScreen.optionList.expandAllGroups(); - yaclScreen.optionList.recacheViewableChildren(); - - yaclScreen.optionList.setScrollAmount(0); - yaclScreen.categoryList.setScrollAmount(0); - } - - public boolean isEmpty() { - return isEmpty; - } - - public Text getEmptyText() { - return emptyText; - } - - public void setEmptyText(Text emptyText) { - this.emptyText = emptyText; - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java b/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java deleted file mode 100644 index 9f153f6..0000000 --- a/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.OrderedText; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; - -public class TextScaledButtonWidget extends ButtonWidget { - public float textScale; - - public TextScaledButtonWidget(int x, int y, int width, int height, float textScale, Text message, PressAction onPress) { - super(x, y, width, height, message, onPress, ButtonWidget.DEFAULT_NARRATION_SUPPLIER); - this.textScale = textScale; - } - - public TextScaledButtonWidget(int x, int y, int width, int height, float textScale, Text message, PressAction onPress, Tooltip tooltip) { - this(x, y, width, height, textScale, message, onPress); - setTooltip(tooltip); - } - - @Override - public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { - // prevents super from rendering text - Text message = getMessage(); - setMessage(Text.empty()); - - super.renderButton(matrices, mouseX, mouseY, delta); - - setMessage(message); - int j = this.active ? 16777215 : 10526880; - OrderedText orderedText = getMessage().asOrderedText(); - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - - matrices.push(); - matrices.translate(((this.getX() + this.width / 2f) - textRenderer.getWidth(orderedText) * textScale / 2), (float)this.getY() + (this.height - 8 * textScale) / 2f / textScale, 0); - matrices.scale(textScale, textScale, 1); - textRenderer.drawWithShadow(matrices, orderedText, 0, 0, j | MathHelper.ceil(this.alpha * 255.0F) << 24); - matrices.pop(); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java b/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java deleted file mode 100644 index 706765a..0000000 --- a/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; - -public class TooltipButtonWidget extends ButtonWidget { - - protected final Screen screen; - protected MultilineText wrappedDescription; - - public TooltipButtonWidget(Screen screen, int x, int y, int width, int height, Text message, Text tooltip, PressAction onPress) { - super(x, y, width, height, message, onPress, ButtonWidget.DEFAULT_NARRATION_SUPPLIER); - this.screen = screen; - setTooltip(tooltip); - } - - public void renderHoveredTooltip(MatrixStack matrices) { - if (isHovered()) { - YACLScreen.renderMultilineTooltip(matrices, MinecraftClient.getInstance().textRenderer, wrappedDescription, getX() + width / 2, getY() - 4, getY() + height + 4, screen.width, screen.height); - } - } - - public void setTooltip(Text tooltip) { - wrappedDescription = MultilineText.create(MinecraftClient.getInstance().textRenderer, tooltip, screen.width / 3 - 5); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java deleted file mode 100644 index e36c8e8..0000000 --- a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java +++ /dev/null @@ -1,269 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; -import dev.isxander.yacl.api.utils.OptionUtils; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.*; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.joml.Matrix4f; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -public class YACLScreen extends Screen { - public final YetAnotherConfigLib config; - private int currentCategoryIdx; - - private final Screen parent; - - public OptionListWidget optionList; - public CategoryListWidget categoryList; - public TooltipButtonWidget finishedSaveButton, cancelResetButton, undoButton; - public SearchFieldWidget searchFieldWidget; - - public Text saveButtonMessage, saveButtonTooltipMessage; - private int saveButtonMessageTime; - - - public YACLScreen(YetAnotherConfigLib config, Screen parent) { - super(config.title()); - this.config = config; - this.parent = parent; - this.currentCategoryIdx = 0; - } - - @Override - protected void init() { - int columnWidth = width / 3; - int padding = columnWidth / 20; - columnWidth = Math.min(columnWidth, 400); - int paddedWidth = columnWidth - padding * 2; - - MutableDimension actionDim = Dimension.ofInt(width / 3 / 2, height - padding - 20, paddedWidth, 20); - finishedSaveButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), Text.empty(), (btn) -> { - saveButtonMessage = null; - - if (pendingChanges()) { - Set flags = new HashSet<>(); - OptionUtils.forEachOptions(config, option -> { - if (option.applyValue()) { - flags.addAll(option.flags()); - } - }); - OptionUtils.forEachOptions(config, option -> { - if (option.changed()) { - option.forgetPendingValue(); - } - }); - config.saveFunction().run(); - - flags.forEach(flag -> flag.accept(client)); - } else close(); - }); - actionDim.expand(-actionDim.width() / 2 - 2, 0).move(-actionDim.width() / 2 - 2, -22); - cancelResetButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), Text.empty(), (btn) -> { - if (pendingChanges()) { - OptionUtils.forEachOptions(config, Option::forgetPendingValue); - close(); - } else { - OptionUtils.forEachOptions(config, Option::requestSetDefault); - } - - }); - actionDim.move(actionDim.width() + 4, 0); - undoButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.translatable("yacl.gui.undo"), Text.translatable("yacl.gui.undo.tooltip"), (btn) -> { - OptionUtils.forEachOptions(config, Option::forgetPendingValue); - }); - - searchFieldWidget = new SearchFieldWidget(this, textRenderer, width / 3 / 2 - paddedWidth / 2 + 1, undoButton.getY() - 22, paddedWidth - 2, 18, Text.translatable("gui.recipebook.search_hint"), Text.translatable("gui.recipebook.search_hint")); - - categoryList = new CategoryListWidget(client, this, width, height); - addSelectableChild(categoryList); - - updateActionAvailability(); - addDrawableChild(searchFieldWidget); - addDrawableChild(cancelResetButton); - addDrawableChild(undoButton); - addDrawableChild(finishedSaveButton); - - optionList = new OptionListWidget(this, client, width, height); - addSelectableChild(optionList); - - config.initConsumer().accept(this); - } - - @Override - public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { - renderBackground(matrices); - - super.render(matrices, mouseX, mouseY, delta); - categoryList.render(matrices, mouseX, mouseY, delta); - searchFieldWidget.render(matrices, mouseX, mouseY, delta); - optionList.render(matrices, mouseX, mouseY, delta); - - categoryList.postRender(matrices, mouseX, mouseY, delta); - optionList.postRender(matrices, mouseX, mouseY, delta); - - for (Element child : children()) { - if (child instanceof TooltipButtonWidget tooltipButtonWidget) { - tooltipButtonWidget.renderHoveredTooltip(matrices); - } - } - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (optionList.keyPressed(keyCode, scanCode, modifiers)) { - return true; - } - - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - if (optionList.charTyped(chr, modifiers)) { - return true; - } - - return super.charTyped(chr, modifiers); - } - - public void changeCategory(int idx) { - if (idx == currentCategoryIdx) - return; - - if (idx != -1 && config.categories().get(idx) instanceof PlaceholderCategory placeholderCategory) { - client.setScreen(placeholderCategory.screen().apply(client, this)); - } else { - currentCategoryIdx = idx; - optionList.refreshOptions(); - } - } - - public int getCurrentCategoryIdx() { - return currentCategoryIdx; - } - - private void updateActionAvailability() { - boolean pendingChanges = pendingChanges(); - - undoButton.active = pendingChanges; - finishedSaveButton.setMessage(pendingChanges ? Text.translatable("yacl.gui.save") : Text.translatable("gui.done")); - finishedSaveButton.setTooltip(pendingChanges ? Text.translatable("yacl.gui.save.tooltip") : Text.translatable("yacl.gui.finished.tooltip")); - cancelResetButton.setMessage(pendingChanges ? Text.translatable("gui.cancel") : Text.translatable("controls.reset")); - cancelResetButton.setTooltip(pendingChanges ? Text.translatable("yacl.gui.cancel.tooltip") : Text.translatable("yacl.gui.reset.tooltip")); - } - - @Override - public void tick() { - searchFieldWidget.tick(); - - updateActionAvailability(); - - if (saveButtonMessage != null) { - if (saveButtonMessageTime > 140) { - saveButtonMessage = null; - saveButtonTooltipMessage = null; - saveButtonMessageTime = 0; - } else { - saveButtonMessageTime++; - finishedSaveButton.setMessage(saveButtonMessage); - if (saveButtonTooltipMessage != null) { - finishedSaveButton.setTooltip(saveButtonTooltipMessage); - } - } - } - } - - private void setSaveButtonMessage(Text message, Text tooltip) { - saveButtonMessage = message; - saveButtonTooltipMessage = tooltip; - saveButtonMessageTime = 0; - } - - private boolean pendingChanges() { - AtomicBoolean pendingChanges = new AtomicBoolean(false); - OptionUtils.consumeOptions(config, (option) -> { - if (option.changed()) { - pendingChanges.set(true); - return true; - } - return false; - }); - - return pendingChanges.get(); - } - - @Override - public boolean shouldCloseOnEsc() { - if (pendingChanges()) { - setSaveButtonMessage(Text.translatable("yacl.gui.save_before_exit").formatted(Formatting.RED), Text.translatable("yacl.gui.save_before_exit.tooltip")); - return false; - } - return true; - } - - @Override - public void close() { - client.setScreen(parent); - } - - public static void renderMultilineTooltip(MatrixStack matrices, TextRenderer textRenderer, MultilineText text, int centerX, int yAbove, int yBelow, int screenWidth, int screenHeight) { - if (text.count() > 0) { - int maxWidth = text.getMaxWidth(); - int lineHeight = textRenderer.fontHeight + 1; - int height = text.count() * lineHeight - 1; - - int belowY = yBelow + 12; - int aboveY = yAbove - height + 12; - int maxBelow = screenHeight - (belowY + height); - int minAbove = aboveY - height; - int y = belowY; - if (maxBelow < -8) - y = maxBelow > minAbove ? belowY : aboveY; - - int x = Math.max(centerX - text.getMaxWidth() / 2 - 12, -6); - - int drawX = x + 12; - int drawY = y - 12; - - matrices.push(); - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder bufferBuilder = tessellator.getBuffer(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); - Matrix4f matrix4f = matrices.peek().getPositionMatrix(); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 4, drawX + maxWidth + 3, drawY - 3, 400, -267386864, -267386864); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY + height + 3, drawX + maxWidth + 3, drawY + height + 4, 400, -267386864, -267386864); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3, drawX + maxWidth + 3, drawY + height + 3, 400, -267386864, -267386864); - fillGradient(matrix4f, bufferBuilder, drawX - 4, drawY - 3, drawX - 3, drawY + height + 3, 400, -267386864, -267386864); - fillGradient(matrix4f, bufferBuilder, drawX + maxWidth + 3, drawY - 3, drawX + maxWidth + 4, drawY + height + 3, 400, -267386864, -267386864); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3 + 1, drawX - 3 + 1, drawY + height + 3 - 1, 400, 1347420415, 1344798847); - fillGradient(matrix4f, bufferBuilder, drawX + maxWidth + 2, drawY - 3 + 1, drawX + maxWidth + 3, drawY + height + 3 - 1, 400, 1347420415, 1344798847); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY - 3, drawX + maxWidth + 3, drawY - 3 + 1, 400, 1347420415, 1347420415); - fillGradient(matrix4f, bufferBuilder, drawX - 3, drawY + height + 2, drawX + maxWidth + 3, drawY + height + 3, 400, 1344798847, 1344798847); - RenderSystem.enableDepthTest(); - RenderSystem.disableTexture(); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); - RenderSystem.disableBlend(); - RenderSystem.enableTexture(); - matrices.translate(0.0, 0.0, 400.0); - - text.drawWithShadow(matrices, drawX, drawY, lineHeight, -1); - - matrices.pop(); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java deleted file mode 100644 index b8e2cd1..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.ButtonOption; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; - -import java.util.function.BiConsumer; - -/** - * Simple controller that simply runs the button action on press - * and renders a {@link} Text on the right. - */ -public class ActionController implements Controller> { - public static final Text DEFAULT_TEXT = Text.translatable("yacl.control.action.execute"); - - private final ButtonOption option; - private final Text text; - - /** - * Constructs an action controller - * with the default formatter of {@link ActionController#DEFAULT_TEXT} - * - * @param option bound option - */ - public ActionController(ButtonOption option) { - this(option, DEFAULT_TEXT); - } - - /** - * Constructs an action controller - * - * @param option bound option - * @param text text to display - */ - public ActionController(ButtonOption option, Text text) { - this.option = option; - this.text = text; - - } - - /** - * {@inheritDoc} - */ - @Override - public ButtonOption option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return text; - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new ActionControllerElement(this, screen, widgetDimension); - } - - public static class ActionControllerElement extends ControllerWidget { - private final String buttonString; - - public ActionControllerElement(ActionController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - buttonString = control.formatValue().getString().toLowerCase(); - } - - public void executeAction() { - playDownSound(); - control.option().action().accept(screen, control.option()); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (isMouseOver(mouseX, mouseY) && isAvailable()) { - executeAction(); - return true; - } - return false; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) { - return false; - } - - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - executeAction(); - return true; - } - - return false; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } - - @Override - public boolean canReset() { - return false; - } - - @Override - public boolean matchesSearch(String query) { - return super.matchesSearch(query) || buttonString.contains(query); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java b/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java deleted file mode 100644 index 7037ff5..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java +++ /dev/null @@ -1,156 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.lwjgl.glfw.GLFW; - -import java.util.function.Function; - -/** - * This controller renders a simple formatted {@link Text} - */ -public class BooleanController implements Controller { - - public static final Function ON_OFF_FORMATTER = (state) -> - state - ? Text.translatable("options.on") - : Text.translatable("options.off"); - - public static final Function TRUE_FALSE_FORMATTER = (state) -> - state - ? Text.translatable("yacl.control.boolean.true") - : Text.translatable("yacl.control.boolean.false"); - - public static final Function YES_NO_FORMATTER = (state) -> - state - ? Text.translatable("gui.yes") - : Text.translatable("gui.no"); - - private final Option option; - private final Function valueFormatter; - private final boolean coloured; - - /** - * Constructs a tickbox controller - * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} - * - * @param option bound option - */ - public BooleanController(Option option) { - this(option, ON_OFF_FORMATTER, false); - } - - /** - * Constructs a tickbox controller - * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} - * - * @param option bound option - * @param coloured value format is green or red depending on the state - */ - public BooleanController(Option option, boolean coloured) { - this(option, ON_OFF_FORMATTER, coloured); - } - - /** - * Constructs a tickbox controller - * - * @param option bound option - * @param valueFormatter format value into any {@link Text} - * @param coloured value format is green or red depending on the state - */ - public BooleanController(Option option, Function valueFormatter, boolean coloured) { - this.option = option; - this.valueFormatter = valueFormatter; - this.coloured = coloured; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * Value format is green or red depending on the state - */ - public boolean coloured() { - return coloured; - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new BooleanControllerElement(this, screen, widgetDimension); - } - - public static class BooleanControllerElement extends ControllerWidget { - public BooleanControllerElement(BooleanController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - @Override - protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { - - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || !isAvailable()) - return false; - - toggleSetting(); - return true; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } - - public void toggleSetting() { - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); - } - - @Override - protected Text getValueText() { - if (control.coloured()) { - return super.getValueText().copy().formatted(control.option().pendingValue() ? Formatting.GREEN : Formatting.RED); - } - - return super.getValueText(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) { - return false; - } - - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - toggleSetting(); - return true; - } - - return false; - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java b/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java deleted file mode 100644 index 0a83fbe..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java +++ /dev/null @@ -1,203 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.string.IStringController; -import dev.isxander.yacl.gui.controllers.string.StringControllerElement; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; - -import java.awt.*; -import java.util.List; - -/** - * A color controller that uses a hex color field as input. - */ -public class ColorController implements IStringController { - private final Option option; - private final boolean allowAlpha; - - /** - * Constructs a color controller with {@link ColorController#allowAlpha()} defaulting to false - * - * @param option bound option - */ - public ColorController(Option option) { - this(option, false); - } - - /** - * Constructs a color controller - * - * @param option bound option - * @param allowAlpha allows the color input to accept alpha values - */ - public ColorController(Option option, boolean allowAlpha) { - this.option = option; - this.allowAlpha = allowAlpha; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - public boolean allowAlpha() { - return allowAlpha; - } - - @Override - public String getString() { - return formatValue().getString(); - } - - @Override - public Text formatValue() { - MutableText text = Text.literal("#"); - text.append(Text.literal(toHex(option().pendingValue().getRed())).formatted(Formatting.RED)); - text.append(Text.literal(toHex(option().pendingValue().getGreen())).formatted(Formatting.GREEN)); - text.append(Text.literal(toHex(option().pendingValue().getBlue())).formatted(Formatting.BLUE)); - if (allowAlpha()) text.append(toHex(option().pendingValue().getAlpha())); - return text; - } - - private String toHex(int value) { - String hex = Integer.toString(value, 16).toUpperCase(); - if (hex.length() == 1) - hex = "0" + hex; - return hex; - } - - @Override - public void setFromString(String value) { - if (value.startsWith("#")) - value = value.substring(1); - - int red = Integer.parseInt(value.substring(0, 2), 16); - int green = Integer.parseInt(value.substring(2, 4), 16); - int blue = Integer.parseInt(value.substring(4, 6), 16); - - if (allowAlpha()) { - int alpha = Integer.parseInt(value.substring(6, 8), 16); - option().requestSet(new Color(red, green, blue, alpha)); - } else { - option().requestSet(new Color(red, green, blue)); - } - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new ColorControllerElement(this, screen, widgetDimension); - } - - public static class ColorControllerElement extends StringControllerElement { - private final ColorController colorController; - - protected MutableDimension colorPreviewDim; - - private final List allowedChars; - - public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - this.colorController = control; - this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); - } - - @Override - protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (isHovered()) { - colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); - super.drawValueText(matrices, mouseX, mouseY, delta); - } - - DrawableHelper.fill(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); - drawOutline(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); - } - - @Override - public void write(String string) { - for (char chr : string.toCharArray()) { - if (!allowedChars.contains(Character.toLowerCase(chr))) { - return; - } - } - - if (caretPos == 0) - return; - - string = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); - - inputField.replace(caretPos, caretPos + string.length(), string); - caretPos += string.length(); - setSelectionLength(); - - updateControl(); - } - - @Override - protected void doBackspace() { - if (caretPos > 1) { - inputField.setCharAt(caretPos - 1, '0'); - caretPos--; - updateControl(); - } - } - - @Override - protected void doDelete() { - - } - - @Override - protected boolean canUseShortcuts() { - return false; - } - - protected void setSelectionLength() { - selectionLength = caretPos < inputField.length() && caretPos > 0 ? 1 : 0; - } - - @Override - protected int getDefaultCarotPos() { - return colorController.allowAlpha() ? 3 : 1; - } - - @Override - public void setDimension(Dimension dim) { - super.setDimension(dim); - - int previewSize = (dim.height() - getYPadding() * 2) / 2; - colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (super.keyPressed(keyCode, scanCode, modifiers)) { - caretPos = Math.max(1, caretPos); - setSelectionLength(); - return true; - } - return false; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (super.mouseClicked(mouseX, mouseY, button)) { - caretPos = Math.max(1, caretPos); - setSelectionLength(); - return true; - } - return false; - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java deleted file mode 100644 index c7f9e97..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; - -public abstract class ControllerWidget> extends AbstractWidget { - protected final T control; - protected MultilineText wrappedTooltip; - protected final YACLScreen screen; - - protected boolean focused = false; - protected boolean hovered = false; - - protected final Text modifiedOptionName; - protected final String optionNameString; - - public ControllerWidget(T control, YACLScreen screen, Dimension dim) { - super(dim); - this.control = control; - this.screen = screen; - control.option().addListener((opt, pending) -> updateTooltip()); - updateTooltip(); - this.modifiedOptionName = control.option().name().copy().formatted(Formatting.ITALIC); - this.optionNameString = control.option().name().getString().toLowerCase(); - } - - @Override - public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { - hovered = isMouseOver(mouseX, mouseY); - - Text name = control.option().changed() ? modifiedOptionName : control.option().name(); - String nameString = name.getString(); - - boolean firstIter = true; - while (textRenderer.getWidth(nameString) > getDimension().width() - getControlWidth() - getXPadding() - 7) { - nameString = nameString.substring(0, Math.max(nameString.length() - (firstIter ? 2 : 5), 0)).trim(); - nameString += "..."; - - firstIter = false; - } - - Text shortenedName = Text.literal(nameString).fillStyle(name.getStyle()); - - drawButtonRect(matrices, getDimension().x(), getDimension().y(), getDimension().xLimit(), getDimension().yLimit(), isHovered(), isAvailable()); - matrices.push(); - matrices.translate(getDimension().x() + getXPadding(), getTextY(), 0); - textRenderer.drawWithShadow(matrices, shortenedName, 0, 0, getValueColor()); - matrices.pop(); - - drawValueText(matrices, mouseX, mouseY, delta); - if (isHovered()) { - drawHoveredControl(matrices, mouseX, mouseY, delta); - } - } - - @Override - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (hovered) { - YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); - } - } - - protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { - - } - - protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { - Text valueText = getValueText(); - matrices.push(); - matrices.translate(getDimension().xLimit() - textRenderer.getWidth(valueText) - getXPadding(), getTextY(), 0); - textRenderer.drawWithShadow(matrices, valueText, 0, 0, getValueColor()); - matrices.pop(); - } - - private void updateTooltip() { - this.wrappedTooltip = MultilineText.create(textRenderer, control.option().tooltip(), screen.width / 3 * 2 - 10); - } - - protected int getControlWidth() { - return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); - } - - public boolean isHovered() { - return isAvailable() && (hovered || focused); - } - - protected abstract int getHoveredControlWidth(); - - protected int getUnhoveredControlWidth() { - return textRenderer.getWidth(getValueText()); - } - - protected int getXPadding() { - return 5; - } - - protected int getYPadding() { - return 2; - } - - protected Text getValueText() { - return control.formatValue(); - } - - protected boolean isAvailable() { - return control.option().available(); - } - - protected int getValueColor() { - return isAvailable() ? -1 : inactiveColor; - } - - @Override - public boolean canReset() { - return true; - } - - protected void drawOutline(MatrixStack matrices, int x1, int y1, int x2, int y2, int width, int color) { - DrawableHelper.fill(matrices, x1, y1, x2, y1 + width, color); - DrawableHelper.fill(matrices, x2, y1, x2 - width, y2, color); - DrawableHelper.fill(matrices, x1, y2, x2, y2 - width, color); - DrawableHelper.fill(matrices, x1, y1, x1 + width, y2, color); - } - - protected float getTextY() { - return getDimension().y() + getDimension().height() / 2f - textRenderer.fontHeight / 2f; - } - - @Override - public boolean changeFocus(boolean lookForwards) { - if (!isAvailable()) - return false; - - this.focused = !this.focused; - return this.focused; - } - - @Override - public void unfocus() { - this.focused = false; - } - - @Override - public boolean matchesSearch(String query) { - return optionNameString.contains(query.toLowerCase()); - } - - @Override - public SelectionType getType() { - return focused ? SelectionType.FOCUSED : isHovered() ? SelectionType.HOVERED : SelectionType.NONE; - } - - @Override - public void appendNarrations(NarrationMessageBuilder builder) { - - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java b/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java deleted file mode 100644 index ebad4ae..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.NameableEnum; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.gui.controllers.cycling.CyclingListController; -import net.minecraft.text.Text; -import net.minecraft.util.TranslatableOption; - -import java.util.Arrays; -import java.util.function.Function; - -@Deprecated -public class EnumController> extends CyclingListController { - public static > Function getDefaultFormatter() { - return value -> { - if (value instanceof NameableEnum nameableEnum) - return nameableEnum.getDisplayName(); - if (value instanceof TranslatableOption translatableOption) - return translatableOption.getText(); - return Text.of(value.toString()); - }; - } - - public EnumController(Option option) { - this(option, getDefaultFormatter()); - } - - public EnumController(Option option, Function valueFormatter) { - this(option, valueFormatter, option.typeClass().getEnumConstants()); - } - - public EnumController(Option option, Function valueFormatter, T[] availableValues) { - super(option, Arrays.asList(availableValues), valueFormatter); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java b/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java deleted file mode 100644 index 8369680..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java +++ /dev/null @@ -1,144 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.item.ItemStack; -import net.minecraft.text.*; - -import java.util.List; - -/** - * Simply renders some text as a label. - */ -public class LabelController implements Controller { - private final Option option; - /** - * Constructs a label controller - * - * @param option bound option - */ - public LabelController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - @Override - public Text formatValue() { - return option().pendingValue(); - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new LabelControllerElement(screen, widgetDimension); - } - - public class LabelControllerElement extends AbstractWidget { - private List wrappedText; - protected MultilineText wrappedTooltip; - - protected final YACLScreen screen; - - public LabelControllerElement(YACLScreen screen, Dimension dim) { - super(dim); - this.screen = screen; - option().addListener((opt, pending) -> updateTooltip()); - updateTooltip(); - updateText(); - } - - @Override - public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { - updateText(); - - float y = getDimension().y(); - for (OrderedText text : wrappedText) { - textRenderer.drawWithShadow(matrices, text, getDimension().x(), y + getYPadding(), option().available() ? -1 : 0xFFA0A0A0); - y += textRenderer.fontHeight; - } - } - - @Override - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (isMouseOver(mouseX, mouseY)) { - YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); - - Style style = getStyle(mouseX, mouseY); - if (style != null && style.getHoverEvent() != null) { - HoverEvent hoverEvent = style.getHoverEvent(); - HoverEvent.ItemStackContent itemStackContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ITEM); - if (itemStackContent != null) { - ItemStack stack = itemStackContent.asStack(); - screen.renderTooltip(matrices, screen.getTooltipFromItem(stack), stack.getTooltipData(), mouseX, mouseY); - } else { - HoverEvent.EntityContent entityContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ENTITY); - if (entityContent != null) { - if (this.client.options.advancedItemTooltips) { - screen.renderTooltip(matrices, entityContent.asTooltip(), mouseX, mouseY); - } - } else { - Text text = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT); - if (text != null) { - MultilineText multilineText = MultilineText.create(textRenderer, text, getDimension().width()); - YACLScreen.renderMultilineTooltip(matrices, textRenderer, multilineText, getDimension().centerX(), getDimension().y(), getDimension().yLimit(), screen.width, screen.height); - } - } - } - } - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY)) - return false; - - Style style = getStyle((int) mouseX, (int) mouseY); - return screen.handleTextClick(style); - } - - protected Style getStyle(int mouseX, int mouseY) { - if (!getDimension().isPointInside(mouseX, mouseY)) - return null; - - int x = mouseX - getDimension().x(); - int y = mouseY - getDimension().y() - getYPadding(); - int line = y / textRenderer.fontHeight; - - if (x < 0 || x > getDimension().xLimit()) return null; - if (y < 0 || y > getDimension().yLimit()) return null; - if (line < 0 || line >= wrappedText.size()) return null; - - return textRenderer.getTextHandler().getStyleAt(wrappedText.get(line), x); - } - - private int getYPadding() { - return 3; - } - - private void updateText() { - wrappedText = textRenderer.wrapLines(formatValue(), getDimension().width()); - setDimension(getDimension().withHeight(wrappedText.size() * textRenderer.fontHeight + getYPadding() * 2)); - } - - private void updateTooltip() { - this.wrappedTooltip = MultilineText.create(textRenderer, option().tooltip(), screen.width / 3 * 2 - 10); - } - - @Override - public boolean matchesSearch(String query) { - return formatValue().getString().toLowerCase().contains(query.toLowerCase()); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java deleted file mode 100644 index 340983d..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; - -/** - * This controller renders a tickbox - */ -public class TickBoxController implements Controller { - private final Option option; - - /** - * Constructs a tickbox controller - * - * @param option bound option - */ - public TickBoxController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return Text.empty(); - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new TickBoxControllerElement(this, screen, widgetDimension); - } - - public static class TickBoxControllerElement extends ControllerWidget { - public TickBoxControllerElement(TickBoxController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - @Override - protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { - int outlineSize = 10; - int outlineX1 = getDimension().xLimit() - getXPadding() - outlineSize; - int outlineY1 = getDimension().centerY() - outlineSize / 2; - int outlineX2 = getDimension().xLimit() - getXPadding(); - int outlineY2 = getDimension().centerY() + outlineSize / 2; - - int color = getValueColor(); - int shadowColor = multiplyColor(color, 0.25f); - - drawOutline(matrices, outlineX1 + 1, outlineY1 + 1, outlineX2 + 1, outlineY2 + 1, 1, shadowColor); - drawOutline(matrices, outlineX1, outlineY1, outlineX2, outlineY2, 1, color); - if (control.option().pendingValue()) { - DrawableHelper.fill(matrices, outlineX1 + 3, outlineY1 + 3, outlineX2 - 1, outlineY2 - 1, shadowColor); - DrawableHelper.fill(matrices, outlineX1 + 2, outlineY1 + 2, outlineX2 - 2, outlineY2 - 2, color); - } - } - - @Override - protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (!isHovered()) - drawHoveredControl(matrices, mouseX, mouseY, delta); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || !isAvailable()) - return false; - - toggleSetting(); - return true; - } - - @Override - protected int getHoveredControlWidth() { - return 10; - } - - @Override - protected int getUnhoveredControlWidth() { - return 10; - } - - public void toggleSetting() { - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) { - return false; - } - - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - toggleSetting(); - return true; - } - - return false; - } - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java b/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java deleted file mode 100644 index ab0a9c3..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ControllerWidget; -import net.minecraft.client.gui.screen.Screen; -import org.lwjgl.glfw.GLFW; - -public class CyclingControllerElement extends ControllerWidget> { - - public CyclingControllerElement(ICyclingController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - public void cycleValue(int increment) { - int targetIdx = control.getPendingValue() + increment; - if (targetIdx >= control.getCycleLength()) { - targetIdx -= control.getCycleLength(); - } else if (targetIdx < 0) { - targetIdx += control.getCycleLength(); - } - control.setPendingValue(targetIdx); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || (button != 0 && button != 1) || !isAvailable()) - return false; - - playDownSound(); - cycleValue(button == 1 || Screen.hasShiftDown() || Screen.hasControlDown() ? -1 : 1); - - return true; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) - return false; - - switch (keyCode) { - case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> - cycleValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> - cycleValue(1); - case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_SPACE, GLFW.GLFW_KEY_KP_ENTER -> - cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); - default -> { - return false; - } - } - - return true; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java b/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java deleted file mode 100644 index 3b14066..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java +++ /dev/null @@ -1,79 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; - -import java.util.function.Function; - -/** - * A controller where once clicked, cycles through elements - * in the provided list. - */ -public class CyclingListController implements ICyclingController { - private final Option option; - private final Function valueFormatter; - private final ImmutableList values; - - /** - * Constructs a {@link CyclingListController}, with a default - * value formatter of {@link Object#toString()}. - * @param option option of which to bind the controller to - * @param values the values to cycle through - */ - public CyclingListController(Option option, Iterable values) { - this(option, values, value -> Text.of(value.toString())); - } - - /** - * Constructs a {@link CyclingListController} - * @param option option of which to bind the controller to - * @param values the values to cycle through - * @param valueFormatter function of how to convert each value to a string to display - */ - public CyclingListController(Option option, Iterable values, Function valueFormatter) { - this.option = option; - this.valueFormatter = valueFormatter; - this.values = ImmutableList.copyOf(values); - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(int ordinal) { - option().requestSet(values.get(ordinal)); - } - - /** - * {@inheritDoc} - */ - @Override - public int getPendingValue() { - return values.indexOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public int getCycleLength() { - return values.size(); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java b/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java deleted file mode 100644 index bc9f46d..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import dev.isxander.yacl.api.NameableEnum; -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; -import net.minecraft.util.TranslatableOption; - -import java.util.Arrays; -import java.util.function.Function; - -/** - * Simple controller type that displays the enum on the right. - *

- * Cycles forward with left click, cycles backward with right click or when shift is held - * - * @param enum type - */ -public class EnumController> extends CyclingListController { - public static > Function getDefaultFormatter() { - return value -> { - if (value instanceof NameableEnum nameableEnum) - return nameableEnum.getDisplayName(); - if (value instanceof TranslatableOption translatableOption) - return translatableOption.getText(); - return Text.of(value.toString()); - }; - } - - /** - * Constructs a cycling enum controller with a default value formatter and all values being available. - * The default value formatter first searches if the - * enum is a {@link NameableEnum} or {@link TranslatableOption} else, just uses {@link Enum#toString()} - * - * @param option bound option - */ - public EnumController(Option option) { - this(option, getDefaultFormatter()); - } - - /** - * Constructs a cycling enum controller with all values being available. - * - * @param option bound option - * @param valueFormatter format the enum into any {@link Text} - */ - public EnumController(Option option, Function valueFormatter) { - this(option, valueFormatter, option.typeClass().getEnumConstants()); - } - - /** - * Constructs a cycling enum controller. - * - * @param option bound option - * @param valueFormatter format the enum into any {@link Text} - * @param availableValues all enum constants that can be cycled through - */ - public EnumController(Option option, Function valueFormatter, T[] availableValues) { - super(option, Arrays.asList(availableValues), valueFormatter); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java b/src/main/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java deleted file mode 100644 index 081b572..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; - -/** - * This interface simply generifies setting and getting of - * the pending value, using an ordinal so elements can cycle through - * without knowing the content. - */ -public interface ICyclingController extends Controller { - /** - * Sets the pending value to whatever corresponds to the ordinal - * @param ordinal index of element to set - */ - void setPendingValue(int ordinal); - - /** - * Gets the pending ordinal that corresponds to the actual value - * @return ordinal - */ - int getPendingValue(); - - /** - * Allows the element when it should wrap-around back to zeroth ordinal - */ - int getCycleLength(); - - /** - * {@inheritDoc} - */ - @Override - default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new CyclingControllerElement(this, screen, widgetDimension); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/package-info.java b/src/main/java/dev/isxander/yacl/gui/controllers/package-info.java deleted file mode 100644 index 12ce86b..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This package contains all {@link dev.isxander.yacl.api.Controller} implementations - * - *

    - *
  • For numbers: {@link dev.isxander.yacl.gui.controllers.slider}
  • - *
  • For booleans: {@link dev.isxander.yacl.gui.controllers.TickBoxController}
  • - *
  • For lists/enums: {@link dev.isxander.yacl.gui.controllers.cycling}
  • - *
  • For strings: {@link dev.isxander.yacl.gui.controllers.string.StringController}
  • - *
  • For {@link dev.isxander.yacl.api.ButtonOption}: {@link dev.isxander.yacl.gui.controllers.ActionController}
  • - *
- */ -package dev.isxander.yacl.gui.controllers; diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java deleted file mode 100644 index b530e8c..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java +++ /dev/null @@ -1,114 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; - -import java.util.function.Function; - -/** - * {@link ISliderController} for doubles. - */ -public class DoubleSliderController implements ISliderController { - /** - * Formats doubles to two decimal places - */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value)); - - private final Option option; - - private final double min, max, interval; - - private final Function valueFormatter; - - /** - * Constructs a {@link ISliderController} for doubles - * using the default value formatter {@link DoubleSliderController#DEFAULT_FORMATTER}. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - */ - public DoubleSliderController(Option option, double min, double max, double interval) { - this(option, min, max, interval, DEFAULT_FORMATTER); - } - - /** - * Constructs a {@link ISliderController} for doubles. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - * @param valueFormatter format the value into any {@link Text} - */ - public DoubleSliderController(Option option, double min, double max, double interval, Function valueFormatter) { - Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); - Validate.isTrue(interval > 0, "`interval` must be more than 0"); - Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); - - this.option = option; - this.min = min; - this.max = max; - this.interval = interval; - this.valueFormatter = valueFormatter; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return max; - } - - /** - * {@inheritDoc} - */ - @Override - public double interval() { - return interval; - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet(value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } - -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java deleted file mode 100644 index d7c203e..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java +++ /dev/null @@ -1,114 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; - -import java.util.function.Function; - -/** - * {@link ISliderController} for floats. - */ -public class FloatSliderController implements ISliderController { - /** - * Formats floats to one decimal place - */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value)); - - private final Option option; - - private final float min, max, interval; - - private final Function valueFormatter; - - /** - * Constructs a {@link ISliderController} for floats - * using the default value formatter {@link FloatSliderController#DEFAULT_FORMATTER}. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - */ - public FloatSliderController(Option option, float min, float max, float interval) { - this(option, min, max, interval, DEFAULT_FORMATTER); - } - - /** - * Constructs a {@link ISliderController} for floats. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - * @param valueFormatter format the value into any {@link Text} - */ - public FloatSliderController(Option option, float min, float max, float interval, Function valueFormatter) { - Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); - Validate.isTrue(interval > 0, "`interval` must be more than 0"); - Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); - - this.option = option; - this.min = min; - this.max = max; - this.interval = interval; - this.valueFormatter = valueFormatter; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return max; - } - - /** - * {@inheritDoc} - */ - @Override - public double interval() { - return interval; - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((float) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } - -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java deleted file mode 100644 index aa3c18f..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; - -/** - * Simple custom slider implementation that shifts the current value across when shown. - *

- * For simplicity, {@link SliderControllerElement} works in doubles so each - * {@link ISliderController} must cast to double. This is to get around re-writing the element for every type. - */ -public interface ISliderController extends Controller { - /** - * Gets the minimum value for the slider - */ - double min(); - - /** - * Gets the maximum value for the slider - */ - double max(); - - /** - * Gets the interval (or step size) for the slider. - */ - double interval(); - - /** - * Gets the range of the slider. - */ - default double range() { - return max() - min(); - } - - /** - * Sets the {@link dev.isxander.yacl.api.Option}'s pending value - */ - void setPendingValue(double value); - - /** - * Gets the {@link dev.isxander.yacl.api.Option}'s pending value - */ - double pendingValue(); - - /** - * {@inheritDoc} - */ - @Override - default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new SliderControllerElement(this, screen, widgetDimension, min(), max(), interval()); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java deleted file mode 100644 index a8bca7c..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java +++ /dev/null @@ -1,111 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; - -import java.util.function.Function; - -/** - * {@link ISliderController} for integers. - */ -public class IntegerSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); - - private final Option option; - - private final int min, max, interval; - - private final Function valueFormatter; - - /** - * Constructs a {@link ISliderController} for integers - * using the default value formatter {@link IntegerSliderController#DEFAULT_FORMATTER}. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - */ - public IntegerSliderController(Option option, int min, int max, int interval) { - this(option, min, max, interval, DEFAULT_FORMATTER); - } - - /** - * Constructs a {@link ISliderController} for integers. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - * @param valueFormatter format the value into any {@link Text} - */ - public IntegerSliderController(Option option, int min, int max, int interval, Function valueFormatter) { - Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); - Validate.isTrue(interval > 0, "`interval` must be more than 0"); - Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); - - this.option = option; - this.min = min; - this.max = max; - this.interval = interval; - this.valueFormatter = valueFormatter; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return max; - } - - /** - * {@inheritDoc} - */ - @Override - public double interval() { - return interval; - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((int) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } - -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java deleted file mode 100644 index 50559d5..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java +++ /dev/null @@ -1,111 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; -import org.apache.commons.lang3.Validate; - -import java.util.function.Function; - -/** - * {@link ISliderController} for longs. - */ -public class LongSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); - - private final Option option; - - private final long min, max, interval; - - private final Function valueFormatter; - - /** - * Constructs a {@link ISliderController} for longs - * using the default value formatter {@link LongSliderController#DEFAULT_FORMATTER}. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - */ - public LongSliderController(Option option, long min, long max, long interval) { - this(option, min, max, interval, DEFAULT_FORMATTER); - } - - /** - * Constructs a {@link ISliderController} for longs. - * - * @param option bound option - * @param min minimum slider value - * @param max maximum slider value - * @param interval step size (or increments) for the slider - * @param valueFormatter format the value into any {@link Text} - */ - public LongSliderController(Option option, long min, long max, long interval, Function valueFormatter) { - Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); - Validate.isTrue(interval > 0, "`interval` must be more than 0"); - Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); - - this.option = option; - this.min = min; - this.max = max; - this.interval = interval; - this.valueFormatter = valueFormatter; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Text formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return max; - } - - /** - * {@inheritDoc} - */ - @Override - public double interval() { - return interval; - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((long) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } - -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java deleted file mode 100644 index 913cc00..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java +++ /dev/null @@ -1,160 +0,0 @@ -package dev.isxander.yacl.gui.controllers.slider; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ControllerWidget; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.MathHelper; -import org.lwjgl.glfw.GLFW; - -public class SliderControllerElement extends ControllerWidget> { - private final double min, max, interval; - - private float interpolation; - - private Dimension sliderBounds; - - private boolean mouseDown = false; - - public SliderControllerElement(ISliderController option, YACLScreen screen, Dimension dim, double min, double max, double interval) { - super(option, screen, dim); - this.min = min; - this.max = max; - this.interval = interval; - setDimension(dim); - } - - @Override - public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { - super.render(matrices, mouseX, mouseY, delta); - - calculateInterpolation(); - } - - @Override - protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { - // track - DrawableHelper.fill(matrices, sliderBounds.x(), sliderBounds.centerY() - 1, sliderBounds.xLimit(), sliderBounds.centerY(), -1); - // track shadow - DrawableHelper.fill(matrices, sliderBounds.x() + 1, sliderBounds.centerY(), sliderBounds.xLimit() + 1, sliderBounds.centerY() + 1, 0xFF404040); - - // thumb shadow - DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2 + 1, sliderBounds.y() + 1, getThumbX() + getThumbWidth() / 2 + 1, sliderBounds.yLimit() + 1, 0xFF404040); - // thumb - DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2, sliderBounds.y(), getThumbX() + getThumbWidth() / 2, sliderBounds.yLimit(), -1); - } - - @Override - protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { - matrices.push(); - if (isHovered()) - matrices.translate(-(sliderBounds.width() + 6 + getThumbWidth() / 2f), 0, 0); - super.drawValueText(matrices, mouseX, mouseY, delta); - matrices.pop(); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isAvailable() || button != 0 || !sliderBounds.isPointInside((int) mouseX, (int) mouseY)) - return false; - - mouseDown = true; - - setValueFromMouse(mouseX); - return true; - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (!isAvailable() || button != 0 || !mouseDown) - return false; - - setValueFromMouse(mouseX); - return true; - } - - public void incrementValue(double amount) { - control.setPendingValue(MathHelper.clamp(control.pendingValue() + interval * amount, min, max)); - calculateInterpolation(); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - if (!isAvailable() || (!isMouseOver(mouseX, mouseY)) || (!Screen.hasShiftDown() && !Screen.hasControlDown())) - return false; - - incrementValue(amount); - return true; - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - if (isAvailable() && mouseDown) - playDownSound(); - mouseDown = false; - - return super.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) - return false; - - switch (keyCode) { - case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> incrementValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); - default -> { - return false; - } - } - - return true; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - return super.isMouseOver(mouseX, mouseY) || mouseDown; - } - - protected void setValueFromMouse(double mouseX) { - double value = (mouseX - sliderBounds.x()) / sliderBounds.width() * control.range(); - control.setPendingValue(roundToInterval(value)); - calculateInterpolation(); - } - - protected double roundToInterval(double value) { - return MathHelper.clamp(min + (interval * Math.round(value / interval)), min, max); // extremely imprecise, requires clamping - } - - @Override - protected int getHoveredControlWidth() { - return sliderBounds.width() + getUnhoveredControlWidth() + 6 + getThumbWidth() / 2; - } - - protected void calculateInterpolation() { - interpolation = MathHelper.clamp((float) ((control.pendingValue() - control.min()) * 1 / control.range()), 0f, 1f); - } - - @Override - public void setDimension(Dimension dim) { - super.setDimension(dim); - sliderBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - getThumbWidth() / 2 - dim.width() / 3, dim.centerY() - 5, dim.width() / 3, 10); - } - - protected int getThumbX() { - return (int) (sliderBounds.x() + sliderBounds.width() * interpolation); - } - - protected int getThumbWidth() { - return 4; - } - - @Override - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (super.isMouseOver(mouseX, mouseY)) - super.postRender(matrices, mouseX, mouseY, delta); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/package-info.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/package-info.java deleted file mode 100644 index bff0d57..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This package contains implementations of sliders for different number types - *

    - *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.slider.DoubleSliderController}
  • - *
  • For floats: {@link dev.isxander.yacl.gui.controllers.slider.FloatSliderController}
  • - *
  • For integers: {@link dev.isxander.yacl.gui.controllers.slider.IntegerSliderController}
  • - *
  • For longs: {@link dev.isxander.yacl.gui.controllers.slider.LongSliderController}
  • - *
- */ -package dev.isxander.yacl.gui.controllers.slider; diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java deleted file mode 100644 index 41843b8..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import net.minecraft.text.Text; - -/** - * A controller that can be any type but can input and output a string. - */ -public interface IStringController extends Controller { - /** - * Gets the option's pending value as a string. - * - * @see Option#pendingValue() - */ - String getString(); - - /** - * Sets the option's pending value from a string. - * - * @see Option#requestSet(Object) - */ - void setFromString(String value); - - /** - * {@inheritDoc} - */ - @Override - default Text formatValue() { - return Text.of(getString()); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java deleted file mode 100644 index 0caaa93..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; - -/** - * A custom text field implementation for strings. - */ -public class StringController implements IStringController { - private final Option option; - - /** - * Constructs a string controller - * - * @param option bound option - */ - public StringController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - @Override - public String getString() { - return option().pendingValue(); - } - - @Override - public void setFromString(String value) { - option().requestSet(value); - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new StringControllerElement(this, screen, widgetDimension); - } -} diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java deleted file mode 100644 index 0c3b7c9..0000000 --- a/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java +++ /dev/null @@ -1,283 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ControllerWidget; -import net.minecraft.client.gui.DrawableHelper; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.lwjgl.glfw.GLFW; - -public class StringControllerElement extends ControllerWidget> { - protected StringBuilder inputField; - protected Dimension inputFieldBounds; - protected boolean inputFieldFocused; - - protected int caretPos; - protected int selectionLength; - - protected float ticks; - - private final Text emptyText; - - public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - inputField = new StringBuilder(control.getString()); - inputFieldFocused = false; - selectionLength = 0; - emptyText = Text.literal("Click to type...").formatted(Formatting.GRAY); - setDimension(dim); - } - - @Override - protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { - ticks += delta; - - DrawableHelper.fill(matrices, inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1); - DrawableHelper.fill(matrices, inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040); - - if (inputFieldFocused || focused) { - int caretX = inputFieldBounds.x() + textRenderer.getWidth(control.getString().substring(0, caretPos)) - 1; - if (inputField.isEmpty()) - caretX += inputFieldBounds.width() / 2; - - if (ticks % 20 <= 10) { - DrawableHelper.fill(matrices, caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1); - } - - if (selectionLength != 0) { - int selectionX = inputFieldBounds.x() + textRenderer.getWidth(control.getString().substring(0, caretPos + selectionLength)); - DrawableHelper.fill(matrices, caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF); - } - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (isAvailable() && inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) { - if (!inputFieldFocused) { - inputFieldFocused = true; - caretPos = getDefaultCarotPos(); - } else { - int textWidth = (int) mouseX - inputFieldBounds.x(); - caretPos = textRenderer.trimToWidth(control.getString(), textWidth).length(); - selectionLength = 0; - } - return true; - } else { - inputFieldFocused = false; - } - - return false; - } - - protected int getDefaultCarotPos() { - return inputField.length(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!inputFieldFocused) - return false; - - switch (keyCode) { - case GLFW.GLFW_KEY_ESCAPE -> { - inputFieldFocused = false; - return true; - } - case GLFW.GLFW_KEY_LEFT -> { - if (Screen.hasShiftDown()) { - if (Screen.hasControlDown()) { - int spaceChar = findSpaceIndex(true); - selectionLength += caretPos - spaceChar; - caretPos = spaceChar; - } else if (caretPos > 0) { - caretPos--; - selectionLength += 1; - } - } else { - if (caretPos > 0) - caretPos--; - selectionLength = 0; - } - - return true; - } - case GLFW.GLFW_KEY_RIGHT -> { - if (Screen.hasShiftDown()) { - if (Screen.hasControlDown()) { - int spaceChar = findSpaceIndex(false); - selectionLength -= spaceChar - caretPos; - caretPos = spaceChar; - } else if (caretPos < inputField.length()) { - caretPos++; - selectionLength -= 1; - } - } else { - if (caretPos < inputField.length()) - caretPos++; - selectionLength = 0; - } - - return true; - } - case GLFW.GLFW_KEY_BACKSPACE -> { - doBackspace(); - return true; - } - case GLFW.GLFW_KEY_DELETE -> { - doDelete(); - return true; - } - } - - if (canUseShortcuts()) { - if (Screen.isPaste(keyCode)) { - this.write(client.keyboard.getClipboard()); - return true; - } else if (Screen.isCopy(keyCode) && selectionLength != 0) { - client.keyboard.setClipboard(getSelection()); - return true; - } else if (Screen.isCut(keyCode) && selectionLength != 0) { - client.keyboard.setClipboard(getSelection()); - this.write(""); - return true; - } else if (Screen.isSelectAll(keyCode)) { - caretPos = inputField.length(); - selectionLength = -caretPos; - return true; - } - } - - return false; - } - - @Override - public boolean charTyped(char chr, int modifiers) { - if (!inputFieldFocused) - return false; - - write(Character.toString(chr)); - - return true; - } - - protected boolean canUseShortcuts() { - return true; - } - - protected void doBackspace() { - if (selectionLength != 0) { - write(""); - } else if (caretPos > 0) { - inputField.deleteCharAt(caretPos - 1); - caretPos--; - updateControl(); - } - } - - protected void doDelete() { - if (caretPos < inputField.length()) { - inputField.deleteCharAt(caretPos); - updateControl(); - } - } - - public void write(String string) { - if (selectionLength == 0) { - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString())); - - inputField.insert(caretPos, string); - caretPos += string.length(); - } else { - int start = getSelectionStart(); - int end = getSelectionEnd(); - - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()) + textRenderer.getWidth(inputField.substring(start, end))); - - inputField.replace(start, end, string); - caretPos = start + string.length(); - selectionLength = 0; - } - updateControl(); - } - - public int getMaxLength() { - return getDimension().width() / 8 * 7; - } - - public int getSelectionStart() { - return Math.min(caretPos, caretPos + selectionLength); - } - - public int getSelectionEnd() { - return Math.max(caretPos, caretPos + selectionLength); - } - - protected String getSelection() { - return inputField.substring(getSelectionStart(), getSelectionEnd()); - } - - protected int findSpaceIndex(boolean reverse) { - int i; - int fromIndex = caretPos; - if (reverse) { - if (caretPos > 0) - fromIndex -= 1; - i = this.inputField.lastIndexOf(" ", fromIndex); - - if (i == -1) i = 0; - } else { - if (caretPos < inputField.length()) - fromIndex += 1; - i = this.inputField.indexOf(" ", fromIndex); - - if (i == -1) i = inputField.length(); - } - - return i; - } - - @Override - public boolean changeFocus(boolean lookForwards) { - return inputFieldFocused = super.changeFocus(lookForwards); - } - - @Override - public void unfocus() { - super.unfocus(); - inputFieldFocused = false; - } - - @Override - public void setDimension(Dimension dim) { - super.setDimension(dim); - - int width = Math.max(6, textRenderer.getWidth(getValueText())); - inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.fontHeight / 2, width, textRenderer.fontHeight); - } - - @Override - public boolean isHovered() { - return super.isHovered() || inputFieldFocused; - } - - protected void updateControl() { - control.setFromString(inputField.toString()); - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } - - @Override - protected Text getValueText() { - if (!inputFieldFocused && inputField.isEmpty()) - return emptyText; - - return super.getValueText(); - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java deleted file mode 100644 index dcb9c7a..0000000 --- a/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java +++ /dev/null @@ -1,142 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.text.Text; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.function.BiConsumer; -import java.util.function.Function; - -public class ButtonOptionImpl implements ButtonOption { - private final Text name; - private final Text tooltip; - private final BiConsumer action; - private boolean available; - private final Controller> controller; - private final Binding> binding; - - public ButtonOptionImpl( - @NotNull Text name, - @Nullable Text tooltip, - @NotNull BiConsumer action, - boolean available, - @NotNull Function>> controlGetter - ) { - this.name = name; - this.tooltip = tooltip; - this.action = action; - this.available = available; - this.controller = controlGetter.apply(this); - this.binding = new EmptyBinderImpl(); - } - - @Override - public @NotNull Text name() { - return name; - } - - @Override - public @NotNull Text tooltip() { - return tooltip; - } - - @Override - public BiConsumer action() { - return action; - } - - @Override - public boolean available() { - return available; - } - - @Override - public void setAvailable(boolean available) { - this.available = available; - } - - @Override - public @NotNull Controller> controller() { - return controller; - } - - @Override - public @NotNull Binding> binding() { - return binding; - } - - @Override - public @NotNull Class> typeClass() { - throw new UnsupportedOperationException(); - } - - @Override - public @NotNull ImmutableSet flags() { - return ImmutableSet.of(); - } - - @Override - public boolean requiresRestart() { - return false; - } - - @Override - public boolean changed() { - return false; - } - - @Override - public @NotNull BiConsumer pendingValue() { - throw new UnsupportedOperationException(); - } - - @Override - public void requestSet(BiConsumer value) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean applyValue() { - return false; - } - - @Override - public void forgetPendingValue() { - - } - - @Override - public void requestSetDefault() { - - } - - @Override - public boolean isPendingValueDefault() { - throw new UnsupportedOperationException(); - } - - @Override - public void addListener(BiConsumer>, BiConsumer> changedListener) { - - } - - private static class EmptyBinderImpl implements Binding> { - @Override - public void setValue(BiConsumer value) { - - } - - @Override - public BiConsumer getValue() { - throw new UnsupportedOperationException(); - } - - @Override - public BiConsumer defaultValue() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java deleted file mode 100644 index 971fecf..0000000 --- a/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.OptionGroup; -import net.minecraft.text.Text; - -public record ConfigCategoryImpl(Text name, ImmutableList groups, Text tooltip) implements ConfigCategory { - -} diff --git a/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java b/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java deleted file mode 100644 index 1867bb6..0000000 --- a/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.isxander.yacl.impl; - -import dev.isxander.yacl.api.Binding; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class GenericBindingImpl implements Binding { - private final T def; - private final Supplier getter; - private final Consumer setter; - - public GenericBindingImpl(T def, Supplier getter, Consumer setting) { - this.def = def; - this.getter = getter; - this.setter = setting; - } - - - @Override - public void setValue(T value) { - setter.accept(value); - } - - @Override - public T getValue() { - return getter.get(); - } - - @Override - public T defaultValue() { - return def; - } - -} diff --git a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java deleted file mode 100644 index 58bc96b..0000000 --- a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionGroup; -import net.minecraft.text.Text; -import org.jetbrains.annotations.NotNull; - -public record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList> options, boolean collapsed, boolean isRoot) implements OptionGroup { -} diff --git a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionImpl.java deleted file mode 100644 index c76f115..0000000 --- a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java +++ /dev/null @@ -1,145 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.Binding; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionFlag; -import net.minecraft.text.Text; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Function; - -public class OptionImpl implements Option { - private final Text name; - private Text tooltip; - private final Controller controller; - private final Binding binding; - private boolean available; - - private final ImmutableSet flags; - - private final Class typeClass; - - private T pendingValue; - - private final List, T>> listeners; - - public OptionImpl( - @NotNull Text name, - @Nullable Function tooltipGetter, - @NotNull Function, Controller> controlGetter, - @NotNull Binding binding, - boolean available, - ImmutableSet flags, - @NotNull Class typeClass, - @NotNull Collection, T>> listeners - ) { - this.name = name; - this.binding = binding; - this.available = available; - this.flags = flags; - this.typeClass = typeClass; - this.listeners = new ArrayList<>(listeners); - this.controller = controlGetter.apply(this); - - addListener((opt, pending) -> tooltip = tooltipGetter.apply(pending)); - requestSet(binding().getValue()); - } - - @Override - public @NotNull Text name() { - return name; - } - - @Override - public @NotNull Text tooltip() { - return tooltip; - } - - @Override - public @NotNull Controller controller() { - return controller; - } - - @Override - public @NotNull Binding binding() { - return binding; - } - - @Override - public boolean available() { - return available; - } - - @Override - public void setAvailable(boolean available) { - this.available = available; - } - - @Override - public @NotNull Class typeClass() { - return typeClass; - } - - @Override - public @NotNull ImmutableSet flags() { - return flags; - } - - @Override - public boolean requiresRestart() { - return flags.contains(OptionFlag.GAME_RESTART); - } - - @Override - public boolean changed() { - return !binding().getValue().equals(pendingValue); - } - - @Override - public @NotNull T pendingValue() { - return pendingValue; - } - - @Override - public void requestSet(T value) { - pendingValue = value; - listeners.forEach(listener -> listener.accept(this, pendingValue)); - } - - @Override - public boolean applyValue() { - if (changed()) { - binding().setValue(pendingValue); - return true; - } - return false; - } - - @Override - public void forgetPendingValue() { - requestSet(binding().getValue()); - } - - @Override - public void requestSetDefault() { - requestSet(binding().defaultValue()); - } - - @Override - public boolean isPendingValueDefault() { - return binding().defaultValue().equals(pendingValue()); - } - - @Override - public void addListener(BiConsumer, T> changedListener) { - this.listeners.add(changedListener); - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java deleted file mode 100644 index a5180ad..0000000 --- a/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.OptionGroup; -import dev.isxander.yacl.api.PlaceholderCategory; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BiFunction; - -public record PlaceholderCategoryImpl(Text name, BiFunction screen, Text tooltip) implements PlaceholderCategory { - @Override - public @NotNull ImmutableList groups() { - return ImmutableList.of(); - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java b/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java deleted file mode 100644 index eb23eac..0000000 --- a/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.YetAnotherConfigLib; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; - -import java.util.function.Consumer; - -public record YetAnotherConfigLibImpl(Text title, ImmutableList categories, Runnable saveFunction, Consumer initConsumer) implements YetAnotherConfigLib { - @Override - public Screen generateScreen(Screen parent) { - YACLConstants.LOGGER.info("Generating YACL screen"); - return new YACLScreen(this, parent); - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java deleted file mode 100644 index 6c7508d..0000000 --- a/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; - -public class DimensionIntegerImpl implements MutableDimension { - private int x, y; - private int width, height; - - public DimensionIntegerImpl(int x, int y, int width, int height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - @Override - public Integer x() { - return x; - } - - @Override - public Integer y() { - return y; - } - - @Override - public Integer width() { - return width; - } - - @Override - public Integer height() { - return height; - } - - @Override - public Integer xLimit() { - return x + width; - } - - @Override - public Integer yLimit() { - return y + height; - } - - @Override - public Integer centerX() { - return x + width / 2; - } - - @Override - public Integer centerY() { - return y + height / 2; - } - - @Override - public boolean isPointInside(Integer x, Integer y) { - return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); - } - - @Override - public MutableDimension clone() { - return new DimensionIntegerImpl(x, y, width, height); - } - - @Override public MutableDimension setX(Integer x) { this.x = x; return this; } - @Override public MutableDimension setY(Integer y) { this.y = y; return this; } - @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } - @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } - - @Override - public Dimension withX(Integer x) { - return clone().setX(x); - } - - @Override - public Dimension withY(Integer y) { - return clone().setY(y); - } - - @Override - public Dimension withWidth(Integer width) { - return clone().setWidth(width); - } - - @Override - public Dimension withHeight(Integer height) { - return clone().setHeight(height); - } - - @Override - public MutableDimension move(Integer x, Integer y) { - this.x += x; - this.y += y; - return this; - } - - @Override - public MutableDimension expand(Integer width, Integer height) { - this.width += width; - this.height += height; - return this; - } - - @Override - public Dimension moved(Integer x, Integer y) { - return clone().move(x, y); - } - - @Override - public Dimension expanded(Integer width, Integer height) { - return clone().expand(width, height); - } -} diff --git a/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java deleted file mode 100644 index 3d382d4..0000000 --- a/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class YACLConstants { - public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); -} diff --git a/src/main/java/dev/isxander/yacl/mixin/SimpleOptionAccessor.java b/src/main/java/dev/isxander/yacl/mixin/SimpleOptionAccessor.java deleted file mode 100644 index 9207225..0000000 --- a/src/main/java/dev/isxander/yacl/mixin/SimpleOptionAccessor.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.mixin; - -import net.minecraft.client.option.SimpleOption; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(SimpleOption.class) -public interface SimpleOptionAccessor { - @Accessor - T getDefaultValue(); -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 234a281..0bd51c5 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -25,7 +25,10 @@ "fabric-resource-loader-v0": "*" }, "mixins": [ - "yet-another-config-lib.mixins.json" + { + "config": "yet-another-config-lib.client.mixins.json", + "environment": "client" + } ], "accessWidener": "yacl.accesswidener", "custom": { diff --git a/src/main/resources/yet-another-config-lib.mixins.json b/src/main/resources/yet-another-config-lib.mixins.json deleted file mode 100644 index cb96c48..0000000 --- a/src/main/resources/yet-another-config-lib.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "dev.isxander.yacl.mixin", - "compatibilityLevel": "JAVA_17", - "injectors": { - "defaultRequire": 1 - }, - "client": [ - "SimpleOptionAccessor" - ] -} diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java index d50164d..2944d69 100644 --- a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -2,7 +2,6 @@ package dev.isxander.yacl.test; import dev.isxander.yacl.api.*; import dev.isxander.yacl.gui.RequireRestartScreen; -import dev.isxander.yacl.gui.controllers.*; import dev.isxander.yacl.gui.controllers.cycling.EnumController; import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -- cgit From d6a5bf1d333586c267a5b156eca6b576529fce74 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 27 Nov 2022 18:43:24 +0000 Subject: replacement of ConfigInstance#buildConfig in YetAnotherConfigLib.java move some things to main that can be and fix testmod --- build.gradle.kts | 1 + .../dev/isxander/yacl/api/YetAnotherConfigLib.java | 15 +++ .../dev/isxander/yacl/api/utils/Dimension.java | 33 ------ .../isxander/yacl/api/utils/MutableDimension.java | 11 -- .../yacl/impl/utils/DimensionIntegerImpl.java | 115 --------------------- .../isxander/yacl/impl/utils/YACLConstants.java | 8 -- .../dev/isxander/yacl/api/utils/Dimension.java | 33 ++++++ .../isxander/yacl/api/utils/MutableDimension.java | 11 ++ .../dev/isxander/yacl/config/ConfigInstance.java | 2 - .../isxander/yacl/config/GsonConfigInstance.java | 5 +- .../isxander/yacl/config/NbtConfigInstance.java | 13 +-- .../yacl/impl/utils/DimensionIntegerImpl.java | 115 +++++++++++++++++++++ .../isxander/yacl/impl/utils/YACLConstants.java | 8 ++ .../java/dev/isxander/yacl/test/GuiTest.java | 115 +++++++++++---------- 14 files changed, 251 insertions(+), 234 deletions(-) delete mode 100644 src/client/java/dev/isxander/yacl/api/utils/Dimension.java delete mode 100644 src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java delete mode 100644 src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java delete mode 100644 src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java create mode 100644 src/main/java/dev/isxander/yacl/api/utils/Dimension.java create mode 100644 src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java create mode 100644 src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java create mode 100644 src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java (limited to 'src/testmod/java/dev/isxander') diff --git a/build.gradle.kts b/build.gradle.kts index b30a011..e39c697 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { "modClientImplementation"(fabricApi.module("fabric-resource-loader-v0", "0.67.2+1.19.3")) "testmodImplementation"(sourceSets.main.get().output) + "testmodImplementation"(sourceSets["client"].output) } java { diff --git a/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java index a69ae4e..ae6c060 100644 --- a/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java +++ b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java @@ -1,6 +1,7 @@ package dev.isxander.yacl.api; import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.config.ConfigInstance; import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.impl.YetAnotherConfigLibImpl; import net.minecraft.client.gui.screen.Screen; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Consumer; /** @@ -53,6 +55,14 @@ public interface YetAnotherConfigLib { return new Builder(); } + /** + * Creates an instance using a {@link ConfigInstance} which autofills the save() builder method. + * This also takes an easy functional interface that provides defaults and config to help build YACL bindings. + */ + static YetAnotherConfigLib create(ConfigInstance configInstance, ConfigBackedBuilder builder) { + return builder.build(configInstance.getDefaults(), configInstance.getConfig(), createBuilder().save(configInstance::save)).build(); + } + class Builder { private Text title; private final List categories = new ArrayList<>(); @@ -133,4 +143,9 @@ public interface YetAnotherConfigLib { return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); } } + + @FunctionalInterface + interface ConfigBackedBuilder { + YetAnotherConfigLib.Builder build(T defaults, T config, YetAnotherConfigLib.Builder builder); + } } diff --git a/src/client/java/dev/isxander/yacl/api/utils/Dimension.java b/src/client/java/dev/isxander/yacl/api/utils/Dimension.java deleted file mode 100644 index 0de0a58..0000000 --- a/src/client/java/dev/isxander/yacl/api/utils/Dimension.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.isxander.yacl.api.utils; - -import dev.isxander.yacl.impl.utils.DimensionIntegerImpl; - -public interface Dimension { - T x(); - T y(); - - T width(); - T height(); - - T xLimit(); - T yLimit(); - - T centerX(); - T centerY(); - - boolean isPointInside(T x, T y); - - MutableDimension clone(); - - Dimension withX(T x); - Dimension withY(T y); - Dimension withWidth(T width); - Dimension withHeight(T height); - - Dimension moved(T x, T y); - Dimension expanded(T width, T height); - - static MutableDimension ofInt(int x, int y, int width, int height) { - return new DimensionIntegerImpl(x, y, width, height); - } -} diff --git a/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java b/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java deleted file mode 100644 index eff0186..0000000 --- a/src/client/java/dev/isxander/yacl/api/utils/MutableDimension.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.api.utils; - -public interface MutableDimension extends Dimension { - MutableDimension setX(T x); - MutableDimension setY(T y); - MutableDimension setWidth(T width); - MutableDimension setHeight(T height); - - MutableDimension move(T x, T y); - MutableDimension expand(T width, T height); -} diff --git a/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java deleted file mode 100644 index 6c7508d..0000000 --- a/src/client/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; - -public class DimensionIntegerImpl implements MutableDimension { - private int x, y; - private int width, height; - - public DimensionIntegerImpl(int x, int y, int width, int height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - @Override - public Integer x() { - return x; - } - - @Override - public Integer y() { - return y; - } - - @Override - public Integer width() { - return width; - } - - @Override - public Integer height() { - return height; - } - - @Override - public Integer xLimit() { - return x + width; - } - - @Override - public Integer yLimit() { - return y + height; - } - - @Override - public Integer centerX() { - return x + width / 2; - } - - @Override - public Integer centerY() { - return y + height / 2; - } - - @Override - public boolean isPointInside(Integer x, Integer y) { - return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); - } - - @Override - public MutableDimension clone() { - return new DimensionIntegerImpl(x, y, width, height); - } - - @Override public MutableDimension setX(Integer x) { this.x = x; return this; } - @Override public MutableDimension setY(Integer y) { this.y = y; return this; } - @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } - @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } - - @Override - public Dimension withX(Integer x) { - return clone().setX(x); - } - - @Override - public Dimension withY(Integer y) { - return clone().setY(y); - } - - @Override - public Dimension withWidth(Integer width) { - return clone().setWidth(width); - } - - @Override - public Dimension withHeight(Integer height) { - return clone().setHeight(height); - } - - @Override - public MutableDimension move(Integer x, Integer y) { - this.x += x; - this.y += y; - return this; - } - - @Override - public MutableDimension expand(Integer width, Integer height) { - this.width += width; - this.height += height; - return this; - } - - @Override - public Dimension moved(Integer x, Integer y) { - return clone().move(x, y); - } - - @Override - public Dimension expanded(Integer width, Integer height) { - return clone().expand(width, height); - } -} diff --git a/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java deleted file mode 100644 index 3d382d4..0000000 --- a/src/client/java/dev/isxander/yacl/impl/utils/YACLConstants.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class YACLConstants { - public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); -} diff --git a/src/main/java/dev/isxander/yacl/api/utils/Dimension.java b/src/main/java/dev/isxander/yacl/api/utils/Dimension.java new file mode 100644 index 0000000..0de0a58 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/utils/Dimension.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl.api.utils; + +import dev.isxander.yacl.impl.utils.DimensionIntegerImpl; + +public interface Dimension { + T x(); + T y(); + + T width(); + T height(); + + T xLimit(); + T yLimit(); + + T centerX(); + T centerY(); + + boolean isPointInside(T x, T y); + + MutableDimension clone(); + + Dimension withX(T x); + Dimension withY(T y); + Dimension withWidth(T width); + Dimension withHeight(T height); + + Dimension moved(T x, T y); + Dimension expanded(T width, T height); + + static MutableDimension ofInt(int x, int y, int width, int height) { + return new DimensionIntegerImpl(x, y, width, height); + } +} diff --git a/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java b/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java new file mode 100644 index 0000000..eff0186 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl.api.utils; + +public interface MutableDimension extends Dimension { + MutableDimension setX(T x); + MutableDimension setY(T y); + MutableDimension setWidth(T width); + MutableDimension setHeight(T height); + + MutableDimension move(T x, T y); + MutableDimension expand(T width, T height); +} diff --git a/src/main/java/dev/isxander/yacl/config/ConfigInstance.java b/src/main/java/dev/isxander/yacl/config/ConfigInstance.java index 18733f3..3ceee6d 100644 --- a/src/main/java/dev/isxander/yacl/config/ConfigInstance.java +++ b/src/main/java/dev/isxander/yacl/config/ConfigInstance.java @@ -16,8 +16,6 @@ import java.lang.reflect.InvocationTargetException; * @param config data type */ public abstract class ConfigInstance { - protected final static Logger logger = LoggerFactory.getLogger("YetAnotherConfigLib"); - private final Class configClass; private final T defaultInstance; private T instance; diff --git a/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java b/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java index 3e075ab..40c2c99 100644 --- a/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java +++ b/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java @@ -1,6 +1,7 @@ package dev.isxander.yacl.config; import com.google.gson.*; +import dev.isxander.yacl.impl.utils.YACLConstants; import net.minecraft.text.Style; import net.minecraft.text.Text; @@ -53,7 +54,7 @@ public class GsonConfigInstance extends ConfigInstance { @Override public void save() { try { - logger.info("Saving {}...", getConfigClass().getSimpleName()); + YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName()); Files.writeString(path, gson.toJson(getConfig()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); } catch (IOException e) { e.printStackTrace(); @@ -68,7 +69,7 @@ public class GsonConfigInstance extends ConfigInstance { return; } - logger.info("Loading {}...", getConfigClass().getSimpleName()); + YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName()); setConfig(gson.fromJson(Files.readString(path), getConfigClass())); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/dev/isxander/yacl/config/NbtConfigInstance.java b/src/main/java/dev/isxander/yacl/config/NbtConfigInstance.java index 8f817cb..5749695 100644 --- a/src/main/java/dev/isxander/yacl/config/NbtConfigInstance.java +++ b/src/main/java/dev/isxander/yacl/config/NbtConfigInstance.java @@ -1,5 +1,6 @@ package dev.isxander.yacl.config; +import dev.isxander.yacl.impl.utils.YACLConstants; import net.minecraft.nbt.*; import java.awt.*; @@ -57,13 +58,13 @@ public class NbtConfigInstance extends ConfigInstance { @Override public void save() { - logger.info("Saving {}...", getConfigClass().getSimpleName()); + YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName()); NbtCompound nbt; try { nbt = (NbtCompound) serializeObject(getConfig(), nbtSerializerHolder, field -> field.isAnnotationPresent(ConfigEntry.class)); } catch (IllegalAccessException e) { - logger.error("Failed to convert '{}' -> NBT", getConfigClass().getName(), e); + YACLConstants.LOGGER.error("Failed to convert '{}' -> NBT", getConfigClass().getName(), e); return; } @@ -76,7 +77,7 @@ public class NbtConfigInstance extends ConfigInstance { else NbtIo.write(nbt, new DataOutputStream(fos)); } catch (IOException e) { - logger.error("Failed to write NBT to '{}'", path, e); + YACLConstants.LOGGER.error("Failed to write NBT to '{}'", path, e); } } @@ -87,19 +88,19 @@ public class NbtConfigInstance extends ConfigInstance { return; } - logger.info("Loading {}...", getConfigClass().getSimpleName()); + YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName()); NbtCompound nbt; try { nbt = compressed ? NbtIo.readCompressed(path.toFile()) : NbtIo.read(path.toFile()); } catch (IOException e) { - logger.error("Failed to read NBT file '{}'", path, e); + YACLConstants.LOGGER.error("Failed to read NBT file '{}'", path, e); return; } try { setConfig(deserializeObject(nbt, getConfigClass(), nbtSerializerHolder, field -> field.isAnnotationPresent(ConfigEntry.class))); } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { - logger.error("Failed to convert NBT -> '{}'", getConfigClass().getName(), e); + YACLConstants.LOGGER.error("Failed to convert NBT -> '{}'", getConfigClass().getName(), e); } } diff --git a/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java new file mode 100644 index 0000000..6c7508d --- /dev/null +++ b/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java @@ -0,0 +1,115 @@ +package dev.isxander.yacl.impl.utils; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; + +public class DimensionIntegerImpl implements MutableDimension { + private int x, y; + private int width, height; + + public DimensionIntegerImpl(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public Integer x() { + return x; + } + + @Override + public Integer y() { + return y; + } + + @Override + public Integer width() { + return width; + } + + @Override + public Integer height() { + return height; + } + + @Override + public Integer xLimit() { + return x + width; + } + + @Override + public Integer yLimit() { + return y + height; + } + + @Override + public Integer centerX() { + return x + width / 2; + } + + @Override + public Integer centerY() { + return y + height / 2; + } + + @Override + public boolean isPointInside(Integer x, Integer y) { + return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); + } + + @Override + public MutableDimension clone() { + return new DimensionIntegerImpl(x, y, width, height); + } + + @Override public MutableDimension setX(Integer x) { this.x = x; return this; } + @Override public MutableDimension setY(Integer y) { this.y = y; return this; } + @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } + @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } + + @Override + public Dimension withX(Integer x) { + return clone().setX(x); + } + + @Override + public Dimension withY(Integer y) { + return clone().setY(y); + } + + @Override + public Dimension withWidth(Integer width) { + return clone().setWidth(width); + } + + @Override + public Dimension withHeight(Integer height) { + return clone().setHeight(height); + } + + @Override + public MutableDimension move(Integer x, Integer y) { + this.x += x; + this.y += y; + return this; + } + + @Override + public MutableDimension expand(Integer width, Integer height) { + this.width += width; + this.height += height; + return this; + } + + @Override + public Dimension moved(Integer x, Integer y) { + return clone().move(x, y); + } + + @Override + public Dimension expanded(Integer width, Integer height) { + return clone().expand(width, height); + } +} diff --git a/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java new file mode 100644 index 0000000..3d382d4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java @@ -0,0 +1,8 @@ +package dev.isxander.yacl.impl.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YACLConstants { + public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); +} diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java index 2944d69..7377bc9 100644 --- a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -2,6 +2,7 @@ package dev.isxander.yacl.test; import dev.isxander.yacl.api.*; import dev.isxander.yacl.gui.RequireRestartScreen; +import dev.isxander.yacl.gui.controllers.*; import dev.isxander.yacl.gui.controllers.cycling.EnumController; import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; @@ -22,7 +23,7 @@ import java.awt.*; public class GuiTest { public static Screen getModConfigScreenFactory(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder + return YetAnotherConfigLib.create(Entrypoint.getConfig(), (defaults, config, builder) -> builder .title(Text.of("Test Suites")) .category(ConfigCategory.createBuilder() .name(Text.of("Suites")) @@ -52,7 +53,7 @@ public class GuiTest { } private static Screen getFullTestSuite(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder + return YetAnotherConfigLib.create(Entrypoint.getConfig(), (defaults, config, builder) -> builder .title(Text.of("Test GUI")) .category(ConfigCategory.createBuilder() .name(Text.of("Control Examples")) @@ -65,9 +66,9 @@ public class GuiTest { .name(Text.of("Boolean Toggle")) .tooltip(value -> Text.of("A simple toggle button that contains the value '" + value + "'")) .binding( - config.getDefaults().booleanToggle, - () -> config.getConfig().booleanToggle, - (value) -> config.getConfig().booleanToggle = value + defaults.booleanToggle, + () -> config.booleanToggle, + (value) -> config.booleanToggle = value ) .controller(BooleanController::new) .flag(OptionFlag.GAME_RESTART) @@ -77,9 +78,9 @@ public class GuiTest { .name(Text.of("Custom Boolean Toggle")) .tooltip(Text.of("You can customize these controllers like this!")) .binding( - config.getDefaults().customBooleanToggle, - () -> config.getConfig().customBooleanToggle, - (value) -> config.getConfig().customBooleanToggle = value + defaults.customBooleanToggle, + () -> config.customBooleanToggle, + (value) -> config.customBooleanToggle = value ) .controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true)) .build()) @@ -87,9 +88,9 @@ public class GuiTest { .name(Text.of("Tick Box aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) .tooltip(Text.of("There are even alternate methods of displaying the same data type!")) .binding( - config.getDefaults().tickbox, - () -> config.getConfig().tickbox, - (value) -> config.getConfig().tickbox = value + defaults.tickbox, + () -> config.tickbox, + (value) -> config.tickbox = value ) .controller(TickBoxController::new) .build()) @@ -100,9 +101,9 @@ public class GuiTest { .name(Text.of("Int Slider that is cut off because the slider")) .instant(true) .binding( - config.getDefaults().intSlider, - () -> config.getConfig().intSlider, - value -> config.getConfig().intSlider = value + defaults.intSlider, + () -> config.intSlider, + value -> config.intSlider = value ) .controller(opt -> new IntegerSliderController(opt, 0, 3, 1)) @@ -110,27 +111,27 @@ public class GuiTest { .option(Option.createBuilder(double.class) .name(Text.of("Double Slider")) .binding( - config.getDefaults().doubleSlider, - () -> config.getConfig().doubleSlider, - (value) -> config.getConfig().doubleSlider = value + defaults.doubleSlider, + () -> config.doubleSlider, + (value) -> config.doubleSlider = value ) .controller(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) .build()) .option(Option.createBuilder(float.class) .name(Text.of("Float Slider")) .binding( - config.getDefaults().floatSlider, - () -> config.getConfig().floatSlider, - (value) -> config.getConfig().floatSlider = value + defaults.floatSlider, + () -> config.floatSlider, + (value) -> config.floatSlider = value ) .controller(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) .build()) .option(Option.createBuilder(long.class) .name(Text.of("Long Slider")) .binding( - config.getDefaults().longSlider, - () -> config.getConfig().longSlider, - (value) -> config.getConfig().longSlider = value + defaults.longSlider, + () -> config.longSlider, + (value) -> config.longSlider = value ) .controller(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) .build()) @@ -140,18 +141,18 @@ public class GuiTest { .option(Option.createBuilder(String.class) .name(Text.of("Text Option")) .binding( - config.getDefaults().textField, - () -> config.getConfig().textField, - value -> config.getConfig().textField = value + defaults.textField, + () -> config.textField, + value -> config.textField = value ) .controller(StringController::new) .build()) .option(Option.createBuilder(Color.class) .name(Text.of("Color Option")) .binding( - config.getDefaults().colorOption, - () -> config.getConfig().colorOption, - value -> config.getConfig().colorOption = value + defaults.colorOption, + () -> config.colorOption, + value -> config.colorOption = value ) .controller(ColorController::new) .build()) @@ -161,9 +162,9 @@ public class GuiTest { .option(Option.createBuilder(ConfigData.Alphabet.class) .name(Text.of("Enum Cycler")) .binding( - config.getDefaults().enumOption, - () -> config.getConfig().enumOption, - (value) -> config.getConfig().enumOption = value + defaults.enumOption, + () -> config.enumOption, + (value) -> config.enumOption = value ) .controller(EnumController::new) .build()) @@ -211,9 +212,9 @@ public class GuiTest { .option(Option.createBuilder(boolean.class) .name(Text.of("Root Test")) .binding( - config.getDefaults().groupTestRoot, - () -> config.getConfig().groupTestRoot, - value -> config.getConfig().groupTestRoot = value + defaults.groupTestRoot, + () -> config.groupTestRoot, + value -> config.groupTestRoot = value ) .controller(TickBoxController::new) .build()) @@ -222,18 +223,18 @@ public class GuiTest { .option(Option.createBuilder(boolean.class) .name(Text.of("First Group Test 1")) .binding( - config.getDefaults().groupTestFirstGroup, - () -> config.getConfig().groupTestFirstGroup, - value -> config.getConfig().groupTestFirstGroup = value + defaults.groupTestFirstGroup, + () -> config.groupTestFirstGroup, + value -> config.groupTestFirstGroup = value ) .controller(TickBoxController::new) .build()) .option(Option.createBuilder(boolean.class) .name(Text.of("First Group Test 2")) .binding( - config.getDefaults().groupTestFirstGroup2, - () -> config.getConfig().groupTestFirstGroup2, - value -> config.getConfig().groupTestFirstGroup2 = value + defaults.groupTestFirstGroup2, + () -> config.groupTestFirstGroup2, + value -> config.groupTestFirstGroup2 = value ) .controller(TickBoxController::new) .build()) @@ -243,9 +244,9 @@ public class GuiTest { .option(Option.createBuilder(boolean.class) .name(Text.of("Second Group Test")) .binding( - config.getDefaults().groupTestSecondGroup, - () -> config.getConfig().groupTestSecondGroup, - value -> config.getConfig().groupTestSecondGroup = value + defaults.groupTestSecondGroup, + () -> config.groupTestSecondGroup, + value -> config.groupTestSecondGroup = value ) .controller(TickBoxController::new) .build()) @@ -256,9 +257,9 @@ public class GuiTest { .option(Option.createBuilder(int.class) .name(Text.of("Int Slider that is cut off because the slider")) .binding( - config.getDefaults().scrollingSlider, - () -> config.getConfig().scrollingSlider, - (value) -> config.getConfig().scrollingSlider = value + defaults.scrollingSlider, + () -> config.scrollingSlider, + (value) -> config.scrollingSlider = value ) .controller(opt -> new IntegerSliderController(opt, 0, 10, 1)) .build()) @@ -330,14 +331,14 @@ public class GuiTest { .build()) .save(() -> { MinecraftClient.getInstance().options.write(); - config.save(); + Entrypoint.getConfig().save(); }) ) .generateScreen(parent); } private static Screen getDisabledTest(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder + return YetAnotherConfigLib.create(Entrypoint.getConfig(), (defaults, config, builder) -> builder .title(Text.empty()) .category(ConfigCategory.createBuilder() .name(Text.of("Disabled Test")) @@ -370,7 +371,7 @@ public class GuiTest { } private static Screen getWikiBasic(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder + return YetAnotherConfigLib.create(Entrypoint.getConfig(), (defaults, config, builder) -> builder .title(Text.of("Mod Name")) .category(ConfigCategory.createBuilder() .name(Text.of("My Category")) @@ -379,9 +380,9 @@ public class GuiTest { .name(Text.of("My Boolean Option")) .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional .binding( - config.getDefaults().booleanToggle, // default - () -> config.getConfig().booleanToggle, // getter - newValue -> config.getConfig().booleanToggle = newValue // setter + defaults.booleanToggle, // default + () -> config.booleanToggle, // getter + newValue -> config.booleanToggle = newValue // setter ) .controller(BooleanController::new) .build()) @@ -391,7 +392,7 @@ public class GuiTest { } private static Screen getWikiGroups(Screen parent) { - return Entrypoint.getConfig().buildConfig((config, builder) -> builder + return YetAnotherConfigLib.create(Entrypoint.getConfig(), (defaults, config, builder) -> builder .title(Text.of("Mod Name")) .category(ConfigCategory.createBuilder() .name(Text.of("My Category")) @@ -402,9 +403,9 @@ public class GuiTest { .name(Text.of("My Boolean Option")) .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional .binding( - config.getDefaults().booleanToggle, // default - () -> config.getConfig().booleanToggle, // getter - newValue -> config.getConfig().booleanToggle = newValue // setter + defaults.booleanToggle, // default + () -> config.booleanToggle, // getter + newValue -> config.booleanToggle = newValue // setter ) .controller(BooleanController::new) .build()) -- cgit From daf3168671bc9c0cd64693d7129cddfdb1a08bb2 Mon Sep 17 00:00:00 2001 From: isXander Date: Sat, 3 Dec 2022 18:57:03 +0000 Subject: number fields + StringControllerElement improvements when highlighted text, arrow keys go to each side of the selection switch to InputUtil instead of GLFW bump yarn --- build.gradle.kts | 2 +- .../yacl/gui/controllers/ActionController.java | 4 +- .../yacl/gui/controllers/BooleanController.java | 3 +- .../yacl/gui/controllers/ColorController.java | 21 +++-- .../yacl/gui/controllers/TickBoxController.java | 4 +- .../cycling/CyclingControllerElement.java | 8 +- .../controllers/slider/DoubleSliderController.java | 2 +- .../controllers/slider/FloatSliderController.java | 2 +- .../slider/IntegerSliderController.java | 2 +- .../controllers/slider/LongSliderController.java | 2 +- .../slider/SliderControllerElement.java | 6 +- .../gui/controllers/string/IStringController.java | 12 +++ .../gui/controllers/string/StringController.java | 5 - .../string/StringControllerElement.java | 91 +++++++++++------- .../string/number/DoubleFieldController.java | 105 +++++++++++++++++++++ .../string/number/FloatFieldController.java | 105 +++++++++++++++++++++ .../string/number/IntegerFieldController.java | 104 ++++++++++++++++++++ .../string/number/LongFieldController.java | 104 ++++++++++++++++++++ .../string/number/NumberFieldController.java | 69 ++++++++++++++ .../controllers/string/number/package-info.java | 10 ++ .../java/dev/isxander/yacl/test/GuiTest.java | 43 +++++++++ .../dev/isxander/yacl/test/config/ConfigData.java | 4 + 22 files changed, 644 insertions(+), 64 deletions(-) create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java (limited to 'src/testmod/java/dev/isxander') diff --git a/build.gradle.kts b/build.gradle.kts index 4a47fb0..1ece3df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,7 +64,7 @@ val fabricLoaderVersion: String by project dependencies { minecraft("com.mojang:minecraft:$minecraftVersion") - mappings("net.fabricmc:yarn:$minecraftVersion+build.1:v2") + mappings("net.fabricmc:yarn:$minecraftVersion+build.2:v2") modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") "modClientImplementation"(fabricApi.module("fabric-resource-loader-v0", "0.68.1+1.19.3")) diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java index b8e2cd1..7666dff 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java @@ -5,8 +5,8 @@ import dev.isxander.yacl.api.Controller; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.AbstractWidget; import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.util.InputUtil; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; import java.util.function.BiConsumer; @@ -94,7 +94,7 @@ public class ActionController implements Controller { return false; } - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { toggleSetting(); return true; } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java index a68475d..6853a03 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java @@ -108,7 +108,7 @@ public class ColorController implements IStringController { private final List allowedChars; public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); + super(control, screen, dim, true); this.colorController = control; this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); } @@ -135,21 +135,22 @@ public class ColorController implements IStringController { if (caretPos == 0) return; - string = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); + String trimmed = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); - inputField.replace(caretPos, caretPos + string.length(), string); - caretPos += string.length(); - setSelectionLength(); - - updateControl(); + if (modifyInput(builder -> builder.replace(caretPos, caretPos + trimmed.length(), trimmed))) { + caretPos += trimmed.length(); + setSelectionLength(); + updateControl(); + } } @Override protected void doBackspace() { if (caretPos > 1) { - inputField.setCharAt(caretPos - 1, '0'); - caretPos--; - updateControl(); + if (modifyInput(builder -> builder.setCharAt(caretPos - 1, '0'))) { + caretPos--; + updateControl(); + } } } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java index 340983d..b0ae449 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java @@ -6,9 +6,9 @@ import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.AbstractWidget; import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; /** * This controller renders a tickbox @@ -109,7 +109,7 @@ public class TickBoxController implements Controller { return false; } - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { toggleSetting(); return true; } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java index ab0a9c3..246fbec 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java @@ -4,7 +4,7 @@ import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.gui.controllers.ControllerWidget; import net.minecraft.client.gui.screen.Screen; -import org.lwjgl.glfw.GLFW; +import net.minecraft.client.util.InputUtil; public class CyclingControllerElement extends ControllerWidget> { @@ -39,11 +39,11 @@ public class CyclingControllerElement extends ControllerWidget + case InputUtil.GLFW_KEY_LEFT, InputUtil.GLFW_KEY_DOWN -> cycleValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> + case InputUtil.GLFW_KEY_RIGHT, InputUtil.GLFW_KEY_UP -> cycleValue(1); - case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_SPACE, GLFW.GLFW_KEY_KP_ENTER -> + case InputUtil.GLFW_KEY_ENTER, InputUtil.GLFW_KEY_SPACE, InputUtil.GLFW_KEY_KP_ENTER -> cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); default -> { return false; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java index b530e8c..54c7476 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java @@ -13,7 +13,7 @@ public class DoubleSliderController implements ISliderController { /** * Formats doubles to two decimal places */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java index d7c203e..84ca9a2 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java @@ -13,7 +13,7 @@ public class FloatSliderController implements ISliderController { /** * Formats floats to one decimal place */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java index a8bca7c..50ec9d2 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java @@ -10,7 +10,7 @@ import java.util.function.Function; * {@link ISliderController} for integers. */ public class IntegerSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java index 50559d5..3038402 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java @@ -10,7 +10,7 @@ import java.util.function.Function; * {@link ISliderController} for longs. */ public class LongSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java index 913cc00..c78e0eb 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java @@ -5,9 +5,9 @@ import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.gui.controllers.ControllerWidget; import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.MathHelper; -import org.lwjgl.glfw.GLFW; public class SliderControllerElement extends ControllerWidget> { private final double min, max, interval; @@ -104,8 +104,8 @@ public class SliderControllerElement extends ControllerWidget incrementValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); + case InputUtil.GLFW_KEY_LEFT, InputUtil.GLFW_KEY_DOWN -> incrementValue(-1); + case InputUtil.GLFW_KEY_RIGHT, InputUtil.GLFW_KEY_UP -> incrementValue(1); default -> { return false; } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java index 41843b8..553e278 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java @@ -2,6 +2,9 @@ package dev.isxander.yacl.gui.controllers.string; import dev.isxander.yacl.api.Controller; import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.text.Text; /** @@ -29,4 +32,13 @@ public interface IStringController extends Controller { default Text formatValue() { return Text.of(getString()); } + + default boolean isInputValid(String input) { + return true; + } + + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension, true); + } } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java index 0caaa93..3a07641 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java @@ -37,9 +37,4 @@ public class StringController implements IStringController { public void setFromString(String value) { option().requestSet(value); } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new StringControllerElement(this, screen, widgetDimension); - } } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java index 0c3b7c9..bce6906 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java @@ -5,13 +5,17 @@ import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.gui.controllers.ControllerWidget; import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import org.lwjgl.glfw.GLFW; + +import java.util.function.Consumer; public class StringControllerElement extends ControllerWidget> { - protected StringBuilder inputField; + protected final boolean instantApply; + + protected String inputField; protected Dimension inputFieldBounds; protected boolean inputFieldFocused; @@ -22,12 +26,14 @@ public class StringControllerElement extends ControllerWidget control, YACLScreen screen, Dimension dim) { + public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim, boolean instantApply) { super(control, screen, dim); - inputField = new StringBuilder(control.getString()); + this.instantApply = instantApply; + inputField = control.getString(); inputFieldFocused = false; selectionLength = 0; emptyText = Text.literal("Click to type...").formatted(Formatting.GRAY); + control.option().addListener((opt, val) -> inputField = control.getString()); setDimension(dim); } @@ -35,12 +41,14 @@ public class StringControllerElement extends ControllerWidget { - inputFieldFocused = false; + case InputUtil.GLFW_KEY_ESCAPE, InputUtil.GLFW_KEY_ENTER -> { + unfocus(); return true; } - case GLFW.GLFW_KEY_LEFT -> { + case InputUtil.GLFW_KEY_LEFT -> { if (Screen.hasShiftDown()) { if (Screen.hasControlDown()) { int spaceChar = findSpaceIndex(true); @@ -98,14 +106,18 @@ public class StringControllerElement extends ControllerWidget 0) - caretPos--; + if (caretPos > 0) { + if (selectionLength != 0) + caretPos += Math.min(selectionLength, 0); + else + caretPos--; + } selectionLength = 0; } return true; } - case GLFW.GLFW_KEY_RIGHT -> { + case InputUtil.GLFW_KEY_RIGHT -> { if (Screen.hasShiftDown()) { if (Screen.hasControlDown()) { int spaceChar = findSpaceIndex(false); @@ -116,18 +128,22 @@ public class StringControllerElement extends ControllerWidget { + case InputUtil.GLFW_KEY_BACKSPACE -> { doBackspace(); return true; } - case GLFW.GLFW_KEY_DELETE -> { + case InputUtil.GLFW_KEY_DELETE -> { doDelete(); return true; } @@ -172,36 +188,46 @@ public class StringControllerElement extends ControllerWidget 0) { - inputField.deleteCharAt(caretPos - 1); - caretPos--; - updateControl(); + if (modifyInput(builder -> builder.deleteCharAt(caretPos - 1))) + caretPos--; } } protected void doDelete() { if (caretPos < inputField.length()) { - inputField.deleteCharAt(caretPos); - updateControl(); + modifyInput(builder -> builder.deleteCharAt(caretPos)); } } public void write(String string) { if (selectionLength == 0) { - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString())); + String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField)); - inputField.insert(caretPos, string); - caretPos += string.length(); + if (modifyInput(builder -> builder.insert(caretPos, trimmed))) { + caretPos += trimmed.length(); + } } else { int start = getSelectionStart(); int end = getSelectionEnd(); - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()) + textRenderer.getWidth(inputField.substring(start, end))); + String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField) + textRenderer.getWidth(inputField.substring(start, end))); - inputField.replace(start, end, string); - caretPos = start + string.length(); - selectionLength = 0; + if (modifyInput(builder -> builder.replace(start, end, trimmed))) { + caretPos = start + trimmed.length(); + selectionLength = 0; + } } - updateControl(); + } + + public boolean modifyInput(Consumer consumer) { + StringBuilder temp = new StringBuilder(inputField); + consumer.accept(temp); + if (!control.isInputValid(temp.toString())) + return false; + inputField = temp.toString(); + if (instantApply) + updateControl(); + return true; } public int getMaxLength() { @@ -249,6 +275,7 @@ public class StringControllerElement extends ControllerWidget { + private final double min, max; + + /** + * Constructs a double field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public DoubleFieldController(Option option, double min, double max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a double field controller. + * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public DoubleFieldController(Option option, double min, double max) { + this(option, min, max, DoubleSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a double field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public DoubleFieldController(Option option, Function formatter) { + this(option, -Double.MAX_VALUE, Double.MAX_VALUE, formatter); + } + + /** + * Constructs a double field controller. + * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public DoubleFieldController(Option option) { + this(option, -Double.MAX_VALUE, Double.MAX_VALUE, DoubleSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return BigDecimal.valueOf(option().pendingValue()).stripTrailingZeros().toPlainString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet(value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java new file mode 100644 index 0000000..4b34d7f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java @@ -0,0 +1,105 @@ +package dev.isxander.yacl.gui.controllers.string.number; + +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; +import net.minecraft.text.Text; + +import java.math.BigDecimal; +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class FloatFieldController extends NumberFieldController { + private final float min, max; + + /** + * Constructs a double field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public FloatFieldController(Option option, float min, float max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a double field controller. + * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public FloatFieldController(Option option, float min, float max) { + this(option, min, max, FloatSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a double field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public FloatFieldController(Option option, Function formatter) { + this(option, -Float.MAX_VALUE, Float.MAX_VALUE, formatter); + } + + /** + * Constructs a double field controller. + * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public FloatFieldController(Option option) { + this(option, -Float.MAX_VALUE, Float.MAX_VALUE, FloatSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return BigDecimal.valueOf(option().pendingValue()).stripTrailingZeros().toPlainString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((float) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java new file mode 100644 index 0000000..5f0121a --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java @@ -0,0 +1,104 @@ +package dev.isxander.yacl.gui.controllers.string.number; + +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; +import net.minecraft.text.Text; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class IntegerFieldController extends NumberFieldController { + private final int min, max; + + /** + * Constructs a double field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public IntegerFieldController(Option option, int min, int max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a double field controller. + * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public IntegerFieldController(Option option, int min, int max) { + this(option, min, max, IntegerSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a double field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public IntegerFieldController(Option option, Function formatter) { + this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, formatter); + } + + /** + * Constructs a double field controller. + * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public IntegerFieldController(Option option) { + this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, IntegerSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((int) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java new file mode 100644 index 0000000..713d39f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java @@ -0,0 +1,104 @@ +package dev.isxander.yacl.gui.controllers.string.number; + +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.gui.controllers.slider.LongSliderController; +import net.minecraft.text.Text; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class LongFieldController extends NumberFieldController { + private final long min, max; + + /** + * Constructs a double field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public LongFieldController(Option option, long min, long max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a double field controller. + * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public LongFieldController(Option option, long min, long max) { + this(option, min, max, LongSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a double field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public LongFieldController(Option option, Function formatter) { + this(option, -Long.MAX_VALUE, Long.MAX_VALUE, formatter); + } + + /** + * Constructs a double field controller. + * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public LongFieldController(Option option) { + this(option, -Long.MAX_VALUE, Long.MAX_VALUE, LongSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((long) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java new file mode 100644 index 0000000..bf0354a --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java @@ -0,0 +1,69 @@ +package dev.isxander.yacl.gui.controllers.string.number; + +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.slider.ISliderController; +import dev.isxander.yacl.gui.controllers.string.IStringController; +import dev.isxander.yacl.gui.controllers.string.StringControllerElement; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; + +import java.text.DecimalFormatSymbols; +import java.util.function.Function; + +/** + * Controller that allows you to enter in numbers using a text field. + * + * @param number type + */ +public abstract class NumberFieldController implements ISliderController, IStringController { + private final Option option; + private final Function displayFormatter; + + public NumberFieldController(Option option, Function displayFormatter) { + this.option = option; + this.displayFormatter = displayFormatter; + } + + @Override + public Option option() { + return this.option; + } + + @Override + public void setFromString(String value) { + if (value.isEmpty() || value.equals(".") || value.equals("-")) value = "0"; + setPendingValue(MathHelper.clamp(Double.parseDouble(cleanupNumberString(value)), min(), max())); + } + + @Override + public double pendingValue() { + return option().pendingValue().doubleValue(); + } + + @Override + public boolean isInputValid(String input) { + return input.matches("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)|[.]||-"); + } + + @Override + public Text formatValue() { + return displayFormatter.apply(option().pendingValue()); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension, false); + } + + protected String cleanupNumberString(String number) { + return number.replace(String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()), ""); + } + + @Override + public double interval() { + return -1; + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java new file mode 100644 index 0000000..86b9314 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains implementations of input fields for different number types + *
    + *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController}
  • + *
  • For floats: {@link dev.isxander.yacl.gui.controllers.string.number.FloatFieldController}
  • + *
  • For integers: {@link dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController}
  • + *
  • For longs: {@link dev.isxander.yacl.gui.controllers.string.number.LongFieldController}
  • + *
+ */ +package dev.isxander.yacl.gui.controllers.string.number; diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java index 7377bc9..492a573 100644 --- a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -9,6 +9,10 @@ import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; import dev.isxander.yacl.gui.controllers.slider.LongSliderController; import dev.isxander.yacl.gui.controllers.string.StringController; +import dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController; +import dev.isxander.yacl.gui.controllers.string.number.FloatFieldController; +import dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController; +import dev.isxander.yacl.gui.controllers.string.number.LongFieldController; import dev.isxander.yacl.test.config.ConfigData; import dev.isxander.yacl.test.config.Entrypoint; import net.minecraft.client.MinecraftClient; @@ -157,6 +161,45 @@ public class GuiTest { .controller(ColorController::new) .build()) .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Number Fields")) + .option(Option.createBuilder(double.class) + .name(Text.of("Double Field")) + .binding( + defaults.doubleField, + () -> config.doubleField, + value -> config.doubleField = value + ) + .controller(DoubleFieldController::new) + .build()) + .option(Option.createBuilder(float.class) + .name(Text.of("Float Field")) + .binding( + defaults.floatField, + () -> config.floatField, + value -> config.floatField = value + ) + .controller(FloatFieldController::new) + .build()) + .option(Option.createBuilder(int.class) + .name(Text.of("Integer Field")) + .binding( + defaults.intField, + () -> config.intField, + value -> config.intField = value + ) + .controller(IntegerFieldController::new) + .build()) + .option(Option.createBuilder(long.class) + .name(Text.of("Long Field")) + .binding( + defaults.longField, + () -> config.longField, + value -> config.longField = value + ) + .controller(LongFieldController::new) + .build()) + .build()) .group(OptionGroup.createBuilder() .name(Text.of("Enum Controllers")) .option(Option.createBuilder(ConfigData.Alphabet.class) diff --git a/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java b/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java index 35e57dd..4eedd9f 100644 --- a/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java +++ b/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java @@ -14,6 +14,10 @@ public class ConfigData { @ConfigEntry public long longSlider = 0; @ConfigEntry public String textField = "Hello"; @ConfigEntry public Color colorOption = Color.red; + @ConfigEntry public double doubleField = 0.5; + @ConfigEntry public float floatField = 0.5f; + @ConfigEntry public int intField = 5; + @ConfigEntry public long longField = 5; @ConfigEntry public Alphabet enumOption = Alphabet.A; @ConfigEntry public boolean groupTestRoot = false; -- cgit