From 3e36feeef60e56ef8cb7f737ac8eeab9fbcd6abb Mon Sep 17 00:00:00 2001 From: isXander Date: Sat, 3 Jun 2023 23:10:03 +0100 Subject: Change package and modid to yacl3 and yet_another_config_lib_3 respectively --- .../main/java/dev/isxander/yacl/api/Binding.java | 64 --- .../java/dev/isxander/yacl/api/ButtonOption.java | 51 -- .../java/dev/isxander/yacl/api/ConfigCategory.java | 94 ---- .../java/dev/isxander/yacl/api/Controller.java | 28 - .../java/dev/isxander/yacl/api/LabelOption.java | 41 -- .../java/dev/isxander/yacl/api/ListOption.java | 146 ------ .../dev/isxander/yacl/api/ListOptionEntry.java | 18 - .../java/dev/isxander/yacl/api/NameableEnum.java | 10 - .../main/java/dev/isxander/yacl/api/Option.java | 223 -------- .../java/dev/isxander/yacl/api/OptionAddable.java | 19 - .../dev/isxander/yacl/api/OptionDescription.java | 161 ------ .../java/dev/isxander/yacl/api/OptionFlag.java | 23 - .../java/dev/isxander/yacl/api/OptionGroup.java | 90 ---- .../dev/isxander/yacl/api/PlaceholderCategory.java | 55 -- .../dev/isxander/yacl/api/YetAnotherConfigLib.java | 107 ---- .../api/controller/BooleanControllerBuilder.java | 16 - .../api/controller/ColorControllerBuilder.java | 14 - .../yacl/api/controller/ControllerBuilder.java | 9 - .../controller/CyclingListControllerBuilder.java | 15 - .../controller/DoubleFieldControllerBuilder.java | 10 - .../controller/DoubleSliderControllerBuilder.java | 10 - .../yacl/api/controller/EnumControllerBuilder.java | 12 - .../controller/FloatFieldControllerBuilder.java | 10 - .../controller/FloatSliderControllerBuilder.java | 10 - .../controller/IntegerFieldControllerBuilder.java | 10 - .../controller/IntegerSliderControllerBuilder.java | 10 - .../api/controller/LongFieldControllerBuilder.java | 10 - .../controller/LongSliderControllerBuilder.java | 10 - .../controller/NumberFieldControllerBuilder.java | 7 - .../api/controller/SliderControllerBuilder.java | 6 - .../api/controller/StringControllerBuilder.java | 10 - .../api/controller/TickBoxControllerBuilder.java | 10 - .../api/controller/ValueFormattableController.java | 9 - .../dev/isxander/yacl/api/utils/Dimension.java | 33 -- .../isxander/yacl/api/utils/MutableDimension.java | 11 - .../dev/isxander/yacl/api/utils/OptionUtils.java | 39 -- .../java/dev/isxander/yacl/config/ConfigEntry.java | 11 - .../dev/isxander/yacl/config/ConfigInstance.java | 48 -- .../isxander/yacl/config/GsonConfigInstance.java | 212 -------- .../java/dev/isxander/yacl/gui/AbstractWidget.java | 94 ---- .../dev/isxander/yacl/gui/DescriptionWithName.java | 11 - .../isxander/yacl/gui/ElementListWidgetExt.java | 222 -------- .../java/dev/isxander/yacl/gui/ImageRenderer.java | 386 -------------- .../isxander/yacl/gui/LowProfileButtonWidget.java | 29 -- .../isxander/yacl/gui/OptionDescriptionWidget.java | 215 -------- .../dev/isxander/yacl/gui/OptionListWidget.java | 572 --------------------- .../isxander/yacl/gui/RequireRestartScreen.java | 21 - .../dev/isxander/yacl/gui/SearchFieldWidget.java | 62 --- .../isxander/yacl/gui/TextScaledButtonWidget.java | 34 -- .../dev/isxander/yacl/gui/TooltipButtonWidget.java | 25 - .../java/dev/isxander/yacl/gui/YACLScreen.java | 358 ------------- .../isxander/yacl/gui/YACLTooltipPositioner.java | 48 -- .../yacl/gui/controllers/ActionController.java | 120 ----- .../yacl/gui/controllers/BooleanController.java | 157 ------ .../yacl/gui/controllers/ColorController.java | 220 -------- .../yacl/gui/controllers/ControllerWidget.java | 157 ------ .../yacl/gui/controllers/LabelController.java | 194 ------- .../yacl/gui/controllers/ListEntryWidget.java | 129 ----- .../yacl/gui/controllers/TickBoxController.java | 120 ----- .../cycling/CyclingControllerElement.java | 60 --- .../controllers/cycling/CyclingListController.java | 79 --- .../gui/controllers/cycling/EnumController.java | 43 -- .../controllers/cycling/ICyclingController.java | 38 -- .../yacl/gui/controllers/package-info.java | 12 - .../controllers/slider/DoubleSliderController.java | 114 ---- .../controllers/slider/FloatSliderController.java | 114 ---- .../gui/controllers/slider/ISliderController.java | 54 -- .../slider/IntegerSliderController.java | 111 ---- .../controllers/slider/LongSliderController.java | 111 ---- .../slider/SliderControllerElement.java | 158 ------ .../yacl/gui/controllers/slider/package-info.java | 10 - .../gui/controllers/string/IStringController.java | 44 -- .../gui/controllers/string/StringController.java | 37 -- .../string/StringControllerElement.java | 404 --------------- .../string/number/DoubleFieldController.java | 104 ---- .../string/number/FloatFieldController.java | 104 ---- .../string/number/IntegerFieldController.java | 109 ---- .../string/number/LongFieldController.java | 109 ---- .../string/number/NumberFieldController.java | 69 --- .../controllers/string/number/package-info.java | 10 - .../isxander/yacl/gui/tab/ListHolderWidget.java | 116 ----- .../yacl/gui/tab/ScrollableNavigationBar.java | 110 ---- .../java/dev/isxander/yacl/gui/tab/TabExt.java | 9 - .../java/dev/isxander/yacl/gui/utils/GuiUtils.java | 32 -- .../dev/isxander/yacl/impl/ButtonOptionImpl.java | 195 ------- .../dev/isxander/yacl/impl/ConfigCategoryImpl.java | 135 ----- .../dev/isxander/yacl/impl/GenericBindingImpl.java | 35 -- .../dev/isxander/yacl/impl/LabelOptionImpl.java | 158 ------ .../isxander/yacl/impl/ListOptionEntryImpl.java | 154 ------ .../dev/isxander/yacl/impl/ListOptionImpl.java | 325 ------------ .../isxander/yacl/impl/OptionDescriptionImpl.java | 147 ------ .../dev/isxander/yacl/impl/OptionGroupImpl.java | 121 ----- .../java/dev/isxander/yacl/impl/OptionImpl.java | 257 --------- .../yacl/impl/PlaceholderCategoryImpl.java | 99 ---- .../yacl/impl/YetAnotherConfigLibImpl.java | 122 ----- .../controller/AbstractControllerBuilderImpl.java | 12 - .../controller/BooleanControllerBuilderImpl.java | 56 -- .../controller/ColorControllerBuilderImpl.java | 27 - .../CyclingListControllerBuilderImpl.java | 43 -- .../DoubleFieldControllerBuilderImpl.java | 50 -- .../DoubleSliderControllerBuilderImpl.java | 43 -- .../impl/controller/EnumControllerBuilderImpl.java | 35 -- .../FloatFieldControllerBuilderImpl.java | 50 -- .../FloatSliderControllerBuilderImpl.java | 43 -- .../IntegerFieldControllerBuilderImpl.java | 50 -- .../IntegerSliderControllerBuilderImpl.java | 43 -- .../controller/LongFieldControllerBuilderImpl.java | 50 -- .../LongSliderControllerBuilderImpl.java | 43 -- .../controller/StringControllerBuilderImpl.java | 17 - .../controller/TickBoxControllerBuilderImpl.java | 17 - .../yacl/impl/utils/DimensionIntegerImpl.java | 115 ----- .../isxander/yacl/impl/utils/YACLConstants.java | 13 - .../yacl/mixin/AbstractSelectionListMixin.java | 25 - .../yacl/mixin/ContainerEventHandlerMixin.java | 31 -- .../dev/isxander/yacl/mixin/MinecraftMixin.java | 16 - .../yacl/mixin/OptionInstanceAccessor.java | 13 - .../main/java/dev/isxander/yacl3/api/Binding.java | 64 +++ .../java/dev/isxander/yacl3/api/ButtonOption.java | 50 ++ .../dev/isxander/yacl3/api/ConfigCategory.java | 94 ++++ .../java/dev/isxander/yacl3/api/Controller.java | 28 + .../java/dev/isxander/yacl3/api/LabelOption.java | 41 ++ .../java/dev/isxander/yacl3/api/ListOption.java | 146 ++++++ .../dev/isxander/yacl3/api/ListOptionEntry.java | 18 + .../java/dev/isxander/yacl3/api/NameableEnum.java | 10 + .../main/java/dev/isxander/yacl3/api/Option.java | 223 ++++++++ .../java/dev/isxander/yacl3/api/OptionAddable.java | 19 + .../dev/isxander/yacl3/api/OptionDescription.java | 161 ++++++ .../java/dev/isxander/yacl3/api/OptionFlag.java | 23 + .../java/dev/isxander/yacl3/api/OptionGroup.java | 90 ++++ .../isxander/yacl3/api/PlaceholderCategory.java | 55 ++ .../isxander/yacl3/api/YetAnotherConfigLib.java | 107 ++++ .../api/controller/BooleanControllerBuilder.java | 16 + .../api/controller/ColorControllerBuilder.java | 14 + .../yacl3/api/controller/ControllerBuilder.java | 9 + .../controller/CyclingListControllerBuilder.java | 15 + .../controller/DoubleFieldControllerBuilder.java | 10 + .../controller/DoubleSliderControllerBuilder.java | 10 + .../api/controller/EnumControllerBuilder.java | 12 + .../controller/FloatFieldControllerBuilder.java | 10 + .../controller/FloatSliderControllerBuilder.java | 10 + .../controller/IntegerFieldControllerBuilder.java | 10 + .../controller/IntegerSliderControllerBuilder.java | 10 + .../api/controller/LongFieldControllerBuilder.java | 10 + .../controller/LongSliderControllerBuilder.java | 10 + .../controller/NumberFieldControllerBuilder.java | 7 + .../api/controller/SliderControllerBuilder.java | 6 + .../api/controller/StringControllerBuilder.java | 10 + .../api/controller/TickBoxControllerBuilder.java | 10 + .../api/controller/ValueFormattableController.java | 9 + .../dev/isxander/yacl3/api/utils/Dimension.java | 33 ++ .../isxander/yacl3/api/utils/MutableDimension.java | 11 + .../dev/isxander/yacl3/api/utils/OptionUtils.java | 39 ++ .../dev/isxander/yacl3/config/ConfigEntry.java | 11 + .../dev/isxander/yacl3/config/ConfigInstance.java | 48 ++ .../isxander/yacl3/config/GsonConfigInstance.java | 212 ++++++++ .../dev/isxander/yacl3/gui/AbstractWidget.java | 94 ++++ .../isxander/yacl3/gui/DescriptionWithName.java | 11 + .../isxander/yacl3/gui/ElementListWidgetExt.java | 222 ++++++++ .../java/dev/isxander/yacl3/gui/ImageRenderer.java | 386 ++++++++++++++ .../isxander/yacl3/gui/LowProfileButtonWidget.java | 28 + .../yacl3/gui/OptionDescriptionWidget.java | 215 ++++++++ .../dev/isxander/yacl3/gui/OptionListWidget.java | 572 +++++++++++++++++++++ .../isxander/yacl3/gui/RequireRestartScreen.java | 21 + .../dev/isxander/yacl3/gui/SearchFieldWidget.java | 61 +++ .../isxander/yacl3/gui/TextScaledButtonWidget.java | 34 ++ .../isxander/yacl3/gui/TooltipButtonWidget.java | 25 + .../java/dev/isxander/yacl3/gui/YACLScreen.java | 358 +++++++++++++ .../isxander/yacl3/gui/YACLTooltipPositioner.java | 48 ++ .../yacl3/gui/controllers/ActionController.java | 120 +++++ .../yacl3/gui/controllers/BooleanController.java | 157 ++++++ .../yacl3/gui/controllers/ColorController.java | 220 ++++++++ .../yacl3/gui/controllers/ControllerWidget.java | 157 ++++++ .../yacl3/gui/controllers/LabelController.java | 193 +++++++ .../yacl3/gui/controllers/ListEntryWidget.java | 128 +++++ .../yacl3/gui/controllers/TickBoxController.java | 119 +++++ .../cycling/CyclingControllerElement.java | 60 +++ .../controllers/cycling/CyclingListController.java | 79 +++ .../gui/controllers/cycling/EnumController.java | 43 ++ .../controllers/cycling/ICyclingController.java | 38 ++ .../yacl3/gui/controllers/package-info.java | 12 + .../controllers/slider/DoubleSliderController.java | 114 ++++ .../controllers/slider/FloatSliderController.java | 114 ++++ .../gui/controllers/slider/ISliderController.java | 54 ++ .../slider/IntegerSliderController.java | 111 ++++ .../controllers/slider/LongSliderController.java | 111 ++++ .../slider/SliderControllerElement.java | 157 ++++++ .../yacl3/gui/controllers/slider/package-info.java | 10 + .../gui/controllers/string/IStringController.java | 44 ++ .../gui/controllers/string/StringController.java | 37 ++ .../string/StringControllerElement.java | 403 +++++++++++++++ .../string/number/DoubleFieldController.java | 104 ++++ .../string/number/FloatFieldController.java | 104 ++++ .../string/number/IntegerFieldController.java | 109 ++++ .../string/number/LongFieldController.java | 109 ++++ .../string/number/NumberFieldController.java | 69 +++ .../controllers/string/number/package-info.java | 10 + .../isxander/yacl3/gui/tab/ListHolderWidget.java | 116 +++++ .../yacl3/gui/tab/ScrollableNavigationBar.java | 110 ++++ .../java/dev/isxander/yacl3/gui/tab/TabExt.java | 9 + .../dev/isxander/yacl3/gui/utils/GuiUtils.java | 32 ++ .../dev/isxander/yacl3/impl/ButtonOptionImpl.java | 195 +++++++ .../isxander/yacl3/impl/ConfigCategoryImpl.java | 135 +++++ .../isxander/yacl3/impl/GenericBindingImpl.java | 35 ++ .../dev/isxander/yacl3/impl/LabelOptionImpl.java | 158 ++++++ .../isxander/yacl3/impl/ListOptionEntryImpl.java | 154 ++++++ .../dev/isxander/yacl3/impl/ListOptionImpl.java | 325 ++++++++++++ .../isxander/yacl3/impl/OptionDescriptionImpl.java | 146 ++++++ .../dev/isxander/yacl3/impl/OptionGroupImpl.java | 121 +++++ .../java/dev/isxander/yacl3/impl/OptionImpl.java | 257 +++++++++ .../yacl3/impl/PlaceholderCategoryImpl.java | 99 ++++ .../yacl3/impl/YetAnotherConfigLibImpl.java | 122 +++++ .../controller/AbstractControllerBuilderImpl.java | 12 + .../controller/BooleanControllerBuilderImpl.java | 56 ++ .../controller/ColorControllerBuilderImpl.java | 27 + .../CyclingListControllerBuilderImpl.java | 43 ++ .../DoubleFieldControllerBuilderImpl.java | 50 ++ .../DoubleSliderControllerBuilderImpl.java | 43 ++ .../impl/controller/EnumControllerBuilderImpl.java | 35 ++ .../FloatFieldControllerBuilderImpl.java | 50 ++ .../FloatSliderControllerBuilderImpl.java | 43 ++ .../IntegerFieldControllerBuilderImpl.java | 50 ++ .../IntegerSliderControllerBuilderImpl.java | 43 ++ .../controller/LongFieldControllerBuilderImpl.java | 50 ++ .../LongSliderControllerBuilderImpl.java | 43 ++ .../controller/StringControllerBuilderImpl.java | 17 + .../controller/TickBoxControllerBuilderImpl.java | 17 + .../yacl3/impl/utils/DimensionIntegerImpl.java | 115 +++++ .../isxander/yacl3/impl/utils/YACLConstants.java | 13 + .../yacl3/mixin/AbstractSelectionListMixin.java | 25 + .../yacl3/mixin/ContainerEventHandlerMixin.java | 31 ++ .../dev/isxander/yacl3/mixin/MinecraftMixin.java | 16 + .../yacl3/mixin/OptionInstanceAccessor.java | 13 + common/src/main/resources/yacl.mixins.json | 2 +- fabric/src/main/resources/fabric.mod.json | 3 +- .../isxander/yacl/forge/YACLForgeEntrypoint.java | 2 +- gradle.properties | 2 +- .../java/dev/isxander/yacl/test/ConfigTest.java | 51 -- .../main/java/dev/isxander/yacl/test/GuiTest.java | 391 -------------- .../java/dev/isxander/yacl3/test/ConfigTest.java | 51 ++ .../main/java/dev/isxander/yacl3/test/GuiTest.java | 391 ++++++++++++++ .../yacl/test/fabric/ModMenuEntrypoint.java | 12 - .../yacl3/test/fabric/ModMenuEntrypoint.java | 12 + test-fabric/src/main/resources/fabric.mod.json | 2 +- .../dev/isxander/yacl/test/forge/ForgeTest.java | 4 +- 244 files changed, 10000 insertions(+), 10008 deletions(-) delete mode 100644 common/src/main/java/dev/isxander/yacl/api/Binding.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/ButtonOption.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/ConfigCategory.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/Controller.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/LabelOption.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/ListOption.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/ListOptionEntry.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/NameableEnum.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/Option.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/OptionAddable.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/OptionDescription.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/OptionFlag.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/OptionGroup.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/BooleanControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/ColorControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/ControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/CyclingListControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/DoubleFieldControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/DoubleSliderControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/EnumControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/FloatFieldControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/FloatSliderControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/IntegerFieldControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/IntegerSliderControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/LongFieldControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/LongSliderControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/NumberFieldControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/SliderControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/StringControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/TickBoxControllerBuilder.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/controller/ValueFormattableController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/utils/Dimension.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java delete mode 100644 common/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java delete mode 100644 common/src/main/java/dev/isxander/yacl/config/ConfigEntry.java delete mode 100644 common/src/main/java/dev/isxander/yacl/config/ConfigInstance.java delete mode 100644 common/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/DescriptionWithName.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/YACLTooltipPositioner.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/package-info.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/slider/package-info.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/tab/ListHolderWidget.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/tab/ScrollableNavigationBar.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/tab/TabExt.java delete mode 100644 common/src/main/java/dev/isxander/yacl/gui/utils/GuiUtils.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/AbstractControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/BooleanControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/ColorControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/CyclingListControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/DoubleFieldControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/DoubleSliderControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/EnumControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/FloatFieldControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/FloatSliderControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/IntegerFieldControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/IntegerSliderControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/LongFieldControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/LongSliderControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/StringControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/controller/TickBoxControllerBuilderImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java delete mode 100644 common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java delete mode 100644 common/src/main/java/dev/isxander/yacl/mixin/AbstractSelectionListMixin.java delete mode 100644 common/src/main/java/dev/isxander/yacl/mixin/ContainerEventHandlerMixin.java delete mode 100644 common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java delete mode 100644 common/src/main/java/dev/isxander/yacl/mixin/OptionInstanceAccessor.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/Binding.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/ButtonOption.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/Controller.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/LabelOption.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/ListOption.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/ListOptionEntry.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/NameableEnum.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/Option.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/OptionAddable.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/OptionDescription.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/OptionFlag.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/OptionGroup.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/PlaceholderCategory.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/YetAnotherConfigLib.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/BooleanControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/ColorControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/ControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/CyclingListControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/DoubleFieldControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/DoubleSliderControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/EnumControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/FloatFieldControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/FloatSliderControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/IntegerFieldControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/IntegerSliderControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/LongFieldControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/LongSliderControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/NumberFieldControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/SliderControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/StringControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/TickBoxControllerBuilder.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/controller/ValueFormattableController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/utils/Dimension.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/utils/MutableDimension.java create mode 100644 common/src/main/java/dev/isxander/yacl3/api/utils/OptionUtils.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/LowProfileButtonWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/OptionDescriptionWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/OptionListWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/RequireRestartScreen.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/SearchFieldWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/TextScaledButtonWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/TooltipButtonWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/YACLTooltipPositioner.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/ActionController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/BooleanController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/ListEntryWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/TickBoxController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingControllerElement.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingListController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/EnumController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/ICyclingController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/package-info.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/DoubleSliderController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/FloatSliderController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/ISliderController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/IntegerSliderController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/LongSliderController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/SliderControllerElement.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/package-info.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/IStringController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/DoubleFieldController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/FloatFieldController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/IntegerFieldController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/LongFieldController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/NumberFieldController.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/package-info.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/tab/ListHolderWidget.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/tab/ScrollableNavigationBar.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/tab/TabExt.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java create mode 100644 common/src/main/java/dev/isxander/yacl3/mixin/AbstractSelectionListMixin.java create mode 100644 common/src/main/java/dev/isxander/yacl3/mixin/ContainerEventHandlerMixin.java create mode 100644 common/src/main/java/dev/isxander/yacl3/mixin/MinecraftMixin.java create mode 100644 common/src/main/java/dev/isxander/yacl3/mixin/OptionInstanceAccessor.java delete mode 100644 test-common/src/main/java/dev/isxander/yacl/test/ConfigTest.java delete mode 100644 test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java create mode 100644 test-common/src/main/java/dev/isxander/yacl3/test/ConfigTest.java create mode 100644 test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java delete mode 100644 test-fabric/src/main/java/dev/isxander/yacl/test/fabric/ModMenuEntrypoint.java create mode 100644 test-fabric/src/main/java/dev/isxander/yacl3/test/fabric/ModMenuEntrypoint.java diff --git a/common/src/main/java/dev/isxander/yacl/api/Binding.java b/common/src/main/java/dev/isxander/yacl/api/Binding.java deleted file mode 100644 index b4cd2d0..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/Binding.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.impl.GenericBindingImpl; -import dev.isxander.yacl.mixin.OptionInstanceAccessor; -import net.minecraft.client.OptionInstance; -import org.apache.commons.lang3.Validate; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * Controls modifying the bound option. - * Provides the default value, a setter and a getter. - */ -public interface Binding { - void setValue(T value); - - T getValue(); - - T defaultValue(); - - /** - * Creates a generic binding. - * - * @param def default value of the option, used to reset - * @param getter should return the current value of the option - * @param setter should set the option to the supplied value - */ - static Binding generic(T def, Supplier getter, Consumer setter) { - Validate.notNull(def, "`def` must not be null"); - Validate.notNull(getter, "`getter` must not be null"); - Validate.notNull(setter, "`setter` must not be null"); - - return new GenericBindingImpl<>(def, getter, setter); - } - - /** - * Creates a {@link Binding} for Minecraft's {@link OptionInstance} - */ - static Binding minecraft(OptionInstance minecraftOption) { - Validate.notNull(minecraftOption, "`minecraftOption` must not be null"); - - return new GenericBindingImpl<>( - ((OptionInstanceAccessor) (Object) minecraftOption).getInitialValue(), - minecraftOption::get, - minecraftOption::set - ); - } - - /** - * Creates an immutable binding that has no default and cannot be modified. - * - * @param value the value for the binding - */ - static Binding immutable(T value) { - Validate.notNull(value, "`value` must not be null"); - - return new GenericBindingImpl<>( - value, - () -> value, - changed -> {} - ); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/ButtonOption.java b/common/src/main/java/dev/isxander/yacl/api/ButtonOption.java deleted file mode 100644 index 4acbe0e..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/ButtonOption.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.ButtonOptionImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; - -public interface ButtonOption extends Option> { - /** - * Action to be executed upon button press - */ - BiConsumer action(); - - static Builder createBuilder() { - return new ButtonOptionImpl.BuilderImpl(); - } - - interface Builder { - /** - * Sets the name to be used by the option. - * - * @see Option#name() - */ - Builder name(@NotNull Component name); - - Builder description(@NotNull OptionDescription description); - - Builder action(@NotNull BiConsumer action); - - /** - * Action to be executed upon button press - * - * @see ButtonOption#action() - */ - @Deprecated - Builder action(@NotNull Consumer action); - - /** - * Sets if the option can be configured - * - * @see Option#available() - */ - Builder available(boolean available); - - ButtonOption build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/common/src/main/java/dev/isxander/yacl/api/ConfigCategory.java deleted file mode 100644 index 7764479..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/ConfigCategory.java +++ /dev/null @@ -1,94 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.impl.ConfigCategoryImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -/** - * Separates {@link Option}s or {@link OptionGroup}s into multiple distinct sections. - * Served to a user as a button in the left column, - * upon pressing, the options list is filled with options contained within this category. - */ -public interface ConfigCategory { - /** - * Name of category, displayed as a button on the left column. - */ - @NotNull Component name(); - - /** - * Gets every {@link OptionGroup} in this category. - */ - @NotNull ImmutableList groups(); - - /** - * Tooltip (or description) of the category. - * Rendered on hover. - */ - @NotNull Component tooltip(); - - /** - * Creates a builder to construct a {@link ConfigCategory} - */ - static Builder createBuilder() { - return new ConfigCategoryImpl.BuilderImpl(); - } - - interface Builder extends OptionAddable { - /** - * Sets name of the category - * - * @see ConfigCategory#name() - */ - Builder name(@NotNull Component name); - - /** - * Adds an option to the root group of the category. - * To add to another group, use {@link Builder#group(OptionGroup)}. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see ConfigCategory#groups() - * @see OptionGroup#isRoot() - */ - @Override - Builder option(@NotNull Option option); - - /** - * Adds multiple options to the root group of the category. - * To add to another group, use {@link Builder#groups(Collection)}. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see ConfigCategory#groups() - * @see OptionGroup#isRoot() - */ - @Override - Builder options(@NotNull Collection> options); - - /** - * Adds an option group. - * To add an option to the root group, use {@link Builder#option(Option)} - * To construct a group, use {@link OptionGroup#createBuilder()} - */ - Builder group(@NotNull OptionGroup group); - - /** - * Adds multiple option groups. - * To add multiple options to the root group, use {@link Builder#options(Collection)} - * To construct a group, use {@link OptionGroup#createBuilder()} - */ - Builder groups(@NotNull Collection groups); - - /** - * Sets the tooltip to be used by the category. - * Can be invoked twice to append more lines. - * No need to wrap the text yourself, the gui does this itself. - * - * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. - */ - Builder tooltip(@NotNull Component... tooltips); - - ConfigCategory build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/Controller.java b/common/src/main/java/dev/isxander/yacl/api/Controller.java deleted file mode 100644 index 0b8e2ed..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/Controller.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.network.chat.Component; - -/** - * Provides a widget to control the option. - */ -public interface Controller { - /** - * Gets the dedicated {@link Option} for this controller - */ - Option option(); - - /** - * Gets the formatted value based on {@link Option#pendingValue()} - */ - Component formatValue(); - - /** - * Provides a widget to display - * - * @param screen parent screen - */ - AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/LabelOption.java b/common/src/main/java/dev/isxander/yacl/api/LabelOption.java deleted file mode 100644 index f646c55..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/LabelOption.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.impl.LabelOptionImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -/** - * A label option is an easier way of creating a label with a {@link dev.isxander.yacl.gui.controllers.LabelController}. - * This option is immutable and cannot be disabled. Tooltips are supported through - * {@link Component} styling. - */ -public interface LabelOption extends Option { - @NotNull Component label(); - - /** - * Creates a new label option with the given label, skipping a builder for ease. - */ - static LabelOption create(@NotNull Component label) { - return new LabelOptionImpl(label); - } - - static Builder createBuilder() { - return new LabelOptionImpl.BuilderImpl(); - } - - interface Builder { - /** - * Appends a line to the label - */ - Builder line(@NotNull Component line); - - /** - * Appends multiple lines to the label - */ - Builder lines(@NotNull Collection lines); - - LabelOption build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/ListOption.java b/common/src/main/java/dev/isxander/yacl/api/ListOption.java deleted file mode 100644 index e370f36..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/ListOption.java +++ /dev/null @@ -1,146 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.controller.ControllerBuilder; -import dev.isxander.yacl.impl.ListOptionImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * A list option that takes form as an option group for UX. - * You add this option through {@link ConfigCategory.Builder#group(OptionGroup)}. Do NOT add as an option. - * Users can add remove and reshuffle a list type. You can use any controller you wish, there are no dedicated - * controllers for list types. List options do not manipulate your list but get and set the list with a - * regular binding for simplicity. - * - * You may apply option flags like a normal option and collapse like a normal group, it is a merge of them both. - * Methods in this interface marked with {@link ApiStatus.Internal} should not be used, and could be subject to - * change at any time - * @param - */ -public interface ListOption extends OptionGroup, Option> { - @Override - @NotNull ImmutableList> options(); - - @ApiStatus.Internal - ListOptionEntry insertNewEntryToTop(); - - @ApiStatus.Internal - void insertEntry(int index, ListOptionEntry entry); - - @ApiStatus.Internal - int indexOf(ListOptionEntry entry); - - @ApiStatus.Internal - void removeEntry(ListOptionEntry entry); - - @ApiStatus.Internal - void addRefreshListener(Runnable changedListener); - - static Builder createBuilder() { - return new ListOptionImpl.BuilderImpl<>(); - } - - @Deprecated - static Builder createBuilder(Class typeClass) { - return createBuilder(); - } - - interface Builder { - /** - * Sets name of the list, for UX purposes, a name should always be given, - * but isn't enforced. - * - * @see ListOption#name() - */ - Builder name(@NotNull Component name); - - Builder description(@NotNull OptionDescription description); - - /** - * Sets the value that is used when creating new entries - */ - Builder initial(@NotNull T initialValue); - - Builder controller(@NotNull Function, ControllerBuilder> controller); - - /** - * Sets the controller for the option. - * This is how you interact and change the options. - * - * @see dev.isxander.yacl.gui.controllers - */ - Builder customController(@NotNull Function, Controller> control); - - /** - * Sets the binding for the option. - * Used for default, getter and setter. - * - * @see Binding - */ - Builder binding(@NotNull Binding> binding); - - /** - * Sets the binding for the option. - * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} - * - * @param def default value of the option, used to reset - * @param getter should return the current value of the option - * @param setter should set the option to the supplied value - * @see Binding - */ - Builder binding(@NotNull List def, @NotNull Supplier<@NotNull List> getter, @NotNull Consumer<@NotNull List> setter); - - /** - * Sets if the option can be configured - * - * @see Option#available() - */ - Builder available(boolean available); - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - Builder flag(@NotNull OptionFlag... flag); - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - Builder flags(@NotNull Collection flags); - - /** - * Dictates if the group should be collapsed by default. - * If not set, it will not be collapsed by default. - * - * @see OptionGroup#collapsed() - */ - Builder collapsed(boolean collapsible); - - /** - * Adds a listener to the option. Invoked upon changing any of the list's entries. - * - * @see Option#addListener(BiConsumer) - */ - ListOption.Builder listener(@NotNull BiConsumer>, List> listener); - - /** - * Adds multiple listeners to the option. Invoked upon changing of any of the list's entries. - * - * @see Option#addListener(BiConsumer) - */ - ListOption.Builder listeners(@NotNull Collection>, List>> listeners); - - ListOption build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/ListOptionEntry.java b/common/src/main/java/dev/isxander/yacl/api/ListOptionEntry.java deleted file mode 100644 index 2679fa3..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/ListOptionEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableSet; -import org.jetbrains.annotations.NotNull; - -public interface ListOptionEntry extends Option { - ListOption parentGroup(); - - @Override - default @NotNull ImmutableSet flags() { - return parentGroup().flags(); - } - - @Override - default boolean available() { - return parentGroup().available(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/NameableEnum.java b/common/src/main/java/dev/isxander/yacl/api/NameableEnum.java deleted file mode 100644 index 4b04057..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/NameableEnum.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api; - -import net.minecraft.network.chat.Component; - -/** - * Used for the default value formatter of {@link dev.isxander.yacl.gui.controllers.cycling.EnumController} - */ -public interface NameableEnum { - Component getDisplayName(); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/Option.java b/common/src/main/java/dev/isxander/yacl/api/Option.java deleted file mode 100644 index df48a62..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/Option.java +++ /dev/null @@ -1,223 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.controller.ControllerBuilder; -import dev.isxander.yacl.impl.OptionImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -public interface Option { - /** - * Name of the option - */ - @NotNull Component name(); - - @NotNull OptionDescription description(); - - /** - * Tooltip (or description) of the option. - * Rendered on hover. - */ - @Deprecated - @NotNull Component tooltip(); - - /** - * Widget provider for a type of option. - * - * @see dev.isxander.yacl.gui.controllers - */ - @NotNull Controller controller(); - - /** - * Binding for the option. - * Controls setting, getting and default value. - * - * @see Binding - */ - @NotNull Binding binding(); - - /** - * If the option can be configured - */ - boolean available(); - - /** - * Sets if the option can be configured after being built - * - * @see Option#available() - */ - void setAvailable(boolean available); - - /** - * Tasks that needs to be executed upon applying changes. - */ - @NotNull ImmutableSet flags(); - - /** - * Checks if the pending value is not equal to the current set value - */ - boolean changed(); - - /** - * Value in the GUI, ready to set the actual bound value or be undone. - */ - @NotNull T pendingValue(); - - /** - * Sets the pending value - */ - void requestSet(T value); - - /** - * Applies the pending value to the bound value. - * Cannot be undone. - * - * @return if there were changes to apply {@link Option#changed()} - */ - boolean applyValue(); - - /** - * Sets the pending value to the bound value. - */ - void forgetPendingValue(); - - /** - * Sets the pending value to the default bound value. - */ - void requestSetDefault(); - - /** - * Checks if the current pending value is equal to its default value - */ - boolean isPendingValueDefault(); - - default boolean canResetToDefault() { - return true; - } - - /** - * Adds a listener for when the pending value changes - */ - void addListener(BiConsumer, T> changedListener); - - static Builder createBuilder() { - return new OptionImpl.BuilderImpl<>(); - } - - /** - * Creates a builder to construct an {@link Option} - * - * @param type of the option's value - * @param typeClass used to capture the type - */ - @Deprecated - static Builder createBuilder(Class typeClass) { - return createBuilder(); - } - - interface Builder { - /** - * Sets the name to be used by the option. - * - * @see Option#name() - */ - Builder name(@NotNull Component name); - - /** - * Sets the description to be used by the option. - * @see OptionDescription - * @param description the static description. - * @return this builder - */ - Builder description(@NotNull OptionDescription description); - - /** - * Sets the function to get the description by the option's current value. - * - * @see OptionDescription - * @param descriptionFunction the function to get the description by the option's current value. - * @return this builder - */ - Builder description(@NotNull Function descriptionFunction); - - Builder controller(@NotNull Function, ControllerBuilder> controllerBuilder); - - /** - * Sets the controller for the option. - * This is how you interact and change the options. - * - * @see dev.isxander.yacl.gui.controllers - */ - Builder customController(@NotNull Function, Controller> control); - - /** - * Sets the binding for the option. - * Used for default, getter and setter. - * - * @see Binding - */ - Builder binding(@NotNull Binding binding); - - /** - * Sets the binding for the option. - * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} - * - * @param def default value of the option, used to reset - * @param getter should return the current value of the option - * @param setter should set the option to the supplied value - * @see Binding - */ - Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter); - - /** - * Sets if the option can be configured - * - * @see Option#available() - */ - Builder available(boolean available); - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - Builder flag(@NotNull OptionFlag... flag); - - /** - * Adds a flag to the option. - * Upon applying changes, all flags are executed. - * {@link Option#flags()} - */ - Builder flags(@NotNull Collection flags); - - /** - * Instantly invokes the binder's setter when modified in the GUI. - * Prevents the user from undoing the change - *

- * Does not support {@link Option#flags()}! - */ - Builder instant(boolean instant); - - /** - * Adds a listener to the option. Invoked upon changing the pending value. - * - * @see Option#addListener(BiConsumer) - */ - Builder listener(@NotNull BiConsumer, T> listener); - - /** - * Adds multiple listeners to the option. Invoked upon changing the pending value. - * - * @see Option#addListener(BiConsumer) - */ - Builder listeners(@NotNull Collection, T>> listeners); - - Option build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionAddable.java b/common/src/main/java/dev/isxander/yacl/api/OptionAddable.java deleted file mode 100644 index 57be06c..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/OptionAddable.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.isxander.yacl.api; - -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -public interface OptionAddable { - /** - * Adds an option to an abstract builder. - * To construct an option, use {@link Option#createBuilder(Class)} - */ - OptionAddable option(@NotNull Option option); - - /** - * Adds multiple options to an abstract builder. - * To construct an option, use {@link Option#createBuilder(Class)} - */ - OptionAddable options(@NotNull Collection> options); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java b/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java deleted file mode 100644 index 849b601..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java +++ /dev/null @@ -1,161 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.ImageRenderer; -import dev.isxander.yacl.impl.OptionDescriptionImpl; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -import java.nio.file.Path; -import java.util.Collection; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -/** - * Provides all information for the description panel in the GUI. - * This provides no functional benefit, and is purely for UX. - */ -public interface OptionDescription { - /** - * The description of the option, this is automatically wrapped and supports all styling, - * including {@link net.minecraft.network.chat.ClickEvent}s and {@link net.minecraft.network.chat.HoverEvent}s. - * @return The description of the option, with all lines merged with \n. - */ - Component text(); - - /** - * The image to display with the description. If the Optional is empty, no image has been provided. - * Usually, the image renderers are constructed asynchronously, so this method returns a {@link CompletableFuture}. - *

- * Image renderers are cached throughout the whole lifecycle of the game, and should not be generated more than once - * per image. See {@link ImageRenderer#getOrMakeAsync(ResourceLocation, Supplier)} for implementation details. - */ - CompletableFuture> image(); - - /** - * @return a new builder for an {@link OptionDescription}. - */ - static Builder createBuilder() { - return new OptionDescriptionImpl.BuilderImpl(); - } - - static OptionDescription of(Component... description) { - return createBuilder().text(description).build(); - } - - OptionDescription EMPTY = new OptionDescriptionImpl(CommonComponents.EMPTY, CompletableFuture.completedFuture(Optional.empty())); - - interface Builder { - /** - * Appends lines to the main description of the option. This can be called multiple times. - * On {@link Builder#build()}, the lines are merged with \n. - * @see OptionDescription#text() - * - * @param description the lines to append to the description. - * @return this builder - */ - Builder text(Component... description); - - /** - * Appends lines to the main description of the option. This can be called multiple times. - * On {@link Builder#build()}, the lines are merged with \n. - * @see OptionDescription#text() - * - * @param lines the lines to append to the description. - * @return this builder - */ - Builder text(Collection lines); - - /** - * Sets a static image to display with the description. This is backed by a regular minecraft resource - * in your mod's /assets folder. - * - * @param image the location of the image to display from the resource manager - * @param width the width of the texture - * @param height the height of the texture - * @return this builder - */ - Builder image(ResourceLocation image, int width, int height); - - /** - * Sets a static image to display with the description. This is backed by a regular minecraft resource - * in your mod's /assets folder. This overload method allows you to specify a subsection of the texture to render. - * - * @param image the location of the image to display from the resource manager - * @param u the u coordinate - * @param v the v coordinate - * @param width the width of the subsection - * @param height the height of the subsection - * @param textureWidth the width of the whole texture file - * @param textureHeight the height of whole texture file - * @return this builder - */ - Builder image(ResourceLocation image, float u, float v, int width, int height, int textureWidth, int textureHeight); - - /** - * Sets a static image to display with the description. This is backed by a file on disk. - * The width and height is automatically determined from the image processing. - * - * @param path the absolute path to the image file - * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar - * @return this builder - */ - Builder image(Path path, ResourceLocation uniqueLocation); - - /** - * Sets a static OR ANIMATED webP image to display with the description. This is backed by a regular minecraft resource - * in your mod's /assets folder. - * - * @param image the location of the image to display from the resource manager - * @return this builder - */ - Builder webpImage(ResourceLocation image); - - /** - * Sets a static OR ANIMATED webP image to display with the description. This is backed by a file on disk. - * The width and height is automatically determined from the image processing. - * - * @param path the absolute path to the image file - * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar - * @return this builder - */ - Builder webpImage(Path path, ResourceLocation uniqueLocation); - - /** - * Sets a custom image renderer to display with the description. - * This is useful for rendering other abstract things relevant to your mod. - *

- * However, THIS IS NOT API SAFE! As part of the gui package, things - * may change that could break compatibility with future versions of YACL. - * A helpful utility (that is also not API safe) is {@link ImageRenderer#getOrMakeAsync(ResourceLocation, Supplier)} - * which will cache the image renderer for the whole game lifecycle and construct it asynchronously to the render thread. - * @param image the image renderer to display - * @return this builder - */ - Builder customImage(CompletableFuture> image); - - /** - * Sets an animated GIF image to display with the description. This is backed by a regular minecraft resource - * in your mod's /assets folder. - * - * @param image the location of the image to display from the resource manager - * @return this builder - */ - @Deprecated - Builder gifImage(ResourceLocation image); - - /** - * Sets an animated GIF image to display with the description. This is backed by a file on disk. - * The width and height is automatically determined from the image processing. - * - * @param path the absolute path to the image file - * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar - * @return this builder - */ - @Deprecated - Builder gifImage(Path path, ResourceLocation uniqueLocation); - - OptionDescription build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionFlag.java b/common/src/main/java/dev/isxander/yacl/api/OptionFlag.java deleted file mode 100644 index 51d57e4..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/OptionFlag.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.RequireRestartScreen; -import net.minecraft.client.Minecraft; - -import java.util.function.Consumer; - -/** - * Code that is executed upon certain options being applied. - * Each flag is executed only once per save, no matter the amount of options with the flag. - */ -@FunctionalInterface -public interface OptionFlag extends Consumer { - /** Warns the user that a game restart is required for the changes to take effect */ - OptionFlag GAME_RESTART = client -> client.setScreen(new RequireRestartScreen(client.screen)); - - /** Reloads chunks upon applying (F3+A) */ - OptionFlag RELOAD_CHUNKS = client -> client.levelRenderer.allChanged(); - - OptionFlag WORLD_RENDER_UPDATE = client -> client.levelRenderer.needsUpdate(); - - OptionFlag ASSET_RELOAD = Minecraft::delayTextureReload; -} diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java b/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java deleted file mode 100644 index e4a0eeb..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java +++ /dev/null @@ -1,90 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.impl.OptionGroupImpl; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -/** - * Serves as a separator between multiple chunks of options - * that may be too similar or too few to be placed in a separate {@link ConfigCategory}. - * Or maybe you just want your config to feel less dense. - */ -public interface OptionGroup { - /** - * Name of the option group, displayed as a separator in the option lists. - * Can be empty. - */ - Component name(); - - OptionDescription description(); - - /** - * Tooltip displayed on hover. - */ - @Deprecated - Component tooltip(); - - /** - * List of all options in the group - */ - @NotNull ImmutableList> options(); - - /** - * Dictates if the group should be collapsed by default. - */ - boolean collapsed(); - - /** - * Always false when using the {@link Builder} - * used to not render the separator if true - */ - boolean isRoot(); - - /** - * Creates a builder to construct a {@link OptionGroup} - */ - static Builder createBuilder() { - return new OptionGroupImpl.BuilderImpl(); - } - - interface Builder extends OptionAddable { - /** - * Sets name of the group, can be {@link Component#empty()} to just separate options, like sodium. - * - * @see OptionGroup#name() - */ - Builder name(@NotNull Component name); - - Builder description(@NotNull OptionDescription description); - - /** - * Adds an option to group. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see OptionGroup#options() - */ - @Override - Builder option(@NotNull Option option); - - /** - * Adds multiple options to group. - * To construct an option, use {@link Option#createBuilder(Class)} - * - * @see OptionGroup#options() - */ - @Override - Builder options(@NotNull Collection> options); - - /** - * Dictates if the group should be collapsed by default - * - * @see OptionGroup#collapsed() - */ - Builder collapsed(boolean collapsible); - - OptionGroup build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java b/common/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java deleted file mode 100644 index 417dd83..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.isxander.yacl.api; - -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.PlaceholderCategoryImpl; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BiFunction; - -/** - * A placeholder category that actually just opens another screen, - * instead of displaying options. - *

- * Use of this is discouraged, as it is not very user-friendly and navigating to a placeholder - * tab that opens another screen is not very intuitive, making keyboard navigation impossible. - */ -public interface PlaceholderCategory extends ConfigCategory { - /** - * Function to create a screen to open upon changing to this category - */ - BiFunction screen(); - - static Builder createBuilder() { - return new PlaceholderCategoryImpl.BuilderImpl(); - } - - interface Builder { - /** - * Sets name of the category - * - * @see ConfigCategory#name() - */ - Builder name(@NotNull Component name); - - /** - * Sets the tooltip to be used by the category. - * Can be invoked twice to append more lines. - * No need to wrap the Component yourself, the gui does this itself. - * - * @param tooltips Component lines - merged with a new-line on {@link dev.isxander.yacl.api.PlaceholderCategory.Builder#build()}. - */ - Builder tooltip(@NotNull Component... tooltips); - - /** - * Screen to open upon selecting this category - * - * @see PlaceholderCategory#screen() - */ - Builder screen(@NotNull BiFunction screenFunction); - - PlaceholderCategory build(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/common/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java deleted file mode 100644 index c6da1d1..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java +++ /dev/null @@ -1,107 +0,0 @@ -package dev.isxander.yacl.api; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.config.ConfigInstance; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.YetAnotherConfigLibImpl; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.function.Consumer; - -/** - * Main class of the mod. - * Contains all data and used to provide a {@link Screen} - */ -public interface YetAnotherConfigLib { - /** - * Title of the GUI. Only used for Minecraft narration. - */ - Component title(); - - /** - * Gets all config categories. - */ - ImmutableList categories(); - - /** - * Ran when changes are saved. Can be used to save config to a file etc. - */ - Runnable saveFunction(); - - /** - * Ran every time the YACL screen initialises. Can be paired with FAPI to add custom widgets. - */ - Consumer initConsumer(); - - /** - * Generates a Screen to display based on this instance. - * - * @param parent parent screen to open once closed - */ - Screen generateScreen(@Nullable Screen parent); - - /** - * Creates a builder to construct YACL - */ - static Builder createBuilder() { - return new YetAnotherConfigLibImpl.BuilderImpl(); - } - - /** - * Creates an instance using a {@link ConfigInstance} which autofills the save() builder method. - * This also takes an easy functional interface that provides defaults and config to help build YACL bindings. - */ - static YetAnotherConfigLib create(ConfigInstance configInstance, ConfigBackedBuilder builder) { - return builder.build(configInstance.getDefaults(), configInstance.getConfig(), createBuilder().save(configInstance::save)).build(); - } - - interface Builder { - /** - * Sets title of GUI for Minecraft narration - * - * @see YetAnotherConfigLib#title() - */ - Builder title(@NotNull Component title); - - /** - * Adds a new category. - * To create a category you need to use {@link ConfigCategory#createBuilder()} - * - * @see YetAnotherConfigLib#categories() - */ - Builder category(@NotNull ConfigCategory category); - - /** - * Adds multiple categories at once. - * To create a category you need to use {@link ConfigCategory#createBuilder()} - * - * @see YetAnotherConfigLib#categories() - */ - Builder categories(@NotNull Collection categories); - - /** - * Used to define a save function for when user clicks the Save Changes button - * - * @see YetAnotherConfigLib#saveFunction() - */ - Builder save(@NotNull Runnable saveFunction); - - /** - * Defines a consumer that is accepted every time the YACL screen initialises - * - * @see YetAnotherConfigLib#initConsumer() - */ - Builder screenInit(@NotNull Consumer initConsumer); - - YetAnotherConfigLib build(); - } - - @FunctionalInterface - interface ConfigBackedBuilder { - YetAnotherConfigLib.Builder build(T defaults, T config, YetAnotherConfigLib.Builder builder); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/BooleanControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/BooleanControllerBuilder.java deleted file mode 100644 index 3af91f0..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/BooleanControllerBuilder.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.BooleanControllerBuilderImpl; - -public interface BooleanControllerBuilder extends ValueFormattableController { - BooleanControllerBuilder coloured(boolean coloured); - - BooleanControllerBuilder onOffFormatter(); - BooleanControllerBuilder yesNoFormatter(); - BooleanControllerBuilder trueFalseFormatter(); - - static BooleanControllerBuilder create(Option option) { - return new BooleanControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/ColorControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/ColorControllerBuilder.java deleted file mode 100644 index c4c8f9e..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/ColorControllerBuilder.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.ColorControllerBuilderImpl; - -import java.awt.Color; - -public interface ColorControllerBuilder extends ControllerBuilder { - ColorControllerBuilder allowAlpha(boolean allowAlpha); - - static ColorControllerBuilder create(Option option) { - return new ColorControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/ControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/ControllerBuilder.java deleted file mode 100644 index 378de08..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/ControllerBuilder.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Controller; -import org.jetbrains.annotations.ApiStatus; - -public interface ControllerBuilder { - @ApiStatus.Internal - Controller build(); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/CyclingListControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/CyclingListControllerBuilder.java deleted file mode 100644 index d6d6c83..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/CyclingListControllerBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.CyclingListControllerBuilderImpl; - -public interface CyclingListControllerBuilder extends ValueFormattableController> { - @SuppressWarnings("unchecked") - CyclingListControllerBuilder values(T... values); - - CyclingListControllerBuilder values(Iterable values); - - static CyclingListControllerBuilder create(Option option) { - return new CyclingListControllerBuilderImpl<>(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/DoubleFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/DoubleFieldControllerBuilder.java deleted file mode 100644 index 23f6270..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/DoubleFieldControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.DoubleFieldControllerBuilderImpl; - -public interface DoubleFieldControllerBuilder extends NumberFieldControllerBuilder { - static DoubleFieldControllerBuilder create(Option option) { - return new DoubleFieldControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/DoubleSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/DoubleSliderControllerBuilder.java deleted file mode 100644 index 995b7c9..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/DoubleSliderControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.DoubleSliderControllerBuilderImpl; - -public interface DoubleSliderControllerBuilder extends SliderControllerBuilder { - static DoubleSliderControllerBuilder create(Option option) { - return new DoubleSliderControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/EnumControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/EnumControllerBuilder.java deleted file mode 100644 index f545f9c..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/EnumControllerBuilder.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.EnumControllerBuilderImpl; - -public interface EnumControllerBuilder> extends ValueFormattableController> { - EnumControllerBuilder enumClass(Class enumClass); - - static > EnumControllerBuilder create(Option option) { - return new EnumControllerBuilderImpl<>(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/FloatFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/FloatFieldControllerBuilder.java deleted file mode 100644 index 202da79..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/FloatFieldControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.FloatFieldControllerBuilderImpl; - -public interface FloatFieldControllerBuilder extends NumberFieldControllerBuilder { - static FloatFieldControllerBuilder create(Option option) { - return new FloatFieldControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/FloatSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/FloatSliderControllerBuilder.java deleted file mode 100644 index e4600f6..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/FloatSliderControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.FloatSliderControllerBuilderImpl; - -public interface FloatSliderControllerBuilder extends SliderControllerBuilder { - static FloatSliderControllerBuilder create(Option option) { - return new FloatSliderControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/IntegerFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/IntegerFieldControllerBuilder.java deleted file mode 100644 index d256cd2..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/IntegerFieldControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.IntegerFieldControllerBuilderImpl; - -public interface IntegerFieldControllerBuilder extends NumberFieldControllerBuilder { - static IntegerFieldControllerBuilder create(Option option) { - return new IntegerFieldControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/IntegerSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/IntegerSliderControllerBuilder.java deleted file mode 100644 index 6733a6e..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/IntegerSliderControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.IntegerSliderControllerBuilderImpl; - -public interface IntegerSliderControllerBuilder extends SliderControllerBuilder { - static IntegerSliderControllerBuilder create(Option option) { - return new IntegerSliderControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/LongFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/LongFieldControllerBuilder.java deleted file mode 100644 index 8e162ae..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/LongFieldControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.LongFieldControllerBuilderImpl; - -public interface LongFieldControllerBuilder extends NumberFieldControllerBuilder { - static LongFieldControllerBuilder create(Option option) { - return new LongFieldControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/LongSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/LongSliderControllerBuilder.java deleted file mode 100644 index 9624cd2..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/LongSliderControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.LongSliderControllerBuilderImpl; - -public interface LongSliderControllerBuilder extends SliderControllerBuilder { - static LongSliderControllerBuilder create(Option option) { - return new LongSliderControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/NumberFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/NumberFieldControllerBuilder.java deleted file mode 100644 index a8e8f4d..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/NumberFieldControllerBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.isxander.yacl.api.controller; - -public interface NumberFieldControllerBuilder> extends ValueFormattableController { - B min(T min); - B max(T max); - B range(T min, T max); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/SliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/SliderControllerBuilder.java deleted file mode 100644 index b175014..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/SliderControllerBuilder.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.isxander.yacl.api.controller; - -public interface SliderControllerBuilder> extends ValueFormattableController { - B range(T min, T max); - B step(T step); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/StringControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/StringControllerBuilder.java deleted file mode 100644 index fcb3001..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/StringControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.StringControllerBuilderImpl; - -public interface StringControllerBuilder extends ControllerBuilder { - static StringControllerBuilder create(Option option) { - return new StringControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/TickBoxControllerBuilder.java b/common/src/main/java/dev/isxander/yacl/api/controller/TickBoxControllerBuilder.java deleted file mode 100644 index 6dd52b9..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/TickBoxControllerBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.impl.controller.TickBoxControllerBuilderImpl; - -public interface TickBoxControllerBuilder extends ControllerBuilder { - static TickBoxControllerBuilder create(Option option) { - return new TickBoxControllerBuilderImpl(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/controller/ValueFormattableController.java b/common/src/main/java/dev/isxander/yacl/api/controller/ValueFormattableController.java deleted file mode 100644 index 032b34e..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/controller/ValueFormattableController.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.isxander.yacl.api.controller; - -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public interface ValueFormattableController> extends ControllerBuilder { - B valueFormatter(Function formatter); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/utils/Dimension.java b/common/src/main/java/dev/isxander/yacl/api/utils/Dimension.java deleted file mode 100644 index 0de0a58..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/utils/Dimension.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.isxander.yacl.api.utils; - -import dev.isxander.yacl.impl.utils.DimensionIntegerImpl; - -public interface Dimension { - T x(); - T y(); - - T width(); - T height(); - - T xLimit(); - T yLimit(); - - T centerX(); - T centerY(); - - boolean isPointInside(T x, T y); - - MutableDimension clone(); - - Dimension withX(T x); - Dimension withY(T y); - Dimension withWidth(T width); - Dimension withHeight(T height); - - Dimension moved(T x, T y); - Dimension expanded(T width, T height); - - static MutableDimension ofInt(int x, int y, int width, int height) { - return new DimensionIntegerImpl(x, y, width, height); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java b/common/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java deleted file mode 100644 index eff0186..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/utils/MutableDimension.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.api.utils; - -public interface MutableDimension extends Dimension { - MutableDimension setX(T x); - MutableDimension setY(T y); - MutableDimension setWidth(T width); - MutableDimension setHeight(T height); - - MutableDimension move(T x, T y); - MutableDimension expand(T width, T height); -} diff --git a/common/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java b/common/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java deleted file mode 100644 index 22032bd..0000000 --- a/common/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.isxander.yacl.api.utils; - -import dev.isxander.yacl.api.*; - -import java.util.function.Consumer; -import java.util.function.Function; - -public class OptionUtils { - /** - * Consumes all options, ignoring groups and categories. - * When consumer returns true, this function stops iterating. - */ - public static void consumeOptions(YetAnotherConfigLib yacl, Function, Boolean> consumer) { - for (ConfigCategory category : yacl.categories()) { - for (OptionGroup group : category.groups()) { - if (group instanceof ListOption list) { - if (consumer.apply(list)) return; - } else { - for (Option option : group.options()) { - if (consumer.apply(option)) return; - } - } - - } - } - } - - /** - * Consumes all options, ignoring groups and categories. - * - * @see OptionUtils#consumeOptions(YetAnotherConfigLib, Function) - */ - public static void forEachOptions(YetAnotherConfigLib yacl, Consumer> consumer) { - consumeOptions(yacl, (opt) -> { - consumer.accept(opt); - return false; - }); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/config/ConfigEntry.java b/common/src/main/java/dev/isxander/yacl/config/ConfigEntry.java deleted file mode 100644 index 7f04c33..0000000 --- a/common/src/main/java/dev/isxander/yacl/config/ConfigEntry.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.config; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface ConfigEntry { -} diff --git a/common/src/main/java/dev/isxander/yacl/config/ConfigInstance.java b/common/src/main/java/dev/isxander/yacl/config/ConfigInstance.java deleted file mode 100644 index c207161..0000000 --- a/common/src/main/java/dev/isxander/yacl/config/ConfigInstance.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.isxander.yacl.config; - -import java.lang.reflect.InvocationTargetException; - -/** - * Responsible for handing the actual config data type. - * Holds the instance along with a final default instance - * to reference default values for options and should not be changed. - * - * Abstract methods to save and load the class, implementations are responsible for - * how it saves and load. - * - * @param config data type - */ -public abstract class ConfigInstance { - private final Class configClass; - private final T defaultInstance; - private T instance; - - public ConfigInstance(Class configClass) { - this.configClass = configClass; - - try { - this.defaultInstance = this.instance = configClass.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new IllegalStateException(String.format("Could not create default instance of config for %s. Make sure there is a default constructor!", this.configClass.getSimpleName())); - } - } - - public abstract void save(); - public abstract void load(); - - public T getConfig() { - return this.instance; - } - - protected void setConfig(T instance) { - this.instance = instance; - } - - public T getDefaults() { - return this.defaultInstance; - } - - public Class getConfigClass() { - return this.configClass; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java b/common/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java deleted file mode 100644 index ad7f550..0000000 --- a/common/src/main/java/dev/isxander/yacl/config/GsonConfigInstance.java +++ /dev/null @@ -1,212 +0,0 @@ -package dev.isxander.yacl.config; - -import com.google.gson.*; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; - -import java.awt.*; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.function.UnaryOperator; - -/** - * Uses GSON to serialize and deserialize config data from JSON to a file. - * - * Only fields annotated with {@link ConfigEntry} are included in the JSON. - * {@link Component}, {@link Style} and {@link Color} have default type adapters, so there is no need to provide them in your GSON instance. - * GSON is automatically configured to format fields as {@code lower_camel_case}. - * - * @param config data type - */ -public class GsonConfigInstance extends ConfigInstance { - private final Gson gson; - private final Path path; - - @Deprecated - public GsonConfigInstance(Class configClass, Path path) { - this(configClass, path, new GsonBuilder()); - } - - @Deprecated - public GsonConfigInstance(Class configClass, Path path, Gson gson) { - this(configClass, path, gson.newBuilder()); - } - - @Deprecated - public GsonConfigInstance(Class configClass, Path path, UnaryOperator builder) { - this(configClass, path, builder.apply(new GsonBuilder())); - } - - @Deprecated - public GsonConfigInstance(Class configClass, Path path, GsonBuilder builder) { - super(configClass); - this.path = path; - this.gson = builder - .setExclusionStrategies(new ConfigExclusionStrategy()) - .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) - .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) - .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) - .serializeNulls() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - } - - private GsonConfigInstance(Class configClass, Path path, Gson gson, boolean fromBuilder) { - super(configClass); - this.path = path; - this.gson = gson; - } - - @Override - public void save() { - try { - YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName()); - Files.writeString(path, gson.toJson(getConfig()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void load() { - try { - if (Files.notExists(path)) { - save(); - return; - } - - YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName()); - setConfig(gson.fromJson(Files.readString(path), getConfigClass())); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public Path getPath() { - return this.path; - } - - private static class ConfigExclusionStrategy implements ExclusionStrategy { - @Override - public boolean shouldSkipField(FieldAttributes fieldAttributes) { - return fieldAttributes.getAnnotation(ConfigEntry.class) == null; - } - - @Override - public boolean shouldSkipClass(Class aClass) { - return false; - } - } - - public static class ColorTypeAdapter implements JsonSerializer, JsonDeserializer { - @Override - public Color deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { - return new Color(jsonElement.getAsInt(), true); - } - - @Override - public JsonElement serialize(Color color, Type type, JsonSerializationContext jsonSerializationContext) { - return new JsonPrimitive(color.getRGB()); - } - } - - /** - * Creates a builder for a GSON config instance. - * @param configClass the config class - * @return a new builder - * @param the config type - */ - public static Builder createBuilder(Class configClass) { - return new Builder<>(configClass); - } - - public static class Builder { - private final Class configClass; - private Path path; - private UnaryOperator gsonBuilder = builder -> builder - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .serializeNulls() - .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) - .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) - .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()); - - private Builder(Class configClass) { - this.configClass = configClass; - } - - /** - * Sets the file path to save and load the config from. - */ - public Builder setPath(Path path) { - this.path = path; - return this; - } - - /** - * Sets the GSON instance to use. Overrides all YACL defaults such as: - *

    - *
  • lower_camel_case field naming policy
  • - *
  • null serialization
  • - *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • - *
- * Still respects the exclusion strategy to only serialize {@link ConfigEntry} - * but these can be added to with setExclusionStrategies. - * - * @param gsonBuilder gson builder to use - */ - public Builder overrideGsonBuilder(GsonBuilder gsonBuilder) { - this.gsonBuilder = builder -> gsonBuilder; - return this; - } - - /** - * Sets the GSON instance to use. Overrides all YACL defaults such as: - *
    - *
  • lower_camel_case field naming policy
  • - *
  • null serialization
  • - *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • - *
- * Still respects the exclusion strategy to only serialize {@link ConfigEntry} - * but these can be added to with setExclusionStrategies. - * - * @param gson gson instance to be converted to a builder - */ - public Builder overrideGsonBuilder(Gson gson) { - return this.overrideGsonBuilder(gson.newBuilder()); - } - - /** - * Appends extra configuration to a GSON builder. - * This is the intended way to add functionality to the GSON instance. - *

- * By default, YACL sets the GSON with the following options: - *

    - *
  • lower_camel_case field naming policy
  • - *
  • null serialization
  • - *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • - *
- * - * @param gsonBuilder the function to apply to the builder - */ - public Builder appendGsonBuilder(UnaryOperator gsonBuilder) { - this.gsonBuilder = builder -> gsonBuilder.apply(this.gsonBuilder.apply(builder)); - return this; - } - - /** - * Builds the config instance. - * @return the built config instance - */ - public GsonConfigInstance build() { - UnaryOperator gsonBuilder = builder -> this.gsonBuilder.apply(builder) - .addSerializationExclusionStrategy(new ConfigExclusionStrategy()) - .addDeserializationExclusionStrategy(new ConfigExclusionStrategy()); - - return new GsonConfigInstance<>(configClass, path, gsonBuilder.apply(new GsonBuilder()).create(), true); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java b/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java deleted file mode 100644 index 06a6e23..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java +++ /dev/null @@ -1,94 +0,0 @@ -package dev.isxander.yacl.gui; - -import dev.isxander.yacl.api.utils.Dimension; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Renderable; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.sounds.SoundEvents; - -import java.awt.*; - -public abstract class AbstractWidget implements GuiEventListener, Renderable, NarratableEntry { - protected final Minecraft client = Minecraft.getInstance(); - protected final Font textRenderer = client.font; - protected final int inactiveColor = 0xFFA0A0A0; - - private Dimension dim; - - public AbstractWidget(Dimension dim) { - this.dim = dim; - } - - public boolean canReset() { - return false; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - if (dim == null) return false; - return this.dim.isPointInside((int) mouseX, (int) mouseY); - } - - public void setDimension(Dimension dim) { - this.dim = dim; - } - - public Dimension getDimension() { - return dim; - } - - @Override - public NarrationPriority narrationPriority() { - return NarrationPriority.NONE; - } - - public void unfocus() { - - } - - public boolean matchesSearch(String query) { - return true; - } - - @Override - public void updateNarration(NarrationElementOutput builder) { - - } - - protected void drawButtonRect(GuiGraphics graphics, int x1, int y1, int x2, int y2, boolean hovered, boolean enabled) { - if (x1 > x2) { - int xx1 = x1; - x1 = x2; - x2 = xx1; - } - if (y1 > y2) { - int yy1 = y1; - y1 = y2; - y2 = yy1; - } - int width = x2 - x1; - int height = y2 - y1; - - int i = !enabled ? 0 : hovered ? 2 : 1; - graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); - graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); - } - - protected int multiplyColor(int hex, float amount) { - Color color = new Color(hex, true); - - return new Color(Math.max((int)(color.getRed() * amount), 0), - Math.max((int)(color.getGreen() * amount), 0), - Math.max((int)(color.getBlue() * amount), 0), - color.getAlpha()).getRGB(); - } - - public void playDownSound() { - Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/DescriptionWithName.java b/common/src/main/java/dev/isxander/yacl/gui/DescriptionWithName.java deleted file mode 100644 index c29e0ca..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/DescriptionWithName.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.isxander.yacl.gui; - -import dev.isxander.yacl.api.OptionDescription; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; - -public record DescriptionWithName(Component name, OptionDescription description) { - public static DescriptionWithName of(Component name, OptionDescription description) { - return new DescriptionWithName(name.copy().withStyle(ChatFormatting.BOLD), description); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java b/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java deleted file mode 100644 index 6b3ab1c..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java +++ /dev/null @@ -1,222 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.platform.InputConstants; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.ContainerObjectSelectionList; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.layouts.LayoutElement; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.Nullable; - -import java.util.function.Consumer; - -public class ElementListWidgetExt> extends ContainerObjectSelectionList implements LayoutElement { - protected int x, y; - - private double smoothScrollAmount = getScrollAmount(); - private boolean returnSmoothAmount = false; - private final boolean doSmoothScrolling; - - public ElementListWidgetExt(Minecraft client, int x, int y, int width, int height, boolean smoothScrolling) { - super(client, width, height, y, y + height, 22); - this.x = this.x0 = x; - this.y = y; - this.x1 = this.x0 + width; - this.doSmoothScrolling = smoothScrolling; - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - // default implementation bases scroll step from total height of entries, this is constant - this.setScrollAmount(this.getScrollAmount() - amount * 20); - return true; - } - - @Override - protected void renderBackground(GuiGraphics graphics) { - // render transparent background if in-game. - setRenderBackground(true); - setRenderTopAndBottom(false); - } - - @Override - protected int getScrollbarPosition() { - // default implementation does not respect left/right - return this.x1 - 2; - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - smoothScrollAmount = Mth.lerp(Minecraft.getInstance().getDeltaFrameTime() * 0.5, smoothScrollAmount, getScrollAmount()); - returnSmoothAmount = true; - - graphics.enableScissor(x0, y0, x1, y1); - - super.render(graphics, mouseX, mouseY, delta); - - graphics.disableScissor(); - - returnSmoothAmount = false; - } - - public void updateDimensions(ScreenRectangle rectangle) { - this.x0 = rectangle.left(); - this.y0 = rectangle.top(); - this.x1 = rectangle.right(); - this.y1 = rectangle.bottom(); - this.width = rectangle.width(); - this.height = rectangle.height(); - } - - /** - * awful code to only use smooth scroll state when rendering, - * not other code that needs target scroll amount - */ - @Override - public double getScrollAmount() { - if (returnSmoothAmount && doSmoothScrolling) - return smoothScrollAmount; - - return super.getScrollAmount(); - } - - protected void resetSmoothScrolling() { - this.smoothScrollAmount = getScrollAmount(); - } - - @Nullable - @Override - protected E getEntryAtPosition(double x, double y) { - y += getScrollAmount(); - - if (x < this.x0 || x > this.x1) - return null; - - int currentY = this.y0 - headerHeight + 4; - for (E entry : children()) { - if (y >= currentY && y <= currentY + entry.getItemHeight()) { - return entry; - } - - currentY += entry.getItemHeight(); - } - - return null; - } - - /* - below code is licensed from cloth-config under LGPL3 - modified to inherit vanilla's EntryListWidget and use yarn mappings - - code is responsible for having dynamic item heights - */ - - @Override - protected int getMaxPosition() { - return children().stream().map(E::getItemHeight).reduce(0, Integer::sum) + headerHeight; - } - - @Override - protected void centerScrollOn(E entry) { - double d = (this.height) / -2d; - for (int i = 0; i < this.children().indexOf(entry) && i < this.getItemCount(); i++) - d += children().get(i).getItemHeight(); - this.setScrollAmount(d); - } - - @Override - protected int getRowTop(int index) { - int integer = y0 + 4 - (int) this.getScrollAmount() + headerHeight; - for (int i = 0; i < children().size() && i < index; i++) - integer += children().get(i).getItemHeight(); - return integer; - } - - @Override - protected void renderList(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - int left = this.getRowLeft(); - int right = this.getRowWidth(); - int count = this.getItemCount(); - - for(int i = 0; i < count; ++i) { - E entry = children().get(i); - int top = this.getRowTop(i); - int bottom = top + entry.getItemHeight(); - int entryHeight = entry.getItemHeight() - 4; - if (bottom >= this.y0 && top <= this.y1) { - this.renderItem(graphics, mouseX, mouseY, delta, i, left, top, right, entryHeight); - } - } - } - - /* END cloth config code */ - - @Override - public void setX(int i) { - this.x = x0 = i; - this.x1 = x0 + width; - } - - @Override - public void setY(int i) { - this.y = y0 = i; - this.y1 = y0 + height; - } - - @Override - public int getX() { - return x; - } - - @Override - public int getY() { - return y; - } - - @Override - public int getWidth() { - return width; - } - - @Override - public int getHeight() { - return height; - } - - @Override - public void visitWidgets(Consumer consumer) { - } - - public abstract static class Entry> extends ContainerObjectSelectionList.Entry { - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - for (GuiEventListener child : this.children()) { - if (child.mouseClicked(mouseX, mouseY, button)) { - if (button == InputConstants.MOUSE_BUTTON_LEFT) - this.setDragging(true); - return true; - } - } - - return false; - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (isDragging() && button == InputConstants.MOUSE_BUTTON_LEFT) { - for (GuiEventListener child : this.children()) { - if (child.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) - return true; - } - } - return false; - } - - public int getItemHeight() { - return 22; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java b/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java deleted file mode 100644 index 7389232..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java +++ /dev/null @@ -1,386 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.Blaze3D; -import com.mojang.blaze3d.platform.NativeImage; -import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.CrashReport; -import net.minecraft.CrashReportCategory; -import net.minecraft.ReportedException; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.client.renderer.texture.TextureManager; -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; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataNode; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import java.util.stream.IntStream; - -public interface ImageRenderer { - int render(GuiGraphics graphics, int x, int y, int renderWidth); - - void close(); - - Map>> CACHE = new ConcurrentHashMap<>(); - - static CompletableFuture> getOrMakeAsync(ResourceLocation id, Supplier> factory) { - return CACHE.computeIfAbsent(id, key -> CompletableFuture.supplyAsync(factory, YACLConstants.SINGLE_THREAD_EXECUTOR)); - } - - static CompletableFuture> getOrMakeSync(ResourceLocation id, Supplier> factory) { - return CACHE.computeIfAbsent(id, key -> CompletableFuture.completedFuture(factory.get())); - } - - static void closeAll() { - CACHE.values().forEach(future -> future.thenAccept(opt -> opt.ifPresent(ImageRenderer::close))); - CACHE.clear(); - } - - class TextureBacked implements ImageRenderer { - private final ResourceLocation location; - private final int width, height; - private final int textureWidth, textureHeight; - private final float u, v; - - public TextureBacked(ResourceLocation location, float u, float v, int width, int height, int textureWidth, int textureHeight) { - this.location = location; - this.width = width; - this.height = height; - this.textureWidth = textureWidth; - this.textureHeight = textureHeight; - this.u = u; - this.v = v; - } - - @Override - public int render(GuiGraphics graphics, int x, int y, int renderWidth) { - float ratio = renderWidth / (float)this.width; - int targetHeight = (int) (this.height * ratio); - - graphics.pose().pushPose(); - graphics.pose().translate(x, y, 0); - graphics.pose().scale(ratio, ratio, 1); - graphics.blit(location, 0, 0, this.u, this.v, this.width, this.height, this.textureWidth, this.textureHeight); - graphics.pose().popPose(); - - return targetHeight; - } - - @Override - public void close() { - - } - } - - class NativeImageBacked implements ImageRenderer { - protected static final TextureManager textureManager = Minecraft.getInstance().getTextureManager(); - - protected NativeImage image; - protected DynamicTexture texture; - protected final ResourceLocation uniqueLocation; - protected final int width, height; - - public NativeImageBacked(NativeImage image, ResourceLocation uniqueLocation) { - this.image = image; - this.texture = new DynamicTexture(image); - this.uniqueLocation = uniqueLocation; - textureManager.register(this.uniqueLocation, this.texture); - this.width = image.getWidth(); - this.height = image.getHeight(); - } - - private NativeImageBacked(Path imagePath, ResourceLocation uniqueLocation) throws IOException { - this.uniqueLocation = uniqueLocation; - this.image = NativeImage.read(new FileInputStream(imagePath.toFile())); - this.width = image.getWidth(); - this.height = image.getHeight(); - this.texture = new DynamicTexture(image); - textureManager.register(this.uniqueLocation, this.texture); - } - - public static Optional createFromPath(Path path, ResourceLocation uniqueLocation) { - try { - return Optional.of(new NativeImageBacked(path, uniqueLocation)); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - } - - @Override - public int render(GuiGraphics graphics, int x, int y, int renderWidth) { - if (image == null) return 0; - - float ratio = renderWidth / (float)this.width; - int targetHeight = (int) (this.height * ratio); - - graphics.pose().pushPose(); - graphics.pose().translate(x, y, 0); - graphics.pose().scale(ratio, ratio, 1); - graphics.blit(uniqueLocation, 0, 0, 0, 0, this.width, this.height, this.width, this.height); - graphics.pose().popPose(); - - return targetHeight; - } - - @Override - public void close() { - image.close(); - image = null; - texture = null; - textureManager.release(uniqueLocation); - } - } - - class AnimatedNativeImageBacked extends NativeImageBacked { - private int currentFrame; - private double lastFrameTime; - - private final double[] frameDelays; - private final int frameCount; - - private final int packCols, packRows; - private final int frameWidth, frameHeight; - - public AnimatedNativeImageBacked(NativeImage image, int frameWidth, int frameHeight, int frameCount, double[] frameDelayMS, int packCols, int packRows, ResourceLocation uniqueLocation) { - super(image, uniqueLocation); - this.frameWidth = frameWidth; - this.frameHeight = frameHeight; - this.frameCount = frameCount; - this.frameDelays = frameDelayMS; - this.packCols = packCols; - this.packRows = packRows; - } - - public static AnimatedNativeImageBacked createGIFFromTexture(ResourceLocation textureLocation) throws IOException { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - Resource resource = resourceManager.getResource(textureLocation).orElseThrow(); - - return createGIF(resource.open(), textureLocation); - } - - public static AnimatedNativeImageBacked createWEBPFromTexture(ResourceLocation textureLocation) throws IOException { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - Resource resource = resourceManager.getResource(textureLocation).orElseThrow(); - - return createWEBP(resource.open(), textureLocation); - } - - public static AnimatedNativeImageBacked createGIF(InputStream is, ResourceLocation uniqueLocation) { - try (is) { - ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next(); - reader.setInput(ImageIO.createImageInputStream(is)); - - - - AnimFrameProvider animFrameFunction = i -> { - IIOMetadata metadata = reader.getImageMetadata(i); - String metaFormatName = metadata.getNativeMetadataFormatName(); - IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName); - IIOMetadataNode graphicsControlExtensionNode = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item(0); - int delay = Integer.parseInt(graphicsControlExtensionNode.getAttribute("delayTime")) * 10; - - return new AnimFrame(delay, 0, 0); - }; - - return createFromImageReader(reader, animFrameFunction, uniqueLocation); - } catch (Exception e) { - CrashReport crashReport = CrashReport.forThrowable(e, "Failed to load GIF image"); - CrashReportCategory category = crashReport.addCategory("YACL Gui"); - category.setDetail("Image identifier", uniqueLocation.toString()); - throw new ReportedException(crashReport); - } - } - - public static AnimatedNativeImageBacked createWEBP(InputStream is, ResourceLocation uniqueLocation) { - try (is) { - ImageReader reader = new WebPImageReaderSpi().createReaderInstance(); - reader.setInput(ImageIO.createImageInputStream(is)); - - int numImages = reader.getNumImages(true); // Force reading of all frames - AnimFrameProvider animFrameFunction = i -> null; - if (numImages > 1) { - // WebP reader does not expose frame delay, prepare for reflection hell - Class webpReaderClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.WebPImageReader"); - Field framesField = webpReaderClass.getDeclaredField("frames"); - framesField.setAccessible(true); - List frames = (List) framesField.get(reader); - - Class animationFrameClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.AnimationFrame"); - Field durationField = animationFrameClass.getDeclaredField("duration"); - durationField.setAccessible(true); - Field boundsField = animationFrameClass.getDeclaredField("bounds"); - boundsField.setAccessible(true); - - animFrameFunction = i -> { - Rectangle bounds = (Rectangle) boundsField.get(frames.get(i)); - return new AnimFrame((int) durationField.get(frames.get(i)), bounds.x, bounds.y); - }; - // that was fun - } - - return createFromImageReader(reader, animFrameFunction, uniqueLocation); - } catch (Throwable e) { - CrashReport crashReport = CrashReport.forThrowable(e, "Failed to load WEBP image"); - CrashReportCategory category = crashReport.addCategory("YACL Gui"); - category.setDetail("Image identifier", uniqueLocation.toString()); - throw new ReportedException(crashReport); - } - } - - private static AnimatedNativeImageBacked createFromImageReader(ImageReader reader, AnimFrameProvider animationProvider, ResourceLocation uniqueLocation) throws Exception { - int frameCount = reader.getNumImages(true); - - // Because this is being backed into a texture atlas, we need a maximum dimension - // so you can get the texture atlas size. - // Smaller frames are given black borders - int frameWidth = IntStream.range(reader.getMinIndex(), frameCount).map(i -> { - try { - return reader.getWidth(i); - } catch (IOException e) { - throw new RuntimeException(e); - } - }).max().orElseThrow(); - int frameHeight = IntStream.range(reader.getMinIndex(), frameCount).map(i -> { - try { - return reader.getHeight(i); - } catch (IOException e) { - throw new RuntimeException(e); - } - }).max().orElseThrow(); - - // Packs the frames into an optimal 1:1 texture. - // OpenGL can only have texture axis with a max of 32768 pixels, - // and packing them to that length is not efficient, apparently. - double ratio = frameWidth / (double)frameHeight; - int cols = (int)Math.ceil(Math.sqrt(frameCount) / Math.sqrt(ratio)); - int rows = (int)Math.ceil(frameCount / (double)cols); - - NativeImage image = new NativeImage(frameWidth * cols, frameHeight * rows, true); - - // Fill whole atlas with black, as each frame may have different dimensions - // that would cause borders of transparent pixels to appear around the frames - for (int x = 0; x < frameWidth * cols; x++) { - for (int y = 0; y < frameHeight * rows; y++) { - image.setPixelRGBA(x, y, 0xFF000000); - } - } - - BufferedImage bi = null; - Graphics2D graphics = null; - - // each frame may have a different delay - double[] frameDelays = new double[frameCount]; - - for (int i = reader.getMinIndex(); i < frameCount - 1; i++) { - AnimFrame frame = animationProvider.get(i); - if (frameCount > 1) // frame will be null if not animation - frameDelays[i] = frame.durationMS; - - if (bi == null) { - // first frame... - bi = reader.read(i); - graphics = bi.createGraphics(); - } else { - // WebP reader sometimes provides delta frames, (only the pixels that changed since the last frame) - // so instead of overwriting the image every frame, we draw delta frames on top of the previous frame - // to keep a complete image. - BufferedImage deltaFrame = reader.read(i); - graphics.drawImage(deltaFrame, frame.xOffset, frame.yOffset, null); - } - - // Each frame may have different dimensions, so we need to center them. - int xOffset = (frameWidth - bi.getWidth()) / 2; - int yOffset = (frameHeight - bi.getHeight()) / 2; - - 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 col = i % cols; - int row = (int) Math.floor(i / (double)cols); - - image.setPixelRGBA( - frameWidth * col + w + xOffset, - frameHeight * row + h + yOffset, - FastColor.ABGR32.color(255, b, g, r) // NativeImage uses ABGR for some reason - ); - } - } - } - // gives the texture to GL for rendering - // usually, you create a native image with NativeImage.create, which sets the pixels and - // runs this function itself. In this case, we need to do it manually. - image.upload(0, 0, 0, false); - - graphics.dispose(); - reader.dispose(); - - return new AnimatedNativeImageBacked(image, frameWidth, frameHeight, frameCount, frameDelays, cols, rows, uniqueLocation); - } - - @Override - public int render(GuiGraphics graphics, int x, int y, int renderWidth) { - if (image == null) return 0; - - float ratio = renderWidth / (float)frameWidth; - int targetHeight = (int) (frameHeight * ratio); - - int currentCol = currentFrame % packCols; - int currentRow = (int) Math.floor(currentFrame / (double)packCols); - - graphics.pose().pushPose(); - graphics.pose().translate(x, y, 0); - graphics.pose().scale(ratio, ratio, 1); - graphics.blit( - uniqueLocation, - 0, 0, - frameWidth * currentCol, frameHeight * currentRow, - frameWidth, frameHeight, - this.width, this.height - ); - graphics.pose().popPose(); - - if (frameCount > 1) { - double timeMS = Blaze3D.getTime() * 1000; - if (lastFrameTime == 0) lastFrameTime = timeMS; - if (timeMS - lastFrameTime >= frameDelays[currentFrame]) { - currentFrame++; - lastFrameTime = timeMS; - } - if (currentFrame >= frameCount - 1) - currentFrame = 0; - } - - return targetHeight; - } - - @FunctionalInterface - private interface AnimFrameProvider { - AnimFrame get(int frame) throws Exception; - } - private record AnimFrame(int durationMS, int xOffset, int yOffset) {} - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java deleted file mode 100644 index f002e82..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.network.chat.Component; - -public class LowProfileButtonWidget extends Button { - public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress) { - super(x, y, width, height, message, onPress, DEFAULT_NARRATION); - } - - public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress, Tooltip tooltip) { - this(x, y, width, height, message, onPress); - setTooltip(tooltip); - } - - @Override - public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float deltaTicks) { - if (!isHoveredOrFocused() || !active) { - int j = this.active ? 0xFFFFFF : 0xA0A0A0; - this.renderString(graphics, Minecraft.getInstance().font, j); - } else { - super.renderWidget(graphics, mouseX, mouseY, deltaTicks); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java b/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java deleted file mode 100644 index fceb38a..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java +++ /dev/null @@ -1,215 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.Blaze3D; -import com.mojang.blaze3d.platform.InputConstants; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.narration.NarratedElementType; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.Supplier; - -public class OptionDescriptionWidget extends AbstractWidget { - private static final int AUTO_SCROLL_TIMER = 1500; - private static final float AUTO_SCROLL_SPEED = 1; // lines per second - - private @Nullable DescriptionWithName description; - private List wrappedText; - - private static final Minecraft minecraft = Minecraft.getInstance(); - private static final Font font = minecraft.font; - - private Supplier dimensions; - - private float targetScrollAmount, currentScrollAmount; - private int maxScrollAmount; - private int descriptionY; - - private int lastInteractionTime; - private boolean scrollingBackward; - - public OptionDescriptionWidget(Supplier dimensions, @Nullable DescriptionWithName description) { - super(0, 0, 0, 0, description == null ? Component.empty() : description.name()); - this.dimensions = dimensions; - this.setOptionDescription(description); - } - - @Override - public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - if (description == null) return; - - currentScrollAmount = Mth.lerp(delta * 0.5f, currentScrollAmount, targetScrollAmount); - - ScreenRectangle dimensions = this.dimensions.get(); - this.setX(dimensions.left()); - this.setY(dimensions.top()); - this.width = dimensions.width(); - this.height = dimensions.height(); - - int y = getY(); - - int nameWidth = font.width(description.name()); - if (nameWidth > getWidth()) { - renderScrollingString(graphics, font, description.name(), getX(), y, getX() + getWidth(), y + font.lineHeight, -1); - } else { - graphics.drawString(font, description.name(), getX(), y, 0xFFFFFF); - } - - y += 5 + font.lineHeight; - - graphics.enableScissor(getX(), y, getX() + getWidth(), getY() + getHeight()); - - y -= (int)currentScrollAmount; - - if (description.description().image().isDone()) { - var image = description.description().image().join(); - if (image.isPresent()) { - image.get().render(graphics, getX(), y, getWidth()); - y += image.get().render(graphics, getX(), y, getWidth()) + 5; - } - } - - if (wrappedText == null) - wrappedText = font.split(description.description().text(), getWidth()); - - descriptionY = y; - for (var line : wrappedText) { - graphics.drawString(font, line, getX(), y, 0xFFFFFF); - y += font.lineHeight; - } - - graphics.disableScissor(); - - maxScrollAmount = Math.max(0, y + (int)currentScrollAmount - getY() - getHeight()); - - if (isHoveredOrFocused()) { - lastInteractionTime = currentTimeMS(); - } - Style hoveredStyle = getDescStyle(mouseX, mouseY); - if (hoveredStyle != null && hoveredStyle.getHoverEvent() != null) { - graphics.renderComponentHoverEffect(font, hoveredStyle, mouseX, mouseY); - } - - if (isFocused()) { - graphics.renderOutline(getX(), getY(), getWidth(), getHeight(), -1); - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - Style clickedStyle = getDescStyle((int) mouseX, (int) mouseY); - if (clickedStyle != null && clickedStyle.getClickEvent() != null) { - if (minecraft.screen.handleComponentClicked(clickedStyle)) { - playDownSound(minecraft.getSoundManager()); - return true; - } - return false; - } - - return false; - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - if (isMouseOver(mouseX, mouseY)) { - targetScrollAmount = Mth.clamp(targetScrollAmount - (int) amount * 10, 0, maxScrollAmount); - lastInteractionTime = currentTimeMS(); - return true; - } - return false; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (isFocused()) { - switch (keyCode) { - case InputConstants.KEY_UP -> - targetScrollAmount = Mth.clamp(targetScrollAmount - 10, 0, maxScrollAmount); - case InputConstants.KEY_DOWN -> - targetScrollAmount = Mth.clamp(targetScrollAmount + 10, 0, maxScrollAmount); - default -> { - return false; - } - } - return true; - } - return false; - } - - public void tick() { - float pxPerTick = AUTO_SCROLL_SPEED / 20f * font.lineHeight; - if (maxScrollAmount > 0 && currentTimeMS() - lastInteractionTime > AUTO_SCROLL_TIMER) { - if (scrollingBackward) { - pxPerTick *= -1; - if (targetScrollAmount + pxPerTick < 0) { - scrollingBackward = false; - lastInteractionTime = currentTimeMS(); - } - } else { - if (targetScrollAmount + pxPerTick > maxScrollAmount) { - scrollingBackward = true; - lastInteractionTime = currentTimeMS(); - } - } - - targetScrollAmount = Mth.clamp(targetScrollAmount + pxPerTick, 0, maxScrollAmount); - } - } - - private Style getDescStyle(int mouseX, int mouseY) { - if (!clicked(mouseX, mouseY)) - return null; - - int x = mouseX - getX(); - int y = mouseY - descriptionY; - - if (x < 0 || x > getX() + getWidth()) return null; - if (y < 0 || y > getY() + getHeight()) return null; - - int line = y / font.lineHeight; - - if (line >= wrappedText.size()) return null; - - return font.getSplitter().componentStyleAtWidth(wrappedText.get(line), x); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput builder) { - if (description != null) { - builder.add(NarratedElementType.TITLE, description.name()); - builder.add(NarratedElementType.HINT, description.description().text()); - } - - } - - public void setOptionDescription(DescriptionWithName description) { - this.description = description; - this.wrappedText = null; - this.targetScrollAmount = 0; - this.currentScrollAmount = 0; - this.lastInteractionTime = currentTimeMS(); - } - - private int currentTimeMS() { - return (int)(Blaze3D.getTime() * 1000); - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent event) { - // prevents focusing on this widget - return null; - } - -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java deleted file mode 100644 index 1c3596a..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java +++ /dev/null @@ -1,572 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraft.client.gui.narration.NarratedElementType; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; - -public class OptionListWidget extends ElementListWidgetExt { - private final YACLScreen yaclScreen; - private final ConfigCategory category; - private ImmutableList viewableChildren; - private String searchQuery = ""; - private final Consumer hoverEvent; - private DescriptionWithName lastHoveredOption; - - public OptionListWidget(YACLScreen screen, ConfigCategory category, Minecraft client, int x, int y, int width, int height, Consumer hoverEvent) { - super(client, x, y, width, height, true); - this.yaclScreen = screen; - this.category = category; - this.hoverEvent = hoverEvent; - - refreshOptions(); - - for (OptionGroup group : category.groups()) { - if (group instanceof ListOption listOption) { - listOption.addRefreshListener(() -> refreshListEntries(listOption, category)); - } - } - } - - public void refreshOptions() { - clearEntries(); - - for (OptionGroup group : category.groups()) { - GroupSeparatorEntry groupSeparatorEntry; - if (!group.isRoot()) { - groupSeparatorEntry = group instanceof ListOption listOption - ? new ListGroupSeparatorEntry(listOption, yaclScreen) - : new GroupSeparatorEntry(group, yaclScreen); - addEntry(groupSeparatorEntry); - } else { - groupSeparatorEntry = null; - } - - List optionEntries = new ArrayList<>(); - - // add empty entry to make sure users know it's empty not just bugging out - if (groupSeparatorEntry instanceof ListGroupSeparatorEntry listGroupSeparatorEntry) { - if (listGroupSeparatorEntry.listOption.options().isEmpty()) { - EmptyListLabel emptyListLabel = new EmptyListLabel(listGroupSeparatorEntry, category); - addEntry(emptyListLabel); - optionEntries.add(emptyListLabel); - } - } - - for (Option option : group.options()) { - OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); - addEntry(entry); - optionEntries.add(entry); - } - - if (groupSeparatorEntry != null) { - groupSeparatorEntry.setChildEntries(optionEntries); - } - } - - recacheViewableChildren(); - setScrollAmount(0); - resetSmoothScrolling(); - } - - private void refreshListEntries(ListOption listOption, ConfigCategory category) { - // find group separator for group - ListGroupSeparatorEntry groupSeparator = super.children().stream().filter(e -> e instanceof ListGroupSeparatorEntry gs && gs.group == listOption).map(ListGroupSeparatorEntry.class::cast).findAny().orElse(null); - - if (groupSeparator == null) { - YACLConstants.LOGGER.warn("Can't find group seperator to refresh list option entries for list option " + listOption.name()); - return; - } - - for (Entry entry : groupSeparator.childEntries) - super.removeEntry(entry); - groupSeparator.childEntries.clear(); - - // if no entries, below loop won't run where addEntryBelow() recaches viewable children - if (listOption.options().isEmpty()) { - EmptyListLabel emptyListLabel; - addEntryBelow(groupSeparator, emptyListLabel = new EmptyListLabel(groupSeparator, category)); - groupSeparator.childEntries.add(emptyListLabel); - return; - } - - Entry lastEntry = groupSeparator; - for (ListOptionEntry listOptionEntry : listOption.options()) { - OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); - addEntryBelow(lastEntry, optionEntry); - groupSeparator.childEntries.add(optionEntry); - lastEntry = optionEntry; - } - } - - public Dimension getDefaultEntryDimension() { - return Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20); - } - - public void expandAllGroups() { - for (Entry entry : super.children()) { - if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { - groupSeparatorEntry.setExpanded(true); - } - } - } - - public void updateSearchQuery(String query) { - this.searchQuery = query; - expandAllGroups(); - recacheViewableChildren(); - } - - @Override - public int getRowWidth() { - return Math.min(396, (int)(width / 1.3f)); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - for (Entry child : children()) { - if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry) - optionEntry.widget.unfocus(); - } - - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - super.mouseScrolled(mouseX, mouseY, amount); - - for (Entry child : children()) { - if (child.mouseScrolled(mouseX, mouseY, amount)) - break; - } - - return true; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - for (Entry child : children()) { - if (child.keyPressed(keyCode, scanCode, modifiers)) - return true; - } - - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - for (Entry child : children()) { - if (child.charTyped(chr, modifiers)) - return true; - } - - return super.charTyped(chr, modifiers); - } - - @Override - protected int getScrollbarPosition() { - return x1 - (int)(width * 0.05f); - } - - public void recacheViewableChildren() { - this.viewableChildren = ImmutableList.copyOf(super.children().stream().filter(Entry::isViewable).toList()); - - // update y positions before they need to be rendered are rendered - int i = 0; - for (Entry entry : viewableChildren) { - if (entry instanceof OptionEntry optionEntry) - optionEntry.widget.setDimension(optionEntry.widget.getDimension().withY(getRowTop(i))); - i++; - } - } - - @Override - public List children() { - return viewableChildren; - } - - public void addEntry(int index, Entry entry) { - super.children().add(index, entry); - recacheViewableChildren(); - } - - public void addEntryBelow(Entry below, Entry entry) { - int idx = super.children().indexOf(below) + 1; - - if (idx == 0) - throw new IllegalStateException("The entry to insert below does not exist!"); - - addEntry(idx, entry); - } - - public void addEntryBelowWithoutScroll(Entry below, Entry entry) { - double d = (double)this.getMaxScroll() - this.getScrollAmount(); - addEntryBelow(below, entry); - setScrollAmount(getMaxScroll() - d); - } - - @Override - public boolean removeEntryFromTop(Entry entry) { - boolean ret = super.removeEntryFromTop(entry); - recacheViewableChildren(); - return ret; - } - - @Override - public boolean removeEntry(Entry entry) { - boolean ret = super.removeEntry(entry); - recacheViewableChildren(); - return ret; - } - - private void setHoverDescription(DescriptionWithName description) { - if (description != lastHoveredOption) { - lastHoveredOption = description; - hoverEvent.accept(description); - } - } - - public abstract class Entry extends ElementListWidgetExt.Entry { - public boolean isViewable() { - return true; - } - - protected boolean isHovered() { - return Objects.equals(getHovered(), this); - } - } - - public class OptionEntry extends Entry { - public final Option option; - public final ConfigCategory category; - public final OptionGroup group; - - public final @Nullable GroupSeparatorEntry groupSeparatorEntry; - - public final AbstractWidget widget; - - private final TextScaledButtonWidget resetButton; - - private final String categoryName; - private final String groupName; - - public OptionEntry(Option option, ConfigCategory category, OptionGroup group, @Nullable GroupSeparatorEntry groupSeparatorEntry, AbstractWidget widget) { - this.option = option; - this.category = category; - this.group = group; - this.groupSeparatorEntry = groupSeparatorEntry; - this.widget = widget; - this.categoryName = category.name().getString().toLowerCase(); - this.groupName = group.name().getString().toLowerCase(); - if (option.canResetToDefault() && this.widget.canReset()) { - this.widget.setDimension(this.widget.getDimension().expanded(-20, 0)); - this.resetButton = new TextScaledButtonWidget(yaclScreen, widget.getDimension().xLimit(), -50, 20, 20, 2f, Component.literal("\u21BB"), button -> { - option.requestSetDefault(); - }); - option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); - this.resetButton.active = !option.isPendingValueDefault() && option.available(); - } else { - this.resetButton = null; - } - } - - @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - widget.setDimension(widget.getDimension().withY(y)); - - widget.render(graphics, mouseX, mouseY, tickDelta); - - if (resetButton != null) { - resetButton.setY(y); - resetButton.render(graphics, mouseX, mouseY, tickDelta); - } - - if (isHovered()) { - setHoverDescription(DescriptionWithName.of(option.name(), option.description())); - } - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - return widget.mouseScrolled(mouseX, mouseY, amount); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return widget.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - return widget.charTyped(chr, modifiers); - } - - @Override - public boolean isViewable() { - return (groupSeparatorEntry == null || groupSeparatorEntry.isExpanded()) - && (searchQuery.isEmpty() - || groupName.contains(searchQuery) - || widget.matchesSearch(searchQuery)); - } - - @Override - public int getItemHeight() { - return Math.max(widget.getDimension().height(), resetButton != null ? resetButton.getHeight() : 0) + 2; - } - - @Override - public void setFocused(boolean focused) { - super.setFocused(focused); - if (focused) - setHoverDescription(DescriptionWithName.of(option.name(), option.description())); - } - - @Override - public List narratables() { - if (resetButton == null) - return ImmutableList.of(widget); - - return ImmutableList.of(widget, resetButton); - } - - @Override - public List children() { - if (resetButton == null) - return ImmutableList.of(widget); - - return ImmutableList.of(widget, resetButton); - } - } - - public class GroupSeparatorEntry extends Entry { - protected final OptionGroup group; - protected final MultiLineLabel wrappedName; - protected final MultiLineLabel wrappedTooltip; - - protected final LowProfileButtonWidget expandMinimizeButton; - - protected final Screen screen; - protected final Font font = Minecraft.getInstance().font; - - protected boolean groupExpanded; - - protected List childEntries = new ArrayList<>(); - - private int y; - - private GroupSeparatorEntry(OptionGroup group, Screen screen) { - this.group = group; - this.screen = screen; - this.wrappedName = MultiLineLabel.create(font, group.name(), getRowWidth() - 45); - this.wrappedTooltip = MultiLineLabel.create(font, group.tooltip(), screen.width / 3 * 2 - 10); - this.groupExpanded = !group.collapsed(); - this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Component.empty(), btn -> onExpandButtonPress()); - updateExpandMinimizeText(); - } - - @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - this.y = y; - - int buttonY = y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2 + 1; - - expandMinimizeButton.setY(buttonY); - expandMinimizeButton.setX(x); - expandMinimizeButton.render(graphics, mouseX, mouseY, tickDelta); - - wrappedName.renderCentered(graphics, x + entryWidth / 2, y + getYPadding()); - - if (isHovered()) { - setHoverDescription(DescriptionWithName.of(group.name(), group.description())); - } - } - - public boolean isExpanded() { - return groupExpanded; - } - - public void setExpanded(boolean expanded) { - if (this.groupExpanded == expanded) - return; - - this.groupExpanded = expanded; - updateExpandMinimizeText(); - recacheViewableChildren(); - } - - protected void onExpandButtonPress() { - setExpanded(!isExpanded()); - } - - protected void updateExpandMinimizeText() { - expandMinimizeButton.setMessage(Component.literal(isExpanded() ? "â–¼" : "â–¶")); - } - - public void setChildEntries(List childEntries) { - this.childEntries.clear(); - this.childEntries.addAll(childEntries); - } - - @Override - public boolean isViewable() { - return searchQuery.isEmpty() || childEntries.stream().anyMatch(Entry::isViewable); - } - - @Override - public int getItemHeight() { - return Math.max(wrappedName.getLineCount(), 1) * font.lineHeight + getYPadding() * 2; - } - - private int getYPadding() { - return 6; - } - - @Override - public void setFocused(boolean focused) { - super.setFocused(focused); - if (focused) - setHoverDescription(DescriptionWithName.of(group.name(), group.description())); - } - - @Override - public List narratables() { - return ImmutableList.of(new NarratableEntry() { - @Override - public NarrationPriority narrationPriority() { - return NarrationPriority.HOVERED; - } - - @Override - public void updateNarration(NarrationElementOutput builder) { - builder.add(NarratedElementType.TITLE, group.name()); - builder.add(NarratedElementType.HINT, group.tooltip()); - } - }); - } - - @Override - public List children() { - return ImmutableList.of(expandMinimizeButton); - } - } - - public class ListGroupSeparatorEntry extends GroupSeparatorEntry { - private final ListOption listOption; - private final TextScaledButtonWidget resetListButton; - private final TooltipButtonWidget addListButton; - - private ListGroupSeparatorEntry(ListOption group, Screen screen) { - super(group, screen); - this.listOption = group; - - this.resetListButton = new TextScaledButtonWidget(screen, getRowRight() - 20, -50, 20, 20, 2f, Component.literal("\u21BB"), button -> { - group.requestSetDefault(); - }); - group.addListener((opt, val) -> this.resetListButton.active = !opt.isPendingValueDefault() && opt.available()); - this.resetListButton.active = !group.isPendingValueDefault() && group.available(); - - - this.addListButton = new TooltipButtonWidget(yaclScreen, resetListButton.getX() - 20, -50, 20, 20, Component.literal("+"), Component.translatable("yacl.list.add_top"), btn -> { - group.insertNewEntryToTop(); - setExpanded(true); - }); - - updateExpandMinimizeText(); - minimizeIfUnavailable(); - } - - @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - updateExpandMinimizeText(); // update every render because option could become available/unavailable at any time - - super.render(graphics, index, y, x, entryWidth, entryHeight, mouseX, mouseY, hovered, tickDelta); - - int buttonY = expandMinimizeButton.getY(); - - resetListButton.setY(buttonY); - addListButton.setY(buttonY); - - resetListButton.render(graphics, mouseX, mouseY, tickDelta); - addListButton.render(graphics, mouseX, mouseY, tickDelta); - } - - private void minimizeIfUnavailable() { - if (!listOption.available() && isExpanded()) { - setExpanded(false); - } - } - - @Override - protected void updateExpandMinimizeText() { - super.updateExpandMinimizeText(); - expandMinimizeButton.active = listOption == null || listOption.available(); - if (addListButton != null) - addListButton.active = expandMinimizeButton.active; - } - - @Override - public void setExpanded(boolean expanded) { - super.setExpanded(listOption.available() && expanded); - } - - @Override - public List children() { - return ImmutableList.of(expandMinimizeButton, addListButton, resetListButton); - } - } - - public class EmptyListLabel extends Entry { - private final ListGroupSeparatorEntry parent; - private final String groupName; - private final String categoryName; - - public EmptyListLabel(ListGroupSeparatorEntry parent, ConfigCategory category) { - this.parent = parent; - this.groupName = parent.group.name().getString().toLowerCase(); - this.categoryName = category.name().getString().toLowerCase(); - } - - @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - graphics.drawCenteredString(Minecraft.getInstance().font, Component.translatable("yacl.list.empty").withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC), x + entryWidth / 2, y, -1); - } - - @Override - public boolean isViewable() { - return parent.isExpanded() && (searchQuery.isEmpty() || groupName.contains(searchQuery)); - } - - @Override - public int getItemHeight() { - return 11; - } - - @Override - public List children() { - return ImmutableList.of(); - } - - @Override - public List narratables() { - return ImmutableList.of(); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java b/common/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java deleted file mode 100644 index 18b6033..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/RequireRestartScreen.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.ConfirmScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; - -public class RequireRestartScreen extends ConfirmScreen { - public RequireRestartScreen(Screen parent) { - super(option -> { - if (option) Minecraft.getInstance().stop(); - else Minecraft.getInstance().setScreen(parent); - }, - Component.translatable("yacl.restart.title").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), - Component.translatable("yacl.restart.message"), - Component.translatable("yacl.restart.yes"), - Component.translatable("yacl.restart.no") - ); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java b/common/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java deleted file mode 100644 index 24db7ab..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/SearchFieldWidget.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.network.chat.Component; - -import java.util.function.Consumer; - -public class SearchFieldWidget extends EditBox { - private Component emptyText; - private final YACLScreen yaclScreen; - private final Font font; - private final Consumer updateConsumer; - - private boolean isEmpty = true; - - public SearchFieldWidget(YACLScreen yaclScreen, Font font, int x, int y, int width, int height, Component text, Component emptyText, Consumer updateConsumer) { - super(font, x, y, width, height, text); - setResponder(this::update); - setFilter(string -> !string.endsWith(" ") && !string.startsWith(" ")); - this.yaclScreen = yaclScreen; - this.font = font; - this.emptyText = emptyText; - this.updateConsumer = updateConsumer; - } - - @Override - public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - super.renderWidget(graphics, mouseX, mouseY, delta); - if (isVisible() && isEmpty()) { - graphics.drawString(font, emptyText, getX() + 4, this.getY() + (this.height - 8) / 2, 0x707070, true); - } - } - - private void update(String query) { - boolean wasEmpty = isEmpty; - isEmpty = query.isEmpty(); - - if (isEmpty && wasEmpty) - return; - - updateConsumer.accept(query); - } - - public String getQuery() { - return getValue().toLowerCase(); - } - - public boolean isEmpty() { - return isEmpty; - } - - public Component getEmptyText() { - return emptyText; - } - - public void setEmptyText(Component emptyText) { - this.emptyText = emptyText; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java b/common/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java deleted file mode 100644 index 277e5f9..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/TextScaledButtonWidget.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.util.Mth; - -public class TextScaledButtonWidget extends TooltipButtonWidget { - public float textScale; - - public TextScaledButtonWidget(Screen screen, int x, int y, int width, int height, float textScale, Component message, Component tooltip, OnPress onPress) { - super(screen, x, y, width, height, message, tooltip, onPress); - this.textScale = textScale; - } - - public TextScaledButtonWidget(Screen screen, int x, int y, int width, int height, float textScale, Component message, OnPress onPress) { - this(screen, x, y, width, height, textScale, message, null, onPress); - } - - @Override - public void renderString(GuiGraphics graphics, Font textRenderer, int color) { - Font font = Minecraft.getInstance().font; - PoseStack pose = graphics.pose(); - - pose.pushPose(); - pose.translate(((this.getX() + this.width / 2f) - font.width(getMessage()) * textScale / 2), (float)this.getY() + (this.height - 8 * textScale) / 2f / textScale, 0); - pose.scale(textScale, textScale, 1); - graphics.drawString(font, getMessage(), 0, 0, color | Mth.ceil(this.alpha * 255.0F) << 24, true); - pose.popPose(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java b/common/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java deleted file mode 100644 index c948331..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/TooltipButtonWidget.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; - -public class TooltipButtonWidget extends Button { - - protected final Screen screen; - - public TooltipButtonWidget(Screen screen, int x, int y, int width, int height, Component message, Component tooltip, OnPress onPress) { - super(x, y, width, height, message, onPress, DEFAULT_NARRATION); - this.screen = screen; - if (tooltip != null) - setTooltip(Tooltip.create(tooltip)); - } - - @Override - protected @NotNull ClientTooltipPositioner createTooltipPositioner() { - return new YACLTooltipPositioner(this); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java deleted file mode 100644 index d8bbc13..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java +++ /dev/null @@ -1,358 +0,0 @@ -package dev.isxander.yacl.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; -import dev.isxander.yacl.api.utils.OptionUtils; -import dev.isxander.yacl.gui.tab.ScrollableNavigationBar; -import dev.isxander.yacl.gui.tab.ListHolderWidget; -import dev.isxander.yacl.gui.tab.TabExt; -import dev.isxander.yacl.gui.utils.GuiUtils; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -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.Tab; -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.renderer.GameRenderer; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -public class YACLScreen extends Screen { - public final YetAnotherConfigLib config; - - private final Screen parent; - - public final TabManager tabManager = new TabManager(this::addRenderableWidget, this::removeWidget); - public TabNavigationBar tabNavigationBar; - public Tab[] tabs; - public ScreenRectangle tabArea; - - public Component saveButtonMessage, saveButtonTooltipMessage; - private int saveButtonMessageTime; - - public YACLScreen(YetAnotherConfigLib config, Screen parent) { - super(config.title()); - this.config = config; - this.parent = parent; - } - - @Override - protected void init() { - tabNavigationBar = new ScrollableNavigationBar(this.width, tabManager, config.categories() - .stream() - .map(category -> { - if (category instanceof PlaceholderCategory placeholder) - return new PlaceholderTab(placeholder); - return new CategoryTab(category); - }).toList()); - tabNavigationBar.selectTab(0, false); - tabNavigationBar.arrangeElements(); - ScreenRectangle navBarArea = tabNavigationBar.getRectangle(); - tabArea = new ScreenRectangle(0, navBarArea.height() - 1, this.width, this.height - navBarArea.height() + 1); - tabManager.setTabArea(tabArea); - addRenderableWidget(tabNavigationBar); - - config.initConsumer().accept(this); - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - renderDirtBackground(graphics); - super.render(graphics, mouseX, mouseY, delta); - } - - protected void finishOrSave() { - saveButtonMessage = null; - - if (pendingChanges()) { - Set flags = new HashSet<>(); - OptionUtils.forEachOptions(config, option -> { - if (option.applyValue()) { - flags.addAll(option.flags()); - } - }); - OptionUtils.forEachOptions(config, option -> { - if (option.changed()) { - // if still changed after applying, reset to the current value from binding - // as something has gone wrong. - option.forgetPendingValue(); - YACLConstants.LOGGER.error("Option '{}' value mismatch after applying! Reset to binding's getter.", option.name().getString()); - } - }); - config.saveFunction().run(); - - flags.forEach(flag -> flag.accept(minecraft)); - } else onClose(); - } - - protected void cancelOrReset() { - if (pendingChanges()) { // if pending changes, button acts as a cancel button - OptionUtils.forEachOptions(config, Option::forgetPendingValue); - onClose(); - } else { // if not, button acts as a reset button - OptionUtils.forEachOptions(config, Option::requestSetDefault); - } - } - - protected void undo() { - OptionUtils.forEachOptions(config, Option::forgetPendingValue); - } - - @Override - public void tick() { - tabManager.tickCurrent(); - - if (saveButtonMessage != null) { - if (saveButtonMessageTime > 140) { - saveButtonMessage = null; - saveButtonTooltipMessage = null; - saveButtonMessageTime = 0; - } else { - saveButtonMessageTime++; - //finishedSaveButton.setMessage(saveButtonMessage); - if (saveButtonTooltipMessage != null) { - //finishedSaveButton.setTooltip(saveButtonTooltipMessage); - } - } - } - } - - private void setSaveButtonMessage(Component message, Component tooltip) { - saveButtonMessage = message; - saveButtonTooltipMessage = tooltip; - saveButtonMessageTime = 0; - } - - private boolean pendingChanges() { - AtomicBoolean pendingChanges = new AtomicBoolean(false); - OptionUtils.consumeOptions(config, (option) -> { - if (option.changed()) { - pendingChanges.set(true); - return true; - } - return false; - }); - - return pendingChanges.get(); - } - - @Override - public boolean shouldCloseOnEsc() { - if (pendingChanges()) { - setSaveButtonMessage(Component.translatable("yacl.gui.save_before_exit").withStyle(ChatFormatting.RED), Component.translatable("yacl.gui.save_before_exit.tooltip")); - return false; - } - return true; - } - - @Override - public void onClose() { - minecraft.setScreen(parent); - } - - public static void renderMultilineTooltip(GuiGraphics graphics, Font font, MultiLineLabel text, int centerX, int yAbove, int yBelow, int screenWidth, int screenHeight) { - if (text.getLineCount() > 0) { - int maxWidth = text.getWidth(); - int lineHeight = font.lineHeight + 1; - int height = text.getLineCount() * lineHeight - 1; - - int belowY = yBelow + 12; - int aboveY = yAbove - height + 12; - int maxBelow = screenHeight - (belowY + height); - int minAbove = aboveY - height; - int y = aboveY; - if (minAbove < 8) - y = maxBelow > minAbove ? belowY : aboveY; - - int x = Math.max(centerX - text.getWidth() / 2 - 12, -6); - - int drawX = x + 12; - int drawY = y - 12; - - graphics.pose().pushPose(); - Tesselator tesselator = Tesselator.getInstance(); - BufferBuilder bufferBuilder = tesselator.getBuilder(); - RenderSystem.setShader(GameRenderer::getPositionColorShader); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); - TooltipRenderUtil.renderTooltipBackground( - graphics, - drawX, - drawY, - maxWidth, - height, - 400 - ); - RenderSystem.enableDepthTest(); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - BufferUploader.drawWithShader(bufferBuilder.end()); - RenderSystem.disableBlend(); - graphics.pose().translate(0.0, 0.0, 400.0); - - text.renderLeftAligned(graphics, drawX, drawY, lineHeight, -1); - - graphics.pose().popPose(); - } - } - - public class CategoryTab implements TabExt { - private final ConfigCategory category; - private final Tooltip tooltip; - - private ListHolderWidget optionList; - private final Button saveFinishedButton; - private final Button cancelResetButton; - private final Button undoButton; - private final SearchFieldWidget searchField; - private OptionDescriptionWidget descriptionWidget; - - public CategoryTab(ConfigCategory category) { - this.category = category; - this.tooltip = Tooltip.create(category.tooltip()); - - int columnWidth = width / 3; - int padding = columnWidth / 20; - columnWidth = Math.min(columnWidth, 400); - int paddedWidth = columnWidth - padding * 2; - MutableDimension actionDim = Dimension.ofInt(width / 3 * 2 + width / 6, height - padding - 20, paddedWidth, 20); - - saveFinishedButton = Button.builder(Component.literal("Done"), btn -> finishOrSave()) - .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) - .size(actionDim.width(), actionDim.height()) - .build(); - - actionDim.expand(-actionDim.width() / 2 - 2, 0).move(-actionDim.width() / 2 - 2, -22); - cancelResetButton = Button.builder(Component.literal("Cancel"), btn -> cancelOrReset()) - .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) - .size(actionDim.width(), actionDim.height()) - .build(); - - actionDim.move(actionDim.width() + 4, 0); - undoButton = Button.builder(Component.translatable("yacl.gui.undo"), btn -> undo()) - .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) - .size(actionDim.width(), actionDim.height()) - .tooltip(Tooltip.create(Component.translatable("yacl.gui.undo.tooltip"))) - .build(); - - searchField = new SearchFieldWidget( - YACLScreen.this, - font, - width / 3 * 2 + width / 6 - paddedWidth / 2 + 1, - undoButton.getY() - 22, - paddedWidth - 2, 18, - Component.translatable("gui.recipebook.search_hint"), - Component.translatable("gui.recipebook.search_hint"), - searchQuery -> optionList.getList().updateSearchQuery(searchQuery) - ); - - this.optionList = new ListHolderWidget<>( - () -> new ScreenRectangle(tabArea.position(), tabArea.width() / 3 * 2 + 1, tabArea.height()), - new OptionListWidget(YACLScreen.this, category, minecraft, 0, 0, width / 3 * 2 + 1, height, desc -> { - descriptionWidget.setOptionDescription(desc); - }) - ); - - descriptionWidget = new OptionDescriptionWidget( - () -> new ScreenRectangle( - width / 3 * 2 + padding, - tabArea.top() + padding, - paddedWidth, - searchField.getY() - 1 - tabArea.top() - padding * 2 - ), - null - ); - - updateButtons(); - } - - @Override - public Component getTabTitle() { - return category.name(); - } - - @Override - public void visitChildren(Consumer consumer) { - consumer.accept(optionList); - consumer.accept(saveFinishedButton); - consumer.accept(cancelResetButton); - consumer.accept(undoButton); - consumer.accept(searchField); - consumer.accept(descriptionWidget); - } - - @Override - public void doLayout(ScreenRectangle screenRectangle) { - - } - - @Override - public void tick() { - updateButtons(); - searchField.tick(); - descriptionWidget.tick(); - } - - @Nullable - @Override - public Tooltip getTooltip() { - return tooltip; - } - - private void updateButtons() { - boolean pendingChanges = pendingChanges(); - - undoButton.active = pendingChanges; - saveFinishedButton.setMessage(pendingChanges ? Component.translatable("yacl.gui.save") : GuiUtils.translatableFallback("yacl.gui.done", CommonComponents.GUI_DONE)); - saveFinishedButton.setTooltip(Tooltip.create(pendingChanges ? Component.translatable("yacl.gui.save.tooltip") : Component.translatable("yacl.gui.finished.tooltip"))); - cancelResetButton.setMessage(pendingChanges ? GuiUtils.translatableFallback("yacl.gui.cancel", CommonComponents.GUI_CANCEL) : Component.translatable("controls.reset")); - cancelResetButton.setTooltip(Tooltip.create(pendingChanges ? Component.translatable("yacl.gui.cancel.tooltip") : Component.translatable("yacl.gui.reset.tooltip"))); - } - } - - public class PlaceholderTab implements TabExt { - private final PlaceholderCategory category; - private final Tooltip tooltip; - - public PlaceholderTab(PlaceholderCategory category) { - this.category = category; - this.tooltip = Tooltip.create(category.tooltip()); - } - - @Override - public Component getTabTitle() { - return category.name(); - } - - @Override - public void visitChildren(Consumer consumer) { - - } - - @Override - public void doLayout(ScreenRectangle screenRectangle) { - minecraft.setScreen(category.screen().apply(minecraft, YACLScreen.this)); - } - - @Override - public @Nullable Tooltip getTooltip() { - return this.tooltip; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/YACLTooltipPositioner.java b/common/src/main/java/dev/isxander/yacl/gui/YACLTooltipPositioner.java deleted file mode 100644 index d6e6220..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/YACLTooltipPositioner.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.isxander.yacl.gui; - -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; -import net.minecraft.util.Mth; -import org.joml.Vector2i; -import org.joml.Vector2ic; - -import java.util.function.Supplier; - -public class YACLTooltipPositioner implements ClientTooltipPositioner { - private final Supplier buttonDimensions; - - public YACLTooltipPositioner(net.minecraft.client.gui.components.AbstractWidget widget) { - this.buttonDimensions = widget::getRectangle; - } - - public YACLTooltipPositioner(dev.isxander.yacl.gui.AbstractWidget widget) { - this.buttonDimensions = () -> { - var dim = widget.getDimension(); - return new ScreenRectangle(dim.x(), dim.y(), dim.width(), dim.height()); - }; - } - - public YACLTooltipPositioner(Supplier buttonDimensions) { - this.buttonDimensions = buttonDimensions; - } - - @Override - public Vector2ic positionTooltip(int guiWidth, int guiHeight, int x, int y, int width, int height) { - ScreenRectangle buttonDimensions = this.buttonDimensions.get(); - - int centerX = buttonDimensions.left() + buttonDimensions.width() / 2; - int aboveY = buttonDimensions.top() - height - 4; - int belowY = buttonDimensions.top() + buttonDimensions.height() + 4; - - int maxBelow = guiHeight - (belowY + height); - int minAbove = aboveY - height; - - int yResult = aboveY; - if (minAbove < 8) - yResult = maxBelow > minAbove ? belowY : aboveY; - - int xResult = Mth.clamp(centerX - width / 2, -4, guiWidth - width - 4); - - return new Vector2i(xResult, yResult); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java deleted file mode 100644 index e57cdd2..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.mojang.blaze3d.platform.InputConstants; -import dev.isxander.yacl.api.ButtonOption; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.network.chat.Component; - -import java.util.function.BiConsumer; - -/** - * Simple controller that simply runs the button action on press - * and renders a {@link} Text on the right. - */ -public class ActionController implements Controller> { - public static final Component DEFAULT_TEXT = Component.translatable("yacl.control.action.execute"); - - private final ButtonOption option; - private final Component text; - - /** - * Constructs an action controller - * with the default formatter of {@link ActionController#DEFAULT_TEXT} - * - * @param option bound option - */ - public ActionController(ButtonOption option) { - this(option, DEFAULT_TEXT); - } - - /** - * Constructs an action controller - * - * @param option bound option - * @param text text to display - */ - public ActionController(ButtonOption option, Component text) { - this.option = option; - this.text = text; - - } - - /** - * {@inheritDoc} - */ - @Override - public ButtonOption option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Component formatValue() { - return text; - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new ActionControllerElement(this, screen, widgetDimension); - } - - public static class ActionControllerElement extends ControllerWidget { - private final String buttonString; - - public ActionControllerElement(ActionController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - buttonString = control.formatValue().getString().toLowerCase(); - } - - public void executeAction() { - playDownSound(); - control.option().action().accept(screen, control.option()); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (isMouseOver(mouseX, mouseY) && isAvailable()) { - executeAction(); - return true; - } - return false; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) { - return false; - } - - if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { - executeAction(); - return true; - } - - return false; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } - - @Override - public boolean canReset() { - return false; - } - - @Override - public boolean matchesSearch(String query) { - return super.matchesSearch(query) || buttonString.contains(query); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java deleted file mode 100644 index 1c395d6..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java +++ /dev/null @@ -1,157 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.mojang.blaze3d.platform.InputConstants; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * This controller renders a simple formatted {@link Component} - */ -public class BooleanController implements Controller { - - public static final Function ON_OFF_FORMATTER = (state) -> - state - ? CommonComponents.OPTION_ON - : CommonComponents.OPTION_OFF; - - public static final Function TRUE_FALSE_FORMATTER = (state) -> - state - ? Component.translatable("yacl.control.boolean.true") - : Component.translatable("yacl.control.boolean.false"); - - public static final Function YES_NO_FORMATTER = (state) -> - state - ? CommonComponents.GUI_YES - : CommonComponents.GUI_NO; - - private final Option option; - private final Function valueFormatter; - private final boolean coloured; - - /** - * Constructs a tickbox controller - * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} - * - * @param option bound option - */ - public BooleanController(Option option) { - this(option, ON_OFF_FORMATTER, false); - } - - /** - * Constructs a tickbox controller - * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} - * - * @param option bound option - * @param coloured value format is green or red depending on the state - */ - public BooleanController(Option option, boolean coloured) { - this(option, ON_OFF_FORMATTER, coloured); - } - - /** - * Constructs a tickbox controller - * - * @param option bound option - * @param valueFormatter format value into any {@link Component} - * @param coloured value format is green or red depending on the state - */ - public BooleanController(Option option, Function valueFormatter, boolean coloured) { - this.option = option; - this.valueFormatter = valueFormatter; - this.coloured = coloured; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Component formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * Value format is green or red depending on the state - */ - public boolean coloured() { - return coloured; - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new BooleanControllerElement(this, screen, widgetDimension); - } - - public static class BooleanControllerElement extends ControllerWidget { - public BooleanControllerElement(BooleanController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - @Override - protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || !isAvailable()) - return false; - - toggleSetting(); - return true; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } - - public void toggleSetting() { - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); - } - - @Override - protected Component getValueText() { - if (control.coloured()) { - return super.getValueText().copy().withStyle(control.option().pendingValue() ? ChatFormatting.GREEN : ChatFormatting.RED); - } - - return super.getValueText(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!isFocused()) { - return false; - } - - if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { - toggleSetting(); - return true; - } - - return false; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java deleted file mode 100644 index 4f71248..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/ColorController.java +++ /dev/null @@ -1,220 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.string.IStringController; -import dev.isxander.yacl.gui.controllers.string.StringControllerElement; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; - -import java.awt.*; -import java.util.List; - -/** - * A color controller that uses a hex color field as input. - */ -public class ColorController implements IStringController { - private final Option option; - private final boolean allowAlpha; - - /** - * Constructs a color controller with {@link ColorController#allowAlpha()} defaulting to false - * - * @param option bound option - */ - public ColorController(Option option) { - this(option, false); - } - - /** - * Constructs a color controller - * - * @param option bound option - * @param allowAlpha allows the color input to accept alpha values - */ - public ColorController(Option option, boolean allowAlpha) { - this.option = option; - this.allowAlpha = allowAlpha; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - public boolean allowAlpha() { - return allowAlpha; - } - - @Override - public String getString() { - return formatValue().getString(); - } - - @Override - public Component formatValue() { - MutableComponent text = Component.literal("#"); - text.append(Component.literal(toHex(option().pendingValue().getRed())).withStyle(ChatFormatting.RED)); - text.append(Component.literal(toHex(option().pendingValue().getGreen())).withStyle(ChatFormatting.GREEN)); - text.append(Component.literal(toHex(option().pendingValue().getBlue())).withStyle(ChatFormatting.BLUE)); - if (allowAlpha()) text.append(toHex(option().pendingValue().getAlpha())); - return text; - } - - private String toHex(int value) { - String hex = Integer.toString(value, 16).toUpperCase(); - if (hex.length() == 1) - hex = "0" + hex; - return hex; - } - - @Override - public void setFromString(String value) { - if (value.startsWith("#")) - value = value.substring(1); - - int red = Integer.parseInt(value.substring(0, 2), 16); - int green = Integer.parseInt(value.substring(2, 4), 16); - int blue = Integer.parseInt(value.substring(4, 6), 16); - - if (allowAlpha()) { - int alpha = Integer.parseInt(value.substring(6, 8), 16); - option().requestSet(new Color(red, green, blue, alpha)); - } else { - option().requestSet(new Color(red, green, blue)); - } - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new ColorControllerElement(this, screen, widgetDimension); - } - - public static class ColorControllerElement extends StringControllerElement { - private final ColorController colorController; - - protected MutableDimension colorPreviewDim; - - private final List allowedChars; - - public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim, true); - this.colorController = control; - this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); - } - - @Override - protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - if (isHovered()) { - colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); - super.drawValueText(graphics, mouseX, mouseY, delta); - } - - graphics.fill(colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); - drawOutline(graphics, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); - } - - @Override - public void write(String string) { - if (string.startsWith("0x")) string = string.substring(2); - for (char chr : string.toCharArray()) { - if (!allowedChars.contains(Character.toLowerCase(chr))) { - return; - } - } - - if (caretPos == 0) - return; - - String trimmed = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); - - if (modifyInput(builder -> builder.replace(caretPos, caretPos + trimmed.length(), trimmed))) { - caretPos += trimmed.length(); - setSelectionLength(); - updateControl(); - } - } - - @Override - protected void doBackspace() { - if (caretPos > 1) { - if (modifyInput(builder -> builder.setCharAt(caretPos - 1, '0'))) { - caretPos--; - updateControl(); - } - } - } - - @Override - protected void doDelete() { - if (caretPos >= 1) { - if (modifyInput(builder -> builder.setCharAt(caretPos, '0'))) { - updateControl(); - } - } - } - - @Override - protected boolean doCut() { - return false; - } - - @Override - protected boolean doCopy() { - return false; - } - - @Override - protected boolean doSelectAll() { - return false; - } - - protected void setSelectionLength() { - selectionLength = caretPos < inputField.length() && caretPos > 0 ? 1 : 0; - } - - @Override - protected int getDefaultCaretPos() { - return colorController.allowAlpha() ? 3 : 1; - } - - @Override - public void setDimension(Dimension dim) { - super.setDimension(dim); - - int previewSize = (dim.height() - getYPadding() * 2) / 2; - colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - int prevSelectionLength = selectionLength; - selectionLength = 0; - if (super.keyPressed(keyCode, scanCode, modifiers)) { - caretPos = Math.max(1, caretPos); - setSelectionLength(); - return true; - } else selectionLength = prevSelectionLength; - return false; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (super.mouseClicked(mouseX, mouseY, button)) { - caretPos = Math.max(1, caretPos); - setSelectionLength(); - return true; - } - return false; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java deleted file mode 100644 index a277ad4..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java +++ /dev/null @@ -1,157 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.utils.GuiUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.narration.NarratedElementType; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; - -public abstract class ControllerWidget> extends AbstractWidget { - protected final T control; - protected MultiLineLabel wrappedTooltip; - protected final YACLScreen screen; - - protected boolean focused = false; - protected boolean hovered = false; - - protected final Component modifiedOptionName; - protected final String optionNameString; - - public ControllerWidget(T control, YACLScreen screen, Dimension dim) { - super(dim); - this.control = control; - this.screen = screen; - control.option().addListener((opt, pending) -> updateTooltip()); - updateTooltip(); - this.modifiedOptionName = control.option().name().copy().withStyle(ChatFormatting.ITALIC); - this.optionNameString = control.option().name().getString().toLowerCase(); - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - hovered = isMouseOver(mouseX, mouseY); - - Component name = control.option().changed() ? modifiedOptionName : control.option().name(); - Component shortenedName = Component.literal(GuiUtils.shortenString(name.getString(), textRenderer, getDimension().width() - getControlWidth() - getXPadding() - 7, "...")).setStyle(name.getStyle()); - - drawButtonRect(graphics, getDimension().x(), getDimension().y(), getDimension().xLimit(), getDimension().yLimit(), isHovered(), isAvailable()); - graphics.drawString(textRenderer, shortenedName, getDimension().x() + getXPadding(), getTextY(), getValueColor(), true); - - - drawValueText(graphics, mouseX, mouseY, delta); - if (isHovered()) { - drawHoveredControl(graphics, mouseX, mouseY, delta); - } - } - - protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - - } - - protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - Component valueText = getValueText(); - graphics.drawString(textRenderer, valueText, getDimension().xLimit() - textRenderer.width(valueText) - getXPadding(), getTextY(), getValueColor(), true); - } - - private void updateTooltip() { - this.wrappedTooltip = MultiLineLabel.create(textRenderer, control.option().tooltip(), screen.width / 3 * 2 - 10); - } - - protected int getControlWidth() { - return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); - } - - public boolean isHovered() { - return isAvailable() && (hovered || focused); - } - - protected abstract int getHoveredControlWidth(); - - protected int getUnhoveredControlWidth() { - return textRenderer.width(getValueText()); - } - - protected int getXPadding() { - return 5; - } - - protected int getYPadding() { - return 2; - } - - protected Component getValueText() { - return control.formatValue(); - } - - protected boolean isAvailable() { - return control.option().available(); - } - - protected int getValueColor() { - return isAvailable() ? -1 : inactiveColor; - } - - @Override - public boolean canReset() { - return true; - } - - protected void drawOutline(GuiGraphics graphics, int x1, int y1, int x2, int y2, int width, int color) { - graphics.fill(x1, y1, x2, y1 + width, color); - graphics.fill(x2, y1, x2 - width, y2, color); - graphics.fill(x1, y2, x2, y2 - width, color); - graphics.fill(x1, y1, x1 + width, y2, color); - } - - protected int getTextY() { - return (int)(getDimension().y() + getDimension().height() / 2f - textRenderer.lineHeight / 2f); - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent focusNavigationEvent) { - if (!this.isAvailable()) - return null; - return !this.isFocused() ? ComponentPath.leaf(this) : null; - } - - @Override - public boolean isFocused() { - return focused; - } - - @Override - public void setFocused(boolean focused) { - this.focused = focused; - } - - @Override - public void unfocus() { - this.focused = false; - } - - @Override - public boolean matchesSearch(String query) { - return optionNameString.contains(query.toLowerCase()); - } - - @Override - public NarrationPriority narrationPriority() { - return focused ? NarrationPriority.FOCUSED : isHovered() ? NarrationPriority.HOVERED : NarrationPriority.NONE; - } - - @Override - public void updateNarration(NarrationElementOutput builder) { - builder.add(NarratedElementType.TITLE, control.option().name()); - builder.add(NarratedElementType.HINT, control.option().tooltip()); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java deleted file mode 100644 index a3d385c..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java +++ /dev/null @@ -1,194 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.mojang.blaze3d.vertex.PoseStack; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.narration.NarratedElementType; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.HoverEvent; -import net.minecraft.network.chat.Style; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * Simply renders some text as a label. - */ -public class LabelController implements Controller { - private final Option option; - /** - * Constructs a label controller - * - * @param option bound option - */ - public LabelController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - @Override - public Component formatValue() { - return option().pendingValue(); - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new LabelControllerElement(screen, widgetDimension); - } - - public class LabelControllerElement extends AbstractWidget { - private List wrappedText; - protected MultiLineLabel wrappedTooltip; - protected boolean focused; - - protected final YACLScreen screen; - - public LabelControllerElement(YACLScreen screen, Dimension dim) { - super(dim); - this.screen = screen; - option().addListener((opt, pending) -> updateTooltip()); - updateTooltip(); - updateText(); - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - updateText(); - - int y = getDimension().y(); - for (FormattedCharSequence text : wrappedText) { - graphics.drawString(textRenderer, text, getDimension().x() + getXPadding(), y + getYPadding(), option().available() ? -1 : 0xFFA0A0A0, true); - y += textRenderer.lineHeight; - } - - if (isFocused()) { - graphics.fill(getDimension().x() - 1, getDimension().y() - 1, getDimension().xLimit() + 1, getDimension().y(), -1); - graphics.fill(getDimension().x() - 1, getDimension().y() - 1, getDimension().x(), getDimension().yLimit() + 1, -1); - graphics.fill(getDimension().x() - 1, getDimension().yLimit(), getDimension().xLimit() + 1, getDimension().yLimit() + 1, -1); - graphics.fill(getDimension().xLimit(), getDimension().y() - 1, getDimension().xLimit() + 1, getDimension().yLimit() + 1, -1); - } - - 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(); - HoverEvent.ItemStackInfo itemStackContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ITEM); - if (itemStackContent != null) { - ItemStack stack = itemStackContent.getItemStack(); - graphics.renderTooltip(textRenderer, Screen.getTooltipFromItem(client, stack), stack.getTooltipImage(), mouseX, mouseY); - } else { - HoverEvent.EntityTooltipInfo entityContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ENTITY); - if (entityContent != null) { - if (this.client.options.advancedItemTooltips) { - graphics.renderComponentTooltip(textRenderer, entityContent.getTooltipLines(), mouseX, mouseY); - } - } else { - Component text = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT); - if (text != null) { - MultiLineLabel multilineText = MultiLineLabel.create(textRenderer, text, getDimension().width()); - YACLScreen.renderMultilineTooltip(graphics, textRenderer, multilineText, getDimension().centerX(), getDimension().y(), getDimension().yLimit(), screen.width, screen.height); - } - } - } - } - } - graphics.pose().popPose(); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY)) - return false; - - Style style = getStyle((int) mouseX, (int) mouseY); - return screen.handleComponentClicked(style); - } - - protected Style getStyle(int mouseX, int mouseY) { - if (!getDimension().isPointInside(mouseX, mouseY)) - return null; - - int x = mouseX - getDimension().x(); - int y = mouseY - getDimension().y() - getYPadding(); - int line = y / textRenderer.lineHeight; - - if (x < 0 || x > getDimension().xLimit()) return null; - if (y < 0 || y > getDimension().yLimit()) return null; - if (line < 0 || line >= wrappedText.size()) return null; - - return textRenderer.getSplitter().componentStyleAtWidth(wrappedText.get(line), x); - } - - private int getXPadding() { - return 4; - } - - private int getYPadding() { - return 3; - } - - private void updateText() { - wrappedText = textRenderer.split(formatValue(), getDimension().width() - getXPadding() * 2); - setDimension(getDimension().withHeight(wrappedText.size() * textRenderer.lineHeight + getYPadding() * 2)); - } - - private void updateTooltip() { - this.wrappedTooltip = MultiLineLabel.create(textRenderer, option().tooltip(), screen.width / 3 * 2 - 10); - } - - @Override - public boolean matchesSearch(String query) { - return formatValue().getString().toLowerCase().contains(query.toLowerCase()); - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent focusNavigationEvent) { - if (!option().available()) - return null; - return !this.isFocused() ? ComponentPath.leaf(this) : null; - } - - @Override - public boolean isFocused() { - return focused; - } - - @Override - public void setFocused(boolean focused) { - this.focused = focused; - } - - @Override - public void updateNarration(NarrationElementOutput builder) { - builder.add(NarratedElementType.TITLE, formatValue()); - } - - @Override - public NarrationPriority narrationPriority() { - return NarrationPriority.FOCUSED; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java deleted file mode 100644 index af60c08..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.google.common.collect.ImmutableList; -import com.mojang.blaze3d.vertex.PoseStack; -import dev.isxander.yacl.api.ListOption; -import dev.isxander.yacl.api.ListOptionEntry; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.TooltipButtonWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.events.ContainerEventHandler; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public class ListEntryWidget extends AbstractWidget implements ContainerEventHandler { - private final TooltipButtonWidget removeButton, moveUpButton, moveDownButton; - private final AbstractWidget entryWidget; - - private final ListOption listOption; - private final ListOptionEntry listOptionEntry; - - private final String optionNameString; - - private GuiEventListener focused; - private boolean dragging; - - public ListEntryWidget(YACLScreen screen, ListOptionEntry listOptionEntry, AbstractWidget entryWidget) { - super(entryWidget.getDimension().withHeight(Math.max(entryWidget.getDimension().height(), 20) - ((listOptionEntry.parentGroup().indexOf(listOptionEntry) == listOptionEntry.parentGroup().options().size() - 1) ? 0 : 2))); // -2 to remove the padding - this.listOptionEntry = listOptionEntry; - this.listOption = listOptionEntry.parentGroup(); - this.optionNameString = listOptionEntry.name().getString().toLowerCase(); - this.entryWidget = entryWidget; - - Dimension dim = entryWidget.getDimension(); - entryWidget.setDimension(dim.clone().move(20 * 2, 0).expand(-20 * 3, 0)); - - removeButton = new TooltipButtonWidget(screen, dim.xLimit() - 20, dim.y(), 20, 20, Component.literal("\u274c"), Component.translatable("yacl.list.remove"), btn -> { - listOption.removeEntry(listOptionEntry); - updateButtonStates(); - }); - - moveUpButton = new TooltipButtonWidget(screen, dim.x(), dim.y(), 20, 20, Component.literal("\u2191"), Component.translatable("yacl.list.move_up"), btn -> { - int index = listOption.indexOf(listOptionEntry) - 1; - if (index >= 0) { - listOption.removeEntry(listOptionEntry); - listOption.insertEntry(index, listOptionEntry); - updateButtonStates(); - } - }); - - moveDownButton = new TooltipButtonWidget(screen, dim.x() + 20, dim.y(), 20, 20, Component.literal("\u2193"), Component.translatable("yacl.list.move_down"), btn -> { - int index = listOption.indexOf(listOptionEntry) + 1; - if (index < listOption.options().size()) { - listOption.removeEntry(listOptionEntry); - listOption.insertEntry(index, listOptionEntry); - updateButtonStates(); - } - }); - - updateButtonStates(); - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - updateButtonStates(); // update every render in case option becomes available/unavailable - - removeButton.setY(getDimension().y()); - moveUpButton.setY(getDimension().y()); - moveDownButton.setY(getDimension().y()); - entryWidget.setDimension(entryWidget.getDimension().withY(getDimension().y())); - - removeButton.render(graphics, mouseX, mouseY, delta); - moveUpButton.render(graphics, mouseX, mouseY, delta); - moveDownButton.render(graphics, mouseX, mouseY, delta); - entryWidget.render(graphics, mouseX, mouseY, delta); - } - - protected void updateButtonStates() { - removeButton.active = listOption.available(); - moveUpButton.active = listOption.indexOf(listOptionEntry) > 0 && listOption.available(); - moveDownButton.active = listOption.indexOf(listOptionEntry) < listOption.options().size() - 1 && listOption.available(); - } - - @Override - public void unfocus() { - entryWidget.unfocus(); - } - - @Override - public void updateNarration(NarrationElementOutput builder) { - entryWidget.updateNarration(builder); - } - - @Override - public boolean matchesSearch(String query) { - return optionNameString.contains(query.toLowerCase()); - } - - @Override - public List children() { - return ImmutableList.of(moveUpButton, moveDownButton, entryWidget, removeButton); - } - - @Override - public boolean isDragging() { - return dragging; - } - - @Override - public void setDragging(boolean dragging) { - this.dragging = dragging; - } - - @Nullable - @Override - public GuiEventListener getFocused() { - return focused; - } - - @Override - public void setFocused(@Nullable GuiEventListener focused) { - this.focused = focused; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java deleted file mode 100644 index 31200b6..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.isxander.yacl.gui.controllers; - -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.vertex.PoseStack; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.network.chat.Component; - -/** - * This controller renders a tickbox - */ -public class TickBoxController implements Controller { - private final Option option; - - /** - * Constructs a tickbox controller - * - * @param option bound option - */ - public TickBoxController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Component formatValue() { - return Component.empty(); - } - - /** - * {@inheritDoc} - */ - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new TickBoxControllerElement(this, screen, widgetDimension); - } - - public static class TickBoxControllerElement extends ControllerWidget { - public TickBoxControllerElement(TickBoxController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - @Override - protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - int outlineSize = 10; - int outlineX1 = getDimension().xLimit() - getXPadding() - outlineSize; - int outlineY1 = getDimension().centerY() - outlineSize / 2; - int outlineX2 = getDimension().xLimit() - getXPadding(); - int outlineY2 = getDimension().centerY() + outlineSize / 2; - - int color = getValueColor(); - int shadowColor = multiplyColor(color, 0.25f); - - drawOutline(graphics, outlineX1 + 1, outlineY1 + 1, outlineX2 + 1, outlineY2 + 1, 1, shadowColor); - drawOutline(graphics, outlineX1, outlineY1, outlineX2, outlineY2, 1, color); - if (control.option().pendingValue()) { - graphics.fill(outlineX1 + 3, outlineY1 + 3, outlineX2 - 1, outlineY2 - 1, shadowColor); - graphics.fill(outlineX1 + 2, outlineY1 + 2, outlineX2 - 2, outlineY2 - 2, color); - } - } - - @Override - protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - if (!isHovered()) - drawHoveredControl(graphics, mouseX, mouseY, delta); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || !isAvailable()) - return false; - - toggleSetting(); - return true; - } - - @Override - protected int getHoveredControlWidth() { - return 10; - } - - @Override - protected int getUnhoveredControlWidth() { - return 10; - } - - public void toggleSetting() { - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) { - return false; - } - - if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { - toggleSetting(); - return true; - } - - return false; - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java deleted file mode 100644 index ff20039..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import com.mojang.blaze3d.platform.InputConstants; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ControllerWidget; -import net.minecraft.client.gui.screens.Screen; - -public class CyclingControllerElement extends ControllerWidget> { - - public CyclingControllerElement(ICyclingController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); - } - - public void cycleValue(int increment) { - int targetIdx = control.getPendingValue() + increment; - if (targetIdx >= control.getCycleLength()) { - targetIdx -= control.getCycleLength(); - } else if (targetIdx < 0) { - targetIdx += control.getCycleLength(); - } - control.setPendingValue(targetIdx); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!isMouseOver(mouseX, mouseY) || (button != 0 && button != 1) || !isAvailable()) - return false; - - playDownSound(); - cycleValue(button == 1 || Screen.hasShiftDown() || Screen.hasControlDown() ? -1 : 1); - - return true; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!focused) - return false; - - switch (keyCode) { - case InputConstants.KEY_LEFT -> - cycleValue(-1); - case InputConstants.KEY_RIGHT -> - cycleValue(1); - case InputConstants.KEY_RETURN, InputConstants.KEY_SPACE, InputConstants.KEY_NUMPADENTER -> - cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); - default -> { - return false; - } - } - - return true; - } - - @Override - protected int getHoveredControlWidth() { - return getUnhoveredControlWidth(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java deleted file mode 100644 index 43fa766..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java +++ /dev/null @@ -1,79 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Option; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * A controller where once clicked, cycles through elements - * in the provided list. - */ -public class CyclingListController implements ICyclingController { - private final Option option; - private final Function valueFormatter; - private final ImmutableList values; - - /** - * Constructs a {@link CyclingListController}, with a default - * value formatter of {@link Object#toString()}. - * @param option option of which to bind the controller to - * @param values the values to cycle through - */ - public CyclingListController(Option option, Iterable values) { - this(option, values, value -> Component.literal(value.toString())); - } - - /** - * Constructs a {@link CyclingListController} - * @param option option of which to bind the controller to - * @param values the values to cycle through - * @param valueFormatter function of how to convert each value to a string to display - */ - public CyclingListController(Option option, Iterable values, Function valueFormatter) { - this.option = option; - this.valueFormatter = valueFormatter; - this.values = ImmutableList.copyOf(values); - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - /** - * {@inheritDoc} - */ - @Override - public Component formatValue() { - return valueFormatter.apply(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(int ordinal) { - option().requestSet(values.get(ordinal)); - } - - /** - * {@inheritDoc} - */ - @Override - public int getPendingValue() { - return values.indexOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public int getCycleLength() { - return values.size(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java deleted file mode 100644 index 281f182..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.gui.controllers.cycling; - -import dev.isxander.yacl.api.NameableEnum; -import dev.isxander.yacl.api.Option; -import net.minecraft.network.chat.Component; -import net.minecraft.util.OptionEnum; - -import java.util.Arrays; -import java.util.function.Function; - -/** - * Simple controller type that displays the enum on the right. - *

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

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

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

    - *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.slider.DoubleSliderController}
  • - *
  • For floats: {@link dev.isxander.yacl.gui.controllers.slider.FloatSliderController}
  • - *
  • For integers: {@link dev.isxander.yacl.gui.controllers.slider.IntegerSliderController}
  • - *
  • For longs: {@link dev.isxander.yacl.gui.controllers.slider.LongSliderController}
  • - *
- */ -package dev.isxander.yacl.gui.controllers.slider; diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java deleted file mode 100644 index 6a603d2..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.network.chat.Component; - -/** - * A controller that can be any type but can input and output a string. - */ -public interface IStringController extends Controller { - /** - * Gets the option's pending value as a string. - * - * @see Option#pendingValue() - */ - String getString(); - - /** - * Sets the option's pending value from a string. - * - * @see Option#requestSet(Object) - */ - void setFromString(String value); - - /** - * {@inheritDoc} - */ - @Override - default Component formatValue() { - return Component.literal(getString()); - } - - default boolean isInputValid(String input) { - return true; - } - - @Override - default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new StringControllerElement(this, screen, widgetDimension, true); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java deleted file mode 100644 index 85029b9..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringController.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import dev.isxander.yacl.api.Option; - -/** - * A custom text field implementation for strings. - */ -public class StringController implements IStringController { - private final Option option; - - /** - * Constructs a string controller - * - * @param option bound option - */ - public StringController(Option option) { - this.option = option; - } - - /** - * {@inheritDoc} - */ - @Override - public Option option() { - return option; - } - - @Override - public String getString() { - return option().pendingValue(); - } - - @Override - public void setFromString(String value) { - option().requestSet(value); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java deleted file mode 100644 index 7dc81fe..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java +++ /dev/null @@ -1,404 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string; - -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.systems.RenderSystem; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ControllerWidget; -import dev.isxander.yacl.gui.utils.GuiUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; - -import java.util.function.Consumer; - -public class StringControllerElement extends ControllerWidget> { - protected final boolean instantApply; - - protected String inputField; - protected Dimension inputFieldBounds; - protected boolean inputFieldFocused; - - protected int caretPos; - protected int selectionLength; - - protected int renderOffset; - - protected float ticks; - - private final Component emptyText; - - public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim, boolean instantApply) { - super(control, screen, dim); - this.instantApply = instantApply; - inputField = control.getString(); - inputFieldFocused = false; - selectionLength = 0; - emptyText = Component.literal("Click to type...").withStyle(ChatFormatting.GRAY); - control.option().addListener((opt, val) -> inputField = control.getString()); - setDimension(dim); - } - - @Override - protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - - } - - @Override - protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - Component valueText = getValueText(); - if (!isHovered()) valueText = Component.literal(GuiUtils.shortenString(valueText.getString(), textRenderer, getMaxUnwrapLength(), "...")).setStyle(valueText.getStyle()); - - int textX = getDimension().xLimit() - textRenderer.width(valueText) + renderOffset - getXPadding(); - graphics.enableScissor(inputFieldBounds.x(), inputFieldBounds.y() - 2, inputFieldBounds.width() + 1, inputFieldBounds.height() + 4); - graphics.drawString(textRenderer, valueText, textX, getTextY(), getValueColor(), true); - - if (isHovered()) { - ticks += delta; - - String text = getValueText().getString(); - - graphics.fill(inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1); - graphics.fill(inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040); - - if (inputFieldFocused || focused) { - if (caretPos > text.length()) - caretPos = text.length(); - - int caretX = textX + textRenderer.width(text.substring(0, caretPos)) - 1; - if (text.isEmpty()) - caretX = inputFieldBounds.x() + inputFieldBounds.width() / 2; - - if (ticks % 20 <= 10) { - graphics.fill(caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1); - } - - if (selectionLength != 0) { - int selectionX = textX + textRenderer.width(text.substring(0, caretPos + selectionLength)); - graphics.fill(caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF); - } - } - } - graphics.disableScissor(); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (isAvailable() && getDimension().isPointInside((int) mouseX, (int) mouseY)) { - inputFieldFocused = true; - - if (!inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) { - caretPos = getDefaultCaretPos(); - } else { - // gets the appropriate caret position for where you click - int textX = (int) mouseX - (inputFieldBounds.xLimit() - textRenderer.width(getValueText())); - int pos = -1; - int currentWidth = 0; - for (char ch : inputField.toCharArray()) { - pos++; - int charLength = textRenderer.width(String.valueOf(ch)); - if (currentWidth + charLength / 2 > textX) { // if more than halfway past the characters select in front of that char - caretPos = pos; - break; - } else if (pos == inputField.length() - 1) { - // if we have reached the end and no matches, it must be the second half of the char so the last position - caretPos = pos + 1; - } - currentWidth += charLength; - } - - selectionLength = 0; - } - return true; - } else { - inputFieldFocused = false; - } - - return false; - } - - protected int getDefaultCaretPos() { - return inputField.length(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!inputFieldFocused) - return false; - - switch (keyCode) { - case InputConstants.KEY_ESCAPE, InputConstants.KEY_RETURN -> { - unfocus(); - return true; - } - case InputConstants.KEY_LEFT -> { - if (Screen.hasShiftDown()) { - if (Screen.hasControlDown()) { - int spaceChar = findSpaceIndex(true); - selectionLength += caretPos - spaceChar; - caretPos = spaceChar; - } else if (caretPos > 0) { - caretPos--; - selectionLength += 1; - } - checkRenderOffset(); - } else { - if (caretPos > 0) { - if (selectionLength != 0) - caretPos += Math.min(selectionLength, 0); - else - caretPos--; - } - checkRenderOffset(); - selectionLength = 0; - } - - return true; - } - case InputConstants.KEY_RIGHT -> { - if (Screen.hasShiftDown()) { - if (Screen.hasControlDown()) { - int spaceChar = findSpaceIndex(false); - selectionLength -= spaceChar - caretPos; - caretPos = spaceChar; - } else if (caretPos < inputField.length()) { - caretPos++; - selectionLength -= 1; - } - checkRenderOffset(); - } else { - if (caretPos < inputField.length()) { - if (selectionLength != 0) - caretPos += Math.max(selectionLength, 0); - else - caretPos++; - checkRenderOffset(); - } - selectionLength = 0; - } - - return true; - } - case InputConstants.KEY_BACKSPACE -> { - doBackspace(); - return true; - } - case InputConstants.KEY_DELETE -> { - doDelete(); - return true; - } - } - - if (Screen.isPaste(keyCode)) { - return doPaste(); - } else if (Screen.isCopy(keyCode)) { - return doCopy(); - } else if (Screen.isCut(keyCode)) { - return doCut(); - } else if (Screen.isSelectAll(keyCode)) { - return doSelectAll(); - } - - return false; - } - - protected boolean doPaste() { - this.write(client.keyboardHandler.getClipboard()); - return true; - } - - protected boolean doCopy() { - if (selectionLength != 0) { - client.keyboardHandler.setClipboard(getSelection()); - return true; - } - return false; - } - - protected boolean doCut() { - if (selectionLength != 0) { - client.keyboardHandler.setClipboard(getSelection()); - this.write(""); - return true; - } - return false; - } - - protected boolean doSelectAll() { - caretPos = inputField.length(); - checkRenderOffset(); - selectionLength = -caretPos; - return true; - } - - protected void checkRenderOffset() { - if (textRenderer.width(inputField) < getUnshiftedLength()) { - renderOffset = 0; - return; - } - - int textX = getDimension().xLimit() - textRenderer.width(inputField) - getXPadding(); - int caretX = textX + textRenderer.width(inputField.substring(0, caretPos)) - 1; - - int minX = getDimension().xLimit() - getXPadding() - getUnshiftedLength(); - int maxX = minX + getUnshiftedLength(); - - if (caretX + renderOffset < minX) { - renderOffset = minX - caretX; - } else if (caretX + renderOffset > maxX) { - renderOffset = maxX - caretX; - } - } - - @Override - public boolean charTyped(char chr, int modifiers) { - if (!inputFieldFocused) - return false; - - write(Character.toString(chr)); - - return true; - } - - protected void doBackspace() { - if (selectionLength != 0) { - write(""); - } else if (caretPos > 0) { - if (modifyInput(builder -> builder.deleteCharAt(caretPos - 1))) { - caretPos--; - checkRenderOffset(); - } - } - } - - protected void doDelete() { - if (selectionLength != 0) { - write(""); - } else if (caretPos < inputField.length()) { - modifyInput(builder -> builder.deleteCharAt(caretPos)); - } - } - - public void write(String string) { - if (selectionLength == 0) { - if (modifyInput(builder -> builder.insert(caretPos, string))) { - caretPos += string.length(); - checkRenderOffset(); - } - } else { - int start = getSelectionStart(); - int end = getSelectionEnd(); - - if (modifyInput(builder -> builder.replace(start, end, string))) { - caretPos = start + string.length(); - selectionLength = 0; - checkRenderOffset(); - } - } - } - - public boolean modifyInput(Consumer consumer) { - StringBuilder temp = new StringBuilder(inputField); - consumer.accept(temp); - if (!control.isInputValid(temp.toString())) - return false; - inputField = temp.toString(); - if (instantApply) - updateControl(); - return true; - } - - public int getUnshiftedLength() { - if (optionNameString.isEmpty()) - return getDimension().width() - getXPadding() * 2; - return getDimension().width() / 8 * 5; - } - - public int getMaxUnwrapLength() { - if (optionNameString.isEmpty()) - return getDimension().width() - getXPadding() * 2; - return getDimension().width() / 2; - } - - public int getSelectionStart() { - return Math.min(caretPos, caretPos + selectionLength); - } - - public int getSelectionEnd() { - return Math.max(caretPos, caretPos + selectionLength); - } - - protected String getSelection() { - return inputField.substring(getSelectionStart(), getSelectionEnd()); - } - - protected int findSpaceIndex(boolean reverse) { - int i; - int fromIndex = caretPos; - if (reverse) { - if (caretPos > 0) - fromIndex -= 1; - i = this.inputField.lastIndexOf(" ", fromIndex); - - if (i == -1) i = 0; - } else { - if (caretPos < inputField.length()) - fromIndex += 1; - i = this.inputField.indexOf(" ", fromIndex); - - if (i == -1) i = inputField.length(); - } - - return i; - } - - @Override - public void setFocused(boolean focused) { - super.setFocused(focused); - inputFieldFocused = focused; - } - - @Override - public void unfocus() { - super.unfocus(); - inputFieldFocused = false; - renderOffset = 0; - if (!instantApply) updateControl(); - } - - @Override - public void setDimension(Dimension dim) { - super.setDimension(dim); - - int width = Math.max(6, Math.min(textRenderer.width(getValueText()), getUnshiftedLength())); - inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.lineHeight / 2, width, textRenderer.lineHeight); - } - - @Override - public boolean isHovered() { - return super.isHovered() || inputFieldFocused; - } - - protected void updateControl() { - control.setFromString(inputField); - } - - @Override - protected int getUnhoveredControlWidth() { - return !isHovered() ? Math.min(getHoveredControlWidth(), getMaxUnwrapLength()) : getHoveredControlWidth(); - } - - @Override - protected int getHoveredControlWidth() { - return Math.min(textRenderer.width(getValueText()), getUnshiftedLength()); - } - - @Override - protected Component getValueText() { - if (!inputFieldFocused && inputField.isEmpty()) - return emptyText; - - return instantApply || !inputFieldFocused ? control.formatValue() : Component.literal(inputField); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java deleted file mode 100644 index df28241..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java +++ /dev/null @@ -1,104 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string.number; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * {@inheritDoc} - */ -public class DoubleFieldController extends NumberFieldController { - private final double min, max; - - /** - * Constructs a double field controller - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - * @param formatter display text, not used whilst editing - */ - public DoubleFieldController(Option option, double min, double max, Function formatter) { - super(option, formatter); - this.min = min; - this.max = max; - } - - /** - * Constructs a double field controller. - * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - */ - public DoubleFieldController(Option option, double min, double max) { - this(option, min, max, DoubleSliderController.DEFAULT_FORMATTER); - } - - /** - * Constructs a double field controller. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - * @param formatter display text, not used whilst editing - */ - public DoubleFieldController(Option option, Function formatter) { - this(option, -Double.MAX_VALUE, Double.MAX_VALUE, formatter); - } - - /** - * Constructs a double field controller. - * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - */ - public DoubleFieldController(Option option) { - this(option, -Double.MAX_VALUE, Double.MAX_VALUE, DoubleSliderController.DEFAULT_FORMATTER); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return this.min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return this.max; - } - - /** - * {@inheritDoc} - */ - @Override - public String getString() { - return String.valueOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet(value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java deleted file mode 100644 index 08084e6..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java +++ /dev/null @@ -1,104 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string.number; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * {@inheritDoc} - */ -public class FloatFieldController extends NumberFieldController { - private final float min, max; - - /** - * Constructs a float field controller - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - * @param formatter display text, not used whilst editing - */ - public FloatFieldController(Option option, float min, float max, Function formatter) { - super(option, formatter); - this.min = min; - this.max = max; - } - - /** - * Constructs a float field controller. - * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - */ - public FloatFieldController(Option option, float min, float max) { - this(option, min, max, FloatSliderController.DEFAULT_FORMATTER); - } - - /** - * Constructs a float field controller. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - * @param formatter display text, not used whilst editing - */ - public FloatFieldController(Option option, Function formatter) { - this(option, -Float.MAX_VALUE, Float.MAX_VALUE, formatter); - } - - /** - * Constructs a float field controller. - * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - */ - public FloatFieldController(Option option) { - this(option, -Float.MAX_VALUE, Float.MAX_VALUE, FloatSliderController.DEFAULT_FORMATTER); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return this.min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return this.max; - } - - /** - * {@inheritDoc} - */ - @Override - public String getString() { - return String.valueOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((float) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java deleted file mode 100644 index 726a590..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java +++ /dev/null @@ -1,109 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string.number; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * {@inheritDoc} - */ -public class IntegerFieldController extends NumberFieldController { - private final int min, max; - - /** - * Constructs a integer field controller - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - * @param formatter display text, not used whilst editing - */ - public IntegerFieldController(Option option, int min, int max, Function formatter) { - super(option, formatter); - this.min = min; - this.max = max; - } - - /** - * Constructs a integer field controller. - * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - */ - public IntegerFieldController(Option option, int min, int max) { - this(option, min, max, IntegerSliderController.DEFAULT_FORMATTER); - } - - /** - * Constructs a integer field controller. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - * @param formatter display text, not used whilst editing - */ - public IntegerFieldController(Option option, Function formatter) { - this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, formatter); - } - - /** - * Constructs a integer field controller. - * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - */ - public IntegerFieldController(Option option) { - this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, IntegerSliderController.DEFAULT_FORMATTER); - } - - @Override - public boolean isInputValid(String input) { - return input.matches("\\d+|-|"); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return this.min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return this.max; - } - - /** - * {@inheritDoc} - */ - @Override - public String getString() { - return String.valueOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((int) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java deleted file mode 100644 index d0c60b4..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java +++ /dev/null @@ -1,109 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string.number; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.gui.controllers.slider.LongSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -/** - * {@inheritDoc} - */ -public class LongFieldController extends NumberFieldController { - private final long min, max; - - /** - * Constructs a long field controller - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - * @param formatter display text, not used whilst editing - */ - public LongFieldController(Option option, long min, long max, Function formatter) { - super(option, formatter); - this.min = min; - this.max = max; - } - - /** - * Constructs a long field controller. - * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * - * @param option option to bind controller to - * @param min minimum allowed value (clamped on apply) - * @param max maximum allowed value (clamped on apply) - */ - public LongFieldController(Option option, long min, long max) { - this(option, min, max, LongSliderController.DEFAULT_FORMATTER); - } - - /** - * Constructs a long field controller. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - * @param formatter display text, not used whilst editing - */ - public LongFieldController(Option option, Function formatter) { - this(option, -Long.MAX_VALUE, Long.MAX_VALUE, formatter); - } - - /** - * Constructs a long field controller. - * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, - * not used whilst editing. - * Does not have a minimum or a maximum range. - * - * @param option option to bind controller to - */ - public LongFieldController(Option option) { - this(option, -Long.MAX_VALUE, Long.MAX_VALUE, LongSliderController.DEFAULT_FORMATTER); - } - - @Override - public boolean isInputValid(String input) { - return input.matches("\\d+|-|"); - } - - /** - * {@inheritDoc} - */ - @Override - public double min() { - return this.min; - } - - /** - * {@inheritDoc} - */ - @Override - public double max() { - return this.max; - } - - /** - * {@inheritDoc} - */ - @Override - public String getString() { - return String.valueOf(option().pendingValue()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPendingValue(double value) { - option().requestSet((long) value); - } - - /** - * {@inheritDoc} - */ - @Override - public double pendingValue() { - return option().pendingValue(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java deleted file mode 100644 index 4240849..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.isxander.yacl.gui.controllers.string.number; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.slider.ISliderController; -import dev.isxander.yacl.gui.controllers.string.IStringController; -import dev.isxander.yacl.gui.controllers.string.StringControllerElement; -import net.minecraft.network.chat.Component; -import net.minecraft.util.Mth; - -import java.text.DecimalFormatSymbols; -import java.util.function.Function; - -/** - * Controller that allows you to enter in numbers using a text field. - * - * @param number type - */ -public abstract class NumberFieldController implements ISliderController, IStringController { - private final Option option; - private final Function displayFormatter; - - public NumberFieldController(Option option, Function displayFormatter) { - this.option = option; - this.displayFormatter = displayFormatter; - } - - @Override - public Option option() { - return this.option; - } - - @Override - public void setFromString(String value) { - if (value.isEmpty() || value.equals(".") || value.equals("-")) value = "0"; - setPendingValue(Mth.clamp(Double.parseDouble(cleanupNumberString(value)), min(), max())); - } - - @Override - public double pendingValue() { - return option().pendingValue().doubleValue(); - } - - @Override - public boolean isInputValid(String input) { - return input.matches("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)|[.]||-"); - } - - @Override - public Component formatValue() { - return displayFormatter.apply(option().pendingValue()); - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new StringControllerElement(this, screen, widgetDimension, false); - } - - protected String cleanupNumberString(String number) { - return number.replace(String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()), ""); - } - - @Override - public double interval() { - return -1; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java b/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java deleted file mode 100644 index 86b9314..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This package contains implementations of input fields for different number types - *
    - *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController}
  • - *
  • For floats: {@link dev.isxander.yacl.gui.controllers.string.number.FloatFieldController}
  • - *
  • For integers: {@link dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController}
  • - *
  • For longs: {@link dev.isxander.yacl.gui.controllers.string.number.LongFieldController}
  • - *
- */ -package dev.isxander.yacl.gui.controllers.string.number; diff --git a/common/src/main/java/dev/isxander/yacl/gui/tab/ListHolderWidget.java b/common/src/main/java/dev/isxander/yacl/gui/tab/ListHolderWidget.java deleted file mode 100644 index 84aba61..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/tab/ListHolderWidget.java +++ /dev/null @@ -1,116 +0,0 @@ -package dev.isxander.yacl.gui.tab; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.gui.ElementListWidgetExt; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.events.ContainerEventHandler; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.network.chat.CommonComponents; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.Supplier; - -public class ListHolderWidget> extends AbstractWidget implements ContainerEventHandler { - private final Supplier dimensions; - private final T list; - - public ListHolderWidget(Supplier dimensions, T list) { - super(0, 0, 100, 0, CommonComponents.EMPTY); - this.dimensions = dimensions; - this.list = list; - } - - @Override - public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float deltaTick) { - ScreenRectangle dimensions = this.dimensions.get(); - this.setX(dimensions.left()); - this.setY(dimensions.top()); - this.width = dimensions.width(); - this.height = dimensions.height(); - this.list.updateDimensions(dimensions); - this.list.render(guiGraphics, mouseX, mouseY, deltaTick); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput output) { - this.list.updateNarration(output); - } - - @Override - public List children() { - return ImmutableList.of(this.list); - } - - public T getList() { - return list; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return this.list.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - return this.list.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - return this.list.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - return this.list.mouseScrolled(mouseX, mouseY, amount); - } - - @Override - public boolean keyPressed(int i, int j, int k) { - return this.list.keyPressed(i, j, k); - } - - @Override - public boolean charTyped(char c, int i) { - return this.list.charTyped(c, i); - } - - @Override - public boolean isDragging() { - return this.list.isDragging(); - } - - @Override - public void setDragging(boolean dragging) { - this.list.setDragging(dragging); - } - - @Nullable - @Override - public GuiEventListener getFocused() { - return this.list.getFocused(); - } - - @Override - public void setFocused(@Nullable GuiEventListener listener) { - this.list.setFocused(listener); - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent event) { - return this.list.nextFocusPath(event); - } - - @Nullable - @Override - public ComponentPath getCurrentFocusPath() { - return this.list.getCurrentFocusPath(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/tab/ScrollableNavigationBar.java b/common/src/main/java/dev/isxander/yacl/gui/tab/ScrollableNavigationBar.java deleted file mode 100644 index b452495..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/tab/ScrollableNavigationBar.java +++ /dev/null @@ -1,110 +0,0 @@ -package dev.isxander.yacl.gui.tab; - -import com.google.common.collect.ImmutableList; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.TabButton; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.components.tabs.Tab; -import net.minecraft.client.gui.components.tabs.TabManager; -import net.minecraft.client.gui.components.tabs.TabNavigationBar; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.Nullable; - -public class ScrollableNavigationBar extends TabNavigationBar { - private static final int NAVBAR_MARGIN = 28; - - private static final Font font = Minecraft.getInstance().font; - - private int scrollOffset; - private int maxScrollOffset; - - public ScrollableNavigationBar(int width, TabManager tabManager, Iterable tabs) { - super(width, tabManager, ImmutableList.copyOf(tabs)); - - // add tab tooltips to the tab buttons - for (TabButton tabButton : this.tabButtons) { - if (tabButton.tab() instanceof TabExt tab) { - tabButton.setTooltip(tab.getTooltip()); - } - } - } - - @Override - public void arrangeElements() { - int noScrollWidth = this.width - NAVBAR_MARGIN*2; - - // first pass: set the width of each tab button - for (TabButton tabButton : this.tabButtons) { - tabButton.setWidth(font.width(tabButton.getMessage()) + 10); - } - - // second pass: redistribute remaining width evenly - for (TabButton tabButton : tabButtons) { - int totalWidth = tabButtons.stream().mapToInt(AbstractWidget::getWidth).sum(); - int remainingWidth = noScrollWidth - totalWidth; - if (remainingWidth > 0) { - int extra = remainingWidth / tabButtons.size(); - tabButton.setWidth(tabButton.getWidth() + extra); - } - } - - this.layout.arrangeElements(); - this.layout.setY(0); - this.scrollOffset = 0; - - int allTabsWidth = this.tabButtons.stream().mapToInt(AbstractWidget::getWidth).sum(); - this.layout.setX(Math.max((this.width - allTabsWidth) / 2, NAVBAR_MARGIN)); - this.maxScrollOffset = Math.max(0, allTabsWidth - noScrollWidth); - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - graphics.pose().pushPose(); - // render option list BELOW the navbar without need to scissor - graphics.pose().translate(0, 0, 10); - - super.render(graphics, mouseX, mouseY, delta); - - graphics.pose().popPose(); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - this.setScrollOffset(this.scrollOffset - (int)(amount*10)); - return true; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - return mouseY <= 24; - } - - public void setScrollOffset(int scrollOffset) { - layout.setX(layout.getX() + this.scrollOffset); - this.scrollOffset = Mth.clamp(scrollOffset, 0, maxScrollOffset); - layout.setX(layout.getX() - this.scrollOffset); - } - - public int getScrollOffset() { - return scrollOffset; - } - - @Override - public void setFocused(@Nullable GuiEventListener child) { - super.setFocused(child); - if (child instanceof TabButton tabButton) { - this.ensureVisible(tabButton); - } - } - - protected void ensureVisible(TabButton tabButton) { - if (tabButton.getX() < NAVBAR_MARGIN) { - this.setScrollOffset(this.scrollOffset - (NAVBAR_MARGIN - tabButton.getX())); - } else if (tabButton.getX() + tabButton.getWidth() > this.width - NAVBAR_MARGIN) { - this.setScrollOffset(this.scrollOffset + (tabButton.getX() + tabButton.getWidth() - (this.width - NAVBAR_MARGIN))); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/tab/TabExt.java b/common/src/main/java/dev/isxander/yacl/gui/tab/TabExt.java deleted file mode 100644 index 7462a2c..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/tab/TabExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.isxander.yacl.gui.tab; - -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.components.tabs.Tab; -import org.jetbrains.annotations.Nullable; - -public interface TabExt extends Tab { - @Nullable Tooltip getTooltip(); -} diff --git a/common/src/main/java/dev/isxander/yacl/gui/utils/GuiUtils.java b/common/src/main/java/dev/isxander/yacl/gui/utils/GuiUtils.java deleted file mode 100644 index 0e671e8..0000000 --- a/common/src/main/java/dev/isxander/yacl/gui/utils/GuiUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.isxander.yacl.gui.utils; - -import net.minecraft.client.gui.Font; -import net.minecraft.locale.Language; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; - -public class GuiUtils { - public static MutableComponent translatableFallback(String key, Component fallback) { - if (Language.getInstance().has(key)) - return Component.translatable(key); - return fallback.copy(); - } - - public static String shortenString(String string, Font font, int maxWidth, String suffix) { - if (string.isEmpty()) - return string; - - boolean firstIter = true; - while (font.width(string) > maxWidth) { - string = string.substring(0, Math.max(string.length() - 1 - (firstIter ? 1 : suffix.length() + 1), 0)).trim(); - string += suffix; - - if (string.equals(suffix)) - break; - - firstIter = false; - } - - return string; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java deleted file mode 100644 index d93dd1a..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java +++ /dev/null @@ -1,195 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ActionController; -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.function.BiConsumer; -import java.util.function.Consumer; - -@ApiStatus.Internal -public final class ButtonOptionImpl implements ButtonOption { - private final Component name; - private final OptionDescription description; - private final BiConsumer action; - private boolean available; - private final Controller> controller; - private final Binding> binding; - - public ButtonOptionImpl( - @NotNull Component name, - @Nullable OptionDescription description, - @NotNull BiConsumer action, - boolean available - ) { - this.name = name; - this.description = description; - this.action = action; - this.available = available; - this.controller = new ActionController(this); - this.binding = new EmptyBinderImpl(); - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public @NotNull OptionDescription description() { - return description; - } - - @Override - public @NotNull Component tooltip() { - return description().text(); - } - - @Override - public BiConsumer action() { - return action; - } - - @Override - public boolean available() { - return available; - } - - @Override - public void setAvailable(boolean available) { - this.available = available; - } - - @Override - public @NotNull Controller> controller() { - return controller; - } - - @Override - public @NotNull Binding> binding() { - return binding; - } - - @Override - public @NotNull ImmutableSet flags() { - return ImmutableSet.of(); - } - - @Override - public boolean changed() { - return false; - } - - @Override - public @NotNull BiConsumer pendingValue() { - throw new UnsupportedOperationException(); - } - - @Override - public void requestSet(BiConsumer value) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean applyValue() { - return false; - } - - @Override - public void forgetPendingValue() { - - } - - @Override - public void requestSetDefault() { - - } - - @Override - public boolean isPendingValueDefault() { - throw new UnsupportedOperationException(); - } - - @Override - public void addListener(BiConsumer>, BiConsumer> changedListener) { - - } - - private static class EmptyBinderImpl implements Binding> { - @Override - public void setValue(BiConsumer value) { - - } - - @Override - public BiConsumer getValue() { - throw new UnsupportedOperationException(); - } - - @Override - public BiConsumer defaultValue() { - throw new UnsupportedOperationException(); - } - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component name; - private OptionDescription description = OptionDescription.EMPTY; - private boolean available = true; - private BiConsumer action; - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - @Override - public Builder description(@NotNull OptionDescription description) { - Validate.notNull(description, "`description` cannot be null"); - - this.description = description; - return this; - } - - @Override - public Builder action(@NotNull BiConsumer action) { - Validate.notNull(action, "`action` cannot be null"); - - this.action = action; - return this; - } - - @Override - @Deprecated - public Builder action(@NotNull Consumer action) { - Validate.notNull(action, "`action` cannot be null"); - - this.action = (screen, button) -> action.accept(screen); - return this; - } - - @Override - public Builder available(boolean available) { - this.available = available; - return this; - } - - @Override - public ButtonOption build() { - Validate.notNull(name, "`name` must not be null when building `ButtonOption`"); - Validate.notNull(action, "`action` must not be null when building `ButtonOption`"); - - return new ButtonOptionImpl(name, description, action, available); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java deleted file mode 100644 index 0690cda..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentContents; -import net.minecraft.network.chat.MutableComponent; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -@ApiStatus.Internal -public final class ConfigCategoryImpl implements ConfigCategory { - private final Component name; - private final ImmutableList groups; - private final Component tooltip; - - public ConfigCategoryImpl(Component name, ImmutableList groups, Component tooltip) { - this.name = name; - this.groups = groups; - this.tooltip = tooltip; - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public @NotNull ImmutableList groups() { - return groups; - } - - @Override - public @NotNull Component tooltip() { - return tooltip; - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component name; - - private final List> rootOptions = new ArrayList<>(); - private final List groups = new ArrayList<>(); - - private final List tooltipLines = new ArrayList<>(); - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - @Override - public Builder option(@NotNull Option option) { - Validate.notNull(option, "`option` must not be null"); - - if (option instanceof ListOption listOption) { - YACLConstants.LOGGER.warn("Adding list option as an option is not supported! Rerouting to group!"); - return group(listOption); - } - - this.rootOptions.add(option); - return this; - } - - @Override - public Builder options(@NotNull Collection> options) { - Validate.notNull(options, "`options` must not be null"); - - if (options.stream().anyMatch(ListOption.class::isInstance)) - throw new UnsupportedOperationException("List options must not be added as an option but a group!"); - - this.rootOptions.addAll(options); - return this; - } - - @Override - public Builder group(@NotNull OptionGroup group) { - Validate.notNull(group, "`group` must not be null"); - - this.groups.add(group); - return this; - } - - @Override - public Builder groups(@NotNull Collection groups) { - Validate.notEmpty(groups, "`groups` must not be empty"); - - this.groups.addAll(groups); - return this; - } - - @Override - public Builder tooltip(@NotNull Component... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - @Override - public ConfigCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - List combinedGroups = new ArrayList<>(); - combinedGroups.add(new OptionGroupImpl(CommonComponents.EMPTY, OptionDescription.EMPTY, ImmutableList.copyOf(rootOptions), false, true)); - combinedGroups.addAll(groups); - - Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); - - MutableComponent concatenatedTooltip = Component.empty(); - boolean first = true; - for (Component line : tooltipLines) { - if (line.getContents() == ComponentContents.EMPTY) - continue; - - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java b/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java deleted file mode 100644 index 0d668c6..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.isxander.yacl.impl; - -import dev.isxander.yacl.api.Binding; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public final class GenericBindingImpl implements Binding { - private final T def; - private final Supplier getter; - private final Consumer setter; - - public GenericBindingImpl(T def, Supplier getter, Consumer setting) { - this.def = def; - this.getter = getter; - this.setter = setting; - } - - - @Override - public void setValue(T value) { - setter.accept(value); - } - - @Override - public T getValue() { - return getter.get(); - } - - @Override - public T defaultValue() { - return def; - } - -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java deleted file mode 100644 index ae333a7..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.gui.controllers.LabelController; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -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 binding; - - public LabelOptionImpl(Component label) { - this.label = label; - this.labelController = new LabelController(this); - this.binding = Binding.immutable(label); - this.description = OptionDescription.createBuilder() - .text(this.label) - .build(); - } - - @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 controller() { - return labelController; - } - - @Override - public @NotNull Binding binding() { - return binding; - } - - @Override - public boolean available() { - return true; - } - - @Override - public void setAvailable(boolean available) { - throw new UnsupportedOperationException("Label options cannot be disabled."); - } - - @Override - public @NotNull ImmutableSet flags() { - return ImmutableSet.of(); - } - - @Override - public boolean changed() { - return false; - } - - @Override - public @NotNull Component pendingValue() { - return label; - } - - @Override - public void requestSet(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, Component> changedListener) { - - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private final List lines = new ArrayList<>(); - - @Override - public Builder line(@NotNull Component line) { - Validate.notNull(line, "`line` must not be null"); - - this.lines.add(line); - return this; - } - - @Override - public Builder lines(@NotNull Collection lines) { - this.lines.addAll(lines); - return this; - } - - @Override - public LabelOption build() { - MutableComponent text = Component.empty(); - Iterator iterator = lines.iterator(); - while (iterator.hasNext()) { - text.append(iterator.next()); - - if (iterator.hasNext()) - text.append("\n"); - } - - return new LabelOptionImpl(text); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java deleted file mode 100644 index d02259e..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java +++ /dev/null @@ -1,154 +0,0 @@ -package dev.isxander.yacl.impl; - -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.gui.controllers.ListEntryWidget; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.function.BiConsumer; -import java.util.function.Function; - -@ApiStatus.Internal -public final class ListOptionEntryImpl implements ListOptionEntry { - private final ListOptionImpl group; - - private T value; - - private final Binding binding; - private final Controller controller; - - ListOptionEntryImpl(ListOptionImpl group, T initialValue, @NotNull Function, Controller> controlGetter) { - this.group = group; - this.value = initialValue; - this.binding = new EntryBinding(); - this.controller = new EntryController<>(controlGetter.apply(this), this); - } - - @Override - public @NotNull Component name() { - return Component.empty(); - } - - @Override - public @NotNull OptionDescription description() { - return group.description(); - } - - @Override - public @NotNull Component tooltip() { - return Component.empty(); - } - - @Override - public @NotNull Controller controller() { - return controller; - } - - @Override - public @NotNull Binding binding() { - return binding; - } - - @Override - public boolean available() { - return parentGroup().available(); - } - - @Override - public void setAvailable(boolean available) { - - } - - @Override - public ListOption parentGroup() { - return group; - } - - @Override - public boolean changed() { - return false; - } - - @Override - public @NotNull T pendingValue() { - return value; - } - - @Override - public void requestSet(T value) { - binding.setValue(value); - } - - @Override - public boolean applyValue() { - return false; - } - - @Override - public void forgetPendingValue() { - - } - - @Override - public void requestSetDefault() { - - } - - @Override - public boolean isPendingValueDefault() { - return false; - } - - @Override - public boolean canResetToDefault() { - return false; - } - - @Override - public void addListener(BiConsumer, T> changedListener) { - - } - - /** - * Open in case mods need to find the real controller type. - */ - @ApiStatus.Internal - public record EntryController(Controller controller, ListOptionEntryImpl entry) implements Controller { - @Override - public Option option() { - return controller.option(); - } - - @Override - public Component formatValue() { - return controller.formatValue(); - } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new ListEntryWidget(screen, entry, controller.provideWidget(screen, widgetDimension)); - } - } - - private class EntryBinding implements Binding { - @Override - public void setValue(T newValue) { - value = newValue; - group.callListeners(); - } - - @Override - public T getValue() { - return value; - } - - @Override - public T defaultValue() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java deleted file mode 100644 index e81d702..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java +++ /dev/null @@ -1,325 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.controller.ControllerBuilder; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@ApiStatus.Internal -public final class ListOptionImpl implements ListOption { - private final Component name; - private final OptionDescription description; - private final Binding> binding; - private final T initialValue; - private final List> entries; - private final boolean collapsed; - private boolean available; - private final ImmutableSet flags; - private final EntryFactory entryFactory; - private final List>, List>> listeners; - private final List refreshListeners; - - public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull Binding> binding, @NotNull T initialValue, @NotNull Function, Controller> controllerFunction, ImmutableSet flags, boolean collapsed, boolean available, Collection>, List>> listeners) { - this.name = name; - this.description = description; - this.binding = binding; - this.initialValue = initialValue; - this.entryFactory = new EntryFactory(controllerFunction); - this.entries = createEntries(binding().getValue()); - this.collapsed = collapsed; - this.flags = flags; - this.available = available; - this.listeners = new ArrayList<>(); - this.listeners.addAll(listeners); - this.refreshListeners = new ArrayList<>(); - callListeners(); - } - - @Override - public @NotNull Component name() { - return this.name; - } - - @Override - public @NotNull OptionDescription description() { - return this.description; - } - - @Override - public @NotNull Component tooltip() { - return description().text(); - } - - @Override - public @NotNull ImmutableList> options() { - return ImmutableList.copyOf(entries); - } - - @Override - public @NotNull Controller> controller() { - throw new UnsupportedOperationException(); - } - - @Override - public @NotNull Binding> binding() { - return binding; - } - - @Override - public boolean collapsed() { - return collapsed; - } - - @Override - public @NotNull ImmutableSet flags() { - return flags; - } - - @Override - public @NotNull ImmutableList pendingValue() { - return ImmutableList.copyOf(entries.stream().map(Option::pendingValue).toList()); - } - - @Override - public void insertEntry(int index, ListOptionEntry entry) { - entries.add(index, (ListOptionEntry) entry); - onRefresh(); - } - - @Override - public ListOptionEntry insertNewEntryToTop() { - ListOptionEntry newEntry = entryFactory.create(initialValue); - entries.add(0, newEntry); - onRefresh(); - return newEntry; - } - - @Override - public void removeEntry(ListOptionEntry entry) { - if (entries.remove(entry)) - onRefresh(); - } - - @Override - public int indexOf(ListOptionEntry entry) { - return entries.indexOf(entry); - } - - @Override - public void requestSet(List value) { - entries.clear(); - entries.addAll(createEntries(value)); - onRefresh(); - } - - @Override - public boolean changed() { - return !binding().getValue().equals(pendingValue()); - } - - @Override - public boolean applyValue() { - if (changed()) { - binding().setValue(pendingValue()); - return true; - } - return false; - } - - @Override - public void forgetPendingValue() { - requestSet(binding().getValue()); - } - - @Override - public void requestSetDefault() { - requestSet(binding().defaultValue()); - } - - @Override - public boolean isPendingValueDefault() { - return binding().defaultValue().equals(pendingValue()); - } - - @Override - public boolean available() { - return available; - } - - @Override - public void setAvailable(boolean available) { - this.available = available; - } - - @Override - public void addListener(BiConsumer>, List> changedListener) { - this.listeners.add(changedListener); - } - - @Override - public void addRefreshListener(Runnable changedListener) { - this.refreshListeners.add(changedListener); - } - - @Override - public boolean isRoot() { - return false; - } - - private List> createEntries(Collection values) { - return values.stream().map(entryFactory::create).collect(Collectors.toList()); - } - - void callListeners() { - List pendingValue = pendingValue(); - this.listeners.forEach(listener -> listener.accept(this, pendingValue)); - } - - private void onRefresh() { - refreshListeners.forEach(Runnable::run); - callListeners(); - } - - private class EntryFactory { - private final Function, Controller> controllerFunction; - - private EntryFactory(Function, Controller> controllerFunction) { - this.controllerFunction = controllerFunction; - } - - public ListOptionEntry create(T initialValue) { - return new ListOptionEntryImpl<>(ListOptionImpl.this, initialValue, controllerFunction); - } - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component name = Component.empty(); - private OptionDescription description = OptionDescription.EMPTY; - private Function, Controller> controllerFunction; - private Binding> binding = null; - private final Set flags = new HashSet<>(); - private T initialValue; - private boolean collapsed = false; - private boolean available = true; - private final List>, List>> listeners = new ArrayList<>(); - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` must not be null"); - - this.name = name; - return this; - } - - @Override - public Builder description(@NotNull OptionDescription description) { - Validate.notNull(description, "`description` must not be null"); - - this.description = description; - return this; - } - - @Override - public Builder initial(@NotNull T initialValue) { - Validate.notNull(initialValue, "`initialValue` cannot be empty"); - - this.initialValue = initialValue; - return this; - } - - @Override - public Builder controller(@NotNull Function, ControllerBuilder> controller) { - Validate.notNull(controller, "`controller` cannot be null"); - - this.controllerFunction = opt -> controller.apply(opt).build(); - return this; - } - - @Override - public Builder customController(@NotNull Function, Controller> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controllerFunction = control; - return this; - } - - @Override - public Builder binding(@NotNull Binding> binding) { - Validate.notNull(binding, "`binding` cannot be null"); - - this.binding = binding; - return this; - } - - @Override - public Builder binding(@NotNull List def, @NotNull Supplier<@NotNull List> getter, @NotNull Consumer<@NotNull List> setter) { - Validate.notNull(def, "`def` must not be null"); - Validate.notNull(getter, "`getter` must not be null"); - Validate.notNull(setter, "`setter` must not be null"); - - this.binding = Binding.generic(def, getter, setter); - return this; - } - - @Override - public Builder available(boolean available) { - this.available = available; - return this; - } - - @Override - public Builder flag(@NotNull OptionFlag... flag) { - Validate.notNull(flag, "`flag` must not be null"); - - this.flags.addAll(Arrays.asList(flag)); - return this; - } - - @Override - public Builder flags(@NotNull Collection flags) { - Validate.notNull(flags, "`flags` must not be null"); - - this.flags.addAll(flags); - return this; - } - - @Override - public Builder collapsed(boolean collapsible) { - this.collapsed = collapsible; - return this; - } - - @Override - public Builder listener(@NotNull BiConsumer>, List> listener) { - this.listeners.add(listener); - return this; - } - - @Override - public Builder listeners(@NotNull Collection>, List>> listeners) { - this.listeners.addAll(listeners); - return this; - } - - @Override - public ListOption 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"); - - return new ListOptionImpl<>(name, description, binding, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, listeners); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java deleted file mode 100644 index 12c6ce7..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java +++ /dev/null @@ -1,147 +0,0 @@ -package dev.isxander.yacl.impl; - -import dev.isxander.yacl.api.OptionDescription; -import dev.isxander.yacl.gui.ImageRenderer; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import org.apache.commons.lang3.Validate; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -public record OptionDescriptionImpl(Component text, CompletableFuture> image) implements OptionDescription { - public static class BuilderImpl implements Builder { - private final List descriptionLines = new ArrayList<>(); - private CompletableFuture> image = CompletableFuture.completedFuture(Optional.empty()); - private boolean imageUnset = true; - - @Override - public Builder text(Component... description) { - this.descriptionLines.addAll(Arrays.asList(description)); - return this; - } - - @Override - public Builder text(Collection lines) { - this.descriptionLines.addAll(lines); - return this; - } - - @Override - public Builder image(ResourceLocation image, int width, int height) { - Validate.isTrue(imageUnset, "Image already set!"); - Validate.isTrue(width > 0, "Width must be greater than 0!"); - Validate.isTrue(height > 0, "Height must be greater than 0!"); - - this.image = ImageRenderer.getOrMakeSync(image, () -> Optional.of(new ImageRenderer.TextureBacked(image, 0, 0, width, height, width, height))); - imageUnset = false; - return this; - } - - @Override - public Builder image(ResourceLocation image, float u, float v, int width, int height, int textureWidth, int textureHeight) { - Validate.isTrue(imageUnset, "Image already set!"); - Validate.isTrue(width > 0, "Width must be greater than 0!"); - Validate.isTrue(height > 0, "Height must be greater than 0!"); - - this.image = ImageRenderer.getOrMakeSync(image, () -> Optional.of(new ImageRenderer.TextureBacked(image, u, v, width, height, textureWidth, textureHeight))); - imageUnset = false; - return this; - } - - @Override - public Builder image(Path path, ResourceLocation uniqueLocation) { - Validate.isTrue(imageUnset, "Image already set!"); - this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> ImageRenderer.NativeImageBacked.createFromPath(path, uniqueLocation)); - imageUnset = false; - return this; - } - - @Override - public Builder gifImage(ResourceLocation image) { - Validate.isTrue(imageUnset, "Image already set!"); - this.image = ImageRenderer.getOrMakeAsync(image, () -> { - try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIFFromTexture(image)); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - }); - imageUnset = false; - return this; - } - - @Override - public Builder gifImage(Path path, ResourceLocation uniqueLocation) { - Validate.isTrue(imageUnset, "Image already set!"); - this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { - try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIF(new FileInputStream(path.toFile()), uniqueLocation)); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - }); - imageUnset = false; - return this; - } - - @Override - public Builder webpImage(ResourceLocation image) { - Validate.isTrue(imageUnset, "Image already set!"); - this.image = ImageRenderer.getOrMakeAsync(image, () -> { - try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBPFromTexture(image)); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - }); - imageUnset = false; - return this; - } - - @Override - public Builder webpImage(Path path, ResourceLocation uniqueLocation) { - Validate.isTrue(imageUnset, "Image already set!"); - this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { - try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBP(new FileInputStream(path.toFile()), uniqueLocation)); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - }); - imageUnset = false; - return this; - } - - @Override - public Builder customImage(CompletableFuture> image) { - Validate.notNull(image, "Image cannot be null!"); - Validate.isTrue(imageUnset, "Image already set!"); - - this.image = image; - this.imageUnset = false; - return this; - } - - @Override - public OptionDescription build() { - MutableComponent concatenatedDescription = Component.empty(); - Iterator iter = descriptionLines.iterator(); - while (iter.hasNext()) { - concatenatedDescription.append(iter.next()); - if (iter.hasNext()) concatenatedDescription.append("\n"); - } - - return new OptionDescriptionImpl(concatenatedDescription, image); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java deleted file mode 100644 index 8c2a1cf..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ListOption; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionDescription; -import dev.isxander.yacl.api.OptionGroup; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -@ApiStatus.Internal -public final class OptionGroupImpl implements OptionGroup { - private final @NotNull Component name; - private final @NotNull OptionDescription description; - private final ImmutableList> options; - private final boolean collapsed; - private final boolean isRoot; - - public OptionGroupImpl(@NotNull Component name, @NotNull OptionDescription description, ImmutableList> options, boolean collapsed, boolean isRoot) { - this.name = name; - this.description = description; - this.options = options; - this.collapsed = collapsed; - this.isRoot = isRoot; - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public OptionDescription description() { - return description; - } - - @Override - public @NotNull Component tooltip() { - return description.text(); - } - - @Override - public @NotNull ImmutableList> options() { - return options; - } - - @Override - public boolean collapsed() { - return collapsed; - } - - @Override - public boolean isRoot() { - return isRoot; - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component name = Component.empty(); - private OptionDescription description = OptionDescription.EMPTY; - private final List> options = new ArrayList<>(); - private boolean collapsed = false; - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` must not be null"); - - this.name = name; - return this; - } - - @Override - public Builder description(@NotNull OptionDescription description) { - Validate.notNull(description, "`description` must not be null"); - - this.description = description; - return this; - } - - @Override - public Builder option(@NotNull Option option) { - Validate.notNull(option, "`option` must not be null"); - - if (option instanceof ListOption) - throw new UnsupportedOperationException("List options must not be added as an option but a group!"); - - this.options.add(option); - return this; - } - - @Override - public Builder options(@NotNull Collection> options) { - Validate.notEmpty(options, "`options` must not be empty"); - - if (options.stream().anyMatch(ListOption.class::isInstance)) - throw new UnsupportedOperationException("List options must not be added as an option but a group!"); - - this.options.addAll(options); - return this; - } - - @Override - public Builder collapsed(boolean collapsible) { - this.collapsed = collapsible; - return this; - } - - @Override - public OptionGroup build() { - Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); - - return new OptionGroupImpl(name, description, ImmutableList.copyOf(options), collapsed, false); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java deleted file mode 100644 index a3dd1d5..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java +++ /dev/null @@ -1,257 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.controller.ControllerBuilder; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -@ApiStatus.Internal -public final class OptionImpl implements Option { - private final Component name; - private OptionDescription description; - private final Controller controller; - private final Binding binding; - private boolean available; - - private final ImmutableSet flags; - - private T pendingValue; - - private final List, T>> listeners; - - public OptionImpl( - @NotNull Component name, - @NotNull Function descriptionFunction, - @NotNull Function, Controller> controlGetter, - @NotNull Binding binding, - boolean available, - ImmutableSet flags, - @NotNull Collection, T>> listeners - ) { - this.name = name; - this.binding = binding; - this.available = available; - this.flags = flags; - this.listeners = new ArrayList<>(listeners); - this.controller = controlGetter.apply(this); - - addListener((opt, pending) -> description = descriptionFunction.apply(pending)); - - requestSet(binding().getValue()); - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public @NotNull OptionDescription description() { - return this.description; - } - - @Override - public @NotNull Component tooltip() { - return description.text(); - } - - @Override - public @NotNull Controller controller() { - return controller; - } - - @Override - public @NotNull Binding binding() { - return binding; - } - - @Override - public boolean available() { - return available; - } - - @Override - public void setAvailable(boolean available) { - this.available = available; - } - - @Override - public @NotNull ImmutableSet flags() { - return flags; - } - - @Override - public boolean changed() { - return !binding().getValue().equals(pendingValue); - } - - @Override - public @NotNull T pendingValue() { - return pendingValue; - } - - @Override - public void requestSet(T value) { - pendingValue = value; - listeners.forEach(listener -> listener.accept(this, pendingValue)); - } - - @Override - public boolean applyValue() { - if (changed()) { - binding().setValue(pendingValue); - return true; - } - return false; - } - - @Override - public void forgetPendingValue() { - requestSet(binding().getValue()); - } - - @Override - public void requestSetDefault() { - requestSet(binding().defaultValue()); - } - - @Override - public boolean isPendingValueDefault() { - return binding().defaultValue().equals(pendingValue()); - } - - @Override - public void addListener(BiConsumer, T> changedListener) { - this.listeners.add(changedListener); - } - - @ApiStatus.Internal - public static class BuilderImpl implements Builder { - private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED); - - private Function descriptionFunction = pending -> OptionDescription.EMPTY; - - private Function, Controller> controlGetter; - - private Binding binding; - - private boolean available = true; - - private boolean instant = false; - - private final Set flags = new HashSet<>(); - - private final List, T>> listeners = new ArrayList<>(); - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - @Override - public Builder description(@NotNull OptionDescription description) { - return description(opt -> description); - } - - @Override - public Builder description(@NotNull Function descriptionFunction) { - this.descriptionFunction = descriptionFunction; - return this; - } - - @Override - public Builder controller(@NotNull Function, ControllerBuilder> controllerBuilder) { - Validate.notNull(controllerBuilder, "`controllerBuilder` cannot be null"); - - return customController(opt -> controllerBuilder.apply(opt).build()); - } - - @Override - public Builder customController(@NotNull Function, Controller> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controlGetter = control; - return this; - } - - @Override - public Builder binding(@NotNull Binding binding) { - Validate.notNull(binding, "`binding` cannot be null"); - - this.binding = binding; - return this; - } - - @Override - public Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { - Validate.notNull(def, "`def` must not be null"); - Validate.notNull(getter, "`getter` must not be null"); - Validate.notNull(setter, "`setter` must not be null"); - - this.binding = Binding.generic(def, getter, setter); - return this; - } - - @Override - public Builder available(boolean available) { - this.available = available; - return this; - } - - @Override - public Builder flag(@NotNull OptionFlag... flag) { - Validate.notNull(flag, "`flag` must not be null"); - - this.flags.addAll(Arrays.asList(flag)); - return this; - } - - @Override - public Builder flags(@NotNull Collection flags) { - Validate.notNull(flags, "`flags` must not be null"); - - this.flags.addAll(flags); - return this; - } - - @Override - public Builder instant(boolean instant) { - this.instant = instant; - return this; - } - - @Override - public Builder listener(@NotNull BiConsumer, T> listener) { - this.listeners.add(listener); - return this; - } - - @Override - public Builder listeners(@NotNull Collection, T>> listeners) { - this.listeners.addAll(listeners); - return this; - } - - @Override - public Option build() { - Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); - Validate.notNull(binding, "`binding` must not be null when building `Option`"); - Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); - - return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), listeners); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java deleted file mode 100644 index 4e41a8f..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.OptionGroup; -import dev.isxander.yacl.api.PlaceholderCategory; -import dev.isxander.yacl.gui.YACLScreen; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiFunction; - -@ApiStatus.Internal -public final class PlaceholderCategoryImpl implements PlaceholderCategory { - private final Component name; - private final BiFunction screen; - private final Component tooltip; - - public PlaceholderCategoryImpl(Component name, BiFunction screen, Component tooltip) { - this.name = name; - this.screen = screen; - this.tooltip = tooltip; - } - - @Override - public @NotNull ImmutableList groups() { - return ImmutableList.of(); - } - - @Override - public @NotNull Component name() { - return name; - } - - @Override - public BiFunction screen() { - return screen; - } - - @Override - public @NotNull Component tooltip() { - return tooltip; - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component name; - - private final List tooltipLines = new ArrayList<>(); - - private BiFunction screenFunction; - - @Override - public Builder name(@NotNull Component name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } - - @Override - public Builder tooltip(@NotNull Component... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - @Override - public Builder screen(@NotNull BiFunction screenFunction) { - Validate.notNull(screenFunction, "`screenFunction` cannot be null"); - - this.screenFunction = screenFunction; - return this; - } - - @Override - public PlaceholderCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - MutableComponent concatenatedTooltip = Component.empty(); - boolean first = true; - for (Component line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } - - return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java b/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java deleted file mode 100644 index 3c3cad2..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -package dev.isxander.yacl.impl; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.PlaceholderCategory; -import dev.isxander.yacl.api.YetAnotherConfigLib; -import dev.isxander.yacl.gui.YACLScreen; -import dev.isxander.yacl.impl.utils.YACLConstants; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Consumer; - -@ApiStatus.Internal -public final class YetAnotherConfigLibImpl implements YetAnotherConfigLib { - private final Component title; - private final ImmutableList categories; - private final Runnable saveFunction; - private final Consumer initConsumer; - - private boolean generated = false; - - public YetAnotherConfigLibImpl(Component title, ImmutableList categories, Runnable saveFunction, Consumer initConsumer) { - this.title = title; - this.categories = categories; - this.saveFunction = saveFunction; - this.initConsumer = initConsumer; - } - - @Override - public Screen generateScreen(Screen parent) { - if (generated) - throw new UnsupportedOperationException("To prevent memory leaks, you should only generate a Screen once per instance. Please re-build the instance to generate another GUI."); - - YACLConstants.LOGGER.info("Generating YACL screen"); - generated = true; - return new YACLScreen(this, parent); - } - - @Override - public Component title() { - return title; - } - - @Override - public ImmutableList categories() { - return categories; - } - - @Override - public Runnable saveFunction() { - return saveFunction; - } - - @Override - public Consumer initConsumer() { - return initConsumer; - } - - @ApiStatus.Internal - public static final class BuilderImpl implements Builder { - private Component title; - private final List categories = new ArrayList<>(); - private Runnable saveFunction = () -> {}; - private Consumer initConsumer = screen -> {}; - - @Override - public Builder title(@NotNull Component title) { - Validate.notNull(title, "`title` cannot be null"); - - this.title = title; - return this; - } - - @Override - public Builder category(@NotNull ConfigCategory category) { - Validate.notNull(category, "`category` cannot be null"); - - this.categories.add(category); - return this; - } - - @Override - public Builder categories(@NotNull Collection categories) { - Validate.notNull(categories, "`categories` cannot be null"); - - this.categories.addAll(categories); - return this; - } - - @Override - public Builder save(@NotNull Runnable saveFunction) { - Validate.notNull(saveFunction, "`saveFunction` cannot be null"); - - this.saveFunction = saveFunction; - return this; - } - - @Override - public Builder screenInit(@NotNull Consumer initConsumer) { - Validate.notNull(initConsumer, "`initConsumer` cannot be null"); - - this.initConsumer = initConsumer; - return this; - } - - @Override - public YetAnotherConfigLib build() { - Validate.notNull(title, "`title must not be null to build `YetAnotherConfigLib`"); - Validate.notEmpty(categories, "`categories` must not be empty to build `YetAnotherConfigLib`"); - Validate.isTrue(!categories.stream().allMatch(category -> category instanceof PlaceholderCategory), "At least one regular category is required to build `YetAnotherConfigLib`"); - - return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); - } - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/AbstractControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/AbstractControllerBuilderImpl.java deleted file mode 100644 index 206ab29..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/AbstractControllerBuilderImpl.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.ControllerBuilder; - -public abstract class AbstractControllerBuilderImpl implements ControllerBuilder { - protected final Option option; - - protected AbstractControllerBuilderImpl(Option option) { - this.option = option; - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/BooleanControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/BooleanControllerBuilderImpl.java deleted file mode 100644 index 99c5ffc..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/BooleanControllerBuilderImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl.gui.controllers.BooleanController; -import net.minecraft.network.chat.Component; -import org.apache.commons.lang3.Validate; - -import java.util.function.Function; - -public class BooleanControllerBuilderImpl extends AbstractControllerBuilderImpl implements BooleanControllerBuilder { - private boolean coloured = false; - private Function formatter = BooleanController.ON_OFF_FORMATTER; - - public BooleanControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public BooleanControllerBuilder coloured(boolean coloured) { - this.coloured = coloured; - return this; - } - - @Override - public BooleanControllerBuilder valueFormatter(Function formatter) { - Validate.notNull(formatter); - - this.formatter = formatter; - return this; - } - - @Override - public BooleanControllerBuilder onOffFormatter() { - this.formatter = BooleanController.ON_OFF_FORMATTER; - return this; - } - - @Override - public BooleanControllerBuilder yesNoFormatter() { - this.formatter = BooleanController.YES_NO_FORMATTER; - return this; - } - - @Override - public BooleanControllerBuilder trueFalseFormatter() { - this.formatter = BooleanController.TRUE_FALSE_FORMATTER; - return this; - } - - @Override - public Controller build() { - return new BooleanController(option, formatter, coloured); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/ColorControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/ColorControllerBuilderImpl.java deleted file mode 100644 index 8b7851b..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/ColorControllerBuilderImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.ColorControllerBuilder; -import dev.isxander.yacl.gui.controllers.ColorController; - -import java.awt.Color; - -public class ColorControllerBuilderImpl extends AbstractControllerBuilderImpl implements ColorControllerBuilder { - private boolean allowAlpha = false; - - public ColorControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public ColorControllerBuilder allowAlpha(boolean allowAlpha) { - this.allowAlpha = allowAlpha; - return this; - } - - @Override - public Controller build() { - return new ColorController(option, allowAlpha); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/CyclingListControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/CyclingListControllerBuilderImpl.java deleted file mode 100644 index 1af556c..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/CyclingListControllerBuilderImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.CyclingListControllerBuilder; -import dev.isxander.yacl.gui.controllers.cycling.CyclingListController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public final class CyclingListControllerBuilderImpl extends AbstractControllerBuilderImpl implements CyclingListControllerBuilder { - private Iterable values; - private Function formatter = null; - - public CyclingListControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public CyclingListControllerBuilder values(Iterable values) { - this.values = values; - return this; - } - - @SafeVarargs - @Override - public final CyclingListControllerBuilder values(T... values) { - this.values = ImmutableList.copyOf(values); - return this; - } - - @Override - public CyclingListControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new CyclingListController<>(option, values, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleFieldControllerBuilderImpl.java deleted file mode 100644 index e22e08b..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleFieldControllerBuilderImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.DoubleFieldControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; -import dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class DoubleFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements DoubleFieldControllerBuilder { - private double min = Double.MIN_VALUE; - private double max = Double.MAX_VALUE; - private Function formatter = DoubleSliderController.DEFAULT_FORMATTER; - - public DoubleFieldControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public DoubleFieldControllerBuilder min(Double min) { - this.min = min; - return this; - } - - @Override - public DoubleFieldControllerBuilder max(Double max) { - this.max = max; - return this; - } - - @Override - public DoubleFieldControllerBuilder range(Double min, Double max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public DoubleFieldControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new DoubleFieldController(option, min, max, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleSliderControllerBuilderImpl.java deleted file mode 100644 index 1c3a664..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/DoubleSliderControllerBuilderImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.DoubleSliderControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class DoubleSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements DoubleSliderControllerBuilder { - private double min, max; - private double step; - private Function formatter = DoubleSliderController.DEFAULT_FORMATTER; - - public DoubleSliderControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public DoubleSliderControllerBuilder range(Double min, Double max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public DoubleSliderControllerBuilder step(Double step) { - this.step = step; - return this; - } - - @Override - public DoubleSliderControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new DoubleSliderController(option, min, max, step, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/EnumControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/EnumControllerBuilderImpl.java deleted file mode 100644 index 79016b5..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/EnumControllerBuilderImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.EnumControllerBuilder; -import dev.isxander.yacl.gui.controllers.cycling.EnumController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class EnumControllerBuilderImpl> extends AbstractControllerBuilderImpl implements EnumControllerBuilder { - private Class enumClass; - private Function formatter = EnumController.getDefaultFormatter(); - - public EnumControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public EnumControllerBuilder enumClass(Class enumClass) { - this.enumClass = enumClass; - return this; - } - - @Override - public EnumControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new EnumController<>(option, formatter, enumClass.getEnumConstants()); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/FloatFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/FloatFieldControllerBuilderImpl.java deleted file mode 100644 index 22dbf1a..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/FloatFieldControllerBuilderImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.FloatFieldControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import dev.isxander.yacl.gui.controllers.string.number.FloatFieldController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class FloatFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements FloatFieldControllerBuilder { - private float min = Float.MIN_VALUE; - private float max = Float.MAX_VALUE; - private Function formatter = FloatSliderController.DEFAULT_FORMATTER; - - public FloatFieldControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public FloatFieldControllerBuilder min(Float min) { - this.min = min; - return this; - } - - @Override - public FloatFieldControllerBuilder max(Float max) { - this.max = max; - return this; - } - - @Override - public FloatFieldControllerBuilder range(Float min, Float max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public FloatFieldControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new FloatFieldController(option, min, max, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/FloatSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/FloatSliderControllerBuilderImpl.java deleted file mode 100644 index c49eee7..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/FloatSliderControllerBuilderImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.FloatSliderControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class FloatSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements FloatSliderControllerBuilder { - private float min, max; - private float step; - private Function formatter = FloatSliderController.DEFAULT_FORMATTER; - - public FloatSliderControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public FloatSliderControllerBuilder range(Float min, Float max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public FloatSliderControllerBuilder step(Float step) { - this.step = step; - return this; - } - - @Override - public FloatSliderControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new FloatSliderController(option, min, max, step, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerFieldControllerBuilderImpl.java deleted file mode 100644 index 6514e81..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerFieldControllerBuilderImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.IntegerFieldControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; -import dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class IntegerFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements IntegerFieldControllerBuilder { - private int min = Integer.MIN_VALUE; - private int max = Integer.MAX_VALUE; - private Function formatter = IntegerSliderController.DEFAULT_FORMATTER; - - public IntegerFieldControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public IntegerFieldControllerBuilder min(Integer min) { - this.min = min; - return this; - } - - @Override - public IntegerFieldControllerBuilder max(Integer max) { - this.max = max; - return this; - } - - @Override - public IntegerFieldControllerBuilder range(Integer min, Integer max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public IntegerFieldControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new IntegerFieldController(option, min, max, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerSliderControllerBuilderImpl.java deleted file mode 100644 index 154adb1..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/IntegerSliderControllerBuilderImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.IntegerSliderControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class IntegerSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements IntegerSliderControllerBuilder { - private int min, max; - private int step; - private Function formatter = IntegerSliderController.DEFAULT_FORMATTER; - - public IntegerSliderControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public IntegerSliderControllerBuilder range(Integer min, Integer max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public IntegerSliderControllerBuilder step(Integer step) { - this.step = step; - return this; - } - - @Override - public IntegerSliderControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new IntegerSliderController(option, min, max, step, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/LongFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/LongFieldControllerBuilderImpl.java deleted file mode 100644 index 0e76b3d..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/LongFieldControllerBuilderImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.LongFieldControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.LongSliderController; -import dev.isxander.yacl.gui.controllers.string.number.LongFieldController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class LongFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements LongFieldControllerBuilder { - private long min = Long.MIN_VALUE; - private long max = Long.MAX_VALUE; - private Function formatter = LongSliderController.DEFAULT_FORMATTER; - - public LongFieldControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public LongFieldControllerBuilder min(Long min) { - this.min = min; - return this; - } - - @Override - public LongFieldControllerBuilder max(Long max) { - this.max = max; - return this; - } - - @Override - public LongFieldControllerBuilder range(Long min, Long max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public LongFieldControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new LongFieldController(option, min, max, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/LongSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/LongSliderControllerBuilderImpl.java deleted file mode 100644 index 24926c4..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/LongSliderControllerBuilderImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.LongSliderControllerBuilder; -import dev.isxander.yacl.gui.controllers.slider.LongSliderController; -import net.minecraft.network.chat.Component; - -import java.util.function.Function; - -public class LongSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements LongSliderControllerBuilder { - private long min, max; - private long step; - private Function formatter = LongSliderController.DEFAULT_FORMATTER; - - public LongSliderControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public LongSliderControllerBuilder range(Long min, Long max) { - this.min = min; - this.max = max; - return this; - } - - @Override - public LongSliderControllerBuilder step(Long step) { - this.step = step; - return this; - } - - @Override - public LongSliderControllerBuilder valueFormatter(Function formatter) { - this.formatter = formatter; - return this; - } - - @Override - public Controller build() { - return new LongSliderController(option, min, max, step, formatter); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/StringControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/StringControllerBuilderImpl.java deleted file mode 100644 index 17d9e92..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/StringControllerBuilderImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.StringControllerBuilder; -import dev.isxander.yacl.gui.controllers.string.StringController; - -public class StringControllerBuilderImpl extends AbstractControllerBuilderImpl implements StringControllerBuilder { - public StringControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public Controller build() { - return new StringController(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/controller/TickBoxControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl/impl/controller/TickBoxControllerBuilderImpl.java deleted file mode 100644 index 6d835cb..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/controller/TickBoxControllerBuilderImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.isxander.yacl.impl.controller; - -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.controller.TickBoxControllerBuilder; -import dev.isxander.yacl.gui.controllers.TickBoxController; - -public class TickBoxControllerBuilderImpl extends AbstractControllerBuilderImpl implements TickBoxControllerBuilder { - public TickBoxControllerBuilderImpl(Option option) { - super(option); - } - - @Override - public Controller build() { - return new TickBoxController(option); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java deleted file mode 100644 index 6c7508d..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.api.utils.MutableDimension; - -public class DimensionIntegerImpl implements MutableDimension { - private int x, y; - private int width, height; - - public DimensionIntegerImpl(int x, int y, int width, int height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - @Override - public Integer x() { - return x; - } - - @Override - public Integer y() { - return y; - } - - @Override - public Integer width() { - return width; - } - - @Override - public Integer height() { - return height; - } - - @Override - public Integer xLimit() { - return x + width; - } - - @Override - public Integer yLimit() { - return y + height; - } - - @Override - public Integer centerX() { - return x + width / 2; - } - - @Override - public Integer centerY() { - return y + height / 2; - } - - @Override - public boolean isPointInside(Integer x, Integer y) { - return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); - } - - @Override - public MutableDimension clone() { - return new DimensionIntegerImpl(x, y, width, height); - } - - @Override public MutableDimension setX(Integer x) { this.x = x; return this; } - @Override public MutableDimension setY(Integer y) { this.y = y; return this; } - @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } - @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } - - @Override - public Dimension withX(Integer x) { - return clone().setX(x); - } - - @Override - public Dimension withY(Integer y) { - return clone().setY(y); - } - - @Override - public Dimension withWidth(Integer width) { - return clone().setWidth(width); - } - - @Override - public Dimension withHeight(Integer height) { - return clone().setHeight(height); - } - - @Override - public MutableDimension move(Integer x, Integer y) { - this.x += x; - this.y += y; - return this; - } - - @Override - public MutableDimension expand(Integer width, Integer height) { - this.width += width; - this.height += height; - return this; - } - - @Override - public Dimension moved(Integer x, Integer y) { - return clone().move(x, y); - } - - @Override - public Dimension expanded(Integer width, Integer height) { - return clone().expand(width, height); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java deleted file mode 100644 index 7b84ee1..0000000 --- a/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.isxander.yacl.impl.utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class YACLConstants { - public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); - - public static final ExecutorService SINGLE_THREAD_EXECUTOR = Executors.newSingleThreadExecutor(); -} diff --git a/common/src/main/java/dev/isxander/yacl/mixin/AbstractSelectionListMixin.java b/common/src/main/java/dev/isxander/yacl/mixin/AbstractSelectionListMixin.java deleted file mode 100644 index 1b53e96..0000000 --- a/common/src/main/java/dev/isxander/yacl/mixin/AbstractSelectionListMixin.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.isxander.yacl.mixin; - -import net.minecraft.client.gui.components.AbstractSelectionList; -import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import java.util.List; - -@Mixin(AbstractSelectionList.class) -public abstract class AbstractSelectionListMixin> { - @Shadow public abstract List children(); - - /** - * Mojang use the field access of children to get max index to loop through keyboard navigation to find the next entry. - * YACL modifies these children() method to filter out hidden entries, so we need to redirect the field access to the - * method, so we don't get ArrayIndexOutOfBoundsException. - */ - @Redirect(method = "nextEntry(Lnet/minecraft/client/gui/navigation/ScreenDirection;Ljava/util/function/Predicate;Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry;)Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/components/AbstractSelectionList;children:Ljava/util/List;", opcode = Opcodes.GETFIELD)) - private List modifyChildrenCall(AbstractSelectionList instance) { - return children(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/mixin/ContainerEventHandlerMixin.java b/common/src/main/java/dev/isxander/yacl/mixin/ContainerEventHandlerMixin.java deleted file mode 100644 index d864e17..0000000 --- a/common/src/main/java/dev/isxander/yacl/mixin/ContainerEventHandlerMixin.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.isxander.yacl.mixin; - -import net.minecraft.client.gui.components.events.ContainerEventHandler; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.components.tabs.TabNavigationBar; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.navigation.ScreenAxis; -import net.minecraft.client.gui.navigation.ScreenDirection; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import java.util.List; - -@Mixin(ContainerEventHandler.class) -public interface ContainerEventHandlerMixin { - /** - * This mixin is used to prevent the tab bar from being focused when navigating left or right - * through the YACL options screen. This can also apply to vanilla as navigating left or right - * should never result in focusing the always-at-the-top tab bar. - * Without this, navigating right from the option list focuses the tab bar, not the action buttons/description. - */ - @Redirect(method = {"nextFocusPathVaguelyInDirection", "nextFocusPathInDirection"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/events/ContainerEventHandler;children()Ljava/util/List;")) - private List modifyFocusCandidates(ContainerEventHandler instance, ScreenRectangle screenArea, ScreenDirection direction, @Nullable GuiEventListener focused, FocusNavigationEvent event) { - if (direction.getAxis() == ScreenAxis.HORIZONTAL) - return instance.children().stream().filter(child -> !(child instanceof TabNavigationBar)).toList(); - return instance.children(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java b/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java deleted file mode 100644 index c33eed7..0000000 --- a/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.isxander.yacl.mixin; - -import dev.isxander.yacl.gui.ImageRenderer; -import net.minecraft.client.Minecraft; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(Minecraft.class) -public class MinecraftMixin { - @Inject(method = "close", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/telemetry/ClientTelemetryManager;close()V")) - private void closeImages(CallbackInfo ci) { - ImageRenderer.closeAll(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl/mixin/OptionInstanceAccessor.java b/common/src/main/java/dev/isxander/yacl/mixin/OptionInstanceAccessor.java deleted file mode 100644 index 4eea9a9..0000000 --- a/common/src/main/java/dev/isxander/yacl/mixin/OptionInstanceAccessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.isxander.yacl.mixin; - -import net.minecraft.client.OptionInstance; -import org.jetbrains.annotations.ApiStatus; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@ApiStatus.Internal -@Mixin(OptionInstance.class) -public interface OptionInstanceAccessor { - @Accessor - T getInitialValue(); -} diff --git a/common/src/main/java/dev/isxander/yacl3/api/Binding.java b/common/src/main/java/dev/isxander/yacl3/api/Binding.java new file mode 100644 index 0000000..f41b78b --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/Binding.java @@ -0,0 +1,64 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.impl.GenericBindingImpl; +import dev.isxander.yacl3.mixin.OptionInstanceAccessor; +import net.minecraft.client.OptionInstance; +import org.apache.commons.lang3.Validate; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Controls modifying the bound option. + * Provides the default value, a setter and a getter. + */ +public interface Binding { + void setValue(T value); + + T getValue(); + + T defaultValue(); + + /** + * Creates a generic binding. + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @param setter should set the option to the supplied value + */ + static Binding generic(T def, Supplier getter, Consumer setter) { + Validate.notNull(def, "`def` must not be null"); + Validate.notNull(getter, "`getter` must not be null"); + Validate.notNull(setter, "`setter` must not be null"); + + return new GenericBindingImpl<>(def, getter, setter); + } + + /** + * Creates a {@link Binding} for Minecraft's {@link OptionInstance} + */ + static Binding minecraft(OptionInstance minecraftOption) { + Validate.notNull(minecraftOption, "`minecraftOption` must not be null"); + + return new GenericBindingImpl<>( + ((OptionInstanceAccessor) (Object) minecraftOption).getInitialValue(), + minecraftOption::get, + minecraftOption::set + ); + } + + /** + * Creates an immutable binding that has no default and cannot be modified. + * + * @param value the value for the binding + */ + static Binding immutable(T value) { + Validate.notNull(value, "`value` must not be null"); + + return new GenericBindingImpl<>( + value, + () -> value, + changed -> {} + ); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/ButtonOption.java b/common/src/main/java/dev/isxander/yacl3/api/ButtonOption.java new file mode 100644 index 0000000..943f9ac --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/ButtonOption.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.impl.ButtonOptionImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public interface ButtonOption extends Option> { + /** + * Action to be executed upon button press + */ + BiConsumer action(); + + static Builder createBuilder() { + return new ButtonOptionImpl.BuilderImpl(); + } + + interface Builder { + /** + * Sets the name to be used by the option. + * + * @see Option#name() + */ + Builder name(@NotNull Component name); + + Builder description(@NotNull OptionDescription description); + + Builder action(@NotNull BiConsumer action); + + /** + * Action to be executed upon button press + * + * @see ButtonOption#action() + */ + @Deprecated + Builder action(@NotNull Consumer action); + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + Builder available(boolean available); + + ButtonOption build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java b/common/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java new file mode 100644 index 0000000..d47b3ce --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java @@ -0,0 +1,94 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.impl.ConfigCategoryImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * Separates {@link Option}s or {@link OptionGroup}s into multiple distinct sections. + * Served to a user as a button in the left column, + * upon pressing, the options list is filled with options contained within this category. + */ +public interface ConfigCategory { + /** + * Name of category, displayed as a button on the left column. + */ + @NotNull Component name(); + + /** + * Gets every {@link OptionGroup} in this category. + */ + @NotNull ImmutableList groups(); + + /** + * Tooltip (or description) of the category. + * Rendered on hover. + */ + @NotNull Component tooltip(); + + /** + * Creates a builder to construct a {@link ConfigCategory} + */ + static Builder createBuilder() { + return new ConfigCategoryImpl.BuilderImpl(); + } + + interface Builder extends OptionAddable { + /** + * Sets name of the category + * + * @see ConfigCategory#name() + */ + Builder name(@NotNull Component name); + + /** + * Adds an option to the root group of the category. + * To add to another group, use {@link Builder#group(OptionGroup)}. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see ConfigCategory#groups() + * @see OptionGroup#isRoot() + */ + @Override + Builder option(@NotNull Option option); + + /** + * Adds multiple options to the root group of the category. + * To add to another group, use {@link Builder#groups(Collection)}. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see ConfigCategory#groups() + * @see OptionGroup#isRoot() + */ + @Override + Builder options(@NotNull Collection> options); + + /** + * Adds an option group. + * To add an option to the root group, use {@link Builder#option(Option)} + * To construct a group, use {@link OptionGroup#createBuilder()} + */ + Builder group(@NotNull OptionGroup group); + + /** + * Adds multiple option groups. + * To add multiple options to the root group, use {@link Builder#options(Collection)} + * To construct a group, use {@link OptionGroup#createBuilder()} + */ + Builder groups(@NotNull Collection groups); + + /** + * Sets the tooltip to be used by the category. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + Builder tooltip(@NotNull Component... tooltips); + + ConfigCategory build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/Controller.java b/common/src/main/java/dev/isxander/yacl3/api/Controller.java new file mode 100644 index 0000000..25e4465 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/Controller.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.network.chat.Component; + +/** + * Provides a widget to control the option. + */ +public interface Controller { + /** + * Gets the dedicated {@link Option} for this controller + */ + Option option(); + + /** + * Gets the formatted value based on {@link Option#pendingValue()} + */ + Component formatValue(); + + /** + * Provides a widget to display + * + * @param screen parent screen + */ + AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/LabelOption.java b/common/src/main/java/dev/isxander/yacl3/api/LabelOption.java new file mode 100644 index 0000000..a5f015e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/LabelOption.java @@ -0,0 +1,41 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.impl.LabelOptionImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * A label option is an easier way of creating a label with a {@link dev.isxander.yacl3.gui.controllers.LabelController}. + * This option is immutable and cannot be disabled. Tooltips are supported through + * {@link Component} styling. + */ +public interface LabelOption extends Option { + @NotNull Component label(); + + /** + * Creates a new label option with the given label, skipping a builder for ease. + */ + static LabelOption create(@NotNull Component label) { + return new LabelOptionImpl(label); + } + + static Builder createBuilder() { + return new LabelOptionImpl.BuilderImpl(); + } + + interface Builder { + /** + * Appends a line to the label + */ + Builder line(@NotNull Component line); + + /** + * Appends multiple lines to the label + */ + Builder lines(@NotNull Collection lines); + + LabelOption build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/ListOption.java b/common/src/main/java/dev/isxander/yacl3/api/ListOption.java new file mode 100644 index 0000000..8094ee7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/ListOption.java @@ -0,0 +1,146 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.impl.ListOptionImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * A list option that takes form as an option group for UX. + * You add this option through {@link ConfigCategory.Builder#group(OptionGroup)}. Do NOT add as an option. + * Users can add remove and reshuffle a list type. You can use any controller you wish, there are no dedicated + * controllers for list types. List options do not manipulate your list but get and set the list with a + * regular binding for simplicity. + * + * You may apply option flags like a normal option and collapse like a normal group, it is a merge of them both. + * Methods in this interface marked with {@link ApiStatus.Internal} should not be used, and could be subject to + * change at any time + * @param + */ +public interface ListOption extends OptionGroup, Option> { + @Override + @NotNull ImmutableList> options(); + + @ApiStatus.Internal + ListOptionEntry insertNewEntryToTop(); + + @ApiStatus.Internal + void insertEntry(int index, ListOptionEntry entry); + + @ApiStatus.Internal + int indexOf(ListOptionEntry entry); + + @ApiStatus.Internal + void removeEntry(ListOptionEntry entry); + + @ApiStatus.Internal + void addRefreshListener(Runnable changedListener); + + static Builder createBuilder() { + return new ListOptionImpl.BuilderImpl<>(); + } + + @Deprecated + static Builder createBuilder(Class typeClass) { + return createBuilder(); + } + + interface Builder { + /** + * Sets name of the list, for UX purposes, a name should always be given, + * but isn't enforced. + * + * @see ListOption#name() + */ + Builder name(@NotNull Component name); + + Builder description(@NotNull OptionDescription description); + + /** + * Sets the value that is used when creating new entries + */ + Builder initial(@NotNull T initialValue); + + Builder controller(@NotNull Function, ControllerBuilder> controller); + + /** + * Sets the controller for the option. + * This is how you interact and change the options. + * + * @see dev.isxander.yacl3.gui.controllers + */ + Builder customController(@NotNull Function, Controller> control); + + /** + * Sets the binding for the option. + * Used for default, getter and setter. + * + * @see Binding + */ + Builder binding(@NotNull Binding> binding); + + /** + * Sets the binding for the option. + * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @param setter should set the option to the supplied value + * @see Binding + */ + Builder binding(@NotNull List def, @NotNull Supplier<@NotNull List> getter, @NotNull Consumer<@NotNull List> setter); + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + Builder available(boolean available); + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + Builder flag(@NotNull OptionFlag... flag); + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + Builder flags(@NotNull Collection flags); + + /** + * Dictates if the group should be collapsed by default. + * If not set, it will not be collapsed by default. + * + * @see OptionGroup#collapsed() + */ + Builder collapsed(boolean collapsible); + + /** + * Adds a listener to the option. Invoked upon changing any of the list's entries. + * + * @see Option#addListener(BiConsumer) + */ + ListOption.Builder listener(@NotNull BiConsumer>, List> listener); + + /** + * Adds multiple listeners to the option. Invoked upon changing of any of the list's entries. + * + * @see Option#addListener(BiConsumer) + */ + ListOption.Builder listeners(@NotNull Collection>, List>> listeners); + + ListOption build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/ListOptionEntry.java b/common/src/main/java/dev/isxander/yacl3/api/ListOptionEntry.java new file mode 100644 index 0000000..23ec657 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/ListOptionEntry.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.NotNull; + +public interface ListOptionEntry extends Option { + ListOption parentGroup(); + + @Override + default @NotNull ImmutableSet flags() { + return parentGroup().flags(); + } + + @Override + default boolean available() { + return parentGroup().available(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/NameableEnum.java b/common/src/main/java/dev/isxander/yacl3/api/NameableEnum.java new file mode 100644 index 0000000..5a50207 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/NameableEnum.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api; + +import net.minecraft.network.chat.Component; + +/** + * Used for the default value formatter of {@link dev.isxander.yacl3.gui.controllers.cycling.EnumController} + */ +public interface NameableEnum { + Component getDisplayName(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/Option.java b/common/src/main/java/dev/isxander/yacl3/api/Option.java new file mode 100644 index 0000000..31b7756 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/Option.java @@ -0,0 +1,223 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.impl.OptionImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface Option { + /** + * Name of the option + */ + @NotNull Component name(); + + @NotNull OptionDescription description(); + + /** + * Tooltip (or description) of the option. + * Rendered on hover. + */ + @Deprecated + @NotNull Component tooltip(); + + /** + * Widget provider for a type of option. + * + * @see dev.isxander.yacl3.gui.controllers + */ + @NotNull Controller controller(); + + /** + * Binding for the option. + * Controls setting, getting and default value. + * + * @see Binding + */ + @NotNull Binding binding(); + + /** + * If the option can be configured + */ + boolean available(); + + /** + * Sets if the option can be configured after being built + * + * @see Option#available() + */ + void setAvailable(boolean available); + + /** + * Tasks that needs to be executed upon applying changes. + */ + @NotNull ImmutableSet flags(); + + /** + * Checks if the pending value is not equal to the current set value + */ + boolean changed(); + + /** + * Value in the GUI, ready to set the actual bound value or be undone. + */ + @NotNull T pendingValue(); + + /** + * Sets the pending value + */ + void requestSet(T value); + + /** + * Applies the pending value to the bound value. + * Cannot be undone. + * + * @return if there were changes to apply {@link Option#changed()} + */ + boolean applyValue(); + + /** + * Sets the pending value to the bound value. + */ + void forgetPendingValue(); + + /** + * Sets the pending value to the default bound value. + */ + void requestSetDefault(); + + /** + * Checks if the current pending value is equal to its default value + */ + boolean isPendingValueDefault(); + + default boolean canResetToDefault() { + return true; + } + + /** + * Adds a listener for when the pending value changes + */ + void addListener(BiConsumer, T> changedListener); + + static Builder createBuilder() { + return new OptionImpl.BuilderImpl<>(); + } + + /** + * Creates a builder to construct an {@link Option} + * + * @param type of the option's value + * @param typeClass used to capture the type + */ + @Deprecated + static Builder createBuilder(Class typeClass) { + return createBuilder(); + } + + interface Builder { + /** + * Sets the name to be used by the option. + * + * @see Option#name() + */ + Builder name(@NotNull Component name); + + /** + * Sets the description to be used by the option. + * @see OptionDescription + * @param description the static description. + * @return this builder + */ + Builder description(@NotNull OptionDescription description); + + /** + * Sets the function to get the description by the option's current value. + * + * @see OptionDescription + * @param descriptionFunction the function to get the description by the option's current value. + * @return this builder + */ + Builder description(@NotNull Function descriptionFunction); + + Builder controller(@NotNull Function, ControllerBuilder> controllerBuilder); + + /** + * Sets the controller for the option. + * This is how you interact and change the options. + * + * @see dev.isxander.yacl3.gui.controllers + */ + Builder customController(@NotNull Function, Controller> control); + + /** + * Sets the binding for the option. + * Used for default, getter and setter. + * + * @see Binding + */ + Builder binding(@NotNull Binding binding); + + /** + * Sets the binding for the option. + * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @param setter should set the option to the supplied value + * @see Binding + */ + Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter); + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + Builder available(boolean available); + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + Builder flag(@NotNull OptionFlag... flag); + + /** + * Adds a flag to the option. + * Upon applying changes, all flags are executed. + * {@link Option#flags()} + */ + Builder flags(@NotNull Collection flags); + + /** + * Instantly invokes the binder's setter when modified in the GUI. + * Prevents the user from undoing the change + *

+ * Does not support {@link Option#flags()}! + */ + Builder instant(boolean instant); + + /** + * Adds a listener to the option. Invoked upon changing the pending value. + * + * @see Option#addListener(BiConsumer) + */ + Builder listener(@NotNull BiConsumer, T> listener); + + /** + * Adds multiple listeners to the option. Invoked upon changing the pending value. + * + * @see Option#addListener(BiConsumer) + */ + Builder listeners(@NotNull Collection, T>> listeners); + + Option build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/OptionAddable.java b/common/src/main/java/dev/isxander/yacl3/api/OptionAddable.java new file mode 100644 index 0000000..97ab352 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/OptionAddable.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl3.api; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +public interface OptionAddable { + /** + * Adds an option to an abstract builder. + * To construct an option, use {@link Option#createBuilder(Class)} + */ + OptionAddable option(@NotNull Option option); + + /** + * Adds multiple options to an abstract builder. + * To construct an option, use {@link Option#createBuilder(Class)} + */ + OptionAddable options(@NotNull Collection> options); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/OptionDescription.java b/common/src/main/java/dev/isxander/yacl3/api/OptionDescription.java new file mode 100644 index 0000000..40f1d68 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/OptionDescription.java @@ -0,0 +1,161 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.gui.ImageRenderer; +import dev.isxander.yacl3.impl.OptionDescriptionImpl; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +/** + * Provides all information for the description panel in the GUI. + * This provides no functional benefit, and is purely for UX. + */ +public interface OptionDescription { + /** + * The description of the option, this is automatically wrapped and supports all styling, + * including {@link net.minecraft.network.chat.ClickEvent}s and {@link net.minecraft.network.chat.HoverEvent}s. + * @return The description of the option, with all lines merged with \n. + */ + Component text(); + + /** + * The image to display with the description. If the Optional is empty, no image has been provided. + * Usually, the image renderers are constructed asynchronously, so this method returns a {@link CompletableFuture}. + *

+ * Image renderers are cached throughout the whole lifecycle of the game, and should not be generated more than once + * per image. See {@link ImageRenderer#getOrMakeAsync(ResourceLocation, Supplier)} for implementation details. + */ + CompletableFuture> image(); + + /** + * @return a new builder for an {@link OptionDescription}. + */ + static Builder createBuilder() { + return new OptionDescriptionImpl.BuilderImpl(); + } + + static OptionDescription of(Component... description) { + return createBuilder().text(description).build(); + } + + OptionDescription EMPTY = new OptionDescriptionImpl(CommonComponents.EMPTY, CompletableFuture.completedFuture(Optional.empty())); + + interface Builder { + /** + * Appends lines to the main description of the option. This can be called multiple times. + * On {@link Builder#build()}, the lines are merged with \n. + * @see OptionDescription#text() + * + * @param description the lines to append to the description. + * @return this builder + */ + Builder text(Component... description); + + /** + * Appends lines to the main description of the option. This can be called multiple times. + * On {@link Builder#build()}, the lines are merged with \n. + * @see OptionDescription#text() + * + * @param lines the lines to append to the description. + * @return this builder + */ + Builder text(Collection lines); + + /** + * Sets a static image to display with the description. This is backed by a regular minecraft resource + * in your mod's /assets folder. + * + * @param image the location of the image to display from the resource manager + * @param width the width of the texture + * @param height the height of the texture + * @return this builder + */ + Builder image(ResourceLocation image, int width, int height); + + /** + * Sets a static image to display with the description. This is backed by a regular minecraft resource + * in your mod's /assets folder. This overload method allows you to specify a subsection of the texture to render. + * + * @param image the location of the image to display from the resource manager + * @param u the u coordinate + * @param v the v coordinate + * @param width the width of the subsection + * @param height the height of the subsection + * @param textureWidth the width of the whole texture file + * @param textureHeight the height of whole texture file + * @return this builder + */ + Builder image(ResourceLocation image, float u, float v, int width, int height, int textureWidth, int textureHeight); + + /** + * Sets a static image to display with the description. This is backed by a file on disk. + * The width and height is automatically determined from the image processing. + * + * @param path the absolute path to the image file + * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar + * @return this builder + */ + Builder image(Path path, ResourceLocation uniqueLocation); + + /** + * Sets a static OR ANIMATED webP image to display with the description. This is backed by a regular minecraft resource + * in your mod's /assets folder. + * + * @param image the location of the image to display from the resource manager + * @return this builder + */ + Builder webpImage(ResourceLocation image); + + /** + * Sets a static OR ANIMATED webP image to display with the description. This is backed by a file on disk. + * The width and height is automatically determined from the image processing. + * + * @param path the absolute path to the image file + * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar + * @return this builder + */ + Builder webpImage(Path path, ResourceLocation uniqueLocation); + + /** + * Sets a custom image renderer to display with the description. + * This is useful for rendering other abstract things relevant to your mod. + *

+ * However, THIS IS NOT API SAFE! As part of the gui package, things + * may change that could break compatibility with future versions of YACL. + * A helpful utility (that is also not API safe) is {@link ImageRenderer#getOrMakeAsync(ResourceLocation, Supplier)} + * which will cache the image renderer for the whole game lifecycle and construct it asynchronously to the render thread. + * @param image the image renderer to display + * @return this builder + */ + Builder customImage(CompletableFuture> image); + + /** + * Sets an animated GIF image to display with the description. This is backed by a regular minecraft resource + * in your mod's /assets folder. + * + * @param image the location of the image to display from the resource manager + * @return this builder + */ + @Deprecated + Builder gifImage(ResourceLocation image); + + /** + * Sets an animated GIF image to display with the description. This is backed by a file on disk. + * The width and height is automatically determined from the image processing. + * + * @param path the absolute path to the image file + * @param uniqueLocation the unique identifier for the image, used for caching and resource manager registrar + * @return this builder + */ + @Deprecated + Builder gifImage(Path path, ResourceLocation uniqueLocation); + + OptionDescription build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/OptionFlag.java b/common/src/main/java/dev/isxander/yacl3/api/OptionFlag.java new file mode 100644 index 0000000..6f35495 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/OptionFlag.java @@ -0,0 +1,23 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.gui.RequireRestartScreen; +import net.minecraft.client.Minecraft; + +import java.util.function.Consumer; + +/** + * Code that is executed upon certain options being applied. + * Each flag is executed only once per save, no matter the amount of options with the flag. + */ +@FunctionalInterface +public interface OptionFlag extends Consumer { + /** Warns the user that a game restart is required for the changes to take effect */ + OptionFlag GAME_RESTART = client -> client.setScreen(new RequireRestartScreen(client.screen)); + + /** Reloads chunks upon applying (F3+A) */ + OptionFlag RELOAD_CHUNKS = client -> client.levelRenderer.allChanged(); + + OptionFlag WORLD_RENDER_UPDATE = client -> client.levelRenderer.needsUpdate(); + + OptionFlag ASSET_RELOAD = Minecraft::delayTextureReload; +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/OptionGroup.java b/common/src/main/java/dev/isxander/yacl3/api/OptionGroup.java new file mode 100644 index 0000000..ff31966 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/OptionGroup.java @@ -0,0 +1,90 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.impl.OptionGroupImpl; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * Serves as a separator between multiple chunks of options + * that may be too similar or too few to be placed in a separate {@link ConfigCategory}. + * Or maybe you just want your config to feel less dense. + */ +public interface OptionGroup { + /** + * Name of the option group, displayed as a separator in the option lists. + * Can be empty. + */ + Component name(); + + OptionDescription description(); + + /** + * Tooltip displayed on hover. + */ + @Deprecated + Component tooltip(); + + /** + * List of all options in the group + */ + @NotNull ImmutableList> options(); + + /** + * Dictates if the group should be collapsed by default. + */ + boolean collapsed(); + + /** + * Always false when using the {@link Builder} + * used to not render the separator if true + */ + boolean isRoot(); + + /** + * Creates a builder to construct a {@link OptionGroup} + */ + static Builder createBuilder() { + return new OptionGroupImpl.BuilderImpl(); + } + + interface Builder extends OptionAddable { + /** + * Sets name of the group, can be {@link Component#empty()} to just separate options, like sodium. + * + * @see OptionGroup#name() + */ + Builder name(@NotNull Component name); + + Builder description(@NotNull OptionDescription description); + + /** + * Adds an option to group. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see OptionGroup#options() + */ + @Override + Builder option(@NotNull Option option); + + /** + * Adds multiple options to group. + * To construct an option, use {@link Option#createBuilder(Class)} + * + * @see OptionGroup#options() + */ + @Override + Builder options(@NotNull Collection> options); + + /** + * Dictates if the group should be collapsed by default + * + * @see OptionGroup#collapsed() + */ + Builder collapsed(boolean collapsible); + + OptionGroup build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/PlaceholderCategory.java b/common/src/main/java/dev/isxander/yacl3/api/PlaceholderCategory.java new file mode 100644 index 0000000..6eb82c5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/PlaceholderCategory.java @@ -0,0 +1,55 @@ +package dev.isxander.yacl3.api; + +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.impl.PlaceholderCategoryImpl; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiFunction; + +/** + * A placeholder category that actually just opens another screen, + * instead of displaying options. + *

+ * Use of this is discouraged, as it is not very user-friendly and navigating to a placeholder + * tab that opens another screen is not very intuitive, making keyboard navigation impossible. + */ +public interface PlaceholderCategory extends ConfigCategory { + /** + * Function to create a screen to open upon changing to this category + */ + BiFunction screen(); + + static Builder createBuilder() { + return new PlaceholderCategoryImpl.BuilderImpl(); + } + + interface Builder { + /** + * Sets name of the category + * + * @see ConfigCategory#name() + */ + Builder name(@NotNull Component name); + + /** + * Sets the tooltip to be used by the category. + * Can be invoked twice to append more lines. + * No need to wrap the Component yourself, the gui does this itself. + * + * @param tooltips Component lines - merged with a new-line on {@link dev.isxander.yacl3.api.PlaceholderCategory.Builder#build()}. + */ + Builder tooltip(@NotNull Component... tooltips); + + /** + * Screen to open upon selecting this category + * + * @see PlaceholderCategory#screen() + */ + Builder screen(@NotNull BiFunction screenFunction); + + PlaceholderCategory build(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/YetAnotherConfigLib.java b/common/src/main/java/dev/isxander/yacl3/api/YetAnotherConfigLib.java new file mode 100644 index 0000000..15ce5bc --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/YetAnotherConfigLib.java @@ -0,0 +1,107 @@ +package dev.isxander.yacl3.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.config.ConfigInstance; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.impl.YetAnotherConfigLibImpl; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.function.Consumer; + +/** + * Main class of the mod. + * Contains all data and used to provide a {@link Screen} + */ +public interface YetAnotherConfigLib { + /** + * Title of the GUI. Only used for Minecraft narration. + */ + Component title(); + + /** + * Gets all config categories. + */ + ImmutableList categories(); + + /** + * Ran when changes are saved. Can be used to save config to a file etc. + */ + Runnable saveFunction(); + + /** + * Ran every time the YACL screen initialises. Can be paired with FAPI to add custom widgets. + */ + Consumer initConsumer(); + + /** + * Generates a Screen to display based on this instance. + * + * @param parent parent screen to open once closed + */ + Screen generateScreen(@Nullable Screen parent); + + /** + * Creates a builder to construct YACL + */ + static Builder createBuilder() { + return new YetAnotherConfigLibImpl.BuilderImpl(); + } + + /** + * Creates an instance using a {@link ConfigInstance} which autofills the save() builder method. + * This also takes an easy functional interface that provides defaults and config to help build YACL bindings. + */ + static YetAnotherConfigLib create(ConfigInstance configInstance, ConfigBackedBuilder builder) { + return builder.build(configInstance.getDefaults(), configInstance.getConfig(), createBuilder().save(configInstance::save)).build(); + } + + interface Builder { + /** + * Sets title of GUI for Minecraft narration + * + * @see YetAnotherConfigLib#title() + */ + Builder title(@NotNull Component title); + + /** + * Adds a new category. + * To create a category you need to use {@link ConfigCategory#createBuilder()} + * + * @see YetAnotherConfigLib#categories() + */ + Builder category(@NotNull ConfigCategory category); + + /** + * Adds multiple categories at once. + * To create a category you need to use {@link ConfigCategory#createBuilder()} + * + * @see YetAnotherConfigLib#categories() + */ + Builder categories(@NotNull Collection categories); + + /** + * Used to define a save function for when user clicks the Save Changes button + * + * @see YetAnotherConfigLib#saveFunction() + */ + Builder save(@NotNull Runnable saveFunction); + + /** + * Defines a consumer that is accepted every time the YACL screen initialises + * + * @see YetAnotherConfigLib#initConsumer() + */ + Builder screenInit(@NotNull Consumer initConsumer); + + YetAnotherConfigLib build(); + } + + @FunctionalInterface + interface ConfigBackedBuilder { + YetAnotherConfigLib.Builder build(T defaults, T config, YetAnotherConfigLib.Builder builder); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/BooleanControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/BooleanControllerBuilder.java new file mode 100644 index 0000000..88f9a77 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/BooleanControllerBuilder.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.BooleanControllerBuilderImpl; + +public interface BooleanControllerBuilder extends ValueFormattableController { + BooleanControllerBuilder coloured(boolean coloured); + + BooleanControllerBuilder onOffFormatter(); + BooleanControllerBuilder yesNoFormatter(); + BooleanControllerBuilder trueFalseFormatter(); + + static BooleanControllerBuilder create(Option option) { + return new BooleanControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/ColorControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/ColorControllerBuilder.java new file mode 100644 index 0000000..8e442ff --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/ColorControllerBuilder.java @@ -0,0 +1,14 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.ColorControllerBuilderImpl; + +import java.awt.Color; + +public interface ColorControllerBuilder extends ControllerBuilder { + ColorControllerBuilder allowAlpha(boolean allowAlpha); + + static ColorControllerBuilder create(Option option) { + return new ColorControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/ControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/ControllerBuilder.java new file mode 100644 index 0000000..0cd3a55 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/ControllerBuilder.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Controller; +import org.jetbrains.annotations.ApiStatus; + +public interface ControllerBuilder { + @ApiStatus.Internal + Controller build(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/CyclingListControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/CyclingListControllerBuilder.java new file mode 100644 index 0000000..8c9ea91 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/CyclingListControllerBuilder.java @@ -0,0 +1,15 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.CyclingListControllerBuilderImpl; + +public interface CyclingListControllerBuilder extends ValueFormattableController> { + @SuppressWarnings("unchecked") + CyclingListControllerBuilder values(T... values); + + CyclingListControllerBuilder values(Iterable values); + + static CyclingListControllerBuilder create(Option option) { + return new CyclingListControllerBuilderImpl<>(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleFieldControllerBuilder.java new file mode 100644 index 0000000..db4af94 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleFieldControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.DoubleFieldControllerBuilderImpl; + +public interface DoubleFieldControllerBuilder extends NumberFieldControllerBuilder { + static DoubleFieldControllerBuilder create(Option option) { + return new DoubleFieldControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleSliderControllerBuilder.java new file mode 100644 index 0000000..7e4b6f9 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/DoubleSliderControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.DoubleSliderControllerBuilderImpl; + +public interface DoubleSliderControllerBuilder extends SliderControllerBuilder { + static DoubleSliderControllerBuilder create(Option option) { + return new DoubleSliderControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/EnumControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/EnumControllerBuilder.java new file mode 100644 index 0000000..decb8f9 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/EnumControllerBuilder.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.EnumControllerBuilderImpl; + +public interface EnumControllerBuilder> extends ValueFormattableController> { + EnumControllerBuilder enumClass(Class enumClass); + + static > EnumControllerBuilder create(Option option) { + return new EnumControllerBuilderImpl<>(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/FloatFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/FloatFieldControllerBuilder.java new file mode 100644 index 0000000..de81837 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/FloatFieldControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.FloatFieldControllerBuilderImpl; + +public interface FloatFieldControllerBuilder extends NumberFieldControllerBuilder { + static FloatFieldControllerBuilder create(Option option) { + return new FloatFieldControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/FloatSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/FloatSliderControllerBuilder.java new file mode 100644 index 0000000..2a04dde --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/FloatSliderControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.FloatSliderControllerBuilderImpl; + +public interface FloatSliderControllerBuilder extends SliderControllerBuilder { + static FloatSliderControllerBuilder create(Option option) { + return new FloatSliderControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerFieldControllerBuilder.java new file mode 100644 index 0000000..1e31fac --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerFieldControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.IntegerFieldControllerBuilderImpl; + +public interface IntegerFieldControllerBuilder extends NumberFieldControllerBuilder { + static IntegerFieldControllerBuilder create(Option option) { + return new IntegerFieldControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerSliderControllerBuilder.java new file mode 100644 index 0000000..11e089a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/IntegerSliderControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.IntegerSliderControllerBuilderImpl; + +public interface IntegerSliderControllerBuilder extends SliderControllerBuilder { + static IntegerSliderControllerBuilder create(Option option) { + return new IntegerSliderControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/LongFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/LongFieldControllerBuilder.java new file mode 100644 index 0000000..c53b464 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/LongFieldControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.LongFieldControllerBuilderImpl; + +public interface LongFieldControllerBuilder extends NumberFieldControllerBuilder { + static LongFieldControllerBuilder create(Option option) { + return new LongFieldControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/LongSliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/LongSliderControllerBuilder.java new file mode 100644 index 0000000..fc09423 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/LongSliderControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.LongSliderControllerBuilderImpl; + +public interface LongSliderControllerBuilder extends SliderControllerBuilder { + static LongSliderControllerBuilder create(Option option) { + return new LongSliderControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/NumberFieldControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/NumberFieldControllerBuilder.java new file mode 100644 index 0000000..b5cfa1f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/NumberFieldControllerBuilder.java @@ -0,0 +1,7 @@ +package dev.isxander.yacl3.api.controller; + +public interface NumberFieldControllerBuilder> extends ValueFormattableController { + B min(T min); + B max(T max); + B range(T min, T max); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/SliderControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/SliderControllerBuilder.java new file mode 100644 index 0000000..2fb3fec --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/SliderControllerBuilder.java @@ -0,0 +1,6 @@ +package dev.isxander.yacl3.api.controller; + +public interface SliderControllerBuilder> extends ValueFormattableController { + B range(T min, T max); + B step(T step); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/StringControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/StringControllerBuilder.java new file mode 100644 index 0000000..5e2f8c6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/StringControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.StringControllerBuilderImpl; + +public interface StringControllerBuilder extends ControllerBuilder { + static StringControllerBuilder create(Option option) { + return new StringControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/TickBoxControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/TickBoxControllerBuilder.java new file mode 100644 index 0000000..71a2762 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/TickBoxControllerBuilder.java @@ -0,0 +1,10 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.TickBoxControllerBuilderImpl; + +public interface TickBoxControllerBuilder extends ControllerBuilder { + static TickBoxControllerBuilder create(Option option) { + return new TickBoxControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/ValueFormattableController.java b/common/src/main/java/dev/isxander/yacl3/api/controller/ValueFormattableController.java new file mode 100644 index 0000000..af55e55 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/ValueFormattableController.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.api.controller; + +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public interface ValueFormattableController> extends ControllerBuilder { + B valueFormatter(Function formatter); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/utils/Dimension.java b/common/src/main/java/dev/isxander/yacl3/api/utils/Dimension.java new file mode 100644 index 0000000..ec09238 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/utils/Dimension.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl3.api.utils; + +import dev.isxander.yacl3.impl.utils.DimensionIntegerImpl; + +public interface Dimension { + T x(); + T y(); + + T width(); + T height(); + + T xLimit(); + T yLimit(); + + T centerX(); + T centerY(); + + boolean isPointInside(T x, T y); + + MutableDimension clone(); + + Dimension withX(T x); + Dimension withY(T y); + Dimension withWidth(T width); + Dimension withHeight(T height); + + Dimension moved(T x, T y); + Dimension expanded(T width, T height); + + static MutableDimension ofInt(int x, int y, int width, int height) { + return new DimensionIntegerImpl(x, y, width, height); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/utils/MutableDimension.java b/common/src/main/java/dev/isxander/yacl3/api/utils/MutableDimension.java new file mode 100644 index 0000000..f551232 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/utils/MutableDimension.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.api.utils; + +public interface MutableDimension extends Dimension { + MutableDimension setX(T x); + MutableDimension setY(T y); + MutableDimension setWidth(T width); + MutableDimension setHeight(T height); + + MutableDimension move(T x, T y); + MutableDimension expand(T width, T height); +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/utils/OptionUtils.java b/common/src/main/java/dev/isxander/yacl3/api/utils/OptionUtils.java new file mode 100644 index 0000000..cf33f0f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/utils/OptionUtils.java @@ -0,0 +1,39 @@ +package dev.isxander.yacl3.api.utils; + +import dev.isxander.yacl3.api.*; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class OptionUtils { + /** + * Consumes all options, ignoring groups and categories. + * When consumer returns true, this function stops iterating. + */ + public static void consumeOptions(YetAnotherConfigLib yacl, Function, Boolean> consumer) { + for (ConfigCategory category : yacl.categories()) { + for (OptionGroup group : category.groups()) { + if (group instanceof ListOption list) { + if (consumer.apply(list)) return; + } else { + for (Option option : group.options()) { + if (consumer.apply(option)) return; + } + } + + } + } + } + + /** + * Consumes all options, ignoring groups and categories. + * + * @see OptionUtils#consumeOptions(YetAnotherConfigLib, Function) + */ + public static void forEachOptions(YetAnotherConfigLib yacl, Consumer> consumer) { + consumeOptions(yacl, (opt) -> { + consumer.accept(opt); + return false; + }); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java b/common/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java new file mode 100644 index 0000000..1af09d4 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEntry { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java b/common/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java new file mode 100644 index 0000000..e84f6c1 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java @@ -0,0 +1,48 @@ +package dev.isxander.yacl3.config; + +import java.lang.reflect.InvocationTargetException; + +/** + * Responsible for handing the actual config data type. + * Holds the instance along with a final default instance + * to reference default values for options and should not be changed. + * + * Abstract methods to save and load the class, implementations are responsible for + * how it saves and load. + * + * @param config data type + */ +public abstract class ConfigInstance { + private final Class configClass; + private final T defaultInstance; + private T instance; + + public ConfigInstance(Class configClass) { + this.configClass = configClass; + + try { + this.defaultInstance = this.instance = configClass.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new IllegalStateException(String.format("Could not create default instance of config for %s. Make sure there is a default constructor!", this.configClass.getSimpleName())); + } + } + + public abstract void save(); + public abstract void load(); + + public T getConfig() { + return this.instance; + } + + protected void setConfig(T instance) { + this.instance = instance; + } + + public T getDefaults() { + return this.defaultInstance; + } + + public Class getConfigClass() { + return this.configClass; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java b/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java new file mode 100644 index 0000000..7dd68b3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java @@ -0,0 +1,212 @@ +package dev.isxander.yacl3.config; + +import com.google.gson.*; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.function.UnaryOperator; + +/** + * Uses GSON to serialize and deserialize config data from JSON to a file. + * + * Only fields annotated with {@link ConfigEntry} are included in the JSON. + * {@link Component}, {@link Style} and {@link Color} have default type adapters, so there is no need to provide them in your GSON instance. + * GSON is automatically configured to format fields as {@code lower_camel_case}. + * + * @param config data type + */ +public class GsonConfigInstance extends ConfigInstance { + private final Gson gson; + private final Path path; + + @Deprecated + public GsonConfigInstance(Class configClass, Path path) { + this(configClass, path, new GsonBuilder()); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, Gson gson) { + this(configClass, path, gson.newBuilder()); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, UnaryOperator builder) { + this(configClass, path, builder.apply(new GsonBuilder())); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, GsonBuilder builder) { + super(configClass); + this.path = path; + this.gson = builder + .setExclusionStrategies(new ConfigExclusionStrategy()) + .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) + .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .serializeNulls() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + } + + private GsonConfigInstance(Class configClass, Path path, Gson gson, boolean fromBuilder) { + super(configClass); + this.path = path; + this.gson = gson; + } + + @Override + public void save() { + try { + YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName()); + Files.writeString(path, gson.toJson(getConfig()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void load() { + try { + if (Files.notExists(path)) { + save(); + return; + } + + YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName()); + setConfig(gson.fromJson(Files.readString(path), getConfigClass())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Path getPath() { + return this.path; + } + + private static class ConfigExclusionStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return fieldAttributes.getAnnotation(ConfigEntry.class) == null; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return false; + } + } + + public static class ColorTypeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public Color deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return new Color(jsonElement.getAsInt(), true); + } + + @Override + public JsonElement serialize(Color color, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(color.getRGB()); + } + } + + /** + * Creates a builder for a GSON config instance. + * @param configClass the config class + * @return a new builder + * @param the config type + */ + public static Builder createBuilder(Class configClass) { + return new Builder<>(configClass); + } + + public static class Builder { + private final Class configClass; + private Path path; + private UnaryOperator gsonBuilder = builder -> builder + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .serializeNulls() + .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) + .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()); + + private Builder(Class configClass) { + this.configClass = configClass; + } + + /** + * Sets the file path to save and load the config from. + */ + public Builder setPath(Path path) { + this.path = path; + return this; + } + + /** + * Sets the GSON instance to use. Overrides all YACL defaults such as: + *

    + *
  • lower_camel_case field naming policy
  • + *
  • null serialization
  • + *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • + *
+ * Still respects the exclusion strategy to only serialize {@link ConfigEntry} + * but these can be added to with setExclusionStrategies. + * + * @param gsonBuilder gson builder to use + */ + public Builder overrideGsonBuilder(GsonBuilder gsonBuilder) { + this.gsonBuilder = builder -> gsonBuilder; + return this; + } + + /** + * Sets the GSON instance to use. Overrides all YACL defaults such as: + *
    + *
  • lower_camel_case field naming policy
  • + *
  • null serialization
  • + *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • + *
+ * Still respects the exclusion strategy to only serialize {@link ConfigEntry} + * but these can be added to with setExclusionStrategies. + * + * @param gson gson instance to be converted to a builder + */ + public Builder overrideGsonBuilder(Gson gson) { + return this.overrideGsonBuilder(gson.newBuilder()); + } + + /** + * Appends extra configuration to a GSON builder. + * This is the intended way to add functionality to the GSON instance. + *

+ * By default, YACL sets the GSON with the following options: + *

    + *
  • lower_camel_case field naming policy
  • + *
  • null serialization
  • + *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • + *
+ * + * @param gsonBuilder the function to apply to the builder + */ + public Builder appendGsonBuilder(UnaryOperator gsonBuilder) { + this.gsonBuilder = builder -> gsonBuilder.apply(this.gsonBuilder.apply(builder)); + return this; + } + + /** + * Builds the config instance. + * @return the built config instance + */ + public GsonConfigInstance build() { + UnaryOperator gsonBuilder = builder -> this.gsonBuilder.apply(builder) + .addSerializationExclusionStrategy(new ConfigExclusionStrategy()) + .addDeserializationExclusionStrategy(new ConfigExclusionStrategy()); + + return new GsonConfigInstance<>(configClass, path, gsonBuilder.apply(new GsonBuilder()).create(), true); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java new file mode 100644 index 0000000..8b9779c --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java @@ -0,0 +1,94 @@ +package dev.isxander.yacl3.gui; + +import dev.isxander.yacl3.api.utils.Dimension; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; + +import java.awt.*; + +public abstract class AbstractWidget implements GuiEventListener, Renderable, NarratableEntry { + protected final Minecraft client = Minecraft.getInstance(); + protected final Font textRenderer = client.font; + protected final int inactiveColor = 0xFFA0A0A0; + + private Dimension dim; + + public AbstractWidget(Dimension dim) { + this.dim = dim; + } + + public boolean canReset() { + return false; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + if (dim == null) return false; + return this.dim.isPointInside((int) mouseX, (int) mouseY); + } + + public void setDimension(Dimension dim) { + this.dim = dim; + } + + public Dimension getDimension() { + return dim; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.NONE; + } + + public void unfocus() { + + } + + public boolean matchesSearch(String query) { + return true; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + + } + + protected void drawButtonRect(GuiGraphics graphics, int x1, int y1, int x2, int y2, boolean hovered, boolean enabled) { + if (x1 > x2) { + int xx1 = x1; + x1 = x2; + x2 = xx1; + } + if (y1 > y2) { + int yy1 = y1; + y1 = y2; + y2 = yy1; + } + int width = x2 - x1; + int height = y2 - y1; + + int i = !enabled ? 0 : hovered ? 2 : 1; + graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); + graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); + } + + protected int multiplyColor(int hex, float amount) { + Color color = new Color(hex, true); + + return new Color(Math.max((int)(color.getRed() * amount), 0), + Math.max((int)(color.getGreen() * amount), 0), + Math.max((int)(color.getBlue() * amount), 0), + color.getAlpha()).getRGB(); + } + + public void playDownSound() { + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java b/common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java new file mode 100644 index 0000000..6ad72e8 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.gui; + +import dev.isxander.yacl3.api.OptionDescription; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; + +public record DescriptionWithName(Component name, OptionDescription description) { + public static DescriptionWithName of(Component name, OptionDescription description) { + return new DescriptionWithName(name.copy().withStyle(ChatFormatting.BOLD), description); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java b/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java new file mode 100644 index 0000000..e3944ee --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java @@ -0,0 +1,222 @@ +package dev.isxander.yacl3.gui; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.layouts.LayoutElement; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class ElementListWidgetExt> extends ContainerObjectSelectionList implements LayoutElement { + protected int x, y; + + private double smoothScrollAmount = getScrollAmount(); + private boolean returnSmoothAmount = false; + private final boolean doSmoothScrolling; + + public ElementListWidgetExt(Minecraft client, int x, int y, int width, int height, boolean smoothScrolling) { + super(client, width, height, y, y + height, 22); + this.x = this.x0 = x; + this.y = y; + this.x1 = this.x0 + width; + this.doSmoothScrolling = smoothScrolling; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + // default implementation bases scroll step from total height of entries, this is constant + this.setScrollAmount(this.getScrollAmount() - amount * 20); + return true; + } + + @Override + protected void renderBackground(GuiGraphics graphics) { + // render transparent background if in-game. + setRenderBackground(true); + setRenderTopAndBottom(false); + } + + @Override + protected int getScrollbarPosition() { + // default implementation does not respect left/right + return this.x1 - 2; + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + smoothScrollAmount = Mth.lerp(Minecraft.getInstance().getDeltaFrameTime() * 0.5, smoothScrollAmount, getScrollAmount()); + returnSmoothAmount = true; + + graphics.enableScissor(x0, y0, x1, y1); + + super.render(graphics, mouseX, mouseY, delta); + + graphics.disableScissor(); + + returnSmoothAmount = false; + } + + public void updateDimensions(ScreenRectangle rectangle) { + this.x0 = rectangle.left(); + this.y0 = rectangle.top(); + this.x1 = rectangle.right(); + this.y1 = rectangle.bottom(); + this.width = rectangle.width(); + this.height = rectangle.height(); + } + + /** + * awful code to only use smooth scroll state when rendering, + * not other code that needs target scroll amount + */ + @Override + public double getScrollAmount() { + if (returnSmoothAmount && doSmoothScrolling) + return smoothScrollAmount; + + return super.getScrollAmount(); + } + + protected void resetSmoothScrolling() { + this.smoothScrollAmount = getScrollAmount(); + } + + @Nullable + @Override + protected E getEntryAtPosition(double x, double y) { + y += getScrollAmount(); + + if (x < this.x0 || x > this.x1) + return null; + + int currentY = this.y0 - headerHeight + 4; + for (E entry : children()) { + if (y >= currentY && y <= currentY + entry.getItemHeight()) { + return entry; + } + + currentY += entry.getItemHeight(); + } + + return null; + } + + /* + below code is licensed from cloth-config under LGPL3 + modified to inherit vanilla's EntryListWidget and use yarn mappings + + code is responsible for having dynamic item heights + */ + + @Override + protected int getMaxPosition() { + return children().stream().map(E::getItemHeight).reduce(0, Integer::sum) + headerHeight; + } + + @Override + protected void centerScrollOn(E entry) { + double d = (this.height) / -2d; + for (int i = 0; i < this.children().indexOf(entry) && i < this.getItemCount(); i++) + d += children().get(i).getItemHeight(); + this.setScrollAmount(d); + } + + @Override + protected int getRowTop(int index) { + int integer = y0 + 4 - (int) this.getScrollAmount() + headerHeight; + for (int i = 0; i < children().size() && i < index; i++) + integer += children().get(i).getItemHeight(); + return integer; + } + + @Override + protected void renderList(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + int left = this.getRowLeft(); + int right = this.getRowWidth(); + int count = this.getItemCount(); + + for(int i = 0; i < count; ++i) { + E entry = children().get(i); + int top = this.getRowTop(i); + int bottom = top + entry.getItemHeight(); + int entryHeight = entry.getItemHeight() - 4; + if (bottom >= this.y0 && top <= this.y1) { + this.renderItem(graphics, mouseX, mouseY, delta, i, left, top, right, entryHeight); + } + } + } + + /* END cloth config code */ + + @Override + public void setX(int i) { + this.x = x0 = i; + this.x1 = x0 + width; + } + + @Override + public void setY(int i) { + this.y = y0 = i; + this.y1 = y0 + height; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public void visitWidgets(Consumer consumer) { + } + + public abstract static class Entry> extends ContainerObjectSelectionList.Entry { + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (GuiEventListener child : this.children()) { + if (child.mouseClicked(mouseX, mouseY, button)) { + if (button == InputConstants.MOUSE_BUTTON_LEFT) + this.setDragging(true); + return true; + } + } + + return false; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (isDragging() && button == InputConstants.MOUSE_BUTTON_LEFT) { + for (GuiEventListener child : this.children()) { + if (child.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) + return true; + } + } + return false; + } + + public int getItemHeight() { + return 22; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java b/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java new file mode 100644 index 0000000..5b5da97 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java @@ -0,0 +1,386 @@ +package dev.isxander.yacl3.gui; + +import com.mojang.blaze3d.Blaze3D; +import com.mojang.blaze3d.platform.NativeImage; +import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.TextureManager; +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; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import java.util.stream.IntStream; + +public interface ImageRenderer { + int render(GuiGraphics graphics, int x, int y, int renderWidth); + + void close(); + + Map>> CACHE = new ConcurrentHashMap<>(); + + static CompletableFuture> getOrMakeAsync(ResourceLocation id, Supplier> factory) { + return CACHE.computeIfAbsent(id, key -> CompletableFuture.supplyAsync(factory, YACLConstants.SINGLE_THREAD_EXECUTOR)); + } + + static CompletableFuture> getOrMakeSync(ResourceLocation id, Supplier> factory) { + return CACHE.computeIfAbsent(id, key -> CompletableFuture.completedFuture(factory.get())); + } + + static void closeAll() { + CACHE.values().forEach(future -> future.thenAccept(opt -> opt.ifPresent(ImageRenderer::close))); + CACHE.clear(); + } + + class TextureBacked implements ImageRenderer { + private final ResourceLocation location; + private final int width, height; + private final int textureWidth, textureHeight; + private final float u, v; + + public TextureBacked(ResourceLocation location, float u, float v, int width, int height, int textureWidth, int textureHeight) { + this.location = location; + this.width = width; + this.height = height; + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; + this.u = u; + this.v = v; + } + + @Override + public int render(GuiGraphics graphics, int x, int y, int renderWidth) { + float ratio = renderWidth / (float)this.width; + int targetHeight = (int) (this.height * ratio); + + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0); + graphics.pose().scale(ratio, ratio, 1); + graphics.blit(location, 0, 0, this.u, this.v, this.width, this.height, this.textureWidth, this.textureHeight); + graphics.pose().popPose(); + + return targetHeight; + } + + @Override + public void close() { + + } + } + + class NativeImageBacked implements ImageRenderer { + protected static final TextureManager textureManager = Minecraft.getInstance().getTextureManager(); + + protected NativeImage image; + protected DynamicTexture texture; + protected final ResourceLocation uniqueLocation; + protected final int width, height; + + public NativeImageBacked(NativeImage image, ResourceLocation uniqueLocation) { + this.image = image; + this.texture = new DynamicTexture(image); + this.uniqueLocation = uniqueLocation; + textureManager.register(this.uniqueLocation, this.texture); + this.width = image.getWidth(); + this.height = image.getHeight(); + } + + private NativeImageBacked(Path imagePath, ResourceLocation uniqueLocation) throws IOException { + this.uniqueLocation = uniqueLocation; + this.image = NativeImage.read(new FileInputStream(imagePath.toFile())); + this.width = image.getWidth(); + this.height = image.getHeight(); + this.texture = new DynamicTexture(image); + textureManager.register(this.uniqueLocation, this.texture); + } + + public static Optional createFromPath(Path path, ResourceLocation uniqueLocation) { + try { + return Optional.of(new NativeImageBacked(path, uniqueLocation)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + } + + @Override + public int render(GuiGraphics graphics, int x, int y, int renderWidth) { + if (image == null) return 0; + + float ratio = renderWidth / (float)this.width; + int targetHeight = (int) (this.height * ratio); + + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0); + graphics.pose().scale(ratio, ratio, 1); + graphics.blit(uniqueLocation, 0, 0, 0, 0, this.width, this.height, this.width, this.height); + graphics.pose().popPose(); + + return targetHeight; + } + + @Override + public void close() { + image.close(); + image = null; + texture = null; + textureManager.release(uniqueLocation); + } + } + + class AnimatedNativeImageBacked extends NativeImageBacked { + private int currentFrame; + private double lastFrameTime; + + private final double[] frameDelays; + private final int frameCount; + + private final int packCols, packRows; + private final int frameWidth, frameHeight; + + public AnimatedNativeImageBacked(NativeImage image, int frameWidth, int frameHeight, int frameCount, double[] frameDelayMS, int packCols, int packRows, ResourceLocation uniqueLocation) { + super(image, uniqueLocation); + this.frameWidth = frameWidth; + this.frameHeight = frameHeight; + this.frameCount = frameCount; + this.frameDelays = frameDelayMS; + this.packCols = packCols; + this.packRows = packRows; + } + + public static AnimatedNativeImageBacked createGIFFromTexture(ResourceLocation textureLocation) throws IOException { + ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); + Resource resource = resourceManager.getResource(textureLocation).orElseThrow(); + + return createGIF(resource.open(), textureLocation); + } + + public static AnimatedNativeImageBacked createWEBPFromTexture(ResourceLocation textureLocation) throws IOException { + ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); + Resource resource = resourceManager.getResource(textureLocation).orElseThrow(); + + return createWEBP(resource.open(), textureLocation); + } + + public static AnimatedNativeImageBacked createGIF(InputStream is, ResourceLocation uniqueLocation) { + try (is) { + ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next(); + reader.setInput(ImageIO.createImageInputStream(is)); + + + + AnimFrameProvider animFrameFunction = i -> { + IIOMetadata metadata = reader.getImageMetadata(i); + String metaFormatName = metadata.getNativeMetadataFormatName(); + IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName); + IIOMetadataNode graphicsControlExtensionNode = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item(0); + int delay = Integer.parseInt(graphicsControlExtensionNode.getAttribute("delayTime")) * 10; + + return new AnimFrame(delay, 0, 0); + }; + + return createFromImageReader(reader, animFrameFunction, uniqueLocation); + } catch (Exception e) { + CrashReport crashReport = CrashReport.forThrowable(e, "Failed to load GIF image"); + CrashReportCategory category = crashReport.addCategory("YACL Gui"); + category.setDetail("Image identifier", uniqueLocation.toString()); + throw new ReportedException(crashReport); + } + } + + public static AnimatedNativeImageBacked createWEBP(InputStream is, ResourceLocation uniqueLocation) { + try (is) { + ImageReader reader = new WebPImageReaderSpi().createReaderInstance(); + reader.setInput(ImageIO.createImageInputStream(is)); + + int numImages = reader.getNumImages(true); // Force reading of all frames + AnimFrameProvider animFrameFunction = i -> null; + if (numImages > 1) { + // WebP reader does not expose frame delay, prepare for reflection hell + Class webpReaderClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.WebPImageReader"); + Field framesField = webpReaderClass.getDeclaredField("frames"); + framesField.setAccessible(true); + List frames = (List) framesField.get(reader); + + Class animationFrameClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.AnimationFrame"); + Field durationField = animationFrameClass.getDeclaredField("duration"); + durationField.setAccessible(true); + Field boundsField = animationFrameClass.getDeclaredField("bounds"); + boundsField.setAccessible(true); + + animFrameFunction = i -> { + Rectangle bounds = (Rectangle) boundsField.get(frames.get(i)); + return new AnimFrame((int) durationField.get(frames.get(i)), bounds.x, bounds.y); + }; + // that was fun + } + + return createFromImageReader(reader, animFrameFunction, uniqueLocation); + } catch (Throwable e) { + CrashReport crashReport = CrashReport.forThrowable(e, "Failed to load WEBP image"); + CrashReportCategory category = crashReport.addCategory("YACL Gui"); + category.setDetail("Image identifier", uniqueLocation.toString()); + throw new ReportedException(crashReport); + } + } + + private static AnimatedNativeImageBacked createFromImageReader(ImageReader reader, AnimFrameProvider animationProvider, ResourceLocation uniqueLocation) throws Exception { + int frameCount = reader.getNumImages(true); + + // Because this is being backed into a texture atlas, we need a maximum dimension + // so you can get the texture atlas size. + // Smaller frames are given black borders + int frameWidth = IntStream.range(reader.getMinIndex(), frameCount).map(i -> { + try { + return reader.getWidth(i); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).max().orElseThrow(); + int frameHeight = IntStream.range(reader.getMinIndex(), frameCount).map(i -> { + try { + return reader.getHeight(i); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).max().orElseThrow(); + + // Packs the frames into an optimal 1:1 texture. + // OpenGL can only have texture axis with a max of 32768 pixels, + // and packing them to that length is not efficient, apparently. + double ratio = frameWidth / (double)frameHeight; + int cols = (int)Math.ceil(Math.sqrt(frameCount) / Math.sqrt(ratio)); + int rows = (int)Math.ceil(frameCount / (double)cols); + + NativeImage image = new NativeImage(frameWidth * cols, frameHeight * rows, true); + + // Fill whole atlas with black, as each frame may have different dimensions + // that would cause borders of transparent pixels to appear around the frames + for (int x = 0; x < frameWidth * cols; x++) { + for (int y = 0; y < frameHeight * rows; y++) { + image.setPixelRGBA(x, y, 0xFF000000); + } + } + + BufferedImage bi = null; + Graphics2D graphics = null; + + // each frame may have a different delay + double[] frameDelays = new double[frameCount]; + + for (int i = reader.getMinIndex(); i < frameCount - 1; i++) { + AnimFrame frame = animationProvider.get(i); + if (frameCount > 1) // frame will be null if not animation + frameDelays[i] = frame.durationMS; + + if (bi == null) { + // first frame... + bi = reader.read(i); + graphics = bi.createGraphics(); + } else { + // WebP reader sometimes provides delta frames, (only the pixels that changed since the last frame) + // so instead of overwriting the image every frame, we draw delta frames on top of the previous frame + // to keep a complete image. + BufferedImage deltaFrame = reader.read(i); + graphics.drawImage(deltaFrame, frame.xOffset, frame.yOffset, null); + } + + // Each frame may have different dimensions, so we need to center them. + int xOffset = (frameWidth - bi.getWidth()) / 2; + int yOffset = (frameHeight - bi.getHeight()) / 2; + + 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 col = i % cols; + int row = (int) Math.floor(i / (double)cols); + + image.setPixelRGBA( + frameWidth * col + w + xOffset, + frameHeight * row + h + yOffset, + FastColor.ABGR32.color(255, b, g, r) // NativeImage uses ABGR for some reason + ); + } + } + } + // gives the texture to GL for rendering + // usually, you create a native image with NativeImage.create, which sets the pixels and + // runs this function itself. In this case, we need to do it manually. + image.upload(0, 0, 0, false); + + graphics.dispose(); + reader.dispose(); + + return new AnimatedNativeImageBacked(image, frameWidth, frameHeight, frameCount, frameDelays, cols, rows, uniqueLocation); + } + + @Override + public int render(GuiGraphics graphics, int x, int y, int renderWidth) { + if (image == null) return 0; + + float ratio = renderWidth / (float)frameWidth; + int targetHeight = (int) (frameHeight * ratio); + + int currentCol = currentFrame % packCols; + int currentRow = (int) Math.floor(currentFrame / (double)packCols); + + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0); + graphics.pose().scale(ratio, ratio, 1); + graphics.blit( + uniqueLocation, + 0, 0, + frameWidth * currentCol, frameHeight * currentRow, + frameWidth, frameHeight, + this.width, this.height + ); + graphics.pose().popPose(); + + if (frameCount > 1) { + double timeMS = Blaze3D.getTime() * 1000; + if (lastFrameTime == 0) lastFrameTime = timeMS; + if (timeMS - lastFrameTime >= frameDelays[currentFrame]) { + currentFrame++; + lastFrameTime = timeMS; + } + if (currentFrame >= frameCount - 1) + currentFrame = 0; + } + + return targetHeight; + } + + @FunctionalInterface + private interface AnimFrameProvider { + AnimFrame get(int frame) throws Exception; + } + private record AnimFrame(int durationMS, int xOffset, int yOffset) {} + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/LowProfileButtonWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/LowProfileButtonWidget.java new file mode 100644 index 0000000..3f5822f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/LowProfileButtonWidget.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl3.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.network.chat.Component; + +public class LowProfileButtonWidget extends Button { + public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION); + } + + public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress, Tooltip tooltip) { + this(x, y, width, height, message, onPress); + setTooltip(tooltip); + } + + @Override + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float deltaTicks) { + if (!isHoveredOrFocused() || !active) { + int j = this.active ? 0xFFFFFF : 0xA0A0A0; + this.renderString(graphics, Minecraft.getInstance().font, j); + } else { + super.renderWidget(graphics, mouseX, mouseY, deltaTicks); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/OptionDescriptionWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/OptionDescriptionWidget.java new file mode 100644 index 0000000..63371d6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/OptionDescriptionWidget.java @@ -0,0 +1,215 @@ +package dev.isxander.yacl3.gui; + +import com.mojang.blaze3d.Blaze3D; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Supplier; + +public class OptionDescriptionWidget extends AbstractWidget { + private static final int AUTO_SCROLL_TIMER = 1500; + private static final float AUTO_SCROLL_SPEED = 1; // lines per second + + private @Nullable DescriptionWithName description; + private List wrappedText; + + private static final Minecraft minecraft = Minecraft.getInstance(); + private static final Font font = minecraft.font; + + private Supplier dimensions; + + private float targetScrollAmount, currentScrollAmount; + private int maxScrollAmount; + private int descriptionY; + + private int lastInteractionTime; + private boolean scrollingBackward; + + public OptionDescriptionWidget(Supplier dimensions, @Nullable DescriptionWithName description) { + super(0, 0, 0, 0, description == null ? Component.empty() : description.name()); + this.dimensions = dimensions; + this.setOptionDescription(description); + } + + @Override + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (description == null) return; + + currentScrollAmount = Mth.lerp(delta * 0.5f, currentScrollAmount, targetScrollAmount); + + ScreenRectangle dimensions = this.dimensions.get(); + this.setX(dimensions.left()); + this.setY(dimensions.top()); + this.width = dimensions.width(); + this.height = dimensions.height(); + + int y = getY(); + + int nameWidth = font.width(description.name()); + if (nameWidth > getWidth()) { + renderScrollingString(graphics, font, description.name(), getX(), y, getX() + getWidth(), y + font.lineHeight, -1); + } else { + graphics.drawString(font, description.name(), getX(), y, 0xFFFFFF); + } + + y += 5 + font.lineHeight; + + graphics.enableScissor(getX(), y, getX() + getWidth(), getY() + getHeight()); + + y -= (int)currentScrollAmount; + + if (description.description().image().isDone()) { + var image = description.description().image().join(); + if (image.isPresent()) { + image.get().render(graphics, getX(), y, getWidth()); + y += image.get().render(graphics, getX(), y, getWidth()) + 5; + } + } + + if (wrappedText == null) + wrappedText = font.split(description.description().text(), getWidth()); + + descriptionY = y; + for (var line : wrappedText) { + graphics.drawString(font, line, getX(), y, 0xFFFFFF); + y += font.lineHeight; + } + + graphics.disableScissor(); + + maxScrollAmount = Math.max(0, y + (int)currentScrollAmount - getY() - getHeight()); + + if (isHoveredOrFocused()) { + lastInteractionTime = currentTimeMS(); + } + Style hoveredStyle = getDescStyle(mouseX, mouseY); + if (hoveredStyle != null && hoveredStyle.getHoverEvent() != null) { + graphics.renderComponentHoverEffect(font, hoveredStyle, mouseX, mouseY); + } + + if (isFocused()) { + graphics.renderOutline(getX(), getY(), getWidth(), getHeight(), -1); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + Style clickedStyle = getDescStyle((int) mouseX, (int) mouseY); + if (clickedStyle != null && clickedStyle.getClickEvent() != null) { + if (minecraft.screen.handleComponentClicked(clickedStyle)) { + playDownSound(minecraft.getSoundManager()); + return true; + } + return false; + } + + return false; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + if (isMouseOver(mouseX, mouseY)) { + targetScrollAmount = Mth.clamp(targetScrollAmount - (int) amount * 10, 0, maxScrollAmount); + lastInteractionTime = currentTimeMS(); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (isFocused()) { + switch (keyCode) { + case InputConstants.KEY_UP -> + targetScrollAmount = Mth.clamp(targetScrollAmount - 10, 0, maxScrollAmount); + case InputConstants.KEY_DOWN -> + targetScrollAmount = Mth.clamp(targetScrollAmount + 10, 0, maxScrollAmount); + default -> { + return false; + } + } + return true; + } + return false; + } + + public void tick() { + float pxPerTick = AUTO_SCROLL_SPEED / 20f * font.lineHeight; + if (maxScrollAmount > 0 && currentTimeMS() - lastInteractionTime > AUTO_SCROLL_TIMER) { + if (scrollingBackward) { + pxPerTick *= -1; + if (targetScrollAmount + pxPerTick < 0) { + scrollingBackward = false; + lastInteractionTime = currentTimeMS(); + } + } else { + if (targetScrollAmount + pxPerTick > maxScrollAmount) { + scrollingBackward = true; + lastInteractionTime = currentTimeMS(); + } + } + + targetScrollAmount = Mth.clamp(targetScrollAmount + pxPerTick, 0, maxScrollAmount); + } + } + + private Style getDescStyle(int mouseX, int mouseY) { + if (!clicked(mouseX, mouseY)) + return null; + + int x = mouseX - getX(); + int y = mouseY - descriptionY; + + if (x < 0 || x > getX() + getWidth()) return null; + if (y < 0 || y > getY() + getHeight()) return null; + + int line = y / font.lineHeight; + + if (line >= wrappedText.size()) return null; + + return font.getSplitter().componentStyleAtWidth(wrappedText.get(line), x); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) { + if (description != null) { + builder.add(NarratedElementType.TITLE, description.name()); + builder.add(NarratedElementType.HINT, description.description().text()); + } + + } + + public void setOptionDescription(DescriptionWithName description) { + this.description = description; + this.wrappedText = null; + this.targetScrollAmount = 0; + this.currentScrollAmount = 0; + this.lastInteractionTime = currentTimeMS(); + } + + private int currentTimeMS() { + return (int)(Blaze3D.getTime() * 1000); + } + + @Nullable + @Override + public ComponentPath nextFocusPath(FocusNavigationEvent event) { + // prevents focusing on this widget + return null; + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/OptionListWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/OptionListWidget.java new file mode 100644 index 0000000..54d58f4 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/OptionListWidget.java @@ -0,0 +1,572 @@ +package dev.isxander.yacl3.gui; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +public class OptionListWidget extends ElementListWidgetExt { + private final YACLScreen yaclScreen; + private final ConfigCategory category; + private ImmutableList viewableChildren; + private String searchQuery = ""; + private final Consumer hoverEvent; + private DescriptionWithName lastHoveredOption; + + public OptionListWidget(YACLScreen screen, ConfigCategory category, Minecraft client, int x, int y, int width, int height, Consumer hoverEvent) { + super(client, x, y, width, height, true); + this.yaclScreen = screen; + this.category = category; + this.hoverEvent = hoverEvent; + + refreshOptions(); + + for (OptionGroup group : category.groups()) { + if (group instanceof ListOption listOption) { + listOption.addRefreshListener(() -> refreshListEntries(listOption, category)); + } + } + } + + public void refreshOptions() { + clearEntries(); + + for (OptionGroup group : category.groups()) { + GroupSeparatorEntry groupSeparatorEntry; + if (!group.isRoot()) { + groupSeparatorEntry = group instanceof ListOption listOption + ? new ListGroupSeparatorEntry(listOption, yaclScreen) + : new GroupSeparatorEntry(group, yaclScreen); + addEntry(groupSeparatorEntry); + } else { + groupSeparatorEntry = null; + } + + List optionEntries = new ArrayList<>(); + + // add empty entry to make sure users know it's empty not just bugging out + if (groupSeparatorEntry instanceof ListGroupSeparatorEntry listGroupSeparatorEntry) { + if (listGroupSeparatorEntry.listOption.options().isEmpty()) { + EmptyListLabel emptyListLabel = new EmptyListLabel(listGroupSeparatorEntry, category); + addEntry(emptyListLabel); + optionEntries.add(emptyListLabel); + } + } + + for (Option option : group.options()) { + OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); + addEntry(entry); + optionEntries.add(entry); + } + + if (groupSeparatorEntry != null) { + groupSeparatorEntry.setChildEntries(optionEntries); + } + } + + recacheViewableChildren(); + setScrollAmount(0); + resetSmoothScrolling(); + } + + private void refreshListEntries(ListOption listOption, ConfigCategory category) { + // find group separator for group + ListGroupSeparatorEntry groupSeparator = super.children().stream().filter(e -> e instanceof ListGroupSeparatorEntry gs && gs.group == listOption).map(ListGroupSeparatorEntry.class::cast).findAny().orElse(null); + + if (groupSeparator == null) { + YACLConstants.LOGGER.warn("Can't find group seperator to refresh list option entries for list option " + listOption.name()); + return; + } + + for (Entry entry : groupSeparator.childEntries) + super.removeEntry(entry); + groupSeparator.childEntries.clear(); + + // if no entries, below loop won't run where addEntryBelow() recaches viewable children + if (listOption.options().isEmpty()) { + EmptyListLabel emptyListLabel; + addEntryBelow(groupSeparator, emptyListLabel = new EmptyListLabel(groupSeparator, category)); + groupSeparator.childEntries.add(emptyListLabel); + return; + } + + Entry lastEntry = groupSeparator; + for (ListOptionEntry listOptionEntry : listOption.options()) { + OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); + addEntryBelow(lastEntry, optionEntry); + groupSeparator.childEntries.add(optionEntry); + lastEntry = optionEntry; + } + } + + public Dimension getDefaultEntryDimension() { + return Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20); + } + + public void expandAllGroups() { + for (Entry entry : super.children()) { + if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { + groupSeparatorEntry.setExpanded(true); + } + } + } + + public void updateSearchQuery(String query) { + this.searchQuery = query; + expandAllGroups(); + recacheViewableChildren(); + } + + @Override + public int getRowWidth() { + return Math.min(396, (int)(width / 1.3f)); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (Entry child : children()) { + if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry) + optionEntry.widget.unfocus(); + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + super.mouseScrolled(mouseX, mouseY, amount); + + for (Entry child : children()) { + if (child.mouseScrolled(mouseX, mouseY, amount)) + break; + } + + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + for (Entry child : children()) { + if (child.keyPressed(keyCode, scanCode, modifiers)) + return true; + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + for (Entry child : children()) { + if (child.charTyped(chr, modifiers)) + return true; + } + + return super.charTyped(chr, modifiers); + } + + @Override + protected int getScrollbarPosition() { + return x1 - (int)(width * 0.05f); + } + + public void recacheViewableChildren() { + this.viewableChildren = ImmutableList.copyOf(super.children().stream().filter(Entry::isViewable).toList()); + + // update y positions before they need to be rendered are rendered + int i = 0; + for (Entry entry : viewableChildren) { + if (entry instanceof OptionEntry optionEntry) + optionEntry.widget.setDimension(optionEntry.widget.getDimension().withY(getRowTop(i))); + i++; + } + } + + @Override + public List children() { + return viewableChildren; + } + + public void addEntry(int index, Entry entry) { + super.children().add(index, entry); + recacheViewableChildren(); + } + + public void addEntryBelow(Entry below, Entry entry) { + int idx = super.children().indexOf(below) + 1; + + if (idx == 0) + throw new IllegalStateException("The entry to insert below does not exist!"); + + addEntry(idx, entry); + } + + public void addEntryBelowWithoutScroll(Entry below, Entry entry) { + double d = (double)this.getMaxScroll() - this.getScrollAmount(); + addEntryBelow(below, entry); + setScrollAmount(getMaxScroll() - d); + } + + @Override + public boolean removeEntryFromTop(Entry entry) { + boolean ret = super.removeEntryFromTop(entry); + recacheViewableChildren(); + return ret; + } + + @Override + public boolean removeEntry(Entry entry) { + boolean ret = super.removeEntry(entry); + recacheViewableChildren(); + return ret; + } + + private void setHoverDescription(DescriptionWithName description) { + if (description != lastHoveredOption) { + lastHoveredOption = description; + hoverEvent.accept(description); + } + } + + public abstract class Entry extends ElementListWidgetExt.Entry { + public boolean isViewable() { + return true; + } + + protected boolean isHovered() { + return Objects.equals(getHovered(), this); + } + } + + public class OptionEntry extends Entry { + public final Option option; + public final ConfigCategory category; + public final OptionGroup group; + + public final @Nullable GroupSeparatorEntry groupSeparatorEntry; + + public final AbstractWidget widget; + + private final TextScaledButtonWidget resetButton; + + private final String categoryName; + private final String groupName; + + public OptionEntry(Option option, ConfigCategory category, OptionGroup group, @Nullable GroupSeparatorEntry groupSeparatorEntry, AbstractWidget widget) { + this.option = option; + this.category = category; + this.group = group; + this.groupSeparatorEntry = groupSeparatorEntry; + this.widget = widget; + this.categoryName = category.name().getString().toLowerCase(); + this.groupName = group.name().getString().toLowerCase(); + if (option.canResetToDefault() && this.widget.canReset()) { + this.widget.setDimension(this.widget.getDimension().expanded(-20, 0)); + this.resetButton = new TextScaledButtonWidget(yaclScreen, widget.getDimension().xLimit(), -50, 20, 20, 2f, Component.literal("\u21BB"), button -> { + option.requestSetDefault(); + }); + option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); + this.resetButton.active = !option.isPendingValueDefault() && option.available(); + } else { + this.resetButton = null; + } + } + + @Override + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + widget.setDimension(widget.getDimension().withY(y)); + + widget.render(graphics, mouseX, mouseY, tickDelta); + + if (resetButton != null) { + resetButton.setY(y); + resetButton.render(graphics, mouseX, mouseY, tickDelta); + } + + if (isHovered()) { + setHoverDescription(DescriptionWithName.of(option.name(), option.description())); + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + return widget.mouseScrolled(mouseX, mouseY, amount); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + return widget.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + return widget.charTyped(chr, modifiers); + } + + @Override + public boolean isViewable() { + return (groupSeparatorEntry == null || groupSeparatorEntry.isExpanded()) + && (searchQuery.isEmpty() + || groupName.contains(searchQuery) + || widget.matchesSearch(searchQuery)); + } + + @Override + public int getItemHeight() { + return Math.max(widget.getDimension().height(), resetButton != null ? resetButton.getHeight() : 0) + 2; + } + + @Override + public void setFocused(boolean focused) { + super.setFocused(focused); + if (focused) + setHoverDescription(DescriptionWithName.of(option.name(), option.description())); + } + + @Override + public List narratables() { + if (resetButton == null) + return ImmutableList.of(widget); + + return ImmutableList.of(widget, resetButton); + } + + @Override + public List children() { + if (resetButton == null) + return ImmutableList.of(widget); + + return ImmutableList.of(widget, resetButton); + } + } + + public class GroupSeparatorEntry extends Entry { + protected final OptionGroup group; + protected final MultiLineLabel wrappedName; + protected final MultiLineLabel wrappedTooltip; + + protected final LowProfileButtonWidget expandMinimizeButton; + + protected final Screen screen; + protected final Font font = Minecraft.getInstance().font; + + protected boolean groupExpanded; + + protected List childEntries = new ArrayList<>(); + + private int y; + + private GroupSeparatorEntry(OptionGroup group, Screen screen) { + this.group = group; + this.screen = screen; + this.wrappedName = MultiLineLabel.create(font, group.name(), getRowWidth() - 45); + this.wrappedTooltip = MultiLineLabel.create(font, group.tooltip(), screen.width / 3 * 2 - 10); + this.groupExpanded = !group.collapsed(); + this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Component.empty(), btn -> onExpandButtonPress()); + updateExpandMinimizeText(); + } + + @Override + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + this.y = y; + + int buttonY = y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2 + 1; + + expandMinimizeButton.setY(buttonY); + expandMinimizeButton.setX(x); + expandMinimizeButton.render(graphics, mouseX, mouseY, tickDelta); + + wrappedName.renderCentered(graphics, x + entryWidth / 2, y + getYPadding()); + + if (isHovered()) { + setHoverDescription(DescriptionWithName.of(group.name(), group.description())); + } + } + + public boolean isExpanded() { + return groupExpanded; + } + + public void setExpanded(boolean expanded) { + if (this.groupExpanded == expanded) + return; + + this.groupExpanded = expanded; + updateExpandMinimizeText(); + recacheViewableChildren(); + } + + protected void onExpandButtonPress() { + setExpanded(!isExpanded()); + } + + protected void updateExpandMinimizeText() { + expandMinimizeButton.setMessage(Component.literal(isExpanded() ? "â–¼" : "â–¶")); + } + + public void setChildEntries(List childEntries) { + this.childEntries.clear(); + this.childEntries.addAll(childEntries); + } + + @Override + public boolean isViewable() { + return searchQuery.isEmpty() || childEntries.stream().anyMatch(Entry::isViewable); + } + + @Override + public int getItemHeight() { + return Math.max(wrappedName.getLineCount(), 1) * font.lineHeight + getYPadding() * 2; + } + + private int getYPadding() { + return 6; + } + + @Override + public void setFocused(boolean focused) { + super.setFocused(focused); + if (focused) + setHoverDescription(DescriptionWithName.of(group.name(), group.description())); + } + + @Override + public List narratables() { + return ImmutableList.of(new NarratableEntry() { + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + builder.add(NarratedElementType.TITLE, group.name()); + builder.add(NarratedElementType.HINT, group.tooltip()); + } + }); + } + + @Override + public List children() { + return ImmutableList.of(expandMinimizeButton); + } + } + + public class ListGroupSeparatorEntry extends GroupSeparatorEntry { + private final ListOption listOption; + private final TextScaledButtonWidget resetListButton; + private final TooltipButtonWidget addListButton; + + private ListGroupSeparatorEntry(ListOption group, Screen screen) { + super(group, screen); + this.listOption = group; + + this.resetListButton = new TextScaledButtonWidget(screen, getRowRight() - 20, -50, 20, 20, 2f, Component.literal("\u21BB"), button -> { + group.requestSetDefault(); + }); + group.addListener((opt, val) -> this.resetListButton.active = !opt.isPendingValueDefault() && opt.available()); + this.resetListButton.active = !group.isPendingValueDefault() && group.available(); + + + this.addListButton = new TooltipButtonWidget(yaclScreen, resetListButton.getX() - 20, -50, 20, 20, Component.literal("+"), Component.translatable("yacl.list.add_top"), btn -> { + group.insertNewEntryToTop(); + setExpanded(true); + }); + + updateExpandMinimizeText(); + minimizeIfUnavailable(); + } + + @Override + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + updateExpandMinimizeText(); // update every render because option could become available/unavailable at any time + + super.render(graphics, index, y, x, entryWidth, entryHeight, mouseX, mouseY, hovered, tickDelta); + + int buttonY = expandMinimizeButton.getY(); + + resetListButton.setY(buttonY); + addListButton.setY(buttonY); + + resetListButton.render(graphics, mouseX, mouseY, tickDelta); + addListButton.render(graphics, mouseX, mouseY, tickDelta); + } + + private void minimizeIfUnavailable() { + if (!listOption.available() && isExpanded()) { + setExpanded(false); + } + } + + @Override + protected void updateExpandMinimizeText() { + super.updateExpandMinimizeText(); + expandMinimizeButton.active = listOption == null || listOption.available(); + if (addListButton != null) + addListButton.active = expandMinimizeButton.active; + } + + @Override + public void setExpanded(boolean expanded) { + super.setExpanded(listOption.available() && expanded); + } + + @Override + public List children() { + return ImmutableList.of(expandMinimizeButton, addListButton, resetListButton); + } + } + + public class EmptyListLabel extends Entry { + private final ListGroupSeparatorEntry parent; + private final String groupName; + private final String categoryName; + + public EmptyListLabel(ListGroupSeparatorEntry parent, ConfigCategory category) { + this.parent = parent; + this.groupName = parent.group.name().getString().toLowerCase(); + this.categoryName = category.name().getString().toLowerCase(); + } + + @Override + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + graphics.drawCenteredString(Minecraft.getInstance().font, Component.translatable("yacl.list.empty").withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC), x + entryWidth / 2, y, -1); + } + + @Override + public boolean isViewable() { + return parent.isExpanded() && (searchQuery.isEmpty() || groupName.contains(searchQuery)); + } + + @Override + public int getItemHeight() { + return 11; + } + + @Override + public List children() { + return ImmutableList.of(); + } + + @Override + public List narratables() { + return ImmutableList.of(); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/RequireRestartScreen.java b/common/src/main/java/dev/isxander/yacl3/gui/RequireRestartScreen.java new file mode 100644 index 0000000..5ba4b03 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/RequireRestartScreen.java @@ -0,0 +1,21 @@ +package dev.isxander.yacl3.gui; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class RequireRestartScreen extends ConfirmScreen { + public RequireRestartScreen(Screen parent) { + super(option -> { + if (option) Minecraft.getInstance().stop(); + else Minecraft.getInstance().setScreen(parent); + }, + Component.translatable("yacl.restart.title").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), + Component.translatable("yacl.restart.message"), + Component.translatable("yacl.restart.yes"), + Component.translatable("yacl.restart.no") + ); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/SearchFieldWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/SearchFieldWidget.java new file mode 100644 index 0000000..a666886 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/SearchFieldWidget.java @@ -0,0 +1,61 @@ +package dev.isxander.yacl3.gui; + +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +public class SearchFieldWidget extends EditBox { + private Component emptyText; + private final YACLScreen yaclScreen; + private final Font font; + private final Consumer updateConsumer; + + private boolean isEmpty = true; + + public SearchFieldWidget(YACLScreen yaclScreen, Font font, int x, int y, int width, int height, Component text, Component emptyText, Consumer updateConsumer) { + super(font, x, y, width, height, text); + setResponder(this::update); + setFilter(string -> !string.endsWith(" ") && !string.startsWith(" ")); + this.yaclScreen = yaclScreen; + this.font = font; + this.emptyText = emptyText; + this.updateConsumer = updateConsumer; + } + + @Override + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.renderWidget(graphics, mouseX, mouseY, delta); + if (isVisible() && isEmpty()) { + graphics.drawString(font, emptyText, getX() + 4, this.getY() + (this.height - 8) / 2, 0x707070, true); + } + } + + private void update(String query) { + boolean wasEmpty = isEmpty; + isEmpty = query.isEmpty(); + + if (isEmpty && wasEmpty) + return; + + updateConsumer.accept(query); + } + + public String getQuery() { + return getValue().toLowerCase(); + } + + public boolean isEmpty() { + return isEmpty; + } + + public Component getEmptyText() { + return emptyText; + } + + public void setEmptyText(Component emptyText) { + this.emptyText = emptyText; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/TextScaledButtonWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/TextScaledButtonWidget.java new file mode 100644 index 0000000..6ad0d1c --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/TextScaledButtonWidget.java @@ -0,0 +1,34 @@ +package dev.isxander.yacl3.gui; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +public class TextScaledButtonWidget extends TooltipButtonWidget { + public float textScale; + + public TextScaledButtonWidget(Screen screen, int x, int y, int width, int height, float textScale, Component message, Component tooltip, OnPress onPress) { + super(screen, x, y, width, height, message, tooltip, onPress); + this.textScale = textScale; + } + + public TextScaledButtonWidget(Screen screen, int x, int y, int width, int height, float textScale, Component message, OnPress onPress) { + this(screen, x, y, width, height, textScale, message, null, onPress); + } + + @Override + public void renderString(GuiGraphics graphics, Font textRenderer, int color) { + Font font = Minecraft.getInstance().font; + PoseStack pose = graphics.pose(); + + pose.pushPose(); + pose.translate(((this.getX() + this.width / 2f) - font.width(getMessage()) * textScale / 2), (float)this.getY() + (this.height - 8 * textScale) / 2f / textScale, 0); + pose.scale(textScale, textScale, 1); + graphics.drawString(font, getMessage(), 0, 0, color | Mth.ceil(this.alpha * 255.0F) << 24, true); + pose.popPose(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/TooltipButtonWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/TooltipButtonWidget.java new file mode 100644 index 0000000..396f20b --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/TooltipButtonWidget.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.gui; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +public class TooltipButtonWidget extends Button { + + protected final Screen screen; + + public TooltipButtonWidget(Screen screen, int x, int y, int width, int height, Component message, Component tooltip, OnPress onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION); + this.screen = screen; + if (tooltip != null) + setTooltip(Tooltip.create(tooltip)); + } + + @Override + protected @NotNull ClientTooltipPositioner createTooltipPositioner() { + return new YACLTooltipPositioner(this); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java b/common/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java new file mode 100644 index 0000000..5d40a07 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java @@ -0,0 +1,358 @@ +package dev.isxander.yacl3.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.api.utils.MutableDimension; +import dev.isxander.yacl3.api.utils.OptionUtils; +import dev.isxander.yacl3.gui.tab.ScrollableNavigationBar; +import dev.isxander.yacl3.gui.tab.ListHolderWidget; +import dev.isxander.yacl3.gui.tab.TabExt; +import dev.isxander.yacl3.gui.utils.GuiUtils; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +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.Tab; +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.renderer.GameRenderer; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class YACLScreen extends Screen { + public final YetAnotherConfigLib config; + + private final Screen parent; + + public final TabManager tabManager = new TabManager(this::addRenderableWidget, this::removeWidget); + public TabNavigationBar tabNavigationBar; + public Tab[] tabs; + public ScreenRectangle tabArea; + + public Component saveButtonMessage, saveButtonTooltipMessage; + private int saveButtonMessageTime; + + public YACLScreen(YetAnotherConfigLib config, Screen parent) { + super(config.title()); + this.config = config; + this.parent = parent; + } + + @Override + protected void init() { + tabNavigationBar = new ScrollableNavigationBar(this.width, tabManager, config.categories() + .stream() + .map(category -> { + if (category instanceof PlaceholderCategory placeholder) + return new PlaceholderTab(placeholder); + return new CategoryTab(category); + }).toList()); + tabNavigationBar.selectTab(0, false); + tabNavigationBar.arrangeElements(); + ScreenRectangle navBarArea = tabNavigationBar.getRectangle(); + tabArea = new ScreenRectangle(0, navBarArea.height() - 1, this.width, this.height - navBarArea.height() + 1); + tabManager.setTabArea(tabArea); + addRenderableWidget(tabNavigationBar); + + config.initConsumer().accept(this); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + renderDirtBackground(graphics); + super.render(graphics, mouseX, mouseY, delta); + } + + protected void finishOrSave() { + saveButtonMessage = null; + + if (pendingChanges()) { + Set flags = new HashSet<>(); + OptionUtils.forEachOptions(config, option -> { + if (option.applyValue()) { + flags.addAll(option.flags()); + } + }); + OptionUtils.forEachOptions(config, option -> { + if (option.changed()) { + // if still changed after applying, reset to the current value from binding + // as something has gone wrong. + option.forgetPendingValue(); + YACLConstants.LOGGER.error("Option '{}' value mismatch after applying! Reset to binding's getter.", option.name().getString()); + } + }); + config.saveFunction().run(); + + flags.forEach(flag -> flag.accept(minecraft)); + } else onClose(); + } + + protected void cancelOrReset() { + if (pendingChanges()) { // if pending changes, button acts as a cancel button + OptionUtils.forEachOptions(config, Option::forgetPendingValue); + onClose(); + } else { // if not, button acts as a reset button + OptionUtils.forEachOptions(config, Option::requestSetDefault); + } + } + + protected void undo() { + OptionUtils.forEachOptions(config, Option::forgetPendingValue); + } + + @Override + public void tick() { + tabManager.tickCurrent(); + + if (saveButtonMessage != null) { + if (saveButtonMessageTime > 140) { + saveButtonMessage = null; + saveButtonTooltipMessage = null; + saveButtonMessageTime = 0; + } else { + saveButtonMessageTime++; + //finishedSaveButton.setMessage(saveButtonMessage); + if (saveButtonTooltipMessage != null) { + //finishedSaveButton.setTooltip(saveButtonTooltipMessage); + } + } + } + } + + private void setSaveButtonMessage(Component message, Component tooltip) { + saveButtonMessage = message; + saveButtonTooltipMessage = tooltip; + saveButtonMessageTime = 0; + } + + private boolean pendingChanges() { + AtomicBoolean pendingChanges = new AtomicBoolean(false); + OptionUtils.consumeOptions(config, (option) -> { + if (option.changed()) { + pendingChanges.set(true); + return true; + } + return false; + }); + + return pendingChanges.get(); + } + + @Override + public boolean shouldCloseOnEsc() { + if (pendingChanges()) { + setSaveButtonMessage(Component.translatable("yacl.gui.save_before_exit").withStyle(ChatFormatting.RED), Component.translatable("yacl.gui.save_before_exit.tooltip")); + return false; + } + return true; + } + + @Override + public void onClose() { + minecraft.setScreen(parent); + } + + public static void renderMultilineTooltip(GuiGraphics graphics, Font font, MultiLineLabel text, int centerX, int yAbove, int yBelow, int screenWidth, int screenHeight) { + if (text.getLineCount() > 0) { + int maxWidth = text.getWidth(); + int lineHeight = font.lineHeight + 1; + int height = text.getLineCount() * lineHeight - 1; + + int belowY = yBelow + 12; + int aboveY = yAbove - height + 12; + int maxBelow = screenHeight - (belowY + height); + int minAbove = aboveY - height; + int y = aboveY; + if (minAbove < 8) + y = maxBelow > minAbove ? belowY : aboveY; + + int x = Math.max(centerX - text.getWidth() / 2 - 12, -6); + + int drawX = x + 12; + int drawY = y - 12; + + graphics.pose().pushPose(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferBuilder = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + TooltipRenderUtil.renderTooltipBackground( + graphics, + drawX, + drawY, + maxWidth, + height, + 400 + ); + RenderSystem.enableDepthTest(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + BufferUploader.drawWithShader(bufferBuilder.end()); + RenderSystem.disableBlend(); + graphics.pose().translate(0.0, 0.0, 400.0); + + text.renderLeftAligned(graphics, drawX, drawY, lineHeight, -1); + + graphics.pose().popPose(); + } + } + + public class CategoryTab implements TabExt { + private final ConfigCategory category; + private final Tooltip tooltip; + + private ListHolderWidget optionList; + private final Button saveFinishedButton; + private final Button cancelResetButton; + private final Button undoButton; + private final SearchFieldWidget searchField; + private OptionDescriptionWidget descriptionWidget; + + public CategoryTab(ConfigCategory category) { + this.category = category; + this.tooltip = Tooltip.create(category.tooltip()); + + int columnWidth = width / 3; + int padding = columnWidth / 20; + columnWidth = Math.min(columnWidth, 400); + int paddedWidth = columnWidth - padding * 2; + MutableDimension actionDim = Dimension.ofInt(width / 3 * 2 + width / 6, height - padding - 20, paddedWidth, 20); + + saveFinishedButton = Button.builder(Component.literal("Done"), btn -> finishOrSave()) + .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) + .size(actionDim.width(), actionDim.height()) + .build(); + + actionDim.expand(-actionDim.width() / 2 - 2, 0).move(-actionDim.width() / 2 - 2, -22); + cancelResetButton = Button.builder(Component.literal("Cancel"), btn -> cancelOrReset()) + .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) + .size(actionDim.width(), actionDim.height()) + .build(); + + actionDim.move(actionDim.width() + 4, 0); + undoButton = Button.builder(Component.translatable("yacl.gui.undo"), btn -> undo()) + .pos(actionDim.x() - actionDim.width() / 2, actionDim.y()) + .size(actionDim.width(), actionDim.height()) + .tooltip(Tooltip.create(Component.translatable("yacl.gui.undo.tooltip"))) + .build(); + + searchField = new SearchFieldWidget( + YACLScreen.this, + font, + width / 3 * 2 + width / 6 - paddedWidth / 2 + 1, + undoButton.getY() - 22, + paddedWidth - 2, 18, + Component.translatable("gui.recipebook.search_hint"), + Component.translatable("gui.recipebook.search_hint"), + searchQuery -> optionList.getList().updateSearchQuery(searchQuery) + ); + + this.optionList = new ListHolderWidget<>( + () -> new ScreenRectangle(tabArea.position(), tabArea.width() / 3 * 2 + 1, tabArea.height()), + new OptionListWidget(YACLScreen.this, category, minecraft, 0, 0, width / 3 * 2 + 1, height, desc -> { + descriptionWidget.setOptionDescription(desc); + }) + ); + + descriptionWidget = new OptionDescriptionWidget( + () -> new ScreenRectangle( + width / 3 * 2 + padding, + tabArea.top() + padding, + paddedWidth, + searchField.getY() - 1 - tabArea.top() - padding * 2 + ), + null + ); + + updateButtons(); + } + + @Override + public Component getTabTitle() { + return category.name(); + } + + @Override + public void visitChildren(Consumer consumer) { + consumer.accept(optionList); + consumer.accept(saveFinishedButton); + consumer.accept(cancelResetButton); + consumer.accept(undoButton); + consumer.accept(searchField); + consumer.accept(descriptionWidget); + } + + @Override + public void doLayout(ScreenRectangle screenRectangle) { + + } + + @Override + public void tick() { + updateButtons(); + searchField.tick(); + descriptionWidget.tick(); + } + + @Nullable + @Override + public Tooltip getTooltip() { + return tooltip; + } + + private void updateButtons() { + boolean pendingChanges = pendingChanges(); + + undoButton.active = pendingChanges; + saveFinishedButton.setMessage(pendingChanges ? Component.translatable("yacl.gui.save") : GuiUtils.translatableFallback("yacl.gui.done", CommonComponents.GUI_DONE)); + saveFinishedButton.setTooltip(Tooltip.create(pendingChanges ? Component.translatable("yacl.gui.save.tooltip") : Component.translatable("yacl.gui.finished.tooltip"))); + cancelResetButton.setMessage(pendingChanges ? GuiUtils.translatableFallback("yacl.gui.cancel", CommonComponents.GUI_CANCEL) : Component.translatable("controls.reset")); + cancelResetButton.setTooltip(Tooltip.create(pendingChanges ? Component.translatable("yacl.gui.cancel.tooltip") : Component.translatable("yacl.gui.reset.tooltip"))); + } + } + + public class PlaceholderTab implements TabExt { + private final PlaceholderCategory category; + private final Tooltip tooltip; + + public PlaceholderTab(PlaceholderCategory category) { + this.category = category; + this.tooltip = Tooltip.create(category.tooltip()); + } + + @Override + public Component getTabTitle() { + return category.name(); + } + + @Override + public void visitChildren(Consumer consumer) { + + } + + @Override + public void doLayout(ScreenRectangle screenRectangle) { + minecraft.setScreen(category.screen().apply(minecraft, YACLScreen.this)); + } + + @Override + public @Nullable Tooltip getTooltip() { + return this.tooltip; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/YACLTooltipPositioner.java b/common/src/main/java/dev/isxander/yacl3/gui/YACLTooltipPositioner.java new file mode 100644 index 0000000..bb87170 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/YACLTooltipPositioner.java @@ -0,0 +1,48 @@ +package dev.isxander.yacl3.gui; + +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.util.Mth; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.function.Supplier; + +public class YACLTooltipPositioner implements ClientTooltipPositioner { + private final Supplier buttonDimensions; + + public YACLTooltipPositioner(net.minecraft.client.gui.components.AbstractWidget widget) { + this.buttonDimensions = widget::getRectangle; + } + + public YACLTooltipPositioner(dev.isxander.yacl3.gui.AbstractWidget widget) { + this.buttonDimensions = () -> { + var dim = widget.getDimension(); + return new ScreenRectangle(dim.x(), dim.y(), dim.width(), dim.height()); + }; + } + + public YACLTooltipPositioner(Supplier buttonDimensions) { + this.buttonDimensions = buttonDimensions; + } + + @Override + public Vector2ic positionTooltip(int guiWidth, int guiHeight, int x, int y, int width, int height) { + ScreenRectangle buttonDimensions = this.buttonDimensions.get(); + + int centerX = buttonDimensions.left() + buttonDimensions.width() / 2; + int aboveY = buttonDimensions.top() - height - 4; + int belowY = buttonDimensions.top() + buttonDimensions.height() + 4; + + int maxBelow = guiHeight - (belowY + height); + int minAbove = aboveY - height; + + int yResult = aboveY; + if (minAbove < 8) + yResult = maxBelow > minAbove ? belowY : aboveY; + + int xResult = Mth.clamp(centerX - width / 2, -4, guiWidth - width - 4); + + return new Vector2i(xResult, yResult); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/ActionController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ActionController.java new file mode 100644 index 0000000..77938f6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ActionController.java @@ -0,0 +1,120 @@ +package dev.isxander.yacl3.gui.controllers; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.ButtonOption; +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.network.chat.Component; + +import java.util.function.BiConsumer; + +/** + * Simple controller that simply runs the button action on press + * and renders a {@link} Text on the right. + */ +public class ActionController implements Controller> { + public static final Component DEFAULT_TEXT = Component.translatable("yacl.control.action.execute"); + + private final ButtonOption option; + private final Component text; + + /** + * Constructs an action controller + * with the default formatter of {@link ActionController#DEFAULT_TEXT} + * + * @param option bound option + */ + public ActionController(ButtonOption option) { + this(option, DEFAULT_TEXT); + } + + /** + * Constructs an action controller + * + * @param option bound option + * @param text text to display + */ + public ActionController(ButtonOption option, Component text) { + this.option = option; + this.text = text; + + } + + /** + * {@inheritDoc} + */ + @Override + public ButtonOption option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return text; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ActionControllerElement(this, screen, widgetDimension); + } + + public static class ActionControllerElement extends ControllerWidget { + private final String buttonString; + + public ActionControllerElement(ActionController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + buttonString = control.formatValue().getString().toLowerCase(); + } + + public void executeAction() { + playDownSound(); + control.option().action().accept(screen, control.option()); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY) && isAvailable()) { + executeAction(); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { + executeAction(); + return true; + } + + return false; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + @Override + public boolean canReset() { + return false; + } + + @Override + public boolean matchesSearch(String query) { + return super.matchesSearch(query) || buttonString.contains(query); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/BooleanController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/BooleanController.java new file mode 100644 index 0000000..9f21755 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/BooleanController.java @@ -0,0 +1,157 @@ +package dev.isxander.yacl3.gui.controllers; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * This controller renders a simple formatted {@link Component} + */ +public class BooleanController implements Controller { + + public static final Function ON_OFF_FORMATTER = (state) -> + state + ? CommonComponents.OPTION_ON + : CommonComponents.OPTION_OFF; + + public static final Function TRUE_FALSE_FORMATTER = (state) -> + state + ? Component.translatable("yacl.control.boolean.true") + : Component.translatable("yacl.control.boolean.false"); + + public static final Function YES_NO_FORMATTER = (state) -> + state + ? CommonComponents.GUI_YES + : CommonComponents.GUI_NO; + + private final Option option; + private final Function valueFormatter; + private final boolean coloured; + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + */ + public BooleanController(Option option) { + this(option, ON_OFF_FORMATTER, false); + } + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option option, boolean coloured) { + this(option, ON_OFF_FORMATTER, coloured); + } + + /** + * Constructs a tickbox controller + * + * @param option bound option + * @param valueFormatter format value into any {@link Component} + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option option, Function valueFormatter, boolean coloured) { + this.option = option; + this.valueFormatter = valueFormatter; + this.coloured = coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * Value format is green or red depending on the state + */ + public boolean coloured() { + return coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new BooleanControllerElement(this, screen, widgetDimension); + } + + public static class BooleanControllerElement extends ControllerWidget { + public BooleanControllerElement(BooleanController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + @Override + protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || !isAvailable()) + return false; + + toggleSetting(); + return true; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + protected Component getValueText() { + if (control.coloured()) { + return super.getValueText().copy().withStyle(control.option().pendingValue() ? ChatFormatting.GREEN : ChatFormatting.RED); + } + + return super.getValueText(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!isFocused()) { + return false; + } + + if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { + toggleSetting(); + return true; + } + + return false; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java new file mode 100644 index 0000000..56e6d30 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java @@ -0,0 +1,220 @@ +package dev.isxander.yacl3.gui.controllers; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.api.utils.MutableDimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.string.IStringController; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import java.awt.*; +import java.util.List; + +/** + * A color controller that uses a hex color field as input. + */ +public class ColorController implements IStringController { + private final Option option; + private final boolean allowAlpha; + + /** + * Constructs a color controller with {@link ColorController#allowAlpha()} defaulting to false + * + * @param option bound option + */ + public ColorController(Option option) { + this(option, false); + } + + /** + * Constructs a color controller + * + * @param option bound option + * @param allowAlpha allows the color input to accept alpha values + */ + public ColorController(Option option, boolean allowAlpha) { + this.option = option; + this.allowAlpha = allowAlpha; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + public boolean allowAlpha() { + return allowAlpha; + } + + @Override + public String getString() { + return formatValue().getString(); + } + + @Override + public Component formatValue() { + MutableComponent text = Component.literal("#"); + text.append(Component.literal(toHex(option().pendingValue().getRed())).withStyle(ChatFormatting.RED)); + text.append(Component.literal(toHex(option().pendingValue().getGreen())).withStyle(ChatFormatting.GREEN)); + text.append(Component.literal(toHex(option().pendingValue().getBlue())).withStyle(ChatFormatting.BLUE)); + if (allowAlpha()) text.append(toHex(option().pendingValue().getAlpha())); + return text; + } + + private String toHex(int value) { + String hex = Integer.toString(value, 16).toUpperCase(); + if (hex.length() == 1) + hex = "0" + hex; + return hex; + } + + @Override + public void setFromString(String value) { + if (value.startsWith("#")) + value = value.substring(1); + + int red = Integer.parseInt(value.substring(0, 2), 16); + int green = Integer.parseInt(value.substring(2, 4), 16); + int blue = Integer.parseInt(value.substring(4, 6), 16); + + if (allowAlpha()) { + int alpha = Integer.parseInt(value.substring(6, 8), 16); + option().requestSet(new Color(red, green, blue, alpha)); + } else { + option().requestSet(new Color(red, green, blue)); + } + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ColorControllerElement(this, screen, widgetDimension); + } + + public static class ColorControllerElement extends StringControllerElement { + private final ColorController colorController; + + protected MutableDimension colorPreviewDim; + + private final List allowedChars; + + public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim, true); + this.colorController = control; + this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); + } + + @Override + protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (isHovered()) { + colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); + super.drawValueText(graphics, mouseX, mouseY, delta); + } + + graphics.fill(colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); + drawOutline(graphics, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); + } + + @Override + public void write(String string) { + if (string.startsWith("0x")) string = string.substring(2); + for (char chr : string.toCharArray()) { + if (!allowedChars.contains(Character.toLowerCase(chr))) { + return; + } + } + + if (caretPos == 0) + return; + + String trimmed = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); + + if (modifyInput(builder -> builder.replace(caretPos, caretPos + trimmed.length(), trimmed))) { + caretPos += trimmed.length(); + setSelectionLength(); + updateControl(); + } + } + + @Override + protected void doBackspace() { + if (caretPos > 1) { + if (modifyInput(builder -> builder.setCharAt(caretPos - 1, '0'))) { + caretPos--; + updateControl(); + } + } + } + + @Override + protected void doDelete() { + if (caretPos >= 1) { + if (modifyInput(builder -> builder.setCharAt(caretPos, '0'))) { + updateControl(); + } + } + } + + @Override + protected boolean doCut() { + return false; + } + + @Override + protected boolean doCopy() { + return false; + } + + @Override + protected boolean doSelectAll() { + return false; + } + + protected void setSelectionLength() { + selectionLength = caretPos < inputField.length() && caretPos > 0 ? 1 : 0; + } + + @Override + protected int getDefaultCaretPos() { + return colorController.allowAlpha() ? 3 : 1; + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + + int previewSize = (dim.height() - getYPadding() * 2) / 2; + colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + int prevSelectionLength = selectionLength; + selectionLength = 0; + if (super.keyPressed(keyCode, scanCode, modifiers)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } else selectionLength = prevSelectionLength; + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } + return false; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerWidget.java new file mode 100644 index 0000000..cf7d0dc --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerWidget.java @@ -0,0 +1,157 @@ +package dev.isxander.yacl3.gui.controllers; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.utils.GuiUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +public abstract class ControllerWidget> extends AbstractWidget { + protected final T control; + protected MultiLineLabel wrappedTooltip; + protected final YACLScreen screen; + + protected boolean focused = false; + protected boolean hovered = false; + + protected final Component modifiedOptionName; + protected final String optionNameString; + + public ControllerWidget(T control, YACLScreen screen, Dimension dim) { + super(dim); + this.control = control; + this.screen = screen; + control.option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + this.modifiedOptionName = control.option().name().copy().withStyle(ChatFormatting.ITALIC); + this.optionNameString = control.option().name().getString().toLowerCase(); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + hovered = isMouseOver(mouseX, mouseY); + + Component name = control.option().changed() ? modifiedOptionName : control.option().name(); + Component shortenedName = Component.literal(GuiUtils.shortenString(name.getString(), textRenderer, getDimension().width() - getControlWidth() - getXPadding() - 7, "...")).setStyle(name.getStyle()); + + drawButtonRect(graphics, getDimension().x(), getDimension().y(), getDimension().xLimit(), getDimension().yLimit(), isHovered(), isAvailable()); + graphics.drawString(textRenderer, shortenedName, getDimension().x() + getXPadding(), getTextY(), getValueColor(), true); + + + drawValueText(graphics, mouseX, mouseY, delta); + if (isHovered()) { + drawHoveredControl(graphics, mouseX, mouseY, delta); + } + } + + protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + + } + + protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + Component valueText = getValueText(); + graphics.drawString(textRenderer, valueText, getDimension().xLimit() - textRenderer.width(valueText) - getXPadding(), getTextY(), getValueColor(), true); + } + + private void updateTooltip() { + this.wrappedTooltip = MultiLineLabel.create(textRenderer, control.option().tooltip(), screen.width / 3 * 2 - 10); + } + + protected int getControlWidth() { + return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); + } + + public boolean isHovered() { + return isAvailable() && (hovered || focused); + } + + protected abstract int getHoveredControlWidth(); + + protected int getUnhoveredControlWidth() { + return textRenderer.width(getValueText()); + } + + protected int getXPadding() { + return 5; + } + + protected int getYPadding() { + return 2; + } + + protected Component getValueText() { + return control.formatValue(); + } + + protected boolean isAvailable() { + return control.option().available(); + } + + protected int getValueColor() { + return isAvailable() ? -1 : inactiveColor; + } + + @Override + public boolean canReset() { + return true; + } + + protected void drawOutline(GuiGraphics graphics, int x1, int y1, int x2, int y2, int width, int color) { + graphics.fill(x1, y1, x2, y1 + width, color); + graphics.fill(x2, y1, x2 - width, y2, color); + graphics.fill(x1, y2, x2, y2 - width, color); + graphics.fill(x1, y1, x1 + width, y2, color); + } + + protected int getTextY() { + return (int)(getDimension().y() + getDimension().height() / 2f - textRenderer.lineHeight / 2f); + } + + @Nullable + @Override + public ComponentPath nextFocusPath(FocusNavigationEvent focusNavigationEvent) { + if (!this.isAvailable()) + return null; + return !this.isFocused() ? ComponentPath.leaf(this) : null; + } + + @Override + public boolean isFocused() { + return focused; + } + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public void unfocus() { + this.focused = false; + } + + @Override + public boolean matchesSearch(String query) { + return optionNameString.contains(query.toLowerCase()); + } + + @Override + public NarrationPriority narrationPriority() { + return focused ? NarrationPriority.FOCUSED : isHovered() ? NarrationPriority.HOVERED : NarrationPriority.NONE; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + builder.add(NarratedElementType.TITLE, control.option().name()); + builder.add(NarratedElementType.HINT, control.option().tooltip()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java new file mode 100644 index 0000000..fee6c19 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java @@ -0,0 +1,193 @@ +package dev.isxander.yacl3.gui.controllers; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.Style; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Simply renders some text as a label. + */ +public class LabelController implements Controller { + private final Option option; + /** + * Constructs a label controller + * + * @param option bound option + */ + public LabelController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + @Override + public Component formatValue() { + return option().pendingValue(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new LabelControllerElement(screen, widgetDimension); + } + + public class LabelControllerElement extends AbstractWidget { + private List wrappedText; + protected MultiLineLabel wrappedTooltip; + protected boolean focused; + + protected final YACLScreen screen; + + public LabelControllerElement(YACLScreen screen, Dimension dim) { + super(dim); + this.screen = screen; + option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + updateText(); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + updateText(); + + int y = getDimension().y(); + for (FormattedCharSequence text : wrappedText) { + graphics.drawString(textRenderer, text, getDimension().x() + getXPadding(), y + getYPadding(), option().available() ? -1 : 0xFFA0A0A0, true); + y += textRenderer.lineHeight; + } + + if (isFocused()) { + graphics.fill(getDimension().x() - 1, getDimension().y() - 1, getDimension().xLimit() + 1, getDimension().y(), -1); + graphics.fill(getDimension().x() - 1, getDimension().y() - 1, getDimension().x(), getDimension().yLimit() + 1, -1); + graphics.fill(getDimension().x() - 1, getDimension().yLimit(), getDimension().xLimit() + 1, getDimension().yLimit() + 1, -1); + graphics.fill(getDimension().xLimit(), getDimension().y() - 1, getDimension().xLimit() + 1, getDimension().yLimit() + 1, -1); + } + + 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(); + HoverEvent.ItemStackInfo itemStackContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ITEM); + if (itemStackContent != null) { + ItemStack stack = itemStackContent.getItemStack(); + graphics.renderTooltip(textRenderer, Screen.getTooltipFromItem(client, stack), stack.getTooltipImage(), mouseX, mouseY); + } else { + HoverEvent.EntityTooltipInfo entityContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ENTITY); + if (entityContent != null) { + if (this.client.options.advancedItemTooltips) { + graphics.renderComponentTooltip(textRenderer, entityContent.getTooltipLines(), mouseX, mouseY); + } + } else { + Component text = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT); + if (text != null) { + MultiLineLabel multilineText = MultiLineLabel.create(textRenderer, text, getDimension().width()); + YACLScreen.renderMultilineTooltip(graphics, textRenderer, multilineText, getDimension().centerX(), getDimension().y(), getDimension().yLimit(), screen.width, screen.height); + } + } + } + } + } + graphics.pose().popPose(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY)) + return false; + + Style style = getStyle((int) mouseX, (int) mouseY); + return screen.handleComponentClicked(style); + } + + protected Style getStyle(int mouseX, int mouseY) { + if (!getDimension().isPointInside(mouseX, mouseY)) + return null; + + int x = mouseX - getDimension().x(); + int y = mouseY - getDimension().y() - getYPadding(); + int line = y / textRenderer.lineHeight; + + if (x < 0 || x > getDimension().xLimit()) return null; + if (y < 0 || y > getDimension().yLimit()) return null; + if (line < 0 || line >= wrappedText.size()) return null; + + return textRenderer.getSplitter().componentStyleAtWidth(wrappedText.get(line), x); + } + + private int getXPadding() { + return 4; + } + + private int getYPadding() { + return 3; + } + + private void updateText() { + wrappedText = textRenderer.split(formatValue(), getDimension().width() - getXPadding() * 2); + setDimension(getDimension().withHeight(wrappedText.size() * textRenderer.lineHeight + getYPadding() * 2)); + } + + private void updateTooltip() { + this.wrappedTooltip = MultiLineLabel.create(textRenderer, option().tooltip(), screen.width / 3 * 2 - 10); + } + + @Override + public boolean matchesSearch(String query) { + return formatValue().getString().toLowerCase().contains(query.toLowerCase()); + } + + @Nullable + @Override + public ComponentPath nextFocusPath(FocusNavigationEvent focusNavigationEvent) { + if (!option().available()) + return null; + return !this.isFocused() ? ComponentPath.leaf(this) : null; + } + + @Override + public boolean isFocused() { + return focused; + } + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + builder.add(NarratedElementType.TITLE, formatValue()); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.FOCUSED; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/ListEntryWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ListEntryWidget.java new file mode 100644 index 0000000..15deff2 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/ListEntryWidget.java @@ -0,0 +1,128 @@ +package dev.isxander.yacl3.gui.controllers; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.ListOption; +import dev.isxander.yacl3.api.ListOptionEntry; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.TooltipButtonWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ListEntryWidget extends AbstractWidget implements ContainerEventHandler { + private final TooltipButtonWidget removeButton, moveUpButton, moveDownButton; + private final AbstractWidget entryWidget; + + private final ListOption listOption; + private final ListOptionEntry listOptionEntry; + + private final String optionNameString; + + private GuiEventListener focused; + private boolean dragging; + + public ListEntryWidget(YACLScreen screen, ListOptionEntry listOptionEntry, AbstractWidget entryWidget) { + super(entryWidget.getDimension().withHeight(Math.max(entryWidget.getDimension().height(), 20) - ((listOptionEntry.parentGroup().indexOf(listOptionEntry) == listOptionEntry.parentGroup().options().size() - 1) ? 0 : 2))); // -2 to remove the padding + this.listOptionEntry = listOptionEntry; + this.listOption = listOptionEntry.parentGroup(); + this.optionNameString = listOptionEntry.name().getString().toLowerCase(); + this.entryWidget = entryWidget; + + Dimension dim = entryWidget.getDimension(); + entryWidget.setDimension(dim.clone().move(20 * 2, 0).expand(-20 * 3, 0)); + + removeButton = new TooltipButtonWidget(screen, dim.xLimit() - 20, dim.y(), 20, 20, Component.literal("\u274c"), Component.translatable("yacl.list.remove"), btn -> { + listOption.removeEntry(listOptionEntry); + updateButtonStates(); + }); + + moveUpButton = new TooltipButtonWidget(screen, dim.x(), dim.y(), 20, 20, Component.literal("\u2191"), Component.translatable("yacl.list.move_up"), btn -> { + int index = listOption.indexOf(listOptionEntry) - 1; + if (index >= 0) { + listOption.removeEntry(listOptionEntry); + listOption.insertEntry(index, listOptionEntry); + updateButtonStates(); + } + }); + + moveDownButton = new TooltipButtonWidget(screen, dim.x() + 20, dim.y(), 20, 20, Component.literal("\u2193"), Component.translatable("yacl.list.move_down"), btn -> { + int index = listOption.indexOf(listOptionEntry) + 1; + if (index < listOption.options().size()) { + listOption.removeEntry(listOptionEntry); + listOption.insertEntry(index, listOptionEntry); + updateButtonStates(); + } + }); + + updateButtonStates(); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + updateButtonStates(); // update every render in case option becomes available/unavailable + + removeButton.setY(getDimension().y()); + moveUpButton.setY(getDimension().y()); + moveDownButton.setY(getDimension().y()); + entryWidget.setDimension(entryWidget.getDimension().withY(getDimension().y())); + + removeButton.render(graphics, mouseX, mouseY, delta); + moveUpButton.render(graphics, mouseX, mouseY, delta); + moveDownButton.render(graphics, mouseX, mouseY, delta); + entryWidget.render(graphics, mouseX, mouseY, delta); + } + + protected void updateButtonStates() { + removeButton.active = listOption.available(); + moveUpButton.active = listOption.indexOf(listOptionEntry) > 0 && listOption.available(); + moveDownButton.active = listOption.indexOf(listOptionEntry) < listOption.options().size() - 1 && listOption.available(); + } + + @Override + public void unfocus() { + entryWidget.unfocus(); + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + entryWidget.updateNarration(builder); + } + + @Override + public boolean matchesSearch(String query) { + return optionNameString.contains(query.toLowerCase()); + } + + @Override + public List children() { + return ImmutableList.of(moveUpButton, moveDownButton, entryWidget, removeButton); + } + + @Override + public boolean isDragging() { + return dragging; + } + + @Override + public void setDragging(boolean dragging) { + this.dragging = dragging; + } + + @Nullable + @Override + public GuiEventListener getFocused() { + return focused; + } + + @Override + public void setFocused(@Nullable GuiEventListener focused) { + this.focused = focused; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/TickBoxController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/TickBoxController.java new file mode 100644 index 0000000..de19c14 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/TickBoxController.java @@ -0,0 +1,119 @@ +package dev.isxander.yacl3.gui.controllers; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; + +/** + * This controller renders a tickbox + */ +public class TickBoxController implements Controller { + private final Option option; + + /** + * Constructs a tickbox controller + * + * @param option bound option + */ + public TickBoxController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return Component.empty(); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new TickBoxControllerElement(this, screen, widgetDimension); + } + + public static class TickBoxControllerElement extends ControllerWidget { + public TickBoxControllerElement(TickBoxController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + @Override + protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + int outlineSize = 10; + int outlineX1 = getDimension().xLimit() - getXPadding() - outlineSize; + int outlineY1 = getDimension().centerY() - outlineSize / 2; + int outlineX2 = getDimension().xLimit() - getXPadding(); + int outlineY2 = getDimension().centerY() + outlineSize / 2; + + int color = getValueColor(); + int shadowColor = multiplyColor(color, 0.25f); + + drawOutline(graphics, outlineX1 + 1, outlineY1 + 1, outlineX2 + 1, outlineY2 + 1, 1, shadowColor); + drawOutline(graphics, outlineX1, outlineY1, outlineX2, outlineY2, 1, color); + if (control.option().pendingValue()) { + graphics.fill(outlineX1 + 3, outlineY1 + 3, outlineX2 - 1, outlineY2 - 1, shadowColor); + graphics.fill(outlineX1 + 2, outlineY1 + 2, outlineX2 - 2, outlineY2 - 2, color); + } + } + + @Override + protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (!isHovered()) + drawHoveredControl(graphics, mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || !isAvailable()) + return false; + + toggleSetting(); + return true; + } + + @Override + protected int getHoveredControlWidth() { + return 10; + } + + @Override + protected int getUnhoveredControlWidth() { + return 10; + } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_SPACE || keyCode == InputConstants.KEY_NUMPADENTER) { + toggleSetting(); + return true; + } + + return false; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingControllerElement.java new file mode 100644 index 0000000..3d85afe --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingControllerElement.java @@ -0,0 +1,60 @@ +package dev.isxander.yacl3.gui.controllers.cycling; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.ControllerWidget; +import net.minecraft.client.gui.screens.Screen; + +public class CyclingControllerElement extends ControllerWidget> { + + public CyclingControllerElement(ICyclingController control, YACLScreen screen, Dimension dim) { + super(control, screen, dim); + } + + public void cycleValue(int increment) { + int targetIdx = control.getPendingValue() + increment; + if (targetIdx >= control.getCycleLength()) { + targetIdx -= control.getCycleLength(); + } else if (targetIdx < 0) { + targetIdx += control.getCycleLength(); + } + control.setPendingValue(targetIdx); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || (button != 0 && button != 1) || !isAvailable()) + return false; + + playDownSound(); + cycleValue(button == 1 || Screen.hasShiftDown() || Screen.hasControlDown() ? -1 : 1); + + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) + return false; + + switch (keyCode) { + case InputConstants.KEY_LEFT -> + cycleValue(-1); + case InputConstants.KEY_RIGHT -> + cycleValue(1); + case InputConstants.KEY_RETURN, InputConstants.KEY_SPACE, InputConstants.KEY_NUMPADENTER -> + cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); + default -> { + return false; + } + } + + return true; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingListController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingListController.java new file mode 100644 index 0000000..44fa765 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingListController.java @@ -0,0 +1,79 @@ +package dev.isxander.yacl3.gui.controllers.cycling; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.Option; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * A controller where once clicked, cycles through elements + * in the provided list. + */ +public class CyclingListController implements ICyclingController { + private final Option option; + private final Function valueFormatter; + private final ImmutableList values; + + /** + * Constructs a {@link CyclingListController}, with a default + * value formatter of {@link Object#toString()}. + * @param option option of which to bind the controller to + * @param values the values to cycle through + */ + public CyclingListController(Option option, Iterable values) { + this(option, values, value -> Component.literal(value.toString())); + } + + /** + * Constructs a {@link CyclingListController} + * @param option option of which to bind the controller to + * @param values the values to cycle through + * @param valueFormatter function of how to convert each value to a string to display + */ + public CyclingListController(Option option, Iterable values, Function valueFormatter) { + this.option = option; + this.valueFormatter = valueFormatter; + this.values = ImmutableList.copyOf(values); + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(int ordinal) { + option().requestSet(values.get(ordinal)); + } + + /** + * {@inheritDoc} + */ + @Override + public int getPendingValue() { + return values.indexOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public int getCycleLength() { + return values.size(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/EnumController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/EnumController.java new file mode 100644 index 0000000..d2df8f8 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/EnumController.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.gui.controllers.cycling; + +import dev.isxander.yacl3.api.NameableEnum; +import dev.isxander.yacl3.api.Option; +import net.minecraft.network.chat.Component; +import net.minecraft.util.OptionEnum; + +import java.util.Arrays; +import java.util.function.Function; + +/** + * Simple controller type that displays the enum on the right. + *

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

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

+ * For simplicity, {@link SliderControllerElement} works in doubles so each + * {@link ISliderController} must cast to double. This is to get around re-writing the element for every type. + */ +public interface ISliderController extends Controller { + /** + * Gets the minimum value for the slider + */ + double min(); + + /** + * Gets the maximum value for the slider + */ + double max(); + + /** + * Gets the interval (or step size) for the slider. + */ + double interval(); + + /** + * Gets the range of the slider. + */ + default double range() { + return max() - min(); + } + + /** + * Sets the {@link dev.isxander.yacl3.api.Option}'s pending value + */ + void setPendingValue(double value); + + /** + * Gets the {@link dev.isxander.yacl3.api.Option}'s pending value + */ + double pendingValue(); + + /** + * {@inheritDoc} + */ + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new SliderControllerElement(this, screen, widgetDimension, min(), max(), interval()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/IntegerSliderController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/IntegerSliderController.java new file mode 100644 index 0000000..ea658ad --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/IntegerSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl3.gui.controllers.slider; + +import dev.isxander.yacl3.api.Option; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for integers. + */ +public class IntegerSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Component.literal(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); + + private final Option option; + + private final int min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for integers + * using the default value formatter {@link IntegerSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public IntegerSliderController(Option option, int min, int max, int interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for integers. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Component} + */ + public IntegerSliderController(Option option, int min, int max, int interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((int) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/LongSliderController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/LongSliderController.java new file mode 100644 index 0000000..336f064 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/LongSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl3.gui.controllers.slider; + +import dev.isxander.yacl3.api.Option; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for longs. + */ +public class LongSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Component.literal(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); + + private final Option option; + + private final long min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for longs + * using the default value formatter {@link LongSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public LongSliderController(Option option, long min, long max, long interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for longs. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Component} + */ + public LongSliderController(Option option, long min, long max, long interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((long) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/SliderControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/SliderControllerElement.java new file mode 100644 index 0000000..5590dbf --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/SliderControllerElement.java @@ -0,0 +1,157 @@ +package dev.isxander.yacl3.gui.controllers.slider; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.ControllerWidget; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.util.Mth; + +public class SliderControllerElement extends ControllerWidget> { + private final double min, max, interval; + + private float interpolation; + + private Dimension sliderBounds; + + private boolean mouseDown = false; + + public SliderControllerElement(ISliderController option, YACLScreen screen, Dimension dim, double min, double max, double interval) { + super(option, screen, dim); + this.min = min; + this.max = max; + this.interval = interval; + setDimension(dim); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); + + calculateInterpolation(); + } + + @Override + protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + // track + graphics.fill(sliderBounds.x(), sliderBounds.centerY() - 1, sliderBounds.xLimit(), sliderBounds.centerY(), -1); + // track shadow + graphics.fill(sliderBounds.x() + 1, sliderBounds.centerY(), sliderBounds.xLimit() + 1, sliderBounds.centerY() + 1, 0xFF404040); + + // thumb shadow + graphics.fill(getThumbX() - getThumbWidth() / 2 + 1, sliderBounds.y() + 1, getThumbX() + getThumbWidth() / 2 + 1, sliderBounds.yLimit() + 1, 0xFF404040); + // thumb + graphics.fill(getThumbX() - getThumbWidth() / 2, sliderBounds.y(), getThumbX() + getThumbWidth() / 2, sliderBounds.yLimit(), -1); + } + + @Override + protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + graphics.pose().pushPose(); + if (isHovered()) + graphics.pose().translate(-(sliderBounds.width() + 6 + getThumbWidth() / 2f), 0, 0); + super.drawValueText(graphics, mouseX, mouseY, delta); + graphics.pose().popPose(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isAvailable() || button != 0 || !sliderBounds.isPointInside((int) mouseX, (int) mouseY)) + return false; + + mouseDown = true; + + setValueFromMouse(mouseX); + return true; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (!isAvailable() || button != 0 || !mouseDown) + return false; + + setValueFromMouse(mouseX); + return true; + } + + public void incrementValue(double amount) { + control.setPendingValue(Mth.clamp(control.pendingValue() + interval * amount, min, max)); + calculateInterpolation(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + if (!isAvailable() || (!isMouseOver(mouseX, mouseY)) || (!Screen.hasShiftDown() && !Screen.hasControlDown())) + return false; + + incrementValue(amount); + return true; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (isAvailable() && mouseDown) + playDownSound(); + mouseDown = false; + + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) + return false; + + switch (keyCode) { + case InputConstants.KEY_LEFT -> incrementValue(-1); + case InputConstants.KEY_RIGHT -> incrementValue(1); + default -> { + return false; + } + } + + return true; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + return super.isMouseOver(mouseX, mouseY) || mouseDown; + } + + protected void setValueFromMouse(double mouseX) { + double value = (mouseX - sliderBounds.x()) / sliderBounds.width() * control.range(); + control.setPendingValue(roundToInterval(value)); + calculateInterpolation(); + } + + protected double roundToInterval(double value) { + return Mth.clamp(min + (interval * Math.round(value / interval)), min, max); // extremely imprecise, requires clamping + } + + @Override + protected int getHoveredControlWidth() { + return sliderBounds.width() + getUnhoveredControlWidth() + 6 + getThumbWidth() / 2; + } + + protected void calculateInterpolation() { + interpolation = Mth.clamp((float) ((control.pendingValue() - control.min()) * 1 / control.range()), 0f, 1f); + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + int trackWidth = dim.width() / 3; + if (optionNameString.isEmpty()) + trackWidth = dim.width() / 2; + + sliderBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - getThumbWidth() / 2 - trackWidth, dim.centerY() - 5, trackWidth, 10); + } + + protected int getThumbX() { + return (int) (sliderBounds.x() + sliderBounds.width() * interpolation); + } + + protected int getThumbWidth() { + return 4; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/package-info.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/package-info.java new file mode 100644 index 0000000..e2cb0e3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains implementations of sliders for different number types + *

    + *
  • For doubles: {@link dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController}
  • + *
  • For floats: {@link dev.isxander.yacl3.gui.controllers.slider.FloatSliderController}
  • + *
  • For integers: {@link dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController}
  • + *
  • For longs: {@link dev.isxander.yacl3.gui.controllers.slider.LongSliderController}
  • + *
+ */ +package dev.isxander.yacl3.gui.controllers.slider; diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/IStringController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/IStringController.java new file mode 100644 index 0000000..14d10dd --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/IStringController.java @@ -0,0 +1,44 @@ +package dev.isxander.yacl3.gui.controllers.string; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.network.chat.Component; + +/** + * A controller that can be any type but can input and output a string. + */ +public interface IStringController extends Controller { + /** + * Gets the option's pending value as a string. + * + * @see Option#pendingValue() + */ + String getString(); + + /** + * Sets the option's pending value from a string. + * + * @see Option#requestSet(Object) + */ + void setFromString(String value); + + /** + * {@inheritDoc} + */ + @Override + default Component formatValue() { + return Component.literal(getString()); + } + + default boolean isInputValid(String input) { + return true; + } + + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension, true); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringController.java new file mode 100644 index 0000000..4bafc0f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringController.java @@ -0,0 +1,37 @@ +package dev.isxander.yacl3.gui.controllers.string; + +import dev.isxander.yacl3.api.Option; + +/** + * A custom text field implementation for strings. + */ +public class StringController implements IStringController { + private final Option option; + + /** + * Constructs a string controller + * + * @param option bound option + */ + public StringController(Option option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + @Override + public String getString() { + return option().pendingValue(); + } + + @Override + public void setFromString(String value) { + option().requestSet(value); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java new file mode 100644 index 0000000..a889a77 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java @@ -0,0 +1,403 @@ +package dev.isxander.yacl3.gui.controllers.string; + +import com.mojang.blaze3d.platform.InputConstants; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.ControllerWidget; +import dev.isxander.yacl3.gui.utils.GuiUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +public class StringControllerElement extends ControllerWidget> { + protected final boolean instantApply; + + protected String inputField; + protected Dimension inputFieldBounds; + protected boolean inputFieldFocused; + + protected int caretPos; + protected int selectionLength; + + protected int renderOffset; + + protected float ticks; + + private final Component emptyText; + + public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim, boolean instantApply) { + super(control, screen, dim); + this.instantApply = instantApply; + inputField = control.getString(); + inputFieldFocused = false; + selectionLength = 0; + emptyText = Component.literal("Click to type...").withStyle(ChatFormatting.GRAY); + control.option().addListener((opt, val) -> inputField = control.getString()); + setDimension(dim); + } + + @Override + protected void drawHoveredControl(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + + } + + @Override + protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + Component valueText = getValueText(); + if (!isHovered()) valueText = Component.literal(GuiUtils.shortenString(valueText.getString(), textRenderer, getMaxUnwrapLength(), "...")).setStyle(valueText.getStyle()); + + int textX = getDimension().xLimit() - textRenderer.width(valueText) + renderOffset - getXPadding(); + graphics.enableScissor(inputFieldBounds.x(), inputFieldBounds.y() - 2, inputFieldBounds.width() + 1, inputFieldBounds.height() + 4); + graphics.drawString(textRenderer, valueText, textX, getTextY(), getValueColor(), true); + + if (isHovered()) { + ticks += delta; + + String text = getValueText().getString(); + + graphics.fill(inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1); + graphics.fill(inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040); + + if (inputFieldFocused || focused) { + if (caretPos > text.length()) + caretPos = text.length(); + + int caretX = textX + textRenderer.width(text.substring(0, caretPos)) - 1; + if (text.isEmpty()) + caretX = inputFieldBounds.x() + inputFieldBounds.width() / 2; + + if (ticks % 20 <= 10) { + graphics.fill(caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1); + } + + if (selectionLength != 0) { + int selectionX = textX + textRenderer.width(text.substring(0, caretPos + selectionLength)); + graphics.fill(caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF); + } + } + } + graphics.disableScissor(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isAvailable() && getDimension().isPointInside((int) mouseX, (int) mouseY)) { + inputFieldFocused = true; + + if (!inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) { + caretPos = getDefaultCaretPos(); + } else { + // gets the appropriate caret position for where you click + int textX = (int) mouseX - (inputFieldBounds.xLimit() - textRenderer.width(getValueText())); + int pos = -1; + int currentWidth = 0; + for (char ch : inputField.toCharArray()) { + pos++; + int charLength = textRenderer.width(String.valueOf(ch)); + if (currentWidth + charLength / 2 > textX) { // if more than halfway past the characters select in front of that char + caretPos = pos; + break; + } else if (pos == inputField.length() - 1) { + // if we have reached the end and no matches, it must be the second half of the char so the last position + caretPos = pos + 1; + } + currentWidth += charLength; + } + + selectionLength = 0; + } + return true; + } else { + inputFieldFocused = false; + } + + return false; + } + + protected int getDefaultCaretPos() { + return inputField.length(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!inputFieldFocused) + return false; + + switch (keyCode) { + case InputConstants.KEY_ESCAPE, InputConstants.KEY_RETURN -> { + unfocus(); + return true; + } + case InputConstants.KEY_LEFT -> { + if (Screen.hasShiftDown()) { + if (Screen.hasControlDown()) { + int spaceChar = findSpaceIndex(true); + selectionLength += caretPos - spaceChar; + caretPos = spaceChar; + } else if (caretPos > 0) { + caretPos--; + selectionLength += 1; + } + checkRenderOffset(); + } else { + if (caretPos > 0) { + if (selectionLength != 0) + caretPos += Math.min(selectionLength, 0); + else + caretPos--; + } + checkRenderOffset(); + selectionLength = 0; + } + + return true; + } + case InputConstants.KEY_RIGHT -> { + if (Screen.hasShiftDown()) { + if (Screen.hasControlDown()) { + int spaceChar = findSpaceIndex(false); + selectionLength -= spaceChar - caretPos; + caretPos = spaceChar; + } else if (caretPos < inputField.length()) { + caretPos++; + selectionLength -= 1; + } + checkRenderOffset(); + } else { + if (caretPos < inputField.length()) { + if (selectionLength != 0) + caretPos += Math.max(selectionLength, 0); + else + caretPos++; + checkRenderOffset(); + } + selectionLength = 0; + } + + return true; + } + case InputConstants.KEY_BACKSPACE -> { + doBackspace(); + return true; + } + case InputConstants.KEY_DELETE -> { + doDelete(); + return true; + } + } + + if (Screen.isPaste(keyCode)) { + return doPaste(); + } else if (Screen.isCopy(keyCode)) { + return doCopy(); + } else if (Screen.isCut(keyCode)) { + return doCut(); + } else if (Screen.isSelectAll(keyCode)) { + return doSelectAll(); + } + + return false; + } + + protected boolean doPaste() { + this.write(client.keyboardHandler.getClipboard()); + return true; + } + + protected boolean doCopy() { + if (selectionLength != 0) { + client.keyboardHandler.setClipboard(getSelection()); + return true; + } + return false; + } + + protected boolean doCut() { + if (selectionLength != 0) { + client.keyboardHandler.setClipboard(getSelection()); + this.write(""); + return true; + } + return false; + } + + protected boolean doSelectAll() { + caretPos = inputField.length(); + checkRenderOffset(); + selectionLength = -caretPos; + return true; + } + + protected void checkRenderOffset() { + if (textRenderer.width(inputField) < getUnshiftedLength()) { + renderOffset = 0; + return; + } + + int textX = getDimension().xLimit() - textRenderer.width(inputField) - getXPadding(); + int caretX = textX + textRenderer.width(inputField.substring(0, caretPos)) - 1; + + int minX = getDimension().xLimit() - getXPadding() - getUnshiftedLength(); + int maxX = minX + getUnshiftedLength(); + + if (caretX + renderOffset < minX) { + renderOffset = minX - caretX; + } else if (caretX + renderOffset > maxX) { + renderOffset = maxX - caretX; + } + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (!inputFieldFocused) + return false; + + write(Character.toString(chr)); + + return true; + } + + protected void doBackspace() { + if (selectionLength != 0) { + write(""); + } else if (caretPos > 0) { + if (modifyInput(builder -> builder.deleteCharAt(caretPos - 1))) { + caretPos--; + checkRenderOffset(); + } + } + } + + protected void doDelete() { + if (selectionLength != 0) { + write(""); + } else if (caretPos < inputField.length()) { + modifyInput(builder -> builder.deleteCharAt(caretPos)); + } + } + + public void write(String string) { + if (selectionLength == 0) { + if (modifyInput(builder -> builder.insert(caretPos, string))) { + caretPos += string.length(); + checkRenderOffset(); + } + } else { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (modifyInput(builder -> builder.replace(start, end, string))) { + caretPos = start + string.length(); + selectionLength = 0; + checkRenderOffset(); + } + } + } + + public boolean modifyInput(Consumer consumer) { + StringBuilder temp = new StringBuilder(inputField); + consumer.accept(temp); + if (!control.isInputValid(temp.toString())) + return false; + inputField = temp.toString(); + if (instantApply) + updateControl(); + return true; + } + + public int getUnshiftedLength() { + if (optionNameString.isEmpty()) + return getDimension().width() - getXPadding() * 2; + return getDimension().width() / 8 * 5; + } + + public int getMaxUnwrapLength() { + if (optionNameString.isEmpty()) + return getDimension().width() - getXPadding() * 2; + return getDimension().width() / 2; + } + + public int getSelectionStart() { + return Math.min(caretPos, caretPos + selectionLength); + } + + public int getSelectionEnd() { + return Math.max(caretPos, caretPos + selectionLength); + } + + protected String getSelection() { + return inputField.substring(getSelectionStart(), getSelectionEnd()); + } + + protected int findSpaceIndex(boolean reverse) { + int i; + int fromIndex = caretPos; + if (reverse) { + if (caretPos > 0) + fromIndex -= 1; + i = this.inputField.lastIndexOf(" ", fromIndex); + + if (i == -1) i = 0; + } else { + if (caretPos < inputField.length()) + fromIndex += 1; + i = this.inputField.indexOf(" ", fromIndex); + + if (i == -1) i = inputField.length(); + } + + return i; + } + + @Override + public void setFocused(boolean focused) { + super.setFocused(focused); + inputFieldFocused = focused; + } + + @Override + public void unfocus() { + super.unfocus(); + inputFieldFocused = false; + renderOffset = 0; + if (!instantApply) updateControl(); + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + + int width = Math.max(6, Math.min(textRenderer.width(getValueText()), getUnshiftedLength())); + inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.lineHeight / 2, width, textRenderer.lineHeight); + } + + @Override + public boolean isHovered() { + return super.isHovered() || inputFieldFocused; + } + + protected void updateControl() { + control.setFromString(inputField); + } + + @Override + protected int getUnhoveredControlWidth() { + return !isHovered() ? Math.min(getHoveredControlWidth(), getMaxUnwrapLength()) : getHoveredControlWidth(); + } + + @Override + protected int getHoveredControlWidth() { + return Math.min(textRenderer.width(getValueText()), getUnshiftedLength()); + } + + @Override + protected Component getValueText() { + if (!inputFieldFocused && inputField.isEmpty()) + return emptyText; + + return instantApply || !inputFieldFocused ? control.formatValue() : Component.literal(inputField); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/DoubleFieldController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/DoubleFieldController.java new file mode 100644 index 0000000..3f70a12 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/DoubleFieldController.java @@ -0,0 +1,104 @@ +package dev.isxander.yacl3.gui.controllers.string.number; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class DoubleFieldController extends NumberFieldController { + private final double min, max; + + /** + * Constructs a double field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public DoubleFieldController(Option option, double min, double max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a double field controller. + * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public DoubleFieldController(Option option, double min, double max) { + this(option, min, max, DoubleSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a double field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public DoubleFieldController(Option option, Function formatter) { + this(option, -Double.MAX_VALUE, Double.MAX_VALUE, formatter); + } + + /** + * Constructs a double field controller. + * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public DoubleFieldController(Option option) { + this(option, -Double.MAX_VALUE, Double.MAX_VALUE, DoubleSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet(value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/FloatFieldController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/FloatFieldController.java new file mode 100644 index 0000000..57737f7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/FloatFieldController.java @@ -0,0 +1,104 @@ +package dev.isxander.yacl3.gui.controllers.string.number; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class FloatFieldController extends NumberFieldController { + private final float min, max; + + /** + * Constructs a float field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public FloatFieldController(Option option, float min, float max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a float field controller. + * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public FloatFieldController(Option option, float min, float max) { + this(option, min, max, FloatSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a float field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public FloatFieldController(Option option, Function formatter) { + this(option, -Float.MAX_VALUE, Float.MAX_VALUE, formatter); + } + + /** + * Constructs a float field controller. + * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public FloatFieldController(Option option) { + this(option, -Float.MAX_VALUE, Float.MAX_VALUE, FloatSliderController.DEFAULT_FORMATTER); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((float) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/IntegerFieldController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/IntegerFieldController.java new file mode 100644 index 0000000..5c15a2e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/IntegerFieldController.java @@ -0,0 +1,109 @@ +package dev.isxander.yacl3.gui.controllers.string.number; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class IntegerFieldController extends NumberFieldController { + private final int min, max; + + /** + * Constructs a integer field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public IntegerFieldController(Option option, int min, int max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a integer field controller. + * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public IntegerFieldController(Option option, int min, int max) { + this(option, min, max, IntegerSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a integer field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public IntegerFieldController(Option option, Function formatter) { + this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, formatter); + } + + /** + * Constructs a integer field controller. + * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public IntegerFieldController(Option option) { + this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, IntegerSliderController.DEFAULT_FORMATTER); + } + + @Override + public boolean isInputValid(String input) { + return input.matches("\\d+|-|"); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((int) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/LongFieldController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/LongFieldController.java new file mode 100644 index 0000000..b36b0f3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/LongFieldController.java @@ -0,0 +1,109 @@ +package dev.isxander.yacl3.gui.controllers.string.number; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.gui.controllers.slider.LongSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +/** + * {@inheritDoc} + */ +public class LongFieldController extends NumberFieldController { + private final long min, max; + + /** + * Constructs a long field controller + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + * @param formatter display text, not used whilst editing + */ + public LongFieldController(Option option, long min, long max, Function formatter) { + super(option, formatter); + this.min = min; + this.max = max; + } + + /** + * Constructs a long field controller. + * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * + * @param option option to bind controller to + * @param min minimum allowed value (clamped on apply) + * @param max maximum allowed value (clamped on apply) + */ + public LongFieldController(Option option, long min, long max) { + this(option, min, max, LongSliderController.DEFAULT_FORMATTER); + } + + /** + * Constructs a long field controller. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + * @param formatter display text, not used whilst editing + */ + public LongFieldController(Option option, Function formatter) { + this(option, -Long.MAX_VALUE, Long.MAX_VALUE, formatter); + } + + /** + * Constructs a long field controller. + * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text, + * not used whilst editing. + * Does not have a minimum or a maximum range. + * + * @param option option to bind controller to + */ + public LongFieldController(Option option) { + this(option, -Long.MAX_VALUE, Long.MAX_VALUE, LongSliderController.DEFAULT_FORMATTER); + } + + @Override + public boolean isInputValid(String input) { + return input.matches("\\d+|-|"); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return this.min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return this.max; + } + + /** + * {@inheritDoc} + */ + @Override + public String getString() { + return String.valueOf(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPendingValue(double value) { + option().requestSet((long) value); + } + + /** + * {@inheritDoc} + */ + @Override + public double pendingValue() { + return option().pendingValue(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/NumberFieldController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/NumberFieldController.java new file mode 100644 index 0000000..f484aad --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/NumberFieldController.java @@ -0,0 +1,69 @@ +package dev.isxander.yacl3.gui.controllers.string.number; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.slider.ISliderController; +import dev.isxander.yacl3.gui.controllers.string.IStringController; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +import java.text.DecimalFormatSymbols; +import java.util.function.Function; + +/** + * Controller that allows you to enter in numbers using a text field. + * + * @param number type + */ +public abstract class NumberFieldController implements ISliderController, IStringController { + private final Option option; + private final Function displayFormatter; + + public NumberFieldController(Option option, Function displayFormatter) { + this.option = option; + this.displayFormatter = displayFormatter; + } + + @Override + public Option option() { + return this.option; + } + + @Override + public void setFromString(String value) { + if (value.isEmpty() || value.equals(".") || value.equals("-")) value = "0"; + setPendingValue(Mth.clamp(Double.parseDouble(cleanupNumberString(value)), min(), max())); + } + + @Override + public double pendingValue() { + return option().pendingValue().doubleValue(); + } + + @Override + public boolean isInputValid(String input) { + return input.matches("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)|[.]||-"); + } + + @Override + public Component formatValue() { + return displayFormatter.apply(option().pendingValue()); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension, false); + } + + protected String cleanupNumberString(String number) { + return number.replace(String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()), ""); + } + + @Override + public double interval() { + return -1; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/package-info.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/package-info.java new file mode 100644 index 0000000..4d8bbc2 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains implementations of input fields for different number types + *
    + *
  • For doubles: {@link dev.isxander.yacl3.gui.controllers.string.number.DoubleFieldController}
  • + *
  • For floats: {@link dev.isxander.yacl3.gui.controllers.string.number.FloatFieldController}
  • + *
  • For integers: {@link dev.isxander.yacl3.gui.controllers.string.number.IntegerFieldController}
  • + *
  • For longs: {@link dev.isxander.yacl3.gui.controllers.string.number.LongFieldController}
  • + *
+ */ +package dev.isxander.yacl3.gui.controllers.string.number; diff --git a/common/src/main/java/dev/isxander/yacl3/gui/tab/ListHolderWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/tab/ListHolderWidget.java new file mode 100644 index 0000000..5059874 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/tab/ListHolderWidget.java @@ -0,0 +1,116 @@ +package dev.isxander.yacl3.gui.tab; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.gui.ElementListWidgetExt; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.network.chat.CommonComponents; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Supplier; + +public class ListHolderWidget> extends AbstractWidget implements ContainerEventHandler { + private final Supplier dimensions; + private final T list; + + public ListHolderWidget(Supplier dimensions, T list) { + super(0, 0, 100, 0, CommonComponents.EMPTY); + this.dimensions = dimensions; + this.list = list; + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float deltaTick) { + ScreenRectangle dimensions = this.dimensions.get(); + this.setX(dimensions.left()); + this.setY(dimensions.top()); + this.width = dimensions.width(); + this.height = dimensions.height(); + this.list.updateDimensions(dimensions); + this.list.render(guiGraphics, mouseX, mouseY, deltaTick); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput output) { + this.list.updateNarration(output); + } + + @Override + public List children() { + return ImmutableList.of(this.list); + } + + public T getList() { + return list; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return this.list.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return this.list.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + return this.list.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + return this.list.mouseScrolled(mouseX, mouseY, amount); + } + + @Override + public boolean keyPressed(int i, int j, int k) { + return this.list.keyPressed(i, j, k); + } + + @Override + public boolean charTyped(char c, int i) { + return this.list.charTyped(c, i); + } + + @Override + public boolean isDragging() { + return this.list.isDragging(); + } + + @Override + public void setDragging(boolean dragging) { + this.list.setDragging(dragging); + } + + @Nullable + @Override + public GuiEventListener getFocused() { + return this.list.getFocused(); + } + + @Override + public void setFocused(@Nullable GuiEventListener listener) { + this.list.setFocused(listener); + } + + @Nullable + @Override + public ComponentPath nextFocusPath(FocusNavigationEvent event) { + return this.list.nextFocusPath(event); + } + + @Nullable + @Override + public ComponentPath getCurrentFocusPath() { + return this.list.getCurrentFocusPath(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/tab/ScrollableNavigationBar.java b/common/src/main/java/dev/isxander/yacl3/gui/tab/ScrollableNavigationBar.java new file mode 100644 index 0000000..199845d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/tab/ScrollableNavigationBar.java @@ -0,0 +1,110 @@ +package dev.isxander.yacl3.gui.tab; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.TabButton; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.components.tabs.Tab; +import net.minecraft.client.gui.components.tabs.TabManager; +import net.minecraft.client.gui.components.tabs.TabNavigationBar; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; + +public class ScrollableNavigationBar extends TabNavigationBar { + private static final int NAVBAR_MARGIN = 28; + + private static final Font font = Minecraft.getInstance().font; + + private int scrollOffset; + private int maxScrollOffset; + + public ScrollableNavigationBar(int width, TabManager tabManager, Iterable tabs) { + super(width, tabManager, ImmutableList.copyOf(tabs)); + + // add tab tooltips to the tab buttons + for (TabButton tabButton : this.tabButtons) { + if (tabButton.tab() instanceof TabExt tab) { + tabButton.setTooltip(tab.getTooltip()); + } + } + } + + @Override + public void arrangeElements() { + int noScrollWidth = this.width - NAVBAR_MARGIN*2; + + // first pass: set the width of each tab button + for (TabButton tabButton : this.tabButtons) { + tabButton.setWidth(font.width(tabButton.getMessage()) + 10); + } + + // second pass: redistribute remaining width evenly + for (TabButton tabButton : tabButtons) { + int totalWidth = tabButtons.stream().mapToInt(AbstractWidget::getWidth).sum(); + int remainingWidth = noScrollWidth - totalWidth; + if (remainingWidth > 0) { + int extra = remainingWidth / tabButtons.size(); + tabButton.setWidth(tabButton.getWidth() + extra); + } + } + + this.layout.arrangeElements(); + this.layout.setY(0); + this.scrollOffset = 0; + + int allTabsWidth = this.tabButtons.stream().mapToInt(AbstractWidget::getWidth).sum(); + this.layout.setX(Math.max((this.width - allTabsWidth) / 2, NAVBAR_MARGIN)); + this.maxScrollOffset = Math.max(0, allTabsWidth - noScrollWidth); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + graphics.pose().pushPose(); + // render option list BELOW the navbar without need to scissor + graphics.pose().translate(0, 0, 10); + + super.render(graphics, mouseX, mouseY, delta); + + graphics.pose().popPose(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + this.setScrollOffset(this.scrollOffset - (int)(amount*10)); + return true; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + return mouseY <= 24; + } + + public void setScrollOffset(int scrollOffset) { + layout.setX(layout.getX() + this.scrollOffset); + this.scrollOffset = Mth.clamp(scrollOffset, 0, maxScrollOffset); + layout.setX(layout.getX() - this.scrollOffset); + } + + public int getScrollOffset() { + return scrollOffset; + } + + @Override + public void setFocused(@Nullable GuiEventListener child) { + super.setFocused(child); + if (child instanceof TabButton tabButton) { + this.ensureVisible(tabButton); + } + } + + protected void ensureVisible(TabButton tabButton) { + if (tabButton.getX() < NAVBAR_MARGIN) { + this.setScrollOffset(this.scrollOffset - (NAVBAR_MARGIN - tabButton.getX())); + } else if (tabButton.getX() + tabButton.getWidth() > this.width - NAVBAR_MARGIN) { + this.setScrollOffset(this.scrollOffset + (tabButton.getX() + tabButton.getWidth() - (this.width - NAVBAR_MARGIN))); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/tab/TabExt.java b/common/src/main/java/dev/isxander/yacl3/gui/tab/TabExt.java new file mode 100644 index 0000000..3b29594 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/tab/TabExt.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.gui.tab; + +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.tabs.Tab; +import org.jetbrains.annotations.Nullable; + +public interface TabExt extends Tab { + @Nullable Tooltip getTooltip(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java b/common/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java new file mode 100644 index 0000000..2910d0f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.gui.utils; + +import net.minecraft.client.gui.Font; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class GuiUtils { + public static MutableComponent translatableFallback(String key, Component fallback) { + if (Language.getInstance().has(key)) + return Component.translatable(key); + return fallback.copy(); + } + + public static String shortenString(String string, Font font, int maxWidth, String suffix) { + if (string.isEmpty()) + return string; + + boolean firstIter = true; + while (font.width(string) > maxWidth) { + string = string.substring(0, Math.max(string.length() - 1 - (firstIter ? 1 : suffix.length() + 1), 0)).trim(); + string += suffix; + + if (string.equals(suffix)) + break; + + firstIter = false; + } + + return string; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java new file mode 100644 index 0000000..99a6e83 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java @@ -0,0 +1,195 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.ActionController; +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.function.BiConsumer; +import java.util.function.Consumer; + +@ApiStatus.Internal +public final class ButtonOptionImpl implements ButtonOption { + private final Component name; + private final OptionDescription description; + private final BiConsumer action; + private boolean available; + private final Controller> controller; + private final Binding> binding; + + public ButtonOptionImpl( + @NotNull Component name, + @Nullable OptionDescription description, + @NotNull BiConsumer action, + boolean available + ) { + this.name = name; + this.description = description; + this.action = action; + this.available = available; + this.controller = new ActionController(this); + this.binding = new EmptyBinderImpl(); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull OptionDescription description() { + return description; + } + + @Override + public @NotNull Component tooltip() { + return description().text(); + } + + @Override + public BiConsumer action() { + return action; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull Controller> controller() { + return controller; + } + + @Override + public @NotNull Binding> binding() { + return binding; + } + + @Override + public @NotNull ImmutableSet flags() { + return ImmutableSet.of(); + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull BiConsumer pendingValue() { + throw new UnsupportedOperationException(); + } + + @Override + public void requestSet(BiConsumer value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean applyValue() { + return false; + } + + @Override + public void forgetPendingValue() { + + } + + @Override + public void requestSetDefault() { + + } + + @Override + public boolean isPendingValueDefault() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(BiConsumer>, BiConsumer> changedListener) { + + } + + private static class EmptyBinderImpl implements Binding> { + @Override + public void setValue(BiConsumer value) { + + } + + @Override + public BiConsumer getValue() { + throw new UnsupportedOperationException(); + } + + @Override + public BiConsumer defaultValue() { + throw new UnsupportedOperationException(); + } + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name; + private OptionDescription description = OptionDescription.EMPTY; + private boolean available = true; + private BiConsumer action; + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public Builder description(@NotNull OptionDescription description) { + Validate.notNull(description, "`description` cannot be null"); + + this.description = description; + return this; + } + + @Override + public Builder action(@NotNull BiConsumer action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = action; + return this; + } + + @Override + @Deprecated + public Builder action(@NotNull Consumer action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = (screen, button) -> action.accept(screen); + return this; + } + + @Override + public Builder available(boolean available) { + this.available = available; + return this; + } + + @Override + public ButtonOption build() { + Validate.notNull(name, "`name` must not be null when building `ButtonOption`"); + Validate.notNull(action, "`action` must not be null when building `ButtonOption`"); + + return new ButtonOptionImpl(name, description, action, available); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java new file mode 100644 index 0000000..195f6d7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java @@ -0,0 +1,135 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentContents; +import net.minecraft.network.chat.MutableComponent; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@ApiStatus.Internal +public final class ConfigCategoryImpl implements ConfigCategory { + private final Component name; + private final ImmutableList groups; + private final Component tooltip; + + public ConfigCategoryImpl(Component name, ImmutableList groups, Component tooltip) { + this.name = name; + this.groups = groups; + this.tooltip = tooltip; + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull ImmutableList groups() { + return groups; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name; + + private final List> rootOptions = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + private final List tooltipLines = new ArrayList<>(); + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public Builder option(@NotNull Option option) { + Validate.notNull(option, "`option` must not be null"); + + if (option instanceof ListOption listOption) { + YACLConstants.LOGGER.warn("Adding list option as an option is not supported! Rerouting to group!"); + return group(listOption); + } + + this.rootOptions.add(option); + return this; + } + + @Override + public Builder options(@NotNull Collection> options) { + Validate.notNull(options, "`options` must not be null"); + + if (options.stream().anyMatch(ListOption.class::isInstance)) + throw new UnsupportedOperationException("List options must not be added as an option but a group!"); + + this.rootOptions.addAll(options); + return this; + } + + @Override + public Builder group(@NotNull OptionGroup group) { + Validate.notNull(group, "`group` must not be null"); + + this.groups.add(group); + return this; + } + + @Override + public Builder groups(@NotNull Collection groups) { + Validate.notEmpty(groups, "`groups` must not be empty"); + + this.groups.addAll(groups); + return this; + } + + @Override + public Builder tooltip(@NotNull Component... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public ConfigCategory build() { + Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); + + List combinedGroups = new ArrayList<>(); + combinedGroups.add(new OptionGroupImpl(CommonComponents.EMPTY, OptionDescription.EMPTY, ImmutableList.copyOf(rootOptions), false, true)); + combinedGroups.addAll(groups); + + Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); + + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Component line : tooltipLines) { + if (line.getContents() == ComponentContents.EMPTY) + continue; + + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java new file mode 100644 index 0000000..972c891 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.Binding; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class GenericBindingImpl implements Binding { + private final T def; + private final Supplier getter; + private final Consumer setter; + + public GenericBindingImpl(T def, Supplier getter, Consumer setting) { + this.def = def; + this.getter = getter; + this.setter = setting; + } + + + @Override + public void setValue(T value) { + setter.accept(value); + } + + @Override + public T getValue() { + return getter.get(); + } + + @Override + public T defaultValue() { + return def; + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java new file mode 100644 index 0000000..c8287bd --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java @@ -0,0 +1,158 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.gui.controllers.LabelController; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +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 binding; + + public LabelOptionImpl(Component label) { + this.label = label; + this.labelController = new LabelController(this); + this.binding = Binding.immutable(label); + this.description = OptionDescription.createBuilder() + .text(this.label) + .build(); + } + + @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 controller() { + return labelController; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return true; + } + + @Override + public void setAvailable(boolean available) { + throw new UnsupportedOperationException("Label options cannot be disabled."); + } + + @Override + public @NotNull ImmutableSet flags() { + return ImmutableSet.of(); + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull Component pendingValue() { + return label; + } + + @Override + public void requestSet(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, Component> changedListener) { + + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private final List lines = new ArrayList<>(); + + @Override + public Builder line(@NotNull Component line) { + Validate.notNull(line, "`line` must not be null"); + + this.lines.add(line); + return this; + } + + @Override + public Builder lines(@NotNull Collection lines) { + this.lines.addAll(lines); + return this; + } + + @Override + public LabelOption build() { + MutableComponent text = Component.empty(); + Iterator iterator = lines.iterator(); + while (iterator.hasNext()) { + text.append(iterator.next()); + + if (iterator.hasNext()) + text.append("\n"); + } + + return new LabelOptionImpl(text); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java new file mode 100644 index 0000000..0c74976 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java @@ -0,0 +1,154 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.ListEntryWidget; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +@ApiStatus.Internal +public final class ListOptionEntryImpl implements ListOptionEntry { + private final ListOptionImpl group; + + private T value; + + private final Binding binding; + private final Controller controller; + + ListOptionEntryImpl(ListOptionImpl group, T initialValue, @NotNull Function, Controller> controlGetter) { + this.group = group; + this.value = initialValue; + this.binding = new EntryBinding(); + this.controller = new EntryController<>(controlGetter.apply(this), this); + } + + @Override + public @NotNull Component name() { + return Component.empty(); + } + + @Override + public @NotNull OptionDescription description() { + return group.description(); + } + + @Override + public @NotNull Component tooltip() { + return Component.empty(); + } + + @Override + public @NotNull Controller controller() { + return controller; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return parentGroup().available(); + } + + @Override + public void setAvailable(boolean available) { + + } + + @Override + public ListOption parentGroup() { + return group; + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull T pendingValue() { + return value; + } + + @Override + public void requestSet(T value) { + binding.setValue(value); + } + + @Override + public boolean applyValue() { + return false; + } + + @Override + public void forgetPendingValue() { + + } + + @Override + public void requestSetDefault() { + + } + + @Override + public boolean isPendingValueDefault() { + return false; + } + + @Override + public boolean canResetToDefault() { + return false; + } + + @Override + public void addListener(BiConsumer, T> changedListener) { + + } + + /** + * Open in case mods need to find the real controller type. + */ + @ApiStatus.Internal + public record EntryController(Controller controller, ListOptionEntryImpl entry) implements Controller { + @Override + public Option option() { + return controller.option(); + } + + @Override + public Component formatValue() { + return controller.formatValue(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ListEntryWidget(screen, entry, controller.provideWidget(screen, widgetDimension)); + } + } + + private class EntryBinding implements Binding { + @Override + public void setValue(T newValue) { + value = newValue; + group.callListeners(); + } + + @Override + public T getValue() { + return value; + } + + @Override + public T defaultValue() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java new file mode 100644 index 0000000..82f5576 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java @@ -0,0 +1,325 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +@ApiStatus.Internal +public final class ListOptionImpl implements ListOption { + private final Component name; + private final OptionDescription description; + private final Binding> binding; + private final T initialValue; + private final List> entries; + private final boolean collapsed; + private boolean available; + private final ImmutableSet flags; + private final EntryFactory entryFactory; + private final List>, List>> listeners; + private final List refreshListeners; + + public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull Binding> binding, @NotNull T initialValue, @NotNull Function, Controller> controllerFunction, ImmutableSet flags, boolean collapsed, boolean available, Collection>, List>> listeners) { + this.name = name; + this.description = description; + this.binding = binding; + this.initialValue = initialValue; + this.entryFactory = new EntryFactory(controllerFunction); + this.entries = createEntries(binding().getValue()); + this.collapsed = collapsed; + this.flags = flags; + this.available = available; + this.listeners = new ArrayList<>(); + this.listeners.addAll(listeners); + this.refreshListeners = new ArrayList<>(); + callListeners(); + } + + @Override + public @NotNull Component name() { + return this.name; + } + + @Override + public @NotNull OptionDescription description() { + return this.description; + } + + @Override + public @NotNull Component tooltip() { + return description().text(); + } + + @Override + public @NotNull ImmutableList> options() { + return ImmutableList.copyOf(entries); + } + + @Override + public @NotNull Controller> controller() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Binding> binding() { + return binding; + } + + @Override + public boolean collapsed() { + return collapsed; + } + + @Override + public @NotNull ImmutableSet flags() { + return flags; + } + + @Override + public @NotNull ImmutableList pendingValue() { + return ImmutableList.copyOf(entries.stream().map(Option::pendingValue).toList()); + } + + @Override + public void insertEntry(int index, ListOptionEntry entry) { + entries.add(index, (ListOptionEntry) entry); + onRefresh(); + } + + @Override + public ListOptionEntry insertNewEntryToTop() { + ListOptionEntry newEntry = entryFactory.create(initialValue); + entries.add(0, newEntry); + onRefresh(); + return newEntry; + } + + @Override + public void removeEntry(ListOptionEntry entry) { + if (entries.remove(entry)) + onRefresh(); + } + + @Override + public int indexOf(ListOptionEntry entry) { + return entries.indexOf(entry); + } + + @Override + public void requestSet(List value) { + entries.clear(); + entries.addAll(createEntries(value)); + onRefresh(); + } + + @Override + public boolean changed() { + return !binding().getValue().equals(pendingValue()); + } + + @Override + public boolean applyValue() { + if (changed()) { + binding().setValue(pendingValue()); + return true; + } + return false; + } + + @Override + public void forgetPendingValue() { + requestSet(binding().getValue()); + } + + @Override + public void requestSetDefault() { + requestSet(binding().defaultValue()); + } + + @Override + public boolean isPendingValueDefault() { + return binding().defaultValue().equals(pendingValue()); + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public void addListener(BiConsumer>, List> changedListener) { + this.listeners.add(changedListener); + } + + @Override + public void addRefreshListener(Runnable changedListener) { + this.refreshListeners.add(changedListener); + } + + @Override + public boolean isRoot() { + return false; + } + + private List> createEntries(Collection values) { + return values.stream().map(entryFactory::create).collect(Collectors.toList()); + } + + void callListeners() { + List pendingValue = pendingValue(); + this.listeners.forEach(listener -> listener.accept(this, pendingValue)); + } + + private void onRefresh() { + refreshListeners.forEach(Runnable::run); + callListeners(); + } + + private class EntryFactory { + private final Function, Controller> controllerFunction; + + private EntryFactory(Function, Controller> controllerFunction) { + this.controllerFunction = controllerFunction; + } + + public ListOptionEntry create(T initialValue) { + return new ListOptionEntryImpl<>(ListOptionImpl.this, initialValue, controllerFunction); + } + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name = Component.empty(); + private OptionDescription description = OptionDescription.EMPTY; + private Function, Controller> controllerFunction; + private Binding> binding = null; + private final Set flags = new HashSet<>(); + private T initialValue; + private boolean collapsed = false; + private boolean available = true; + private final List>, List>> listeners = new ArrayList<>(); + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + @Override + public Builder description(@NotNull OptionDescription description) { + Validate.notNull(description, "`description` must not be null"); + + this.description = description; + return this; + } + + @Override + public Builder initial(@NotNull T initialValue) { + Validate.notNull(initialValue, "`initialValue` cannot be empty"); + + this.initialValue = initialValue; + return this; + } + + @Override + public Builder controller(@NotNull Function, ControllerBuilder> controller) { + Validate.notNull(controller, "`controller` cannot be null"); + + this.controllerFunction = opt -> controller.apply(opt).build(); + return this; + } + + @Override + public Builder customController(@NotNull Function, Controller> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controllerFunction = control; + return this; + } + + @Override + public Builder binding(@NotNull Binding> binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public Builder binding(@NotNull List def, @NotNull Supplier<@NotNull List> getter, @NotNull Consumer<@NotNull List> setter) { + Validate.notNull(def, "`def` must not be null"); + Validate.notNull(getter, "`getter` must not be null"); + Validate.notNull(setter, "`setter` must not be null"); + + this.binding = Binding.generic(def, getter, setter); + return this; + } + + @Override + public Builder available(boolean available) { + this.available = available; + return this; + } + + @Override + public Builder flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public Builder flags(@NotNull Collection flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public Builder collapsed(boolean collapsible) { + this.collapsed = collapsible; + return this; + } + + @Override + public Builder listener(@NotNull BiConsumer>, List> listener) { + this.listeners.add(listener); + return this; + } + + @Override + public Builder listeners(@NotNull Collection>, List>> listeners) { + this.listeners.addAll(listeners); + return this; + } + + @Override + public ListOption 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"); + + return new ListOptionImpl<>(name, description, binding, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, listeners); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java new file mode 100644 index 0000000..9ea9456 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java @@ -0,0 +1,146 @@ +package dev.isxander.yacl3.impl; + +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.gui.ImageRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public record OptionDescriptionImpl(Component text, CompletableFuture> image) implements OptionDescription { + public static class BuilderImpl implements Builder { + private final List descriptionLines = new ArrayList<>(); + private CompletableFuture> image = CompletableFuture.completedFuture(Optional.empty()); + private boolean imageUnset = true; + + @Override + public Builder text(Component... description) { + this.descriptionLines.addAll(Arrays.asList(description)); + return this; + } + + @Override + public Builder text(Collection lines) { + this.descriptionLines.addAll(lines); + return this; + } + + @Override + public Builder image(ResourceLocation image, int width, int height) { + Validate.isTrue(imageUnset, "Image already set!"); + Validate.isTrue(width > 0, "Width must be greater than 0!"); + Validate.isTrue(height > 0, "Height must be greater than 0!"); + + this.image = ImageRenderer.getOrMakeSync(image, () -> Optional.of(new ImageRenderer.TextureBacked(image, 0, 0, width, height, width, height))); + imageUnset = false; + return this; + } + + @Override + public Builder image(ResourceLocation image, float u, float v, int width, int height, int textureWidth, int textureHeight) { + Validate.isTrue(imageUnset, "Image already set!"); + Validate.isTrue(width > 0, "Width must be greater than 0!"); + Validate.isTrue(height > 0, "Height must be greater than 0!"); + + this.image = ImageRenderer.getOrMakeSync(image, () -> Optional.of(new ImageRenderer.TextureBacked(image, u, v, width, height, textureWidth, textureHeight))); + imageUnset = false; + return this; + } + + @Override + public Builder image(Path path, ResourceLocation uniqueLocation) { + Validate.isTrue(imageUnset, "Image already set!"); + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> ImageRenderer.NativeImageBacked.createFromPath(path, uniqueLocation)); + imageUnset = false; + return this; + } + + @Override + public Builder gifImage(ResourceLocation image) { + Validate.isTrue(imageUnset, "Image already set!"); + this.image = ImageRenderer.getOrMakeAsync(image, () -> { + try { + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIFFromTexture(image)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + }); + imageUnset = false; + return this; + } + + @Override + public Builder gifImage(Path path, ResourceLocation uniqueLocation) { + Validate.isTrue(imageUnset, "Image already set!"); + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { + try { + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIF(new FileInputStream(path.toFile()), uniqueLocation)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + }); + imageUnset = false; + return this; + } + + @Override + public Builder webpImage(ResourceLocation image) { + Validate.isTrue(imageUnset, "Image already set!"); + this.image = ImageRenderer.getOrMakeAsync(image, () -> { + try { + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBPFromTexture(image)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + }); + imageUnset = false; + return this; + } + + @Override + public Builder webpImage(Path path, ResourceLocation uniqueLocation) { + Validate.isTrue(imageUnset, "Image already set!"); + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { + try { + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBP(new FileInputStream(path.toFile()), uniqueLocation)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + }); + imageUnset = false; + return this; + } + + @Override + public Builder customImage(CompletableFuture> image) { + Validate.notNull(image, "Image cannot be null!"); + Validate.isTrue(imageUnset, "Image already set!"); + + this.image = image; + this.imageUnset = false; + return this; + } + + @Override + public OptionDescription build() { + MutableComponent concatenatedDescription = Component.empty(); + Iterator iter = descriptionLines.iterator(); + while (iter.hasNext()) { + concatenatedDescription.append(iter.next()); + if (iter.hasNext()) concatenatedDescription.append("\n"); + } + + return new OptionDescriptionImpl(concatenatedDescription, image); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java new file mode 100644 index 0000000..7805b29 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java @@ -0,0 +1,121 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.ListOption; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.api.OptionGroup; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@ApiStatus.Internal +public final class OptionGroupImpl implements OptionGroup { + private final @NotNull Component name; + private final @NotNull OptionDescription description; + private final ImmutableList> options; + private final boolean collapsed; + private final boolean isRoot; + + public OptionGroupImpl(@NotNull Component name, @NotNull OptionDescription description, ImmutableList> options, boolean collapsed, boolean isRoot) { + this.name = name; + this.description = description; + this.options = options; + this.collapsed = collapsed; + this.isRoot = isRoot; + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public OptionDescription description() { + return description; + } + + @Override + public @NotNull Component tooltip() { + return description.text(); + } + + @Override + public @NotNull ImmutableList> options() { + return options; + } + + @Override + public boolean collapsed() { + return collapsed; + } + + @Override + public boolean isRoot() { + return isRoot; + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name = Component.empty(); + private OptionDescription description = OptionDescription.EMPTY; + private final List> options = new ArrayList<>(); + private boolean collapsed = false; + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + @Override + public Builder description(@NotNull OptionDescription description) { + Validate.notNull(description, "`description` must not be null"); + + this.description = description; + return this; + } + + @Override + public Builder option(@NotNull Option option) { + Validate.notNull(option, "`option` must not be null"); + + if (option instanceof ListOption) + throw new UnsupportedOperationException("List options must not be added as an option but a group!"); + + this.options.add(option); + return this; + } + + @Override + public Builder options(@NotNull Collection> options) { + Validate.notEmpty(options, "`options` must not be empty"); + + if (options.stream().anyMatch(ListOption.class::isInstance)) + throw new UnsupportedOperationException("List options must not be added as an option but a group!"); + + this.options.addAll(options); + return this; + } + + @Override + public Builder collapsed(boolean collapsible) { + this.collapsed = collapsible; + return this; + } + + @Override + public OptionGroup build() { + Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); + + return new OptionGroupImpl(name, description, ImmutableList.copyOf(options), collapsed, false); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java new file mode 100644 index 0000000..9b3a613 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java @@ -0,0 +1,257 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +@ApiStatus.Internal +public final class OptionImpl implements Option { + private final Component name; + private OptionDescription description; + private final Controller controller; + private final Binding binding; + private boolean available; + + private final ImmutableSet flags; + + private T pendingValue; + + private final List, T>> listeners; + + public OptionImpl( + @NotNull Component name, + @NotNull Function descriptionFunction, + @NotNull Function, Controller> controlGetter, + @NotNull Binding binding, + boolean available, + ImmutableSet flags, + @NotNull Collection, T>> listeners + ) { + this.name = name; + this.binding = binding; + this.available = available; + this.flags = flags; + this.listeners = new ArrayList<>(listeners); + this.controller = controlGetter.apply(this); + + addListener((opt, pending) -> description = descriptionFunction.apply(pending)); + + requestSet(binding().getValue()); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull OptionDescription description() { + return this.description; + } + + @Override + public @NotNull Component tooltip() { + return description.text(); + } + + @Override + public @NotNull Controller controller() { + return controller; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull ImmutableSet flags() { + return flags; + } + + @Override + public boolean changed() { + return !binding().getValue().equals(pendingValue); + } + + @Override + public @NotNull T pendingValue() { + return pendingValue; + } + + @Override + public void requestSet(T value) { + pendingValue = value; + listeners.forEach(listener -> listener.accept(this, pendingValue)); + } + + @Override + public boolean applyValue() { + if (changed()) { + binding().setValue(pendingValue); + return true; + } + return false; + } + + @Override + public void forgetPendingValue() { + requestSet(binding().getValue()); + } + + @Override + public void requestSetDefault() { + requestSet(binding().defaultValue()); + } + + @Override + public boolean isPendingValueDefault() { + return binding().defaultValue().equals(pendingValue()); + } + + @Override + public void addListener(BiConsumer, T> changedListener) { + this.listeners.add(changedListener); + } + + @ApiStatus.Internal + public static class BuilderImpl implements Builder { + private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED); + + private Function descriptionFunction = pending -> OptionDescription.EMPTY; + + private Function, Controller> controlGetter; + + private Binding binding; + + private boolean available = true; + + private boolean instant = false; + + private final Set flags = new HashSet<>(); + + private final List, T>> listeners = new ArrayList<>(); + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public Builder description(@NotNull OptionDescription description) { + return description(opt -> description); + } + + @Override + public Builder description(@NotNull Function descriptionFunction) { + this.descriptionFunction = descriptionFunction; + return this; + } + + @Override + public Builder controller(@NotNull Function, ControllerBuilder> controllerBuilder) { + Validate.notNull(controllerBuilder, "`controllerBuilder` cannot be null"); + + return customController(opt -> controllerBuilder.apply(opt).build()); + } + + @Override + public Builder customController(@NotNull Function, Controller> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + @Override + public Builder binding(@NotNull Binding binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { + Validate.notNull(def, "`def` must not be null"); + Validate.notNull(getter, "`getter` must not be null"); + Validate.notNull(setter, "`setter` must not be null"); + + this.binding = Binding.generic(def, getter, setter); + return this; + } + + @Override + public Builder available(boolean available) { + this.available = available; + return this; + } + + @Override + public Builder flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public Builder flags(@NotNull Collection flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public Builder instant(boolean instant) { + this.instant = instant; + return this; + } + + @Override + public Builder listener(@NotNull BiConsumer, T> listener) { + this.listeners.add(listener); + return this; + } + + @Override + public Builder listeners(@NotNull Collection, T>> listeners) { + this.listeners.addAll(listeners); + return this; + } + + @Override + public Option build() { + Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); + Validate.notNull(binding, "`binding` must not be null when building `Option`"); + Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); + + return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), listeners); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java new file mode 100644 index 0000000..5e836a3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java @@ -0,0 +1,99 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.OptionGroup; +import dev.isxander.yacl3.api.PlaceholderCategory; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +@ApiStatus.Internal +public final class PlaceholderCategoryImpl implements PlaceholderCategory { + private final Component name; + private final BiFunction screen; + private final Component tooltip; + + public PlaceholderCategoryImpl(Component name, BiFunction screen, Component tooltip) { + this.name = name; + this.screen = screen; + this.tooltip = tooltip; + } + + @Override + public @NotNull ImmutableList groups() { + return ImmutableList.of(); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public BiFunction screen() { + return screen; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name; + + private final List tooltipLines = new ArrayList<>(); + + private BiFunction screenFunction; + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public Builder tooltip(@NotNull Component... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public Builder screen(@NotNull BiFunction screenFunction) { + Validate.notNull(screenFunction, "`screenFunction` cannot be null"); + + this.screenFunction = screenFunction; + return this; + } + + @Override + public PlaceholderCategory build() { + Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); + + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Component line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java new file mode 100644 index 0000000..0be02a7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java @@ -0,0 +1,122 @@ +package dev.isxander.yacl3.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.ConfigCategory; +import dev.isxander.yacl3.api.PlaceholderCategory; +import dev.isxander.yacl3.api.YetAnotherConfigLib; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +@ApiStatus.Internal +public final class YetAnotherConfigLibImpl implements YetAnotherConfigLib { + private final Component title; + private final ImmutableList categories; + private final Runnable saveFunction; + private final Consumer initConsumer; + + private boolean generated = false; + + public YetAnotherConfigLibImpl(Component title, ImmutableList categories, Runnable saveFunction, Consumer initConsumer) { + this.title = title; + this.categories = categories; + this.saveFunction = saveFunction; + this.initConsumer = initConsumer; + } + + @Override + public Screen generateScreen(Screen parent) { + if (generated) + throw new UnsupportedOperationException("To prevent memory leaks, you should only generate a Screen once per instance. Please re-build the instance to generate another GUI."); + + YACLConstants.LOGGER.info("Generating YACL screen"); + generated = true; + return new YACLScreen(this, parent); + } + + @Override + public Component title() { + return title; + } + + @Override + public ImmutableList categories() { + return categories; + } + + @Override + public Runnable saveFunction() { + return saveFunction; + } + + @Override + public Consumer initConsumer() { + return initConsumer; + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component title; + private final List categories = new ArrayList<>(); + private Runnable saveFunction = () -> {}; + private Consumer initConsumer = screen -> {}; + + @Override + public Builder title(@NotNull Component title) { + Validate.notNull(title, "`title` cannot be null"); + + this.title = title; + return this; + } + + @Override + public Builder category(@NotNull ConfigCategory category) { + Validate.notNull(category, "`category` cannot be null"); + + this.categories.add(category); + return this; + } + + @Override + public Builder categories(@NotNull Collection categories) { + Validate.notNull(categories, "`categories` cannot be null"); + + this.categories.addAll(categories); + return this; + } + + @Override + public Builder save(@NotNull Runnable saveFunction) { + Validate.notNull(saveFunction, "`saveFunction` cannot be null"); + + this.saveFunction = saveFunction; + return this; + } + + @Override + public Builder screenInit(@NotNull Consumer initConsumer) { + Validate.notNull(initConsumer, "`initConsumer` cannot be null"); + + this.initConsumer = initConsumer; + return this; + } + + @Override + public YetAnotherConfigLib build() { + Validate.notNull(title, "`title must not be null to build `YetAnotherConfigLib`"); + Validate.notEmpty(categories, "`categories` must not be empty to build `YetAnotherConfigLib`"); + Validate.isTrue(!categories.stream().allMatch(category -> category instanceof PlaceholderCategory), "At least one regular category is required to build `YetAnotherConfigLib`"); + + return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java new file mode 100644 index 0000000..66c025a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; + +public abstract class AbstractControllerBuilderImpl implements ControllerBuilder { + protected final Option option; + + protected AbstractControllerBuilderImpl(Option option) { + this.option = option; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java new file mode 100644 index 0000000..208f78e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java @@ -0,0 +1,56 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.gui.controllers.BooleanController; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +public class BooleanControllerBuilderImpl extends AbstractControllerBuilderImpl implements BooleanControllerBuilder { + private boolean coloured = false; + private Function formatter = BooleanController.ON_OFF_FORMATTER; + + public BooleanControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public BooleanControllerBuilder coloured(boolean coloured) { + this.coloured = coloured; + return this; + } + + @Override + public BooleanControllerBuilder valueFormatter(Function formatter) { + Validate.notNull(formatter); + + this.formatter = formatter; + return this; + } + + @Override + public BooleanControllerBuilder onOffFormatter() { + this.formatter = BooleanController.ON_OFF_FORMATTER; + return this; + } + + @Override + public BooleanControllerBuilder yesNoFormatter() { + this.formatter = BooleanController.YES_NO_FORMATTER; + return this; + } + + @Override + public BooleanControllerBuilder trueFalseFormatter() { + this.formatter = BooleanController.TRUE_FALSE_FORMATTER; + return this; + } + + @Override + public Controller build() { + return new BooleanController(option, formatter, coloured); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java new file mode 100644 index 0000000..9412165 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java @@ -0,0 +1,27 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ColorControllerBuilder; +import dev.isxander.yacl3.gui.controllers.ColorController; + +import java.awt.Color; + +public class ColorControllerBuilderImpl extends AbstractControllerBuilderImpl implements ColorControllerBuilder { + private boolean allowAlpha = false; + + public ColorControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public ColorControllerBuilder allowAlpha(boolean allowAlpha) { + this.allowAlpha = allowAlpha; + return this; + } + + @Override + public Controller build() { + return new ColorController(option, allowAlpha); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java new file mode 100644 index 0000000..63045f2 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.impl.controller; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.CyclingListControllerBuilder; +import dev.isxander.yacl3.gui.controllers.cycling.CyclingListController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public final class CyclingListControllerBuilderImpl extends AbstractControllerBuilderImpl implements CyclingListControllerBuilder { + private Iterable values; + private Function formatter = null; + + public CyclingListControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public CyclingListControllerBuilder values(Iterable values) { + this.values = values; + return this; + } + + @SafeVarargs + @Override + public final CyclingListControllerBuilder values(T... values) { + this.values = ImmutableList.copyOf(values); + return this; + } + + @Override + public CyclingListControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new CyclingListController<>(option, values, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java new file mode 100644 index 0000000..56e1d44 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.DoubleFieldControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController; +import dev.isxander.yacl3.gui.controllers.string.number.DoubleFieldController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class DoubleFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements DoubleFieldControllerBuilder { + private double min = Double.MIN_VALUE; + private double max = Double.MAX_VALUE; + private Function formatter = DoubleSliderController.DEFAULT_FORMATTER; + + public DoubleFieldControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public DoubleFieldControllerBuilder min(Double min) { + this.min = min; + return this; + } + + @Override + public DoubleFieldControllerBuilder max(Double max) { + this.max = max; + return this; + } + + @Override + public DoubleFieldControllerBuilder range(Double min, Double max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public DoubleFieldControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new DoubleFieldController(option, min, max, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java new file mode 100644 index 0000000..a636abf --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class DoubleSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements DoubleSliderControllerBuilder { + private double min, max; + private double step; + private Function formatter = DoubleSliderController.DEFAULT_FORMATTER; + + public DoubleSliderControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public DoubleSliderControllerBuilder range(Double min, Double max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public DoubleSliderControllerBuilder step(Double step) { + this.step = step; + return this; + } + + @Override + public DoubleSliderControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new DoubleSliderController(option, min, max, step, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java new file mode 100644 index 0000000..3237dbf --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.EnumControllerBuilder; +import dev.isxander.yacl3.gui.controllers.cycling.EnumController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class EnumControllerBuilderImpl> extends AbstractControllerBuilderImpl implements EnumControllerBuilder { + private Class enumClass; + private Function formatter = EnumController.getDefaultFormatter(); + + public EnumControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public EnumControllerBuilder enumClass(Class enumClass) { + this.enumClass = enumClass; + return this; + } + + @Override + public EnumControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new EnumController<>(option, formatter, enumClass.getEnumConstants()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java new file mode 100644 index 0000000..09dbce0 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController; +import dev.isxander.yacl3.gui.controllers.string.number.FloatFieldController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class FloatFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements FloatFieldControllerBuilder { + private float min = Float.MIN_VALUE; + private float max = Float.MAX_VALUE; + private Function formatter = FloatSliderController.DEFAULT_FORMATTER; + + public FloatFieldControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public FloatFieldControllerBuilder min(Float min) { + this.min = min; + return this; + } + + @Override + public FloatFieldControllerBuilder max(Float max) { + this.max = max; + return this; + } + + @Override + public FloatFieldControllerBuilder range(Float min, Float max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public FloatFieldControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new FloatFieldController(option, min, max, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java new file mode 100644 index 0000000..c9699fb --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class FloatSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements FloatSliderControllerBuilder { + private float min, max; + private float step; + private Function formatter = FloatSliderController.DEFAULT_FORMATTER; + + public FloatSliderControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public FloatSliderControllerBuilder range(Float min, Float max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public FloatSliderControllerBuilder step(Float step) { + this.step = step; + return this; + } + + @Override + public FloatSliderControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new FloatSliderController(option, min, max, step, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java new file mode 100644 index 0000000..73b51a0 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController; +import dev.isxander.yacl3.gui.controllers.string.number.IntegerFieldController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class IntegerFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements IntegerFieldControllerBuilder { + private int min = Integer.MIN_VALUE; + private int max = Integer.MAX_VALUE; + private Function formatter = IntegerSliderController.DEFAULT_FORMATTER; + + public IntegerFieldControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public IntegerFieldControllerBuilder min(Integer min) { + this.min = min; + return this; + } + + @Override + public IntegerFieldControllerBuilder max(Integer max) { + this.max = max; + return this; + } + + @Override + public IntegerFieldControllerBuilder range(Integer min, Integer max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public IntegerFieldControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new IntegerFieldController(option, min, max, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java new file mode 100644 index 0000000..106c22a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class IntegerSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements IntegerSliderControllerBuilder { + private int min, max; + private int step; + private Function formatter = IntegerSliderController.DEFAULT_FORMATTER; + + public IntegerSliderControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public IntegerSliderControllerBuilder range(Integer min, Integer max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public IntegerSliderControllerBuilder step(Integer step) { + this.step = step; + return this; + } + + @Override + public IntegerSliderControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new IntegerSliderController(option, min, max, step, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java new file mode 100644 index 0000000..88cb610 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.LongFieldControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.LongSliderController; +import dev.isxander.yacl3.gui.controllers.string.number.LongFieldController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class LongFieldControllerBuilderImpl extends AbstractControllerBuilderImpl implements LongFieldControllerBuilder { + private long min = Long.MIN_VALUE; + private long max = Long.MAX_VALUE; + private Function formatter = LongSliderController.DEFAULT_FORMATTER; + + public LongFieldControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public LongFieldControllerBuilder min(Long min) { + this.min = min; + return this; + } + + @Override + public LongFieldControllerBuilder max(Long max) { + this.max = max; + return this; + } + + @Override + public LongFieldControllerBuilder range(Long min, Long max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public LongFieldControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new LongFieldController(option, min, max, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java new file mode 100644 index 0000000..2bd932a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.LongSliderControllerBuilder; +import dev.isxander.yacl3.gui.controllers.slider.LongSliderController; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public class LongSliderControllerBuilderImpl extends AbstractControllerBuilderImpl implements LongSliderControllerBuilder { + private long min, max; + private long step; + private Function formatter = LongSliderController.DEFAULT_FORMATTER; + + public LongSliderControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public LongSliderControllerBuilder range(Long min, Long max) { + this.min = min; + this.max = max; + return this; + } + + @Override + public LongSliderControllerBuilder step(Long step) { + this.step = step; + return this; + } + + @Override + public LongSliderControllerBuilder valueFormatter(Function formatter) { + this.formatter = formatter; + return this; + } + + @Override + public Controller build() { + return new LongSliderController(option, min, max, step, formatter); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java new file mode 100644 index 0000000..a0f51b9 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.StringControllerBuilder; +import dev.isxander.yacl3.gui.controllers.string.StringController; + +public class StringControllerBuilderImpl extends AbstractControllerBuilderImpl implements StringControllerBuilder { + public StringControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public Controller build() { + return new StringController(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java new file mode 100644 index 0000000..3b29719 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.impl.controller; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.gui.controllers.TickBoxController; + +public class TickBoxControllerBuilderImpl extends AbstractControllerBuilderImpl implements TickBoxControllerBuilder { + public TickBoxControllerBuilderImpl(Option option) { + super(option); + } + + @Override + public Controller build() { + return new TickBoxController(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java new file mode 100644 index 0000000..7d29bbc --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java @@ -0,0 +1,115 @@ +package dev.isxander.yacl3.impl.utils; + +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.api.utils.MutableDimension; + +public class DimensionIntegerImpl implements MutableDimension { + private int x, y; + private int width, height; + + public DimensionIntegerImpl(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public Integer x() { + return x; + } + + @Override + public Integer y() { + return y; + } + + @Override + public Integer width() { + return width; + } + + @Override + public Integer height() { + return height; + } + + @Override + public Integer xLimit() { + return x + width; + } + + @Override + public Integer yLimit() { + return y + height; + } + + @Override + public Integer centerX() { + return x + width / 2; + } + + @Override + public Integer centerY() { + return y + height / 2; + } + + @Override + public boolean isPointInside(Integer x, Integer y) { + return x >= x() && x <= xLimit() && y >= y() && y <= yLimit(); + } + + @Override + public MutableDimension clone() { + return new DimensionIntegerImpl(x, y, width, height); + } + + @Override public MutableDimension setX(Integer x) { this.x = x; return this; } + @Override public MutableDimension setY(Integer y) { this.y = y; return this; } + @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } + @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } + + @Override + public Dimension withX(Integer x) { + return clone().setX(x); + } + + @Override + public Dimension withY(Integer y) { + return clone().setY(y); + } + + @Override + public Dimension withWidth(Integer width) { + return clone().setWidth(width); + } + + @Override + public Dimension withHeight(Integer height) { + return clone().setHeight(height); + } + + @Override + public MutableDimension move(Integer x, Integer y) { + this.x += x; + this.y += y; + return this; + } + + @Override + public MutableDimension expand(Integer width, Integer height) { + this.width += width; + this.height += height; + return this; + } + + @Override + public Dimension moved(Integer x, Integer y) { + return clone().move(x, y); + } + + @Override + public Dimension expanded(Integer width, Integer height) { + return clone().expand(width, height); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java b/common/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java new file mode 100644 index 0000000..9570b02 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java @@ -0,0 +1,13 @@ +package dev.isxander.yacl3.impl.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class YACLConstants { + public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); + + public static final ExecutorService SINGLE_THREAD_EXECUTOR = Executors.newSingleThreadExecutor(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/mixin/AbstractSelectionListMixin.java b/common/src/main/java/dev/isxander/yacl3/mixin/AbstractSelectionListMixin.java new file mode 100644 index 0000000..471fa19 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/mixin/AbstractSelectionListMixin.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.mixin; + +import net.minecraft.client.gui.components.AbstractSelectionList; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +@Mixin(AbstractSelectionList.class) +public abstract class AbstractSelectionListMixin> { + @Shadow public abstract List children(); + + /** + * Mojang use the field access of children to get max index to loop through keyboard navigation to find the next entry. + * YACL modifies these children() method to filter out hidden entries, so we need to redirect the field access to the + * method, so we don't get ArrayIndexOutOfBoundsException. + */ + @Redirect(method = "nextEntry(Lnet/minecraft/client/gui/navigation/ScreenDirection;Ljava/util/function/Predicate;Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry;)Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/components/AbstractSelectionList;children:Ljava/util/List;", opcode = Opcodes.GETFIELD)) + private List modifyChildrenCall(AbstractSelectionList instance) { + return children(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/mixin/ContainerEventHandlerMixin.java b/common/src/main/java/dev/isxander/yacl3/mixin/ContainerEventHandlerMixin.java new file mode 100644 index 0000000..37f0c33 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/mixin/ContainerEventHandlerMixin.java @@ -0,0 +1,31 @@ +package dev.isxander.yacl3.mixin; + +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.components.tabs.TabNavigationBar; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.navigation.ScreenAxis; +import net.minecraft.client.gui.navigation.ScreenDirection; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +@Mixin(ContainerEventHandler.class) +public interface ContainerEventHandlerMixin { + /** + * This mixin is used to prevent the tab bar from being focused when navigating left or right + * through the YACL options screen. This can also apply to vanilla as navigating left or right + * should never result in focusing the always-at-the-top tab bar. + * Without this, navigating right from the option list focuses the tab bar, not the action buttons/description. + */ + @Redirect(method = {"nextFocusPathVaguelyInDirection", "nextFocusPathInDirection"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/events/ContainerEventHandler;children()Ljava/util/List;")) + private List modifyFocusCandidates(ContainerEventHandler instance, ScreenRectangle screenArea, ScreenDirection direction, @Nullable GuiEventListener focused, FocusNavigationEvent event) { + if (direction.getAxis() == ScreenAxis.HORIZONTAL) + return instance.children().stream().filter(child -> !(child instanceof TabNavigationBar)).toList(); + return instance.children(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/mixin/MinecraftMixin.java b/common/src/main/java/dev/isxander/yacl3/mixin/MinecraftMixin.java new file mode 100644 index 0000000..5bc22ab --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/mixin/MinecraftMixin.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.mixin; + +import dev.isxander.yacl3.gui.ImageRenderer; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + @Inject(method = "close", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/telemetry/ClientTelemetryManager;close()V")) + private void closeImages(CallbackInfo ci) { + ImageRenderer.closeAll(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/mixin/OptionInstanceAccessor.java b/common/src/main/java/dev/isxander/yacl3/mixin/OptionInstanceAccessor.java new file mode 100644 index 0000000..429e383 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/mixin/OptionInstanceAccessor.java @@ -0,0 +1,13 @@ +package dev.isxander.yacl3.mixin; + +import net.minecraft.client.OptionInstance; +import org.jetbrains.annotations.ApiStatus; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@ApiStatus.Internal +@Mixin(OptionInstance.class) +public interface OptionInstanceAccessor { + @Accessor + T getInitialValue(); +} diff --git a/common/src/main/resources/yacl.mixins.json b/common/src/main/resources/yacl.mixins.json index 71d69ca..3b67b21 100644 --- a/common/src/main/resources/yacl.mixins.json +++ b/common/src/main/resources/yacl.mixins.json @@ -1,6 +1,6 @@ { "required": true, - "package": "dev.isxander.yacl.mixin", + "package": "dev.isxander.yacl3.mixin", "compatibilityLevel": "JAVA_17", "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 508eeed..c4c74b6 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -2,7 +2,8 @@ "schemaVersion": 1, "id": "${id}", "provides": [ - "yet-another-config-lib" + "yet-another-config-lib", + "yet_another_config_lib" ], "version": "${version}", "name": "${name}", diff --git a/forge/src/main/java/dev/isxander/yacl/forge/YACLForgeEntrypoint.java b/forge/src/main/java/dev/isxander/yacl/forge/YACLForgeEntrypoint.java index 3360184..7c491e1 100644 --- a/forge/src/main/java/dev/isxander/yacl/forge/YACLForgeEntrypoint.java +++ b/forge/src/main/java/dev/isxander/yacl/forge/YACLForgeEntrypoint.java @@ -1,4 +1,4 @@ -package dev.isxander.yacl.forge; +package dev.isxander.yacl3.forge; import net.minecraftforge.fml.common.Mod; diff --git a/gradle.properties b/gradle.properties index b5e8729..30a4ee4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx4G -modId=yet_another_config_lib +modId=yet_another_config_lib_v3 modName=YetAnotherConfigLib modDescription=YetAnotherConfigLib (yacl) is just that. A builder-based configuration library for Minecraft. diff --git a/test-common/src/main/java/dev/isxander/yacl/test/ConfigTest.java b/test-common/src/main/java/dev/isxander/yacl/test/ConfigTest.java deleted file mode 100644 index 71cb285..0000000 --- a/test-common/src/main/java/dev/isxander/yacl/test/ConfigTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.isxander.yacl.test; - -import dev.isxander.yacl.config.ConfigEntry; -import dev.isxander.yacl.config.ConfigInstance; -import dev.isxander.yacl.config.GsonConfigInstance; - -import java.awt.*; -import java.util.List; -import java.nio.file.Path; - -public class ConfigTest { - public static final ConfigInstance GSON = GsonConfigInstance.createBuilder(ConfigTest.class) - .setPath(Path.of("./config/yacl-test.json")) - .build(); - - @ConfigEntry public boolean booleanToggle = false; - @ConfigEntry public boolean customBooleanToggle = false; - @ConfigEntry public boolean tickbox = false; - @ConfigEntry public int intSlider = 0; - @ConfigEntry public double doubleSlider = 0; - @ConfigEntry public float floatSlider = 0; - @ConfigEntry public long longSlider = 0; - @ConfigEntry public String textField = "Hello"; - @ConfigEntry public Color colorOption = Color.red; - @ConfigEntry public double doubleField = 0.5; - @ConfigEntry public float floatField = 0.5f; - @ConfigEntry public int intField = 5; - @ConfigEntry public long longField = 5; - @ConfigEntry public Alphabet enumOption = Alphabet.A; - - @ConfigEntry - public List stringList = List.of("This is quite cool.", "You can add multiple items!", "And it is integrated so well into Option groups!"); - @ConfigEntry - public List intList = List.of(1, 2, 3); - - @ConfigEntry - public boolean groupTestRoot = false; - @ConfigEntry - public boolean groupTestFirstGroup = false; - @ConfigEntry - public boolean groupTestFirstGroup2 = false; - @ConfigEntry - public boolean groupTestSecondGroup = false; - - @ConfigEntry - public int scrollingSlider = 0; - - public enum Alphabet { - A, B, C - } -} diff --git a/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java b/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java deleted file mode 100644 index 9ab34a4..0000000 --- a/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java +++ /dev/null @@ -1,391 +0,0 @@ -package dev.isxander.yacl.test; - -import dev.isxander.yacl.api.*; -import dev.isxander.yacl.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl.api.controller.IntegerSliderControllerBuilder; -import dev.isxander.yacl.api.controller.StringControllerBuilder; -import dev.isxander.yacl.api.controller.TickBoxControllerBuilder; -import dev.isxander.yacl.gui.RequireRestartScreen; -import dev.isxander.yacl.gui.controllers.*; -import dev.isxander.yacl.gui.controllers.cycling.EnumController; -import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; -import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; -import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; -import dev.isxander.yacl.gui.controllers.slider.LongSliderController; -import dev.isxander.yacl.gui.controllers.string.StringController; -import dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController; -import dev.isxander.yacl.gui.controllers.string.number.FloatFieldController; -import dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController; -import dev.isxander.yacl.gui.controllers.string.number.LongFieldController; -import net.minecraft.client.GraphicsStatus; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.toasts.SystemToast; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.HoverEvent; -import net.minecraft.resources.ResourceLocation; - -import java.awt.Color; -import java.nio.file.Path; -import java.util.List; - -public class GuiTest { - public static Screen getModConfigScreenFactory(Screen parent) { - return YetAnotherConfigLib.create(ConfigTest.GSON, (defaults, config, builder) -> builder - .title(Component.literal("Test Suites")) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Suites")) - .option(ButtonOption.createBuilder() - .name(Component.literal("Full Test Suite")) - .action((screen, opt) -> Minecraft.getInstance().setScreen(getFullTestSuite(screen))) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Wiki")) - .option(ButtonOption.createBuilder() - .name(Component.literal("Get Started")) - .action((screen, opt) -> Minecraft.getInstance().setScreen(getWikiGetStarted(screen))) - .build()) - .build()) - .build()) - ) - .generateScreen(parent); - } - - private static Screen getFullTestSuite(Screen parent) { - return YetAnotherConfigLib.create(ConfigTest.GSON, (defaults, config, builder) -> builder - .title(Component.literal("Test GUI")) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Control Examples")) - .tooltip(Component.literal("Example Category Description")) - .group(OptionGroup.createBuilder() - .name(Component.literal("Boolean Controllers")) - .option(Option.createBuilder() - .name(Component.literal("Boolean Toggle")) - .description(OptionDescription.createBuilder() - .text(Component.empty() - .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a"))))) - .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b"))))) - .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c"))))) - .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e"))))) - .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev"))) - ) - .webpImage(Path.of("D:\\Xander\\Code\\isXander\\Controlify\\src\\main\\resources\\assets\\controlify\\textures\\screenshots\\reach-around-placement.webp"), new ResourceLocation("yacl", "e.webp")) - .build()) - .binding( - defaults.booleanToggle, - () -> config.booleanToggle, - (value) -> config.booleanToggle = value - ) - .controller(BooleanControllerBuilder::create) - .flag(OptionFlag.GAME_RESTART) - .build()) - .option(Option.createBuilder() - .name(Component.literal("Custom Boolean Toggle")) - .description(val -> OptionDescription.createBuilder() - .text(Component.literal("You can customize controllers like so! YACL is truly infinitely customizable! This tooltip is long in order to demonstrate the cool, smooth scrolling of these descriptions. Did you know, they are also super clickable?! I know, cool right, YACL 3.x really is amazing.")) - .image(Path.of("D:\\Xander\\Downloads\\_MG_0860-Enhanced-NR.png"), new ResourceLocation("yacl", "f.webp")) - .build()) - .binding( - defaults.customBooleanToggle, - () -> config.customBooleanToggle, - (value) -> config.customBooleanToggle = value - ) - .controller(opt -> BooleanControllerBuilder.create(opt) - .valueFormatter(state -> state ? Component.literal("Amazing") : Component.literal("Not Amazing")) - .coloured(true)) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("Tick Box")) - .description(OptionDescription.of(Component.literal("There are even alternate methods of displaying the same data type!"))) - .binding( - defaults.tickbox, - () -> config.tickbox, - (value) -> config.tickbox = value - ) - .controller(TickBoxControllerBuilder::create) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Slider Controllers")) - .option(Option.createBuilder(int.class) - .name(Component.literal("Int Slider")) - .instant(true) - .binding( - defaults.intSlider, - () -> config.intSlider, - value -> config.intSlider = value - - ) - .customController(opt -> new IntegerSliderController(opt, 0, 3, 1)) - .build()) - .option(Option.createBuilder(double.class) - .name(Component.literal("Double Slider")) - .binding( - defaults.doubleSlider, - () -> config.doubleSlider, - (value) -> config.doubleSlider = value - ) - .customController(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) - .build()) - .option(Option.createBuilder(float.class) - .name(Component.literal("Float Slider")) - .binding( - defaults.floatSlider, - () -> config.floatSlider, - (value) -> config.floatSlider = value - ) - .customController(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) - .build()) - .option(Option.createBuilder(long.class) - .name(Component.literal("Long Slider")) - .binding( - defaults.longSlider, - () -> config.longSlider, - (value) -> config.longSlider = value - ) - .customController(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Input Field Controllers")) - .option(Option.createBuilder(String.class) - .name(Component.literal("Component Option")) - .binding( - defaults.textField, - () -> config.textField, - value -> config.textField = value - ) - .customController(StringController::new) - .build()) - .option(Option.createBuilder(Color.class) - .name(Component.literal("Color Option")) - .binding( - defaults.colorOption, - () -> config.colorOption, - value -> config.colorOption = value - ) - .customController(ColorController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Number Fields")) - .option(Option.createBuilder(double.class) - .name(Component.literal("Double Field")) - .binding( - defaults.doubleField, - () -> config.doubleField, - value -> config.doubleField = value - ) - .customController(DoubleFieldController::new) - .build()) - .option(Option.createBuilder(float.class) - .name(Component.literal("Float Field")) - .binding( - defaults.floatField, - () -> config.floatField, - value -> config.floatField = value - ) - .customController(FloatFieldController::new) - .build()) - .option(Option.createBuilder(int.class) - .name(Component.literal("Integer Field")) - .binding( - defaults.intField, - () -> config.intField, - value -> config.intField = value - ) - .customController(IntegerFieldController::new) - .build()) - .option(Option.createBuilder(long.class) - .name(Component.literal("Long Field")) - .binding( - defaults.longField, - () -> config.longField, - value -> config.longField = value - ) - .customController(LongFieldController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Enum Controllers")) - .option(Option.createBuilder(ConfigTest.Alphabet.class) - .name(Component.literal("Enum Cycler")) - .binding( - defaults.enumOption, - () -> config.enumOption, - (value) -> config.enumOption = value - ) - .customController(opt -> new EnumController<>(opt, ConfigTest.Alphabet.class)) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Options that aren't really options")) - .option(ButtonOption.createBuilder() - .name(Component.literal("Button \"Option\"")) - .action((screen, opt) -> SystemToast.add(Minecraft.getInstance().getToasts(), SystemToast.SystemToastIds.TUTORIAL_HINT, Component.literal("Button Pressed"), Component.literal("Button option was invoked!"))) - .build()) - .option(LabelOption.create( - Component.empty() - .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a"))))) - .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b"))))) - .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c"))))) - .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e"))))) - .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev")))) - ) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("Minecraft Bindings")) - .description(OptionDescription.of(Component.literal("YACL can also bind Minecraft options!"))) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("Minecraft AutoJump")) - .description(OptionDescription.of(Component.literal("You can even bind minecraft options!"))) - .binding(Binding.minecraft(Minecraft.getInstance().options.autoJump())) - .customController(TickBoxController::new) - .build()) - .option(Option.createBuilder() - .name(Component.literal("Minecraft Graphics Mode")) - .binding(Binding.minecraft(Minecraft.getInstance().options.graphicsMode())) - .customController(opt -> new EnumController<>(opt, GraphicsStatus.class)) - .build()) - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("List Test")) - .group(ListOption.createBuilder(String.class) - .name(Component.literal("String List")) - .binding( - defaults.stringList, - () -> config.stringList, - val -> config.stringList = val - ) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .group(ListOption.createBuilder() - .name(Component.literal("Slider List")) - .binding( - defaults.intList, - () -> config.intList, - val -> config.intList = val - ) - .controller(opt -> IntegerSliderControllerBuilder.create(opt) - .range(0, 10).step(1)) - .initial(0) - .available(false) - .build()) - .group(ListOption.createBuilder(Component.class) - .name(Component.literal("Useless Label List")) - .binding(Binding.immutable(List.of(Component.literal("It's quite impressive that literally every single controller works, without problem.")))) - .customController(LabelController::new) - .initial(Component.literal("Initial label")) - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Group Test")) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("Root Test")) - .binding( - defaults.groupTestRoot, - () -> config.groupTestRoot, - value -> config.groupTestRoot = value - ) - .customController(TickBoxController::new) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.literal("First Group")) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("First Group Test 1")) - .binding( - defaults.groupTestFirstGroup, - () -> config.groupTestFirstGroup, - value -> config.groupTestFirstGroup = value - ) - .customController(TickBoxController::new) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("First Group Test 2")) - .binding( - defaults.groupTestFirstGroup2, - () -> config.groupTestFirstGroup2, - value -> config.groupTestFirstGroup2 = value - ) - .customController(TickBoxController::new) - .build()) - .build()) - .group(OptionGroup.createBuilder() - .name(Component.empty()) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("Second Group Test")) - .binding( - defaults.groupTestSecondGroup, - () -> config.groupTestSecondGroup, - value -> config.groupTestSecondGroup = value - ) - .customController(TickBoxController::new) - .build()) - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Category Test")) - .option(LabelOption.create(Component.literal("This is a test category!"))) - .build()) - .category(PlaceholderCategory.createBuilder() - .name(Component.literal("Placeholder Category")) - .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen)) - .build()) - .save(() -> { - Minecraft.getInstance().options.save(); - ConfigTest.GSON.save(); - }) - ) - .generateScreen(parent); - } - - private static boolean myBooleanOption = true; - - private static Screen getWikiGetStarted(Screen parent) { - return YetAnotherConfigLib.createBuilder() - .title(Component.literal("Used for narration. Could be used to render a title in the future.")) - .category(ConfigCategory.createBuilder() - .name(Component.literal("Name of the category")) - .tooltip(Component.literal("This Component will appear as a tooltip when you hover or focus the button with Tab. There is no need to add \n to wrap as YACL will do it for you.")) - .group(OptionGroup.createBuilder() - .name(Component.literal("Name of the group")) - .description(OptionDescription.of(Component.literal("This Component will appear when you hover over the name or focus on the collapse button with Tab."))) - .option(Option.createBuilder(boolean.class) - .name(Component.literal("Boolean Option")) - .description(OptionDescription.of(Component.literal("This Component will appear as a tooltip when you hover over the option."))) - .binding(true, () -> myBooleanOption, newVal -> myBooleanOption = newVal) - .customController(TickBoxController::new) - .build()) - .build()) - .build()) - .build() - .generateScreen(parent); - } -} diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/ConfigTest.java b/test-common/src/main/java/dev/isxander/yacl3/test/ConfigTest.java new file mode 100644 index 0000000..3b5c8a5 --- /dev/null +++ b/test-common/src/main/java/dev/isxander/yacl3/test/ConfigTest.java @@ -0,0 +1,51 @@ +package dev.isxander.yacl3.test; + +import dev.isxander.yacl3.config.ConfigEntry; +import dev.isxander.yacl3.config.ConfigInstance; +import dev.isxander.yacl3.config.GsonConfigInstance; + +import java.awt.*; +import java.util.List; +import java.nio.file.Path; + +public class ConfigTest { + public static final ConfigInstance GSON = GsonConfigInstance.createBuilder(ConfigTest.class) + .setPath(Path.of("./config/yacl-test.json")) + .build(); + + @ConfigEntry public boolean booleanToggle = false; + @ConfigEntry public boolean customBooleanToggle = false; + @ConfigEntry public boolean tickbox = false; + @ConfigEntry public int intSlider = 0; + @ConfigEntry public double doubleSlider = 0; + @ConfigEntry public float floatSlider = 0; + @ConfigEntry public long longSlider = 0; + @ConfigEntry public String textField = "Hello"; + @ConfigEntry public Color colorOption = Color.red; + @ConfigEntry public double doubleField = 0.5; + @ConfigEntry public float floatField = 0.5f; + @ConfigEntry public int intField = 5; + @ConfigEntry public long longField = 5; + @ConfigEntry public Alphabet enumOption = Alphabet.A; + + @ConfigEntry + public List stringList = List.of("This is quite cool.", "You can add multiple items!", "And it is integrated so well into Option groups!"); + @ConfigEntry + public List intList = List.of(1, 2, 3); + + @ConfigEntry + public boolean groupTestRoot = false; + @ConfigEntry + public boolean groupTestFirstGroup = false; + @ConfigEntry + public boolean groupTestFirstGroup2 = false; + @ConfigEntry + public boolean groupTestSecondGroup = false; + + @ConfigEntry + public int scrollingSlider = 0; + + public enum Alphabet { + A, B, C + } +} diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java new file mode 100644 index 0000000..955949f --- /dev/null +++ b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java @@ -0,0 +1,391 @@ +package dev.isxander.yacl3.test; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import dev.isxander.yacl3.api.controller.StringControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.gui.RequireRestartScreen; +import dev.isxander.yacl3.gui.controllers.*; +import dev.isxander.yacl3.gui.controllers.cycling.EnumController; +import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController; +import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController; +import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController; +import dev.isxander.yacl3.gui.controllers.slider.LongSliderController; +import dev.isxander.yacl3.gui.controllers.string.StringController; +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 net.minecraft.client.GraphicsStatus; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.resources.ResourceLocation; + +import java.awt.Color; +import java.nio.file.Path; +import java.util.List; + +public class GuiTest { + public static Screen getModConfigScreenFactory(Screen parent) { + return YetAnotherConfigLib.create(ConfigTest.GSON, (defaults, config, builder) -> builder + .title(Component.literal("Test Suites")) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Suites")) + .option(ButtonOption.createBuilder() + .name(Component.literal("Full Test Suite")) + .action((screen, opt) -> Minecraft.getInstance().setScreen(getFullTestSuite(screen))) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Wiki")) + .option(ButtonOption.createBuilder() + .name(Component.literal("Get Started")) + .action((screen, opt) -> Minecraft.getInstance().setScreen(getWikiGetStarted(screen))) + .build()) + .build()) + .build()) + ) + .generateScreen(parent); + } + + private static Screen getFullTestSuite(Screen parent) { + return YetAnotherConfigLib.create(ConfigTest.GSON, (defaults, config, builder) -> builder + .title(Component.literal("Test GUI")) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Control Examples")) + .tooltip(Component.literal("Example Category Description")) + .group(OptionGroup.createBuilder() + .name(Component.literal("Boolean Controllers")) + .option(Option.createBuilder() + .name(Component.literal("Boolean Toggle")) + .description(OptionDescription.createBuilder() + .text(Component.empty() + .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a"))))) + .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b"))))) + .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c"))))) + .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e"))))) + .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev"))) + ) + .webpImage(Path.of("D:\\Xander\\Code\\isXander\\Controlify\\src\\main\\resources\\assets\\controlify\\textures\\screenshots\\reach-around-placement.webp"), new ResourceLocation("yacl", "e.webp")) + .build()) + .binding( + defaults.booleanToggle, + () -> config.booleanToggle, + (value) -> config.booleanToggle = value + ) + .controller(BooleanControllerBuilder::create) + .flag(OptionFlag.GAME_RESTART) + .build()) + .option(Option.createBuilder() + .name(Component.literal("Custom Boolean Toggle")) + .description(val -> OptionDescription.createBuilder() + .text(Component.literal("You can customize controllers like so! YACL is truly infinitely customizable! This tooltip is long in order to demonstrate the cool, smooth scrolling of these descriptions. Did you know, they are also super clickable?! I know, cool right, YACL 3.x really is amazing.")) + .image(Path.of("D:\\Xander\\Downloads\\_MG_0860-Enhanced-NR.png"), new ResourceLocation("yacl", "f.webp")) + .build()) + .binding( + defaults.customBooleanToggle, + () -> config.customBooleanToggle, + (value) -> config.customBooleanToggle = value + ) + .controller(opt -> BooleanControllerBuilder.create(opt) + .valueFormatter(state -> state ? Component.literal("Amazing") : Component.literal("Not Amazing")) + .coloured(true)) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("Tick Box")) + .description(OptionDescription.of(Component.literal("There are even alternate methods of displaying the same data type!"))) + .binding( + defaults.tickbox, + () -> config.tickbox, + (value) -> config.tickbox = value + ) + .controller(TickBoxControllerBuilder::create) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Slider Controllers")) + .option(Option.createBuilder(int.class) + .name(Component.literal("Int Slider")) + .instant(true) + .binding( + defaults.intSlider, + () -> config.intSlider, + value -> config.intSlider = value + + ) + .customController(opt -> new IntegerSliderController(opt, 0, 3, 1)) + .build()) + .option(Option.createBuilder(double.class) + .name(Component.literal("Double Slider")) + .binding( + defaults.doubleSlider, + () -> config.doubleSlider, + (value) -> config.doubleSlider = value + ) + .customController(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) + .build()) + .option(Option.createBuilder(float.class) + .name(Component.literal("Float Slider")) + .binding( + defaults.floatSlider, + () -> config.floatSlider, + (value) -> config.floatSlider = value + ) + .customController(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) + .build()) + .option(Option.createBuilder(long.class) + .name(Component.literal("Long Slider")) + .binding( + defaults.longSlider, + () -> config.longSlider, + (value) -> config.longSlider = value + ) + .customController(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Input Field Controllers")) + .option(Option.createBuilder(String.class) + .name(Component.literal("Component Option")) + .binding( + defaults.textField, + () -> config.textField, + value -> config.textField = value + ) + .customController(StringController::new) + .build()) + .option(Option.createBuilder(Color.class) + .name(Component.literal("Color Option")) + .binding( + defaults.colorOption, + () -> config.colorOption, + value -> config.colorOption = value + ) + .customController(ColorController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Number Fields")) + .option(Option.createBuilder(double.class) + .name(Component.literal("Double Field")) + .binding( + defaults.doubleField, + () -> config.doubleField, + value -> config.doubleField = value + ) + .customController(DoubleFieldController::new) + .build()) + .option(Option.createBuilder(float.class) + .name(Component.literal("Float Field")) + .binding( + defaults.floatField, + () -> config.floatField, + value -> config.floatField = value + ) + .customController(FloatFieldController::new) + .build()) + .option(Option.createBuilder(int.class) + .name(Component.literal("Integer Field")) + .binding( + defaults.intField, + () -> config.intField, + value -> config.intField = value + ) + .customController(IntegerFieldController::new) + .build()) + .option(Option.createBuilder(long.class) + .name(Component.literal("Long Field")) + .binding( + defaults.longField, + () -> config.longField, + value -> config.longField = value + ) + .customController(LongFieldController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Enum Controllers")) + .option(Option.createBuilder(ConfigTest.Alphabet.class) + .name(Component.literal("Enum Cycler")) + .binding( + defaults.enumOption, + () -> config.enumOption, + (value) -> config.enumOption = value + ) + .customController(opt -> new EnumController<>(opt, ConfigTest.Alphabet.class)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Options that aren't really options")) + .option(ButtonOption.createBuilder() + .name(Component.literal("Button \"Option\"")) + .action((screen, opt) -> SystemToast.add(Minecraft.getInstance().getToasts(), SystemToast.SystemToastIds.TUTORIAL_HINT, Component.literal("Button Pressed"), Component.literal("Button option was invoked!"))) + .build()) + .option(LabelOption.create( + Component.empty() + .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a"))))) + .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b"))))) + .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c"))))) + .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e"))))) + .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev")))) + ) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("Minecraft Bindings")) + .description(OptionDescription.of(Component.literal("YACL can also bind Minecraft options!"))) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("Minecraft AutoJump")) + .description(OptionDescription.of(Component.literal("You can even bind minecraft options!"))) + .binding(Binding.minecraft(Minecraft.getInstance().options.autoJump())) + .customController(TickBoxController::new) + .build()) + .option(Option.createBuilder() + .name(Component.literal("Minecraft Graphics Mode")) + .binding(Binding.minecraft(Minecraft.getInstance().options.graphicsMode())) + .customController(opt -> new EnumController<>(opt, GraphicsStatus.class)) + .build()) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("List Test")) + .group(ListOption.createBuilder(String.class) + .name(Component.literal("String List")) + .binding( + defaults.stringList, + () -> config.stringList, + val -> config.stringList = val + ) + .controller(StringControllerBuilder::create) + .initial("") + .build()) + .group(ListOption.createBuilder() + .name(Component.literal("Slider List")) + .binding( + defaults.intList, + () -> config.intList, + val -> config.intList = val + ) + .controller(opt -> IntegerSliderControllerBuilder.create(opt) + .range(0, 10).step(1)) + .initial(0) + .available(false) + .build()) + .group(ListOption.createBuilder(Component.class) + .name(Component.literal("Useless Label List")) + .binding(Binding.immutable(List.of(Component.literal("It's quite impressive that literally every single controller works, without problem.")))) + .customController(LabelController::new) + .initial(Component.literal("Initial label")) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Group Test")) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("Root Test")) + .binding( + defaults.groupTestRoot, + () -> config.groupTestRoot, + value -> config.groupTestRoot = value + ) + .customController(TickBoxController::new) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.literal("First Group")) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("First Group Test 1")) + .binding( + defaults.groupTestFirstGroup, + () -> config.groupTestFirstGroup, + value -> config.groupTestFirstGroup = value + ) + .customController(TickBoxController::new) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("First Group Test 2")) + .binding( + defaults.groupTestFirstGroup2, + () -> config.groupTestFirstGroup2, + value -> config.groupTestFirstGroup2 = value + ) + .customController(TickBoxController::new) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(Component.empty()) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("Second Group Test")) + .binding( + defaults.groupTestSecondGroup, + () -> config.groupTestSecondGroup, + value -> config.groupTestSecondGroup = value + ) + .customController(TickBoxController::new) + .build()) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Category Test")) + .option(LabelOption.create(Component.literal("This is a test category!"))) + .build()) + .category(PlaceholderCategory.createBuilder() + .name(Component.literal("Placeholder Category")) + .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen)) + .build()) + .save(() -> { + Minecraft.getInstance().options.save(); + ConfigTest.GSON.save(); + }) + ) + .generateScreen(parent); + } + + private static boolean myBooleanOption = true; + + private static Screen getWikiGetStarted(Screen parent) { + return YetAnotherConfigLib.createBuilder() + .title(Component.literal("Used for narration. Could be used to render a title in the future.")) + .category(ConfigCategory.createBuilder() + .name(Component.literal("Name of the category")) + .tooltip(Component.literal("This Component will appear as a tooltip when you hover or focus the button with Tab. There is no need to add \n to wrap as YACL will do it for you.")) + .group(OptionGroup.createBuilder() + .name(Component.literal("Name of the group")) + .description(OptionDescription.of(Component.literal("This Component will appear when you hover over the name or focus on the collapse button with Tab."))) + .option(Option.createBuilder(boolean.class) + .name(Component.literal("Boolean Option")) + .description(OptionDescription.of(Component.literal("This Component will appear as a tooltip when you hover over the option."))) + .binding(true, () -> myBooleanOption, newVal -> myBooleanOption = newVal) + .customController(TickBoxController::new) + .build()) + .build()) + .build()) + .build() + .generateScreen(parent); + } +} diff --git a/test-fabric/src/main/java/dev/isxander/yacl/test/fabric/ModMenuEntrypoint.java b/test-fabric/src/main/java/dev/isxander/yacl/test/fabric/ModMenuEntrypoint.java deleted file mode 100644 index b2dc876..0000000 --- a/test-fabric/src/main/java/dev/isxander/yacl/test/fabric/ModMenuEntrypoint.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.isxander.yacl.test.fabric; - -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; -import dev.isxander.yacl.test.GuiTest; - -public class ModMenuEntrypoint implements ModMenuApi { - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return GuiTest::getModConfigScreenFactory; - } -} diff --git a/test-fabric/src/main/java/dev/isxander/yacl3/test/fabric/ModMenuEntrypoint.java b/test-fabric/src/main/java/dev/isxander/yacl3/test/fabric/ModMenuEntrypoint.java new file mode 100644 index 0000000..708ce97 --- /dev/null +++ b/test-fabric/src/main/java/dev/isxander/yacl3/test/fabric/ModMenuEntrypoint.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.test.fabric; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import dev.isxander.yacl3.test.GuiTest; + +public class ModMenuEntrypoint implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return GuiTest::getModConfigScreenFactory; + } +} diff --git a/test-fabric/src/main/resources/fabric.mod.json b/test-fabric/src/main/resources/fabric.mod.json index 15e28a4..ff9c0f3 100644 --- a/test-fabric/src/main/resources/fabric.mod.json +++ b/test-fabric/src/main/resources/fabric.mod.json @@ -7,7 +7,7 @@ "environment": "*", "entrypoints": { "modmenu": [ - "dev.isxander.yacl.test.fabric.ModMenuEntrypoint" + "dev.isxander.yacl3.test.fabric.ModMenuEntrypoint" ] } } diff --git a/test-forge/src/main/java/dev/isxander/yacl/test/forge/ForgeTest.java b/test-forge/src/main/java/dev/isxander/yacl/test/forge/ForgeTest.java index 182c4a9..fcdb894 100644 --- a/test-forge/src/main/java/dev/isxander/yacl/test/forge/ForgeTest.java +++ b/test-forge/src/main/java/dev/isxander/yacl/test/forge/ForgeTest.java @@ -1,6 +1,6 @@ -package dev.isxander.yacl.test.forge; +package dev.isxander.yacl3.test.forge; -import dev.isxander.yacl.test.GuiTest; +import dev.isxander.yacl3.test.GuiTest; import net.minecraftforge.client.ConfigScreenHandler; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; -- cgit