diff options
author | isXander <xander@isxander.dev> | 2024-10-19 19:22:45 +0100 |
---|---|---|
committer | isXander <xander@isxander.dev> | 2024-10-19 19:22:45 +0100 |
commit | e73a08e6672fb380cab8db71340158969c5ef56b (patch) | |
tree | dd08a311f4eff9a91b465ef1854caa1286fc6f9a | |
parent | 519ac2fc0e23587defcf4a8259979961d35d0ce2 (diff) | |
download | YetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.tar.gz YetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.tar.bz2 YetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.zip |
3.6.0
48 files changed, 1089 insertions, 410 deletions
@@ -1,7 +1,3 @@ -<center><div align="center"> - -![](https://raw.githubusercontent.com/isXander/YetAnotherConfigLib/1.19/src/main/resources/yacl-128x.png) - # YetAnotherConfigLib ![Enviroment](https://img.shields.io/badge/Enviroment-Client-purple) @@ -13,42 +9,76 @@ [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/isxander) -Yet Another Config Lib, like, what were you expecting? - -[![](https://www.bisecthosting.com/partners/custom-banners/08bbd3ff-5c0d-4480-8738-de0f070a04dd.png)](https://bisecthosting.com/xander) +A mod designed to fit a modder's needs for client-side configuration. -</div></center> +[![](https://www.bisecthosting.com/partners/custom-banners/08bbd3ff-5c0d-4480-8738-de0f070a04dd.png)](https://bisecthosting.com/xander) ## Why does this mod even exist? This mod was made to fill a hole in this area of Fabric modding. The existing main config libraries don't achieve what I want from them: -- **[Cloth Config API](https://modrinth.com/mod/cloth-config)**:<br/>**It's stale.** The developer of cloth has clarified that they are likely not going to add any more features. They don't want to touch it. ([citation](https://user-images.githubusercontent.com/43245524/206530322-3ae46008-5356-468e-9a73-63b859364d4e.png)) -- **[SpruceUI](https://github.com/LambdAurora/SpruceUI)**:<br/>**It isn't designed for configuration.** In this essence the design feels cluttered. Further details available in [this issue](https://github.com/isXander/Zoomify/issues/85). -- **[MidnightLib](https://modrinth.com/mod/midnightlib)**:<br/>**It has cosmetics among other utilities.** It may not be large but some players (including me) wouldn't want cosmetics out of nowhere. -- **[OwoLib](https://modrinth.com/mod/owo-lib)**:<br/>**It's content focused.** It does a lot of other things as well as config, adding to the size. +- **[Cloth Config API](https://modrinth.com/mod/cloth-config)**: **It's stale.** The developer of cloth has clarified that they are likely not going to add any more features. They don't want to touch it. ([citation](https://user-images.githubusercontent.com/43245524/206530322-3ae46008-5356-468e-9a73-63b859364d4e.png)) +- **[SpruceUI](https://github.com/LambdAurora/SpruceUI)**: **It isn't designed for configuration.** In this essence the design feels cluttered. Further details available in [this issue](https://github.com/isXander/Zoomify/issues/85). +- **[MidnightLib](https://modrinth.com/mod/midnightlib)**: **It has cosmetics among other utilities.** It may not be large but some players (including me) wouldn't want cosmetics out of nowhere. +- **[OwoLib](https://modrinth.com/mod/owo-lib)**: **It's content focused.** It does a lot of other things as well as config, adding to the size. As you can see, there's sadly a drawback with all of them and this is where YetAnotherConfigLib comes in. -## How is YACL better? +## Why use YACL? -YACL has the favour of hindsight. Whilst developing this fresh library, I can make sure that it does everything right: +### Features -- **Client sided library.** YACL is built for client mods only, making it a smaller size. -- **Easy API.** YACL takes inspiration from [Sodium's](https://modrinth.com/mod/sodium) internal configuration library. -- **It's styled to fit in Minecraft.** YACL's GUI is designed to fit right in. +YACL has a ton of configuration features: -## Usage +- Custom control widgets + - Create your own unique "controller" if the default set does not suit your needs +- Rich descriptions + - Clickable & hoverable text, powered by vanilla's Text component system + - WebP (including animated) image previews + - Custom rich-renderable section to replace image +- Multiple controllers for the same type: + - Sliders or fields for numbers + - Dropdowns, cyclers, or raw text fields for strings + - Tickboxes or ON/OFF text display for booleans + - ...and more! +- Fully-featured color picker +- Accessible with full compatibility for keyboard control (optimised for Controlify usage) +- High organisation with tabs (categories) and collapsable groups +- Built-in serialization/deserialization techniques so you can skip the error-prone config code +- Full alternative Kotlin DSL -[The wiki](https://github.com/isXander/YetAnotherConfigLib/wiki/Usage) contains a full documentation on how to use YACL. +### Version support + +YACL supports a huge amount of versions, all kept up to date and released simultaneously, thanks to the amazing +[Stonecutter](https://stonecutter.kikugie.dev/) build tool. -## Screenshots +| Version | Fabric | Forge | NeoForge | +|-------------------------|--------|-------|----------| +| **1.20.1** | ✅ | ✅ | ⛔ | +| **1.20.4** | ✅ | ⛔ | ✅ | +| **1.20.5 - 1.20.6** | ✅ | ⛔ | ✅ | +| **1.21.0 - 1.21.1** | ✅ | ⛔ | ✅ | +| **1.21.2** (RC version) | ✅ | ⛔ | ⛔ | -<center><div align="center"> +That's **9** different targets, supporting versions that are 500+ days old! -![java_A3zdbksGkC](https://user-images.githubusercontent.com/43245524/206924832-293b0780-2a8c-4b09-8765-155318d09ed9.png) +_**Note**: Forge (LexForge) is not and will not be supported past 1.20.1. +If you're a developer, please port to NeoForge. +If you're a user, you may find that all your favourite mods have already done so._ -</div></center> +Each is a separate build, so make sure your users get the correct YACL version for their target of choice. + +### Design + +YACL is designed to fit right in with the vanilla GUI aesthetic, and will evolve with Minecraft itself. Take a look at +the gallery to see how even in all the currently supported versions, YACL's design looks different to fit in with +vanilla GUI updates. + +![image preview](https://cdn.modrinth.com/data/1eAoo2KR/images/5862570281f5109119c11f21a1bba52b6a2ab17f.png) + +## Usage for Developers + +[The wiki](https://github.com/isXander/YetAnotherConfigLib/wiki/Usage) contains a full documentation on how to use YACL. ## License diff --git a/build.gradle.kts b/build.gradle.kts index 5d5cb9a..2d8162d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ val isForgeLike = isNeoforge || isForge val mcVersion = findProperty("mcVersion").toString() group = "dev.isxander" -val versionWithoutMC = "3.5.0" +val versionWithoutMC = "3.6.0" version = "$versionWithoutMC+${stonecutter.current.project}" val snapshotVer = "${grgit.branch.current().name.replace('/', '.')}-SNAPSHOT" diff --git a/changelog.md b/changelog.md index ff542b8..45b034a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ -# YetAnotherConfigLib 3.5.0 +# YetAnotherConfigLib 3.6.0 This build supports the following versions: +- Fabric 1.21.2 - Fabric 1.20.1 - Fabric 1.20.4 - Fabric 1.20.6 (also supports 1.20.5) @@ -10,80 +11,83 @@ This build supports the following versions: - NeoForge 1.20.4 - MinecraftForge 1.20.1 -## *Experimental* Codec Config +## State Managers -This update brings a new experimental config API that utilises Mojang's Codec for (de)serialization. +Options now no longer hold their state themselves. This job is now delegated to the `StateManager`. + +This change serves to allow multiple options to share the same state, but with different controllers, descriptions, +names, etc. + +### Example + +Take an example of how this might be useful: + +You have a long range of gamepad bindings, utilising a custom YACL _controller_. They are sorted alphabetically in YACL. +You want a specific gamepad binding, let's call it 'Jump', to be featured at the top of the list, but also still +appear alphabetically in the list. + +You add a second 'Jump' YACL _option_ and place it at the beginning of the _category_, you assign it an identical +_binding_. + +This appears to work, but you discover a problem: you can modify the featured _option_ just fine, but when you hit +'Save Changes', you see an error in your log 'Option value mismatch' and your option defaults to the previous, now +unchanged value. + +Why is this? Each instance of _`Option`_ holds its own _pending value_. Which is then applied to the _binding_ when you +click save. When you click save, YACL iterates over each option in-order and applies the pending value to the _binding._ +Which leads to the following series of events: + +1. YACL successfully applies the featured 'Jump' option to the binding, as it's first in the list. +2. YACL then finds the non-featured 'Jump' option, which retained the original default value because the + _pending value_ has not changed. From step 1, the binding's value has now changed to something none-default, so now + it sets the binding again to the pending value of THIS option, now the binding is back to its default state. +3. YACL finishes saving _options_, and checks over each one again. It checks that the _pending value_ matches the + _binding_. Because the featured 'Jump' option now doesn't match, it creates an error in your log: + 'Option value mismatch' and assigns the _pending value_ to the _binding_ value. + +### Solution + +State managers essentially solve this by moving the _pending value_ into a separate object that can then be given to +multiple _options_. They ensure that each option's controller is kept up to date by emitting events upon the state's +change that controllers then listen to, to keep themselves up to date. + +### Code Examples ```java -public class CodecConfig extends JsonFileCodecConfig/*or*/CodecConfig { - public static final CodecConfig INSTANCE = new CodecConfig(); - - public final ConfigEntry<Integer> myInt = - register("my_int", 0, Codec.INT); - - public final ReadonlyConfigEntry<InnerCodecConfig> myInnerConfig = - register("my_inner_config", InnerCodecConfig.INSTANCE); - - public CodecConfig() { - super(path); - } - - void test() { - loadFromFile(); // load like this - saveToFile(); // save like this - - // or if you just extend CodecConfig instead of JsonFileConfig: - JsonElement element = null; - this.decode(element, JsonOps.INSTANCE); // load - DataResult<JsonElement> encoded = this.encodeStart(JsonOps.INSTANCE); // save - } -} -``` -or in Kotlin... -```kotlin -object CodecConfig : JsonFileCodecConfig(path) { - val myInt by register<Int>(0, Codec.INT) - - val myInnerConfig by register(InnerCodecConfig) - - fun test() { - loadFromFile() - saveToFile() - - // blah blah blah - } -} +StateManager<Boolean> stateManager = StateManager.createSimple(getter, setter, def); + +category.option(Option.<Boolean>createOption() + .name(Component.literal("Sharing Tick Box")) + .stateManager(stateManager) + .controller(TickBoxControllerBuilder::create) + .build()); +category.option(Option.<Boolean>createOption() + .name(Component.literal("Sharing Boolean")) + .stateManager(stateManager) + .controller(BooleanControllerBuilder::create) + .build()); ``` -## Rewritten Kotlin DSL - -Completely rewrote the Kotlin DSL! - -```kotlin -YetAnotherConfigLib("namespace") { - val category by categories.registering { - val option by rootOptions.registering<Int> { - controller = slider(range = 5..10) - binding(::thisProp, default) - - val otherOption by categories["category"]["group"].futureRef<Boolean>() - otherOption.onReady { it.setAvailable(false) } - } - - // translation key is generated automagically - val label by rootOptions.registeringLabel - - val group by groups.registering { - val otherOption = options.register<Boolean>("otherOption") { - controller = tickBox() - } - } - } -} +Here, instead of using a `.binding()`, you use a `.stateManager()`. Now both share and update the same state! + +### Event Changes + +Options that listen to events, `.listener((option, newValue) -> {})`, the syntax has slightly changed. The +aforementioned methods have been deprecated, replaced with `.addListener((option, event) -> {})`, you can retrieve the +new value with `option.pendingValue()`. + +### Consolidation of .instant() behaviour + +Because of this open-ness of how an option's pending value gets applied, users of YACL have a lot more power +on what happens with how an option's state is applied. + +```java +.stateManager(StateManager.createInstant(getter, setter, def)) ``` -## Changes +Is the new way to get instantly applied options. You can also implement `StateManager` yourself. + +## Other Changes -- Fix dropdown controllers erroneously showing their dropdown - Crendgrim -- Make cancel/reset and undo buttons public for accessing -- Add compatibility for 1.21 +- Added support for 1.21.2 +- Label Options now allow change of state, so labels can now be changed dynamically. diff --git a/gradle.properties b/gradle.properties index 79fe14e..eaa637b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.jvmargs=-Xmx4G +org.gradle.parallel=true modrinthId=1eAoo2KR curseforgeId=667299 @@ -8,7 +9,7 @@ modId=yet_another_config_lib_v3 modName=YetAnotherConfigLib modDescription=YetAnotherConfigLib (yacl) is just that. A builder-based configuration library for Minecraft. -deps.fabricLoader=0.15.11 +deps.fabricLoader=0.16.7 deps.imageio=3.10.0 deps.quiltParsers=0.2.1 deps.mixinExtras=0.3.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6f86915..ce8f04a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 05 20:43:25 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 35b1b5b..5b12068 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ pluginManagement { } plugins { - id("dev.kikugie.stonecutter") version "0.4-beta.3" + id("dev.kikugie.stonecutter") version "0.4.5" } extensions.configure<StonecutterSettings> { @@ -27,9 +27,10 @@ extensions.configure<StonecutterSettings> { } } + mc("1.21.2", loaders = listOf("fabric")) + mc("1.21", loaders = listOf("fabric", "neoforge")) mc("1.20.6", loaders = listOf("fabric", "neoforge")) mc("1.20.4", loaders = listOf("fabric", "neoforge")) - mc("1.21", loaders = listOf("fabric", "neoforge")) mc("1.20.1", loaders = listOf("fabric", "forge")) } create(rootProject) diff --git a/src/main/java/dev/isxander/yacl3/api/LabelOption.java b/src/main/java/dev/isxander/yacl3/api/LabelOption.java index a5f015e..16372b0 100644 --- a/src/main/java/dev/isxander/yacl3/api/LabelOption.java +++ b/src/main/java/dev/isxander/yacl3/api/LabelOption.java @@ -26,6 +26,8 @@ public interface LabelOption extends Option<Component> { } interface Builder { + Builder state(@NotNull StateManager<Component> stateManager); + /** * Appends a line to the label */ diff --git a/src/main/java/dev/isxander/yacl3/api/ListOption.java b/src/main/java/dev/isxander/yacl3/api/ListOption.java index 1f4adfa..9103254 100644 --- a/src/main/java/dev/isxander/yacl3/api/ListOption.java +++ b/src/main/java/dev/isxander/yacl3/api/ListOption.java @@ -93,6 +93,8 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { */ Builder<T> customController(@NotNull Function<ListOptionEntry<T>, Controller<T>> control); + Builder<T> state(@NotNull StateManager<List<T>> stateManager); + /** * Sets the binding for the option. * Used for default, getter and setter. @@ -159,6 +161,10 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { */ Builder<T> collapsed(boolean collapsible); + ListOption.Builder<T> addListener(@NotNull OptionEventListener<List<T>> listener); + + ListOption.Builder<T> addListeners(@NotNull Collection<OptionEventListener<List<T>>> listeners); + /** * Adds a listener to the option. Invoked upon changing any of the list's entries. * diff --git a/src/main/java/dev/isxander/yacl3/api/Option.java b/src/main/java/dev/isxander/yacl3/api/Option.java index 38bd8ca..9190168 100644 --- a/src/main/java/dev/isxander/yacl3/api/Option.java +++ b/src/main/java/dev/isxander/yacl3/api/Option.java @@ -34,12 +34,15 @@ public interface Option<T> { */ @NotNull Controller<T> controller(); + @NotNull StateManager<T> stateManager(); + /** * Binding for the option. * Controls setting, getting and default value. * * @see Binding */ + @Deprecated @NotNull Binding<T> binding(); /** @@ -101,9 +104,12 @@ public interface Option<T> { return true; } + void addEventListener(OptionEventListener<T> listener); + /** * Adds a listener for when the pending value changes */ + @Deprecated void addListener(BiConsumer<Option<T>, T> changedListener); static <T> Builder<T> createBuilder() { @@ -146,6 +152,10 @@ public interface Option<T> { */ Builder<T> description(@NotNull Function<T, OptionDescription> descriptionFunction); + /** + * Supplies a controller for this option. A controller is the GUI control to interact with the option. + * @return this builder + */ Builder<T> controller(@NotNull Function<Option<T>, ControllerBuilder<T>> controllerBuilder); /** @@ -156,9 +166,12 @@ public interface Option<T> { */ Builder<T> customController(@NotNull Function<Option<T>, Controller<T>> control); + Builder<T> stateManager(@NotNull StateManager<T> stateManager); + /** * Sets the binding for the option. * Used for default, getter and setter. + * Under-the-hood, this creates a state manager that is individual to the option, sharing state with no options. * * @see Binding */ @@ -196,12 +209,17 @@ public interface Option<T> { */ Builder<T> flags(@NotNull Collection<? extends OptionFlag> flags); + Builder<T> addListener(@NotNull OptionEventListener<T> listener); + + Builder<T> addListeners(@NotNull Collection<OptionEventListener<T>> listeners); + /** * Instantly invokes the binder's setter when modified in the GUI. * Prevents the user from undoing the change * <p> * Does not support {@link Option#flags()}! */ + @Deprecated Builder<T> instant(boolean instant); /** @@ -209,6 +227,7 @@ public interface Option<T> { * * @see Option#addListener(BiConsumer) */ + @Deprecated Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener); /** @@ -216,6 +235,7 @@ public interface Option<T> { * * @see Option#addListener(BiConsumer) */ + @Deprecated Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners); Option<T> build(); diff --git a/src/main/java/dev/isxander/yacl3/api/OptionEventListener.java b/src/main/java/dev/isxander/yacl3/api/OptionEventListener.java new file mode 100644 index 0000000..c805948 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/api/OptionEventListener.java @@ -0,0 +1,13 @@ +package dev.isxander.yacl3.api; + +@FunctionalInterface +public interface OptionEventListener<T> { + void onEvent(Option<T> option, Event event); + + enum Event { + INITIAL, + STATE_CHANGE, + AVAILABILITY_CHANGE, + OTHER, + } +} diff --git a/src/main/java/dev/isxander/yacl3/api/StateManager.java b/src/main/java/dev/isxander/yacl3/api/StateManager.java new file mode 100644 index 0000000..07d263e --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/api/StateManager.java @@ -0,0 +1,89 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.impl.ImmutableStateManager; +import dev.isxander.yacl3.impl.InstantStateManager; +import dev.isxander.yacl3.impl.SimpleStateManager; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public interface StateManager<T> { + static <T> StateManager<T> createSimple(Binding<T> binding) { + return new SimpleStateManager<>(binding); + } + + static <T> StateManager<T> createSimple(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { + return new SimpleStateManager<>(Binding.generic(def, getter, setter)); + } + + static <T> StateManager<T> createInstant(Binding<T> binding) { + return new InstantStateManager<>(binding); + } + + static <T> StateManager<T> createInstant(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { + return new InstantStateManager<>(Binding.generic(def, getter, setter)); + } + + static <T> StateManager<T> createImmutable(@NotNull T value) { + return new ImmutableStateManager<>(value); + } + + /** + * Sets the pending value. + */ + void set(T value); + + /** + * @return the pending value. + */ + T get(); + + /** + * Applies the pending value to the backed binding. + */ + void apply(); + + void resetToDefault(ResetAction action); + + /** + * Essentially "forgets" the pending value and reassigns state as backed by the binding. + */ + void sync(); + + /** + * @return true if the pending value is the same as the backed binding value. + */ + boolean isSynced(); + + /** + * @return true if this state manage will always be synced with the backing binding. + */ + default boolean isAlwaysSynced() { + return false; + } + + boolean isDefault(); + + void addListener(StateListener<T> stateListener); + + enum ResetAction { + BY_OPTION, + BY_GLOBAL, + } + + interface StateListener<T> { + static <T> StateListener<T> noop() { + return (oldValue, newValue) -> {}; + } + + void onStateChange(T oldValue, T newValue); + + default StateListener<T> andThen(StateListener<T> after) { + return (oldValue, newValue) -> { + this.onStateChange(oldValue, newValue); + after.onStateChange(oldValue, newValue); + }; + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java index bbb878d..9adc64f 100644 --- a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java @@ -37,7 +37,7 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op }) .available(this.available(annotation, field, optionAccess)) .flags(this.flags(annotation, field, optionAccess)) - .listener((opt, v) -> this.listener(annotation, field, optionAccess, opt, v)) + .addListener((opt, event) -> this.listener(annotation, field, optionAccess, opt, opt.pendingValue())) .build(); postInit(annotation, field, optionAccess, option); diff --git a/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java b/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java index 48f2cc3..111ccdb 100644 --- a/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java +++ b/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java @@ -2,6 +2,7 @@ package dev.isxander.yacl3.gui; import com.mojang.blaze3d.vertex.VertexConsumer; import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.utils.GuiUtils; import dev.isxander.yacl3.gui.utils.YACLRenderHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; @@ -88,23 +89,22 @@ public abstract class AbstractWidget implements GuiEventListener, Renderable, Na graphics.fill(x1, y1, x1 + width, y2, color); } - protected void fillSidewaysGradient(GuiGraphics graphics, int x1, int y1, int x2, int y2, int startColor, int endColor) { + protected void fillSidewaysGradient(GuiGraphics graphics, int x1, int y1, int x2, int y2, int startColor, int endColor, VertexConsumer consumer) { //Fills a gradient, left to right //Uses practically the same method as the GuiGraphics class, but with the x/y moved //Has a custom "z" value in case needed for later - VertexConsumer vertex = graphics.bufferSource().getBuffer(RenderType.gui()); Matrix4f matrix4f = graphics.pose().last().pose(); /*? if >1.20.6 {*/ - vertex.addVertex(matrix4f, x1, y1, 0).setColor(startColor); - vertex.addVertex(matrix4f, x1, y2, 0).setColor(startColor); - vertex.addVertex(matrix4f, x2, y2, 0).setColor(endColor); - vertex.addVertex(matrix4f, x2, y1, 0).setColor(endColor); + consumer.addVertex(matrix4f, x1, y1, 0).setColor(startColor); + consumer.addVertex(matrix4f, x1, y2, 0).setColor(startColor); + consumer.addVertex(matrix4f, x2, y2, 0).setColor(endColor); + consumer.addVertex(matrix4f, x2, y1, 0).setColor(endColor); /*?} else {*/ - /*vertex.vertex(matrix4f, x1, y1, 0).color(startColor).endVertex(); - vertex.vertex(matrix4f, x1, y2, 0).color(startColor).endVertex(); - vertex.vertex(matrix4f, x2, y2, 0).color(endColor).endVertex(); - vertex.vertex(matrix4f, x2, y1, 0).color(endColor).endVertex(); + /*consumer.vertex(matrix4f, x1, y1, 0).color(startColor).endVertex(); + consumer.vertex(matrix4f, x1, y2, 0).color(startColor).endVertex(); + consumer.vertex(matrix4f, x2, y2, 0).color(endColor).endVertex(); + consumer.vertex(matrix4f, x2, y1, 0).color(endColor).endVertex(); *//*?}*/ } @@ -115,16 +115,25 @@ public abstract class AbstractWidget implements GuiEventListener, Renderable, Na Color.cyan.getRGB(), Color.blue.getRGB(), Color.magenta.getRGB(), Color.red.getRGB()}; //all the colors in the gradient int width = x2 - x1; int maxColors = colors.length - 1; - for (int color = 0; color < maxColors; color++) { - //First checks if the final color is being rendered, if true -> uses x2 int instead of x1 - //if false -> it adds the width divided by the max colors multiplied by the current color plus one to the x1 int - //the x2 int for the fillSidewaysGradient is the same formula, excluding the additional plus one. - //The gradient colors is determined by the color int and the color int plus one, which is why red is in the colors array twice - fillSidewaysGradient(graphics, - x1 + (width / maxColors * color), y1, - color == maxColors - 1 ? x2 : x1 + (width / maxColors * (color + 1)), y2, - colors[color], colors[color + 1]); - } + + GuiUtils.drawSpecial(graphics, bufferSource -> { + VertexConsumer consumer = bufferSource.getBuffer(RenderType.gui()); + + for (int color = 0; color < maxColors; color++) { + //First checks if the final color is being rendered, if true -> uses x2 int instead of x1 + //if false -> it adds the width divided by the max colors multiplied by the current color plus one to the x1 int + //the x2 int for the fillSidewaysGradient is the same formula, excluding the additional plus one. + //The gradient colors is determined by the color int and the color int plus one, which is why red is in the colors array twice + fillSidewaysGradient( + graphics, + x1 + (width / maxColors * color), y1, + color == maxColors - 1 ? x2 : x1 + (width / maxColors * (color + 1)), y2, + colors[color], colors[color + 1], + consumer + ); + } + }); + } protected int multiplyColor(int hex, float amount) { diff --git a/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java b/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java index 8412cc8..cbd15fe 100644 --- a/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java +++ b/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java @@ -173,7 +173,8 @@ public class ElementListWidgetExt<E extends ElementListWidgetExt.Entry<E>> exten } @Override - protected int getRowTop(int index) { + /*? if >=1.21.2 {*/ public /*?} else {*/ /*protected *//*?}*/ + int getRowTop(int index) { int integer = getY() + 4 - (int) this.getScrollAmount() + headerHeight; for (int i = 0; i < children().size() && i < index; i++) integer += children().get(i).getItemHeight(); diff --git a/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java index 90bc75f..812a0a1 100644 --- a/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java +++ b/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java @@ -2,7 +2,6 @@ package dev.isxander.yacl3.gui; import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; import com.mojang.math.Axis; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.utils.Dimension; @@ -25,12 +24,10 @@ import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.tabs.TabManager; -import net.minecraft.client.gui.components.tabs.TabNavigationBar; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -285,7 +282,8 @@ public class YACLScreen extends Screen { drawY, maxWidth, height, - 400 + 400/*? if >=1.21.2 {*/, + null/*?}*/ ); graphics.pose().translate(0.0, 0.0, 400.0); @@ -394,19 +392,19 @@ public class YACLScreen extends Screen { public void renderBackground(GuiGraphics graphics) { RenderSystem.enableBlend(); // right pane darker db - graphics.blit(DARKER_BG, rightPaneDim.left(), rightPaneDim.top(), rightPaneDim.right() + 2, rightPaneDim.bottom() + 2, rightPaneDim.width() + 2, rightPaneDim.height() + 2, 32, 32); - + GuiUtils.blitGuiTex(graphics, DARKER_BG, rightPaneDim.left(), rightPaneDim.top(), rightPaneDim.right() + 2, rightPaneDim.bottom() + 2, rightPaneDim.width() + 2, rightPaneDim.height() + 2, 32, 32); + // top separator for right pane graphics.pose().pushPose(); graphics.pose().translate(0, 0, 10); - graphics.blit(CreateWorldScreen.HEADER_SEPARATOR, rightPaneDim.left() - 1, rightPaneDim.top() - 2, 0.0F, 0.0F, rightPaneDim.width() + 1, 2, 32, 2); + GuiUtils.blitGuiTex(graphics, CreateWorldScreen.HEADER_SEPARATOR, rightPaneDim.left() - 1, rightPaneDim.top() - 2, 0.0F, 0.0F, rightPaneDim.width() + 1, 2, 32, 2); graphics.pose().popPose(); // left separator for right pane graphics.pose().pushPose(); graphics.pose().translate(rightPaneDim.left(), rightPaneDim.top() - 1, 0); graphics.pose().rotateAround(Axis.ZP.rotationDegrees(90), 0, 0, 1); - graphics.blit(CreateWorldScreen.FOOTER_SEPARATOR, 0, 0, 0f, 0f, rightPaneDim.height() + 1, 2, 32, 2); + GuiUtils.blitGuiTex(graphics, CreateWorldScreen.FOOTER_SEPARATOR, 0, 0, 0f, 0f, rightPaneDim.height() + 1, 2, 32, 2); graphics.pose().popPose(); RenderSystem.disableBlend(); diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java index 3c0a5fc..a48bdde 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java @@ -260,7 +260,7 @@ public class ColorController implements IStringController<Color> { @Override public void unfocus() { - if(colorPickerVisible) { + if (colorPickerVisible) { removeColorPicker(); } previewOutlineFadeTicks = 0; @@ -271,7 +271,7 @@ public class ColorController implements IStringController<Color> { Color outlineColor = new Color(0xFF000000); Color highlightedColor = getHighlightedOutlineColor(); - if(!hovered && !colorPreviewHovered) { + if (!hovered && !colorPreviewHovered) { previewOutlineFadeTicks = 0; return outlineColor; } @@ -279,11 +279,11 @@ public class ColorController implements IStringController<Color> { int fadeInTicks = 80; int fadeOutTicks = fadeInTicks + 120; - if(colorPreviewHovered) { + if (colorPreviewHovered) { //white/light grey if the color preview is being hovered previewOutlineFadeTicks = 0; return highlightedColor; - } else if(YACLConfig.HANDLER.instance().showColorPickerIndicator) { + } else if (YACLConfig.HANDLER.instance().showColorPickerIndicator) { if(previewOutlineFadeTicks <= fadeInTicks) { //fade to white return getFadedColor(outlineColor, highlightedColor, previewOutlineFadeTicks, fadeInTicks); diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java index efa1aec..a1828f5 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java @@ -3,9 +3,11 @@ package dev.isxander.yacl3.gui.controllers; import dev.isxander.yacl3.api.utils.Dimension; import dev.isxander.yacl3.api.utils.MutableDimension; import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.utils.GuiUtils; import dev.isxander.yacl3.gui.utils.YACLRenderHelper; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderType; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; @@ -89,7 +91,7 @@ public class ColorPickerWidget extends ControllerPopupWidget<ColorController> { //Background /*? if >1.20.3 {*/ - graphics.blitSprite(COLOR_PICKER_LOCATION, colorPickerDim.x() - 5, colorPickerDim.y() - 5, colorPickerDim.width() + 10, colorPickerDim.height() + 10); + GuiUtils.blitSprite(graphics, COLOR_PICKER_LOCATION, colorPickerDim.x() - 5, colorPickerDim.y() - 5, colorPickerDim.width() + 10, colorPickerDim.height() + 10); /*?} else {*/ /*graphics.blitNineSliced(COLOR_PICKER_ATLAS, colorPickerDim.x() - 5, colorPickerDim.y() - 5, colorPickerDim.width() + 10, colorPickerDim.height() + 10, 3, 236, 34, 0, 0); *//*?}*/ @@ -100,7 +102,7 @@ public class ColorPickerWidget extends ControllerPopupWidget<ColorController> { //transparent texture - must be rendered BEFORE the main color preview if(controller.allowAlpha()) { /*? if >1.20.3 {*/ - graphics.blitSprite(TRANSPARENT_TEXTURE_LOCATION, previewColorDim.x(), previewColorDim.y(), previewColorDim.width(), previewColorDim.height()); + GuiUtils.blitSprite(graphics, TRANSPARENT_TEXTURE_LOCATION, previewColorDim.x(), previewColorDim.y(), previewColorDim.width(), previewColorDim.height()); /*?} else {*/ /*graphics.blitRepeating(COLOR_PICKER_ATLAS, previewColorDim.x(), previewColorDim.y(), previewColorDim.width(), previewColorDim.height(), 236, 0, 8, 8); *//*?}*/ @@ -112,7 +114,9 @@ public class ColorPickerWidget extends ControllerPopupWidget<ColorController> { //outline graphics.fill(saturationLightDim.x() - outline, saturationLightDim.y() - outline, saturationLightDim.xLimit() + outline, saturationLightDim.yLimit() + outline, Color.black.getRGB()); //White to pending color's RGB from hue, left to right - fillSidewaysGradient(graphics, saturationLightDim.x(), saturationLightDim.y(), saturationLightDim.xLimit(), saturationLightDim.yLimit(), 0xFFFFFFFF, (int) getRgbFromHueX()); + GuiUtils.drawSpecial(graphics, bufferSource -> { + fillSidewaysGradient(graphics, saturationLightDim.x(), saturationLightDim.y(), saturationLightDim.xLimit(), saturationLightDim.yLimit(), 0xFFFFFFFF, (int) getRgbFromHueX(), bufferSource.getBuffer(RenderType.gui())); + }); //Transparent to black, top to bottom graphics.fillGradient(saturationLightDim.x(), saturationLightDim.y(), saturationLightDim.xLimit(), saturationLightDim.yLimit(), 0x00000000, 0xFF000000); //Sat/light thumb shadow @@ -135,12 +139,14 @@ public class ColorPickerWidget extends ControllerPopupWidget<ColorController> { graphics.fill(alphaGradientDim.x() - outline, alphaGradientDim.y() - outline, alphaGradientDim.xLimit() + outline, alphaGradientDim.yLimit() + outline, Color.black.getRGB()); //Transparent texture /*? if >1.20.3 {*/ - graphics.blitSprite(TRANSPARENT_TEXTURE_LOCATION, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.width(), sliderHeight); + GuiUtils.blitSprite(graphics, TRANSPARENT_TEXTURE_LOCATION, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.width(), sliderHeight); /*?} else {*/ /*graphics.blitRepeating(COLOR_PICKER_ATLAS, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.width(), sliderHeight, 236, 0, 8, 8); *//*?}*/ //Pending color to transparent - fillSidewaysGradient(graphics, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.xLimit(), alphaGradientDim.yLimit(), getRgbWithoutAlpha(), 0x00000000); + GuiUtils.drawSpecial(graphics, bufferSource -> { + fillSidewaysGradient(graphics, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.xLimit(), alphaGradientDim.yLimit(), getRgbWithoutAlpha(), 0x00000000, bufferSource.getBuffer(RenderType.gui())); + }); //Alpha slider thumb shadow graphics.fill(alphaThumbX - thumbWidth / 2 - 1, alphaGradientDim.y() - outline - 1, alphaThumbX + thumbWidth / 2 + 1, alphaGradientDim.yLimit() + outline + 1, 0xFF404040); //Alpha slider thumb diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java index fee6c19..9f38d3b 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java @@ -88,8 +88,6 @@ public class LabelController implements Controller<Component> { graphics.pose().pushPose(); graphics.pose().translate(0, 0, 100); if (isMouseOver(mouseX, mouseY)) { - YACLScreen.renderMultilineTooltip(graphics, 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(); diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java index f799059..464571e 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java @@ -5,6 +5,7 @@ import dev.isxander.yacl3.api.utils.Dimension; import dev.isxander.yacl3.api.utils.MutableDimension; import dev.isxander.yacl3.gui.YACLScreen; import dev.isxander.yacl3.gui.controllers.ControllerPopupWidget; +import dev.isxander.yacl3.gui.utils.GuiUtils; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; @@ -59,26 +60,28 @@ public class DropdownWidget<T> extends ControllerPopupWidget<AbstractDropdownCon matrices.translate(0, 0, 200); // Background - graphics.setColor(0.25f, 0.25f, 0.25f, 1.0f); - graphics.blit( + //graphics.setColor(0.25f, 0.25f, 0.25f, 1.0f); + GuiUtils.blitGuiTexColor( + graphics, /*? if >1.20.4 {*/ Screen.MENU_BACKGROUND, /*?} else {*/ /*Screen.BACKGROUND_LOCATION, *//*?}*/ - dropdownDim.x(), dropdownDim.y(), 0, + dropdownDim.x(), dropdownDim.y(), 0.0f, 0.0f, dropdownDim.width(), dropdownDim.height(), - 32, 32 + 32, 32, + 0xFF3F3F3F ); - graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); + //graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); graphics.renderOutline(dropdownDim.x(), dropdownDim.y(), dropdownDim.width(), dropdownDim.height(), -1); // Highlight the currently selected element - graphics.setColor(0.0f, 0.0f, 0.0f, 0.5f); + //graphics.setColor(0.0f, 0.0f, 0.0f, 0.5f); int y = dropdownDim.y() + 2 + entryHeight() * selectedVisibleIndex(); - graphics.fill(dropdownDim.x(), y, dropdownDim.xLimit(), y + entryHeight(), -1); - graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); + graphics.fill(dropdownDim.x(), y, dropdownDim.xLimit(), y + entryHeight(), 0x7F000000); + //graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); graphics.renderOutline(dropdownDim.x(), y, dropdownDim.width(), entryHeight(), -1); // Render all visible elements diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java index 2c19c13..37911de 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java @@ -3,6 +3,7 @@ package dev.isxander.yacl3.gui.controllers.dropdown; import dev.isxander.yacl3.api.utils.Dimension; import dev.isxander.yacl3.gui.YACLScreen; import dev.isxander.yacl3.gui.utils.ItemRegistryHelper; +import dev.isxander.yacl3.gui.utils.MiscUtil; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; @@ -43,7 +44,7 @@ public class ItemControllerElement extends AbstractDropdownControllerElement<Ite List<ResourceLocation> identifiers = ItemRegistryHelper.getMatchingItemIdentifiers(inputField).toList(); currentItem = ItemRegistryHelper.getItemFromName(inputField, null); for (ResourceLocation identifier : identifiers) { - matchingItems.put(identifier, BuiltInRegistries.ITEM.get(identifier)); + matchingItems.put(identifier, MiscUtil.getFromRegistry(BuiltInRegistries.ITEM, identifier)); } return identifiers; } @@ -86,6 +87,11 @@ public class ItemControllerElement extends AbstractDropdownControllerElement<Ite if (inputFieldFocused) return Component.literal(inputField); - return itemController.option().pendingValue().getDescription(); + return itemController.option().pendingValue() + //? if >=1.21.2 { + .getName(); + //?} else { + /*.getDescription(); + *///?} } } diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java index 689d8e2..7151f89 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java @@ -1,6 +1,7 @@ package dev.isxander.yacl3.gui.controllers.string; import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.OptionEventListener; import dev.isxander.yacl3.api.utils.Dimension; import dev.isxander.yacl3.gui.YACLScreen; import dev.isxander.yacl3.gui.controllers.ControllerWidget; @@ -39,8 +40,10 @@ public class StringControllerElement extends ControllerWidget<IStringController< inputFieldFocused = false; selectionLength = 0; emptyText = Component.literal("Click to type...").withStyle(ChatFormatting.GRAY); - control.option().addListener((opt, val) -> { - inputField = control.getString(); + control.option().addEventListener((opt, event) -> { + if (event == OptionEventListener.Event.STATE_CHANGE) { + inputField = control.getString(); + } }); setDimension(dim); } diff --git a/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java b/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java index fb0695c..235d1d4 100644 --- a/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java +++ b/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java @@ -29,19 +29,20 @@ public class YACLImageReloadListener public @NotNull CompletableFuture<Void> reload( PreparationBarrier preparationBarrier, @NotNull ResourceManager resourceManager, - @NotNull ProfilerFiller preparationsProfiler, + //? if <1.21.2 { + /*@NotNull ProfilerFiller preparationsProfiler, @NotNull ProfilerFiller reloadProfiler, + *///?} @NotNull Executor backgroundExecutor, @NotNull Executor gameExecutor ) { - return prepare(resourceManager, preparationsProfiler, backgroundExecutor) + return prepare(resourceManager, backgroundExecutor) .thenCompose(preparationBarrier::wait) - .thenCompose(suppliers -> apply(suppliers, reloadProfiler, gameExecutor)); + .thenCompose(suppliers -> apply(suppliers, gameExecutor)); } private CompletableFuture<List<Optional<SupplierPreparation>>> prepare( ResourceManager manager, - ProfilerFiller profiler, Executor executor ) { Map<ResourceLocation, Resource> imageResources = manager.listResources( @@ -72,7 +73,6 @@ public class YACLImageReloadListener private CompletableFuture<Void> apply( List<Optional<SupplierPreparation>> suppliers, - ProfilerFiller profiler, Executor executor ) { return CompletableFuture.allOf(suppliers.stream() diff --git a/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java b/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java index 39ddb55..1dd52f2 100644 --- a/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java +++ b/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java @@ -7,7 +7,7 @@ import com.mojang.blaze3d.platform.NativeImage; import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi; import dev.isxander.yacl3.debug.DebugProperties; import dev.isxander.yacl3.gui.image.ImageRendererFactory; -import dev.isxander.yacl3.impl.utils.YACLConstants; +import dev.isxander.yacl3.gui.utils.GuiUtils; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; @@ -16,7 +16,6 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.FastColor; import javax.imageio.ImageIO; import javax.imageio.ImageReader; @@ -71,7 +70,8 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage { GlStateManager._texParameter(GlConst.GL_TEXTURE_2D, GlConst.GL_TEXTURE_MIN_FILTER, GlConst.GL_LINEAR); } - graphics.blit( + GuiUtils.blitGuiTex( + graphics, uniqueLocation, 0, 0, frameWidth * currentCol, frameHeight * currentRow, @@ -251,19 +251,16 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage { for (int w = 0; w < bi.getWidth(); w++) { for (int h = 0; h < bi.getHeight(); h++) { - int rgb = bi.getRGB(w, h); - int r = FastColor.ARGB32.red(rgb); - int g = FastColor.ARGB32.green(rgb); - int b = FastColor.ARGB32.blue(rgb); - int a = FastColor.ARGB32.alpha(rgb); + int argb = bi.getRGB(w, h); int col = i % cols; int row = (int) Math.floor(i / (double)cols); - image.setPixelRGBA( + GuiUtils.setPixelARGB( + image, frameWidth * col + w + xOffset, frameHeight * row + h + yOffset, - FastColor.ABGR32.color(a, b, g, r) // NativeImage uses ABGR for some reason + argb ); } } diff --git a/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java b/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java index 2d2abb9..edfaebc 100644 --- a/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java +++ b/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java @@ -7,6 +7,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import dev.isxander.yacl3.debug.DebugProperties; import dev.isxander.yacl3.gui.image.ImageRenderer; import dev.isxander.yacl3.gui.image.ImageRendererFactory; +import dev.isxander.yacl3.gui.utils.GuiUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.texture.DynamicTexture; @@ -51,7 +52,7 @@ public class DynamicTextureImage implements ImageRenderer { GlStateManager._texParameter(GlConst.GL_TEXTURE_2D, GlConst.GL_TEXTURE_MIN_FILTER, GlConst.GL_LINEAR); } - graphics.blit(uniqueLocation, 0, 0, 0, 0, this.width, this.height, this.width, this.height); + GuiUtils.blitGuiTex(graphics, uniqueLocation, 0, 0, 0, 0, this.width, this.height, this.width, this.height); graphics.pose().popPose(); diff --git a/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java b/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java index abbeec7..baaa4b1 100644 --- a/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java +++ b/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.platform.GlStateManager; import dev.isxander.yacl3.debug.DebugProperties; import dev.isxander.yacl3.gui.image.ImageRenderer; import dev.isxander.yacl3.gui.image.ImageRendererFactory; +import dev.isxander.yacl3.gui.utils.GuiUtils; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; @@ -38,7 +39,7 @@ public class ResourceTextureImage implements ImageRenderer { GlStateManager._texParameter(GlConst.GL_TEXTURE_2D, GlConst.GL_TEXTURE_MIN_FILTER, GlConst.GL_LINEAR); } - graphics.blit(location, 0, 0, this.u, this.v, this.width, this.height, this.textureWidth, this.textureHeight); + GuiUtils.blitGuiTex(graphics, location, 0, 0, this.u, this.v, this.width, this.height, this.textureWidth, this.textureHeight); graphics.pose().popPose(); diff --git a/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java b/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java index 2910d0f..25c4cb7 100644 --- a/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java +++ b/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java @@ -1,11 +1,75 @@ package dev.isxander.yacl3.gui.utils; +import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; public class GuiUtils { + public static void drawSpecial(GuiGraphics graphics, Consumer<MultiBufferSource> consumer) { + //? if >=1.21.2 { + graphics.drawSpecial(consumer); + //?} else { + /*MultiBufferSource.BufferSource bufferSource = graphics.bufferSource(); + consumer.accept(bufferSource); + bufferSource.endBatch(); + *///?} + } + + public static void blitGuiTex(GuiGraphics graphics, ResourceLocation texture, int x, int y, float u, float v, int textureWidth, int textureHeight, int width, int height) { + graphics.blit( + //? if >=1.21.2 + RenderType::guiTextured, + texture, + x, y, + u, v, + textureWidth, textureHeight, + width, height + ); + } + + public static void blitGuiTexColor(GuiGraphics graphics, ResourceLocation texture, int x, int y, float u, float v, int textureWidth, int textureHeight, int width, int height, int color) { + //? if <1.21.2 { + /*float a = (color >> 24 & 255) / 255.0F; + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + graphics.setColor(r, g, b, a); + *///?} + graphics.blit( + //? if >=1.21.2 + RenderType::guiTextured, + texture, + x, y, + u, v, + textureWidth, textureHeight, + width, height + //? if >=1.21.2 + ,color + ); + //? if <1.21.2 + /*graphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);*/ + } + + //? if >1.20.1 { + public static void blitSprite(GuiGraphics graphics, ResourceLocation sprite, int x, int y, int width, int height) { + graphics.blitSprite( + //? if >=1.21.2 + RenderType::guiTextured, + sprite, + x, y, + width, height + ); + } + //?} + public static MutableComponent translatableFallback(String key, Component fallback) { if (Language.getInstance().has(key)) return Component.translatable(key); @@ -29,4 +93,21 @@ public class GuiUtils { return string; } + + + public static void setPixelARGB(NativeImage nativeImage, int x, int y, int argb) { + // In 1.21.2+, you set the pixel color in ARGB format, where it internally converts to ABGR. + // Before this, you need to directly set the pixel color in ABGR format. + + //? if >=1.21.2 { + nativeImage.setPixel(x, y, argb); + //?} else { + /*int a = (argb >> 24) & 0xFF; + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = argb & 0xFF; + int abgr = (a << 24) | (b << 16) | (g << 8) | r; + nativeImage.setPixelRGBA(x, y, abgr); // method name is misleading. It's actually ABGR. + *///?} + } } diff --git a/src/main/java/dev/isxander/yacl3/gui/utils/ItemRegistryHelper.java b/src/main/java/dev/isxander/yacl3/gui/utils/ItemRegistryHelper.java index bb6c664..dc769bc 100644 --- a/src/main/java/dev/isxander/yacl3/gui/utils/ItemRegistryHelper.java +++ b/src/main/java/dev/isxander/yacl3/gui/utils/ItemRegistryHelper.java @@ -46,7 +46,7 @@ public final class ItemRegistryHelper { try { ResourceLocation itemIdentifier = YACLPlatform.parseRl(identifier.toLowerCase()); if (BuiltInRegistries.ITEM.containsKey(itemIdentifier)) { - return BuiltInRegistries.ITEM.get(itemIdentifier); + return MiscUtil.getFromRegistry(BuiltInRegistries.ITEM, itemIdentifier); } } catch (ResourceLocationException ignored) { } @@ -80,14 +80,15 @@ public final class ItemRegistryHelper { if (sep == -1) { filterPredicate = identifier -> identifier.getPath().contains(value) - || BuiltInRegistries.ITEM.get(identifier).getDescription().getString().toLowerCase().contains(value.toLowerCase()); + || MiscUtil.getFromRegistry(BuiltInRegistries.ITEM, identifier) + /*? if >=1.21.2 {*/ .getName() /*?} else {*/ /*.getDescription() *//*?}*/ + .getString().toLowerCase().contains(value.toLowerCase()); } else { String namespace = value.substring(0, sep); String path = value.substring(sep + 1); filterPredicate = identifier -> identifier.getNamespace().equals(namespace) && identifier.getPath().startsWith(path); } - return BuiltInRegistries.ITEM.holders() - .map(holder -> holder.key().location()) + return BuiltInRegistries.ITEM.keySet().stream() .filter(filterPredicate) /* Sort items as follows based on the given "value" string's path: diff --git a/src/main/java/dev/isxander/yacl3/gui/utils/MiscUtil.java b/src/main/java/dev/isxander/yacl3/gui/utils/MiscUtil.java new file mode 100644 index 0000000..354b38e --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/gui/utils/MiscUtil.java @@ -0,0 +1,14 @@ +package dev.isxander.yacl3.gui.utils; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; + +public class MiscUtil { + public static <T> T getFromRegistry(Registry<T> registry, ResourceLocation identifier) { + //? if >=1.21.2 { + return registry.getValue(identifier); + //?} else { + /*return registry.get(identifier); + *///?} + } +} diff --git a/src/main/java/dev/isxander/yacl3/gui/utils/YACLRenderHelper.java b/src/main/java/dev/isxander/yacl3/gui/utils/YACLRenderHelper.java index aadc249..23e97c3 100644 --- a/src/main/java/dev/isxander/yacl3/gui/utils/YACLRenderHelper.java +++ b/src/main/java/dev/isxander/yacl3/gui/utils/YACLRenderHelper.java @@ -19,7 +19,7 @@ public class YACLRenderHelper { public static void renderButtonTexture(GuiGraphics graphics, int x, int y, int width, int height, boolean enabled, boolean focused) { /*? if >1.20.1 {*/ - graphics.blitSprite(SPRITES.get(enabled, focused), x, y, width, height); + GuiUtils.blitSprite(graphics, SPRITES.get(enabled, focused), x, y, width, height); /*?} else {*/ /*int textureV; if (enabled) { diff --git a/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java index 170b8e0..60a9dc9 100644 --- a/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java @@ -17,10 +17,9 @@ import java.util.function.Consumer; public final class ButtonOptionImpl implements ButtonOption { private final Component name; private final OptionDescription description; - private final BiConsumer<YACLScreen, ButtonOption> action; + private final StateManager<BiConsumer<YACLScreen, ButtonOption>> stateManager; private boolean available; private final Controller<BiConsumer<YACLScreen, ButtonOption>> controller; - private final Binding<BiConsumer<YACLScreen, ButtonOption>> binding; public ButtonOptionImpl( @NotNull Component name, @@ -31,10 +30,9 @@ public final class ButtonOptionImpl implements ButtonOption { ) { this.name = name; this.description = description; - this.action = action; + this.stateManager = StateManager.createImmutable(action); this.available = available; this.controller = text != null ? new ActionController(this, text) : new ActionController(this); - this.binding = new EmptyBinderImpl(); } @Override @@ -54,7 +52,7 @@ public final class ButtonOptionImpl implements ButtonOption { @Override public BiConsumer<YACLScreen, ButtonOption> action() { - return action; + return stateManager().get(); } @Override @@ -73,8 +71,13 @@ public final class ButtonOptionImpl implements ButtonOption { } @Override + public @NotNull StateManager<BiConsumer<YACLScreen, ButtonOption>> stateManager() { + return this.stateManager; + } + + @Override public @NotNull Binding<BiConsumer<YACLScreen, ButtonOption>> binding() { - return binding; + return new EmptyBinderImpl(); } @Override @@ -118,6 +121,11 @@ public final class ButtonOptionImpl implements ButtonOption { } @Override + public void addEventListener(OptionEventListener<BiConsumer<YACLScreen, ButtonOption>> listener) { + + } + + @Override public void addListener(BiConsumer<Option<BiConsumer<YACLScreen, ButtonOption>>, BiConsumer<YACLScreen, ButtonOption>> changedListener) { } diff --git a/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java index 64588f2..16d8e14 100644 --- a/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java +++ b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java @@ -31,6 +31,11 @@ public class HiddenNameListOptionEntry<T> implements ListOptionEntry<T> { } @Override + public @NotNull StateManager<T> stateManager() { + return option.stateManager(); + } + + @Override public @NotNull Controller<T> controller() { return option.controller(); } @@ -101,9 +106,13 @@ public class HiddenNameListOptionEntry<T> implements ListOptionEntry<T> { } @Override + @Deprecated public void addListener(BiConsumer<Option<T>, T> changedListener) { option.addListener(changedListener); } - + @Override + public void addEventListener(OptionEventListener<T> listener) { + option.addEventListener(listener); + } } diff --git a/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java b/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java new file mode 100644 index 0000000..f4fd9b2 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java @@ -0,0 +1,56 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.StateManager; + +public class ImmutableStateManager<T> implements StateManager<T> { + private final T value; + + public ImmutableStateManager(T value) { + this.value = value; + } + + @Override + public void set(T value) { + throw new UnsupportedOperationException("Cannot set value of immutable state manager"); + } + + @Override + public T get() { + return value; + } + + @Override + public void apply() { + // no-op + } + + @Override + public void resetToDefault(ResetAction action) { + // always default + } + + @Override + public void sync() { + // always synced + } + + @Override + public boolean isSynced() { + return true; + } + + @Override + public boolean isAlwaysSynced() { + return true; + } + + @Override + public boolean isDefault() { + return true; + } + + @Override + public void addListener(StateListener<T> stateListener) { + // as the values never change, listeners are not needed and would never be called + } +} diff --git a/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java b/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java new file mode 100644 index 0000000..8806617 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java @@ -0,0 +1,68 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.api.StateManager; + +public class InstantStateManager<T> implements StateManager<T>, ProvidesBindingForDeprecation<T> { + private final Binding<T> binding; + private StateListener<T> stateListener; + + public InstantStateManager(Binding<T> binding) { + this.binding = binding; + this.stateListener = StateListener.noop(); + } + + @Override + public void set(T value) { + boolean changed = !this.get().equals(value); + + this.binding.setValue(value); + + if (changed) stateListener.onStateChange(this.get(), value); + } + + @Override + public T get() { + return this.binding.getValue(); + } + + @Override + public void apply() { + // no-op, state is always applied + } + + @Override + public void resetToDefault(ResetAction action) { + this.set(binding.defaultValue()); + } + + @Override + public void sync() { + // no-op, state is always synced + } + + @Override + public boolean isSynced() { + return true; + } + + @Override + public boolean isAlwaysSynced() { + return true; + } + + @Override + public boolean isDefault() { + return binding.defaultValue().equals(this.get()); + } + + @Override + public void addListener(StateListener<T> stateListener) { + this.stateListener = this.stateListener.andThen(stateListener); + } + + @Override + public Binding<T> getBinding() { + return binding; + } +} diff --git a/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java index 2bd2e10..3ad0caf 100644 --- a/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java @@ -13,124 +13,57 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.function.BiConsumer; -@ApiStatus.Internal -public final class LabelOptionImpl implements LabelOption { - private final Component label; - private final Component name = Component.literal("Label Option"); - private final OptionDescription description; - private final Component tooltip = Component.empty(); - private final LabelController labelController; - private final Binding<Component> binding; +public class LabelOptionImpl extends OptionImpl<Component> implements LabelOption { + public LabelOptionImpl( + @NotNull StateManager<Component> stateManager, + @NotNull Collection<OptionEventListener<Component>> optionEventListeners + ) { + super( + Component.literal("Label Option"), + OptionDescription::of, + LabelController::new, + stateManager, + true, + ImmutableSet.of(), + optionEventListeners + ); + } public LabelOptionImpl(Component label) { - Validate.notNull(label, "`label` must not be null"); - - this.label = label; - this.labelController = new LabelController(this); - this.binding = Binding.immutable(label); - this.description = OptionDescription.createBuilder() - .text(this.label) - .build(); + this( + StateManager.createImmutable(label), + ImmutableSet.of() + ); } @Override public @NotNull Component label() { - return label; - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public @NotNull OptionDescription description() { - return description; - } - - @Override - public @NotNull Component tooltip() { - return tooltip; - } - - @Override - public @NotNull Controller<Component> controller() { - return labelController; - } - - @Override - public @NotNull Binding<Component> binding() { - return binding; - } - - @Override - public boolean available() { - return true; + return stateManager().get(); } @Override public void setAvailable(boolean available) { - throw new UnsupportedOperationException("Label options cannot be disabled."); - } - - @Override - public @NotNull ImmutableSet<OptionFlag> flags() { - return ImmutableSet.of(); - } - - @Override - public boolean changed() { - return false; - } - - @Override - public @NotNull Component pendingValue() { - return label; - } - - @Override - public void requestSet(@NotNull Component value) { - - } - - @Override - public boolean applyValue() { - return false; - } - - @Override - public void forgetPendingValue() { - - } - - @Override - public void requestSetDefault() { - - } - - @Override - public boolean isPendingValueDefault() { - return true; - } - - @Override - public boolean canResetToDefault() { - return false; - } - - @Override - public void addListener(BiConsumer<Option<Component>, Component> changedListener) { - + throw new UnsupportedOperationException("Cannot change availability of label option"); } @ApiStatus.Internal - public static final class BuilderImpl implements Builder { + public static final class BuilderImpl implements LabelOption.Builder { + private StateManager<Component> stateManager; private final List<Component> lines = new ArrayList<>(); @Override - public Builder line(@NotNull Component line) { + public LabelOption.Builder state(@NotNull StateManager<Component> stateManager) { + Validate.notNull(stateManager, "`stateManager` must not be null"); + Validate.isTrue(this.lines.isEmpty(), "Cannot set state manager if lines have already been defined"); + + this.stateManager = stateManager; + return this; + } + + @Override + public LabelOption.Builder line(@NotNull Component line) { + Validate.isTrue(stateManager == null, ".line() is a helper to create a state manager for you at build. If you have defined a custom state manager, do not use .line()"); Validate.notNull(line, "`line` must not be null"); this.lines.add(line); @@ -138,23 +71,30 @@ public final class LabelOptionImpl implements LabelOption { } @Override - public Builder lines(@NotNull Collection<? extends Component> lines) { + public LabelOption.Builder lines(@NotNull Collection<? extends Component> lines) { + Validate.isTrue(stateManager == null, ".lines() is a helper to create a state manager for you at build. If you have defined a custom state manager, do not use .lines()"); + this.lines.addAll(lines); return this; } @Override public LabelOption build() { - MutableComponent text = Component.empty(); - Iterator<Component> iterator = lines.iterator(); - while (iterator.hasNext()) { - text.append(iterator.next()); - - if (iterator.hasNext()) - text.append("\n"); + Validate.isTrue(stateManager != null || !lines.isEmpty(), "Cannot build label option without a state manager or lines"); + + if (!lines.isEmpty()) { + MutableComponent text = Component.empty(); + Iterator<Component> iterator = lines.iterator(); + while (iterator.hasNext()) { + text.append(iterator.next()); + + if (iterator.hasNext()) + text.append("\n"); + } + this.stateManager = StateManager.createSimple(new SelfContainedBinding<>(text)); } - return new LabelOptionImpl(text); + return new LabelOptionImpl(this.stateManager, ImmutableSet.of()); } } } diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java index 1cd5e55..dc4e7ff 100644 --- a/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java @@ -49,6 +49,11 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> { } @Override + public @NotNull StateManager<T> stateManager() { + throw new UnsupportedOperationException("ListOptionEntryImpl does not support state managers"); + } + + @Override public @NotNull Binding<T> binding() { return binding; } @@ -109,6 +114,11 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> { } @Override + public void addEventListener(OptionEventListener<T> listener) { + + } + + @Override public void addListener(BiConsumer<Option<T>, T> changedListener) { } @@ -138,7 +148,7 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> { @Override public void setValue(T newValue) { value = newValue; - group.callListeners(true); + group.triggerListener(OptionEventListener.Event.OTHER, true); } @Override diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java index c77d55f..e9dbb70 100644 --- a/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java @@ -21,7 +21,7 @@ import java.util.stream.Collectors; public final class ListOptionImpl<T> implements ListOption<T> { private final Component name; private final OptionDescription description; - private final Binding<List<T>> binding; + private final StateManager<List<T>> stateManager; private final Supplier<T> initialValue; private final List<ListOptionEntry<T>> entries; private final boolean collapsed; @@ -32,14 +32,14 @@ public final class ListOptionImpl<T> implements ListOption<T> { private final ImmutableSet<OptionFlag> flags; private final EntryFactory entryFactory; - private final List<BiConsumer<Option<List<T>>, List<T>>> listeners; + private final List<OptionEventListener<List<T>>> listeners; private final List<Runnable> refreshListeners; - private int listenerTriggerDepth = 0; + private int currentListenerDepth = 0; - public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull Binding<List<T>> binding, @NotNull Supplier<T> initialValue, @NotNull Function<ListOptionEntry<T>, Controller<T>> controllerFunction, ImmutableSet<OptionFlag> flags, boolean collapsed, boolean available, int minimumNumberOfEntries, int maximumNumberOfEntries, boolean insertEntriesAtEnd, Collection<BiConsumer<Option<List<T>>, List<T>>> listeners) { + public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull StateManager<List<T>> stateManager, @NotNull Supplier<T> initialValue, @NotNull Function<ListOptionEntry<T>, Controller<T>> controllerFunction, ImmutableSet<OptionFlag> flags, boolean collapsed, boolean available, int minimumNumberOfEntries, int maximumNumberOfEntries, boolean insertEntriesAtEnd, Collection<OptionEventListener<List<T>>> listeners) { this.name = name; this.description = description; - this.binding = new SafeBinding<>(binding); + this.stateManager = stateManager; this.initialValue = initialValue; this.entryFactory = new EntryFactory(controllerFunction); this.entries = createEntries(binding().getValue()); @@ -52,7 +52,10 @@ public final class ListOptionImpl<T> implements ListOption<T> { this.listeners = new ArrayList<>(); this.listeners.addAll(listeners); this.refreshListeners = new ArrayList<>(); - callListeners(true); + + this.stateManager.addListener((oldValue, newValue) -> + triggerListener(OptionEventListener.Event.STATE_CHANGE, false)); + triggerListener(OptionEventListener.Event.INITIAL, false); } @Override @@ -81,8 +84,17 @@ public final class ListOptionImpl<T> implements ListOption<T> { } @Override + public @NotNull StateManager<List<T>> stateManager() { + return stateManager; + } + + @Override + @Deprecated public @NotNull Binding<List<T>> binding() { - return binding; + if (stateManager instanceof ProvidesBindingForDeprecation) { + return ((ProvidesBindingForDeprecation<List<T>>) stateManager).getBinding(); + } + throw new UnsupportedOperationException("Binding is not available for this option - using a new state manager which does not directly expose the binding as it may not have one."); } @Override @@ -177,8 +189,12 @@ public final class ListOptionImpl<T> implements ListOption<T> { this.available = available; - if (changed) - callListeners(false); + if (changed) { + if (!available) { + this.stateManager.sync(); + } + this.triggerListener(OptionEventListener.Event.AVAILABILITY_CHANGE, !available); + } } @Override @@ -195,8 +211,14 @@ public final class ListOptionImpl<T> implements ListOption<T> { } @Override + public void addEventListener(OptionEventListener<List<T>> listener) { + this.listeners.add(listener); + } + + @Override + @Deprecated public void addListener(BiConsumer<Option<List<T>>, List<T>> changedListener) { - this.listeners.add(changedListener); + addEventListener((opt, event) -> changedListener.accept(opt, opt.pendingValue())); } @Override @@ -213,30 +235,26 @@ public final class ListOptionImpl<T> implements ListOption<T> { return values.stream().map(entryFactory::create).collect(Collectors.toList()); } - void callListeners(boolean bypass) { - List<T> pendingValue = pendingValue(); - if (bypass || listenerTriggerDepth == 0) { - if (listenerTriggerDepth > 10) { - throw new IllegalStateException("Listener trigger depth exceeded 10! This means a listener triggered a listener etc etc 10 times deep. This is likely a bug in the mod using YACL!"); - } + void triggerListener(OptionEventListener.Event event, boolean allowDepth) { + if (allowDepth || currentListenerDepth == 0) { + Validate.isTrue( + currentListenerDepth <= 10, + "Listener depth exceeded 10! Possible cyclic listener pattern: a listener triggered an event that triggered the initial event etc etc." + ); - this.listenerTriggerDepth++; + currentListenerDepth++; - for (BiConsumer<Option<List<T>>, List<T>> listener : listeners) { - try { - listener.accept(this, pendingValue); - } catch (Exception e) { - YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e); - } + for (OptionEventListener<List<T>> listener : listeners) { + listener.onEvent(this, event); } - this.listenerTriggerDepth--; + currentListenerDepth--; } } private void onRefresh() { refreshListeners.forEach(Runnable::run); - callListeners(true); + triggerListener(OptionEventListener.Event.OTHER, true); } private class EntryFactory { @@ -256,7 +274,6 @@ public final class ListOptionImpl<T> implements ListOption<T> { private Component name = Component.empty(); private OptionDescription description = OptionDescription.EMPTY; private Function<ListOptionEntry<T>, Controller<T>> controllerFunction; - private Binding<List<T>> binding = null; private final Set<OptionFlag> flags = new HashSet<>(); private Supplier<T> initialValue; private boolean collapsed = false; @@ -264,7 +281,10 @@ public final class ListOptionImpl<T> implements ListOption<T> { private int minimumNumberOfEntries = 0; private int maximumNumberOfEntries = Integer.MAX_VALUE; private boolean insertEntriesAtEnd = false; - private final List<BiConsumer<Option<List<T>>, List<T>>> listeners = new ArrayList<>(); + private final List<OptionEventListener<List<T>>> listeners = new ArrayList<>(); + + private Binding<List<T>> binding; + private StateManager<List<T>> stateManager; @Override public Builder<T> name(@NotNull Component name) { @@ -315,8 +335,18 @@ public final class ListOptionImpl<T> implements ListOption<T> { } @Override + public Builder<T> state(@NotNull StateManager<List<T>> stateManager) { + Validate.notNull(stateManager, "`stateManager` cannot be null"); + Validate.isTrue(binding == null, "Cannot set state manager if binding is already set"); + + this.stateManager = stateManager; + return this; + } + + @Override public Builder<T> binding(@NotNull Binding<List<T>> binding) { Validate.notNull(binding, "`binding` cannot be null"); + Validate.isTrue(stateManager == null, "Cannot set binding if state manager is already set"); this.binding = binding; return this; @@ -379,24 +409,52 @@ public final class ListOptionImpl<T> implements ListOption<T> { } @Override - public Builder<T> listener(@NotNull BiConsumer<Option<List<T>>, List<T>> listener) { + public Builder<T> addListener(@NotNull OptionEventListener<List<T>> listener) { + Validate.notNull(listener, "`listener` must not be null"); + this.listeners.add(listener); return this; } @Override + public Builder<T> addListeners(@NotNull Collection<@NotNull OptionEventListener<List<T>>> optionEventListeners) { + Validate.notNull(optionEventListeners, "`optionEventListeners` must not be null"); + + this.listeners.addAll(optionEventListeners); + return this; + } + + @Override + public Builder<T> listener(@NotNull BiConsumer<Option<List<T>>, List<T>> listener) { + Validate.notNull(listener, "`listener` must not be null"); + + return this.addListener((opt, event) -> listener.accept(opt, opt.pendingValue())); + } + + @Override public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<List<T>>, List<T>>> listeners) { - this.listeners.addAll(listeners); + Validate.notNull(listeners, "`listeners` must not be null"); + + this.addListeners(listeners.stream() + .map(listener -> + (OptionEventListener<List<T>>) (opt, event) -> + listener.accept(opt, opt.pendingValue()) + ).toList() + ); return this; } @Override public ListOption<T> build() { Validate.notNull(controllerFunction, "`controller` must not be null"); - Validate.notNull(binding, "`binding` must not be null"); Validate.notNull(initialValue, "`initialValue` must not be null"); + Validate.isTrue(stateManager != null || binding != null, "Either a state manager or binding must be set"); + + if (stateManager == null) { + stateManager = StateManager.createSimple(binding); + } - return new ListOptionImpl<>(name, description, binding, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, minimumNumberOfEntries, maximumNumberOfEntries, insertEntriesAtEnd, listeners); + return new ListOptionImpl<>(name, description, stateManager, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, minimumNumberOfEntries, maximumNumberOfEntries, insertEntriesAtEnd, listeners); } } } diff --git a/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java b/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java new file mode 100644 index 0000000..d367b06 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java @@ -0,0 +1,31 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class NotNullBinding<T> implements Binding<T> { + private final Binding<T> binding; + + public NotNullBinding(Binding<T> binding) { + this.binding = binding; + } + + @Override + public @NotNull T getValue() { + return Validate.notNull(binding.getValue(), "Binding's value must not be null, please use Optionals if you want null behaviour."); + } + + @Override + public void setValue(@NotNull T value) { + Validate.notNull(value, "Binding's value must not be set to null, please use Optionals if you want null behaviour."); + binding.setValue(value); + } + + @Override + public @NotNull T defaultValue() { + return Validate.notNull(binding.defaultValue(), "Binding's default value must not be null, please use Optionals if you want null behaviour."); + } +} diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java index afe9517..296c01f 100644 --- a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java @@ -9,6 +9,7 @@ import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; @@ -17,40 +18,39 @@ import java.util.function.Function; import java.util.function.Supplier; @ApiStatus.Internal -public final class OptionImpl<T> implements Option<T> { +public class OptionImpl<T> implements Option<T> { private final Component name; private OptionDescription description; private final Controller<T> controller; - private final Binding<T> binding; private boolean available; private final ImmutableSet<OptionFlag> flags; - private T pendingValue; - - private final List<BiConsumer<Option<T>, T>> listeners; - private int listenerTriggerDepth = 0; + private final StateManager<T> stateManager; + private final List<OptionEventListener<T>> listeners; + private int currentListenerDepth; public OptionImpl( @NotNull Component name, @NotNull Function<T, OptionDescription> descriptionFunction, @NotNull Function<Option<T>, Controller<T>> controlGetter, - @NotNull Binding<T> binding, + @NotNull StateManager<T> stateManager, boolean available, ImmutableSet<OptionFlag> flags, - @NotNull Collection<BiConsumer<Option<T>, T>> listeners + @NotNull Collection<OptionEventListener<T>> listeners ) { this.name = name; - this.binding = new SafeBinding<>(binding); this.available = available; this.flags = flags; this.listeners = new ArrayList<>(listeners); - this.pendingValue = binding.getValue(); + this.stateManager = stateManager; this.controller = controlGetter.apply(this); - addListener((opt, pending) -> description = descriptionFunction.apply(pending)); - triggerListeners(true); + this.stateManager.addListener((oldValue, newValue) -> + triggerListener(OptionEventListener.Event.STATE_CHANGE, false)); + addEventListener((opt, event) -> description = descriptionFunction.apply(opt.pendingValue())); + triggerListener(OptionEventListener.Event.INITIAL, false); } @Override @@ -74,8 +74,17 @@ public final class OptionImpl<T> implements Option<T> { } @Override + public @NotNull StateManager<T> stateManager() { + return stateManager; + } + + @Override + @Deprecated public @NotNull Binding<T> binding() { - return binding; + if (stateManager instanceof ProvidesBindingForDeprecation) { + return ((ProvidesBindingForDeprecation<T>) stateManager).getBinding(); + } + throw new UnsupportedOperationException("Binding is not available for this option - using a new state manager which does not directly expose the binding as it may not have one."); } @Override @@ -91,9 +100,9 @@ public final class OptionImpl<T> implements Option<T> { if (changed) { if (!available) { - this.pendingValue = binding().getValue(); + this.stateManager.sync(); } - this.triggerListeners(!available); + this.triggerListener(OptionEventListener.Event.AVAILABILITY_CHANGE, !available); } } @@ -104,26 +113,25 @@ public final class OptionImpl<T> implements Option<T> { @Override public boolean changed() { - return !binding().getValue().equals(pendingValue); + return !this.stateManager.isSynced(); } @Override public @NotNull T pendingValue() { - return pendingValue; + return this.stateManager.get(); } @Override public void requestSet(@NotNull T value) { Validate.notNull(value, "`value` cannot be null"); - pendingValue = value; - this.triggerListeners(true); + this.stateManager.set(value); } @Override public boolean applyValue() { if (changed()) { - binding().setValue(pendingValue); + this.stateManager.apply(); return true; } return false; @@ -131,41 +139,44 @@ public final class OptionImpl<T> implements Option<T> { @Override public void forgetPendingValue() { - requestSet(binding().getValue()); + this.stateManager.sync(); } @Override public void requestSetDefault() { - requestSet(binding().defaultValue()); + this.stateManager.resetToDefault(StateManager.ResetAction.BY_OPTION); } @Override public boolean isPendingValueDefault() { - return binding().defaultValue().equals(pendingValue()); + return this.stateManager.isDefault(); } @Override + public void addEventListener(OptionEventListener<T> listener) { + this.listeners.add(listener); + } + + @Override + @Deprecated public void addListener(BiConsumer<Option<T>, T> changedListener) { - this.listeners.add(changedListener); + addEventListener((opt, event) -> changedListener.accept(opt, opt.pendingValue())); } - private void triggerListeners(boolean bypass) { - if (bypass || listenerTriggerDepth == 0) { - if (listenerTriggerDepth > 10) { - throw new IllegalStateException("Listener trigger depth exceeded 10! This means a listener triggered a listener etc etc 10 times deep. This is likely a bug in the mod using YACL!"); - } + private void triggerListener(OptionEventListener.Event event, boolean allowDepth) { + if (allowDepth || currentListenerDepth == 0) { + Validate.isTrue( + currentListenerDepth <= 10, + "Listener depth exceeded 10! Possible cyclic listener pattern: a listener triggered an event that triggered the initial event etc etc." + ); - this.listenerTriggerDepth++; + currentListenerDepth++; - for (BiConsumer<Option<T>, T> listener : listeners) { - try { - listener.accept(this, pendingValue); - } catch (Exception e) { - YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e); - } + for (OptionEventListener<T> listener : listeners) { + listener.onEvent(this, event); } - this.listenerTriggerDepth--; + currentListenerDepth--; } } @@ -177,15 +188,17 @@ public final class OptionImpl<T> implements Option<T> { private Function<Option<T>, Controller<T>> controlGetter; - private Binding<T> binding; private boolean available = true; - private boolean instant = false; - private final Set<OptionFlag> flags = new HashSet<>(); - private final List<BiConsumer<Option<T>, T>> listeners = new ArrayList<>(); + private final List<OptionEventListener<T>> listeners = new ArrayList<>(); + + private @Nullable Binding<T> binding; + private boolean instantDeprecated = false; + + private @Nullable StateManager<T> stateManager; @Override public Builder<T> name(@NotNull Component name) { @@ -222,8 +235,20 @@ public final class OptionImpl<T> implements Option<T> { } @Override + public Builder<T> stateManager(@NotNull StateManager<T> stateManager) { + Validate.notNull(stateManager, "`stateManager` cannot be null"); + + Validate.isTrue(binding == null, "Cannot set state manager when binding is set"); + Validate.isTrue(!instantDeprecated, "Cannot set state manager when instant is set"); + + this.stateManager = stateManager; + return this; + } + + @Override public Builder<T> binding(@NotNull Binding<T> binding) { Validate.notNull(binding, "`binding` cannot be null"); + Validate.isTrue(stateManager == null, "Cannot set binding when state manager is set"); this.binding = binding; return this; @@ -235,8 +260,7 @@ public final class OptionImpl<T> implements Option<T> { 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; + return binding(Binding.generic(def, getter, setter)); } @Override @@ -262,34 +286,70 @@ public final class OptionImpl<T> implements Option<T> { } @Override + @Deprecated public Builder<T> instant(boolean instant) { - this.instant = instant; + Validate.isTrue(stateManager == null, "Cannot set instant when state manager is set"); + YACLConstants.LOGGER.error("Option.Builder#instant is deprecated behaviour. Please use a custom state manager instead: `.state(StateManager.createInstant(Binding))`"); + + this.instantDeprecated = instant; return this; } @Override - public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { + public Builder<T> addListener(@NotNull OptionEventListener<T> listener) { + Validate.notNull(listener, "`listener` must not be null"); + this.listeners.add(listener); return this; } @Override + public Builder<T> addListeners(@NotNull Collection<@NotNull OptionEventListener<T>> optionEventListeners) { + Validate.notNull(optionEventListeners, "`optionEventListeners` must not be null"); + + this.listeners.addAll(optionEventListeners); + return this; + } + + @Override + public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { + Validate.notNull(listener, "`listener` must not be null"); + + return this.addListener((opt, event) -> listener.accept(opt, opt.pendingValue())); + } + + @Override public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners) { - this.listeners.addAll(listeners); + Validate.notNull(listeners, "`listeners` must not be null"); + + this.addListeners(listeners.stream() + .map(listener -> + (OptionEventListener<T>) (opt, event) -> + listener.accept(opt, opt.pendingValue()) + ).toList() + ); return this; } @Override public Option<T> 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"); - if (instant) { - listeners.add((opt, pendingValue) -> opt.applyValue()); + if (instantDeprecated) { + if (binding == null) { + throw new IllegalStateException("Cannot build option with instant when binding is not set"); + } + Validate.isTrue(flags.isEmpty(), "instant application does not support option flags"); + + this.stateManager = StateManager.createInstant(binding); + } else if (binding != null) { + stateManager = StateManager.createSimple(binding); } + Validate.notNull(stateManager, "State manager must be set, either by using .binding() to create a simple manager or .state() to create an advanced one"); + + Validate.isTrue(!stateManager.isAlwaysSynced() || flags.isEmpty(), "Always synced state managers do not support option flags."); - return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), listeners); + return new OptionImpl<>(name, descriptionFunction, controlGetter, stateManager, available, ImmutableSet.copyOf(flags), listeners); } } } diff --git a/src/main/java/dev/isxander/yacl3/impl/ProvidesBindingForDeprecation.java b/src/main/java/dev/isxander/yacl3/impl/ProvidesBindingForDeprecation.java new file mode 100644 index 0000000..abff9b1 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/ProvidesBindingForDeprecation.java @@ -0,0 +1,7 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; + +public interface ProvidesBindingForDeprecation<T> { + Binding<T> getBinding(); +} diff --git a/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java b/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java deleted file mode 100644 index c55d2be..0000000 --- a/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.isxander.yacl3.impl; - -import dev.isxander.yacl3.api.Binding; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class SafeBinding<T> implements Binding<T> { - private final Binding<T> binding; - - public SafeBinding(Binding<T> binding) { - this.binding = binding; - } - - @Override - public @NotNull T getValue() { - return Objects.requireNonNull(binding.getValue()); - } - - @Override - public void setValue(@NotNull T value) { - binding.setValue(Objects.requireNonNull(value)); - } - - @Override - public @NotNull T defaultValue() { - return Objects.requireNonNull(binding.defaultValue()); - } -} diff --git a/src/main/java/dev/isxander/yacl3/impl/SelfContainedBinding.java b/src/main/java/dev/isxander/yacl3/impl/SelfContainedBinding.java new file mode 100644 index 0000000..022cba7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/SelfContainedBinding.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; + +public class SelfContainedBinding<T> implements Binding<T> { + private T value; + private final T defaultValue; + + public SelfContainedBinding(T value, T defaultValue) { + this.value = value; + this.defaultValue = defaultValue; + } + + public SelfContainedBinding(T value) { + this(value, value); + } + + @Override + public void setValue(T value) { + this.value = value; + } + + @Override + public T getValue() { + return this.value; + } + + @Override + public T defaultValue() { + return this.defaultValue; + } +} diff --git a/src/main/java/dev/isxander/yacl3/impl/SimpleStateManager.java b/src/main/java/dev/isxander/yacl3/impl/SimpleStateManager.java new file mode 100644 index 0000000..e9c7275 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/impl/SimpleStateManager.java @@ -0,0 +1,65 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.api.StateManager; + +public class SimpleStateManager<T> implements StateManager<T>, ProvidesBindingForDeprecation<T> { + private T pendingValue; + private final Binding<T> binding; + private StateListener<T> stateListener; + + public SimpleStateManager(Binding<T> binding) { + this.binding = binding; + this.pendingValue = binding.getValue(); + this.stateListener = StateListener.noop(); + } + + @Override + public void set(T value) { + boolean changed = !this.pendingValue.equals(value); + + this.pendingValue = value; + + if (changed) stateListener.onStateChange(this.pendingValue, value); + } + + @Override + public T get() { + return pendingValue; + } + + @Override + public void apply() { + binding.setValue(pendingValue); + } + + @Override + public void resetToDefault(ResetAction action) { + this.set(binding.defaultValue()); + } + + @Override + public void sync() { + this.set(binding.getValue()); + } + + @Override + public boolean isSynced() { + return binding.getValue().equals(pendingValue); + } + + @Override + public boolean isDefault() { + return binding.defaultValue().equals(pendingValue); + } + + @Override + public void addListener(StateListener<T> stateListener) { + this.stateListener = this.stateListener.andThen(stateListener); + } + + @Override + public Binding<T> getBinding() { + return binding; + } +} diff --git a/src/testmod/java/dev/isxander/yacl3/test/CodecConfig.java b/src/testmod/java/dev/isxander/yacl3/test/CodecConfig.java index 1a65eb2..1b185ac 100644 --- a/src/testmod/java/dev/isxander/yacl3/test/CodecConfig.java +++ b/src/testmod/java/dev/isxander/yacl3/test/CodecConfig.java @@ -9,7 +9,6 @@ import dev.isxander.yacl3.config.v3.JsonFileCodecConfig; import dev.isxander.yacl3.config.v3.ReadonlyConfigEntry; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.resources.ResourceLocation; public class CodecConfig extends JsonFileCodecConfig<CodecConfig> { @@ -24,8 +23,11 @@ public class CodecConfig extends JsonFileCodecConfig<CodecConfig> { public final ConfigEntry<ResourceLocation> myIdentifier = register("my_identifier", YACLPlatform.rl("test"), ResourceLocation.CODEC); + //? if >=1.21.2 { public final ConfigEntry<Component> myText = - register("my_text", Component.literal("Hello"), ComponentSerialization.CODEC); + register("my_text", Component.literal("Hello"), + net.minecraft.network.chat.ComponentSerialization.CODEC); + //?} public final ReadonlyConfigEntry<InnerCodecConfig> myInnerConfig = register("my_inner_config", InnerCodecConfig.INSTANCE); diff --git a/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java index a515fe0..1552a0a 100644 --- a/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java @@ -14,6 +14,7 @@ import dev.isxander.yacl3.gui.controllers.string.number.DoubleFieldController; import dev.isxander.yacl3.gui.controllers.string.number.FloatFieldController; import dev.isxander.yacl3.gui.controllers.string.number.IntegerFieldController; import dev.isxander.yacl3.gui.controllers.string.number.LongFieldController; +import dev.isxander.yacl3.impl.SelfContainedBinding; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.ChatFormatting; import net.minecraft.Util; @@ -116,7 +117,7 @@ public class GuiTest { .controller(opt -> BooleanControllerBuilder.create(opt) .formatValue(state -> state ? Component.literal("Amazing") : Component.literal("Not Amazing")) .coloured(true)) - .listener((opt, val) -> booleanOption.get().setAvailable(val)) + .addListener((opt, event) -> booleanOption.get().setAvailable(opt.pendingValue())) .build()) .option(Option.<Boolean>createBuilder() .name(Component.literal("Tick Box")) @@ -137,6 +138,7 @@ public class GuiTest { .option(Option.<Integer>createBuilder() .name(Component.literal("Int Slider")) .description(OptionDescription.createBuilder() + // noinspection deprecated .gifImage(imageSample("sample4.gif")) .build()) .binding( @@ -462,6 +464,7 @@ public class GuiTest { .description(OptionDescription.of(Component.literal("Yay!!!!!!"))) .build()) .build()) + .category(sharedStateCategory()) .category(ConfigCategory.createBuilder() .name(Component.literal("Category Test")) .option(LabelOption.create(Component.literal("This is a test category!"))) @@ -496,12 +499,69 @@ public class GuiTest { .build()) .save(() -> { Minecraft.getInstance().options.save(); - ConfigTest.GSON.serializer().save(); + ConfigTest.GSON.save(); }) ) .generateScreen(parent); } + private static ConfigCategory sharedStateCategory() { + ConfigCategory.Builder builder = ConfigCategory.createBuilder() + .name(Component.literal("Shared State Test")); + + StateManager<Boolean> sharedBoolean = StateManager.createSimple(new SelfContainedBinding<>(true)); + builder.option(Option.<Boolean>createBuilder() + .name(Component.literal("Shared Boolean")) + .stateManager(sharedBoolean) + .controller(TickBoxControllerBuilder::create) + .build()); + builder.option(Option.<Boolean>createBuilder() + .name(Component.literal("Shared Boolean")) + .stateManager(sharedBoolean) + .controller(BooleanControllerBuilder::create) + .build()); + + StateManager<String> sharedString = StateManager.createSimple(new SelfContainedBinding<>("")); + builder.option(Option.<String>createBuilder() + .name(Component.literal("Shared String")) + .stateManager(sharedString) + .controller(StringControllerBuilder::create) + .build()); + builder.option(Option.<String>createBuilder() + .name(Component.literal("Shared String")) + .stateManager(sharedString) + .controller(StringControllerBuilder::create) + .build()); + + StateManager<Integer> sharedInt = StateManager.createSimple(new SelfContainedBinding<>(0)); + builder.option(Option.<Integer>createBuilder() + .name(Component.literal("Shared Int")) + .stateManager(sharedInt) + .controller(opt -> IntegerSliderControllerBuilder.create(opt) + .range(0, 10).step(1)) + .build()); + builder.option(Option.<Integer>createBuilder() + .name(Component.literal("Shared Int")) + .stateManager(sharedInt) + .controller(IntegerFieldControllerBuilder::create) + .build()); + + StateManager<Color> sharedColor = StateManager.createSimple(new SelfContainedBinding<>(Color.RED)); + builder.option(Option.<Color>createBuilder() + .name(Component.literal("Shared Color")) + .stateManager(sharedColor) + .controller(ColorControllerBuilder::create) + .build()); + builder.option(Option.<Color>createBuilder() + .name(Component.literal("Shared Color")) + .stateManager(sharedColor) + .controller(opt -> ColorControllerBuilder.create(opt) + .allowAlpha(true)) + .build()); + + return builder.build(); + } + private static ResourceLocation imageSample(String name) { return YACLPlatform.rl("yacl_test", "textures/images/" + name); } diff --git a/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt b/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt index ac162a1..35e1a4d 100644 --- a/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt +++ b/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt @@ -8,6 +8,8 @@ import dev.isxander.yacl3.dsl.* import dev.isxander.yacl3.platform.YACLPlatform import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +// 1.20.1 doesn't have a component codec +//? if >1.20.1 import net.minecraft.network.chat.ComponentSerialization import net.minecraft.resources.ResourceLocation @@ -20,6 +22,8 @@ object CodecConfigKt : JsonFileCodecConfig<CodecConfigKt>(YACLPlatform.getConfig val myIdentifier by register<ResourceLocation>(YACLPlatform.rl("test"), ResourceLocation.CODEC) + // 1.20.1 doesn't have a component codec + //? if >1.20.1 val myText by register<Component>(Component.literal("Hello, World!"), ComponentSerialization.CODEC) init { @@ -110,7 +114,7 @@ object CodecConfigKt : JsonFileCodecConfig<CodecConfigKt>(YACLPlatform.getConfig // you can access other options like this! // `options` field is from the enclosing group dsl - listener { opt, newVal -> + addListener { opt, event -> options.futureRef<String>("myString").onReady { } diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index 79cc121..5d77d3c 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -1,13 +1,13 @@ plugins { id("dev.kikugie.stonecutter") - id("dev.architectury.loom") version "1.6.+" apply false + id("dev.architectury.loom") version "1.7.+" apply false kotlin("jvm") version "1.9.23" apply false id("me.modmuss50.mod-publish-plugin") version "0.5.+" apply false id("org.ajoberstar.grgit") version "5.0.+" apply false } -stonecutter active "1.21-fabric" /* [SC] DO NOT EDIT */ +stonecutter active "1.21.2-fabric" /* [SC] DO NOT EDIT */ stonecutter.configureEach { val platform = project.property("loom.platform") @@ -37,3 +37,8 @@ stonecutter registerChiseled tasks.register("chiseledPublishSnapshots", stonecut group = "mod" ofTask("publishAllPublicationsToXanderSnapshotsRepository") } + +stonecutter registerChiseled tasks.register("chiseledRunTestmod", stonecutter.chiseled) { + group = "mod" + ofTask("runTestmodClient") +} diff --git a/versions/1.21-fabric/gradle.properties b/versions/1.21-fabric/gradle.properties index df44e87..f67c5e8 100644 --- a/versions/1.21-fabric/gradle.properties +++ b/versions/1.21-fabric/gradle.properties @@ -5,4 +5,4 @@ java.version=21 deps.quiltMappings= deps.fabricApi=0.100.1+1.21 -fmj.mcDep=~1.21 +fmj.mcDep=~1.21 <1.21.2 diff --git a/versions/1.21.2-fabric/gradle.properties b/versions/1.21.2-fabric/gradle.properties new file mode 100644 index 0000000..ab1a162 --- /dev/null +++ b/versions/1.21.2-fabric/gradle.properties @@ -0,0 +1,8 @@ +loom.platform=fabric +mcVersion=1.21.2-rc1 + +java.version=21 + +deps.quiltMappings= +deps.fabricApi=0.106.0+1.21.2 +fmj.mcDep=~1.21.2- |