diff --git a/build.gradle b/build.gradle
index 359b7d5..a7075c0 100755
--- a/build.gradle
+++ b/build.gradle
@@ -86,3 +86,9 @@ tasks.withType(JavaCompile).configureEach {
lombok {
version = "1.18.38"
}
+
+test {
+ useJUnitPlatform()
+
+ systemProperty "unit.testing", "true"
+}
diff --git a/dependencies.gradle b/dependencies.gradle
index 3743b18..668a51e 100755
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,6 +1,10 @@
dependencies {
compileOnly(libs.jetbrains.annotations)
+ testImplementation 'org.hamcrest:hamcrest:2.2'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+
// Math Parser
jarJar(implementation(libs.evalEx.get()))
additionalRuntimeClasspath(libs.evalEx.get())
diff --git a/src/main/java/brachy/modularui/ClientProxy.java b/src/main/java/brachy/modularui/ClientProxy.java
index 2be93d7..52421f8 100644
--- a/src/main/java/brachy/modularui/ClientProxy.java
+++ b/src/main/java/brachy/modularui/ClientProxy.java
@@ -2,24 +2,29 @@
import brachy.modularui.animation.AnimatorManager;
import brachy.modularui.api.drawable.IIcon;
+import brachy.modularui.api.drawable.Text;
import brachy.modularui.client.CursorHandler;
import brachy.modularui.client.component.DrawableTooltipComponent;
import brachy.modularui.client.component.TooltipComponentIcon;
import brachy.modularui.drawable.ClientTooltipComponentIcon;
import brachy.modularui.drawable.DelegateIcon;
-import brachy.modularui.drawable.DrawableSerialization;
import brachy.modularui.drawable.HoverableIcon;
import brachy.modularui.drawable.Icon;
import brachy.modularui.drawable.InteractableIcon;
import brachy.modularui.drawable.text.KeyIcon;
+import brachy.modularui.drawable.text.ModularComponent;
import brachy.modularui.drawable.text.TextIcon;
import brachy.modularui.network.ModularNetwork;
import brachy.modularui.theme.ThemeManager;
+import brachy.modularui.utils.Alignment;
+import brachy.modularui.utils.Color;
+import brachy.modularui.utils.serialization.json.JsonHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Timer;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.serialization.Codec;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
import net.minecraftforge.client.event.RegisterClientTooltipComponentFactoriesEvent;
import net.minecraftforge.common.MinecraftForge;
@@ -28,8 +33,10 @@
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
+import com.google.gson.JsonElement;
import lombok.Getter;
+import java.util.Objects;
import java.util.function.Function;
public class ClientProxy extends CommonProxy {
@@ -47,7 +54,6 @@ public class ClientProxy extends CommonProxy {
if (!ModularUI.isDataGen()) {
CursorHandler.init();
AnimatorManager.init();
- DrawableSerialization.init();
}
}
@@ -58,6 +64,18 @@ protected void onInit(FMLCommonSetupEvent event) {
// enable stencil bits, must call on render thread
RenderSystem.recordRenderCall(() -> Minecraft.getInstance().getMainRenderTarget().enableStencil());
}
+ test(ModularComponent.CODEC.mutableCodec(), Text.comp(
+ Text.str("Hello ").color(Color.withAlpha(Color.BLUE.main, 0)),
+ Text.lang("World").scale(1.5f)).alignment(Alignment.BottomCenter), true);
+ }
+
+ private static void test(Codec codec, A obj, boolean checkObjEquals) {
+ JsonElement json1 = JsonHelper.toJson(codec, obj);
+ A obj2 = JsonHelper.fromJson(codec, json1);
+ if (checkObjEquals) ModularUI.LOGGER.info("Equals: {}", Objects.equals(obj, obj2));
+ JsonElement json2 = JsonHelper.toJson(codec, obj2);
+ ModularUI.LOGGER.info("Equals: {}", Objects.equals(json1, json2));
+ ModularUI.LOGGER.info(JsonHelper.GSON.toJson(json1));
}
private void onRegisterClientTooltipComponents(RegisterClientTooltipComponentFactoriesEvent event) {
diff --git a/src/main/java/brachy/modularui/ModularUI.java b/src/main/java/brachy/modularui/ModularUI.java
index fa286ae..45b88e7 100644
--- a/src/main/java/brachy/modularui/ModularUI.java
+++ b/src/main/java/brachy/modularui/ModularUI.java
@@ -32,6 +32,7 @@ public class ModularUI {
private static final ResourceLocation TEMPLATE_LOCATION = new ResourceLocation(MOD_ID, "");
public static final Logger LOGGER = LogManager.getLogger(NAME);
+ public static final boolean UNIT_TEST = Boolean.getBoolean("unit.testing");
public ModularUI() {
DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new);
@@ -69,6 +70,10 @@ public static boolean isDev() {
return !isProd();
}
+ public static boolean isTestEnv() {
+ return UNIT_TEST;
+ }
+
/**
* @return if we're running data generation
*/
@@ -99,7 +104,7 @@ public static boolean isModLoaded(String modId) {
* @return if the current thread is the client thread
*/
public static boolean isClientThread() {
- return isClientSide() && Minecraft.getInstance().isSameThread();
+ return isTestEnv() || (isClientSide() && Minecraft.getInstance().isSameThread());
}
/**
diff --git a/src/main/java/brachy/modularui/ModularUIConfig.java b/src/main/java/brachy/modularui/ModularUIConfig.java
index 3264d8e..11994c1 100644
--- a/src/main/java/brachy/modularui/ModularUIConfig.java
+++ b/src/main/java/brachy/modularui/ModularUIConfig.java
@@ -188,7 +188,7 @@ public static final class Dev {
private Dev() {}
public static boolean debugUI() {
- return DEBUG_UI.get();
+ return ModularUI.isTestEnv() || DEBUG_UI.get();
}
public static int textColor() {
diff --git a/src/main/java/brachy/modularui/api/IJsonSerializable.java b/src/main/java/brachy/modularui/api/IJsonSerializable.java
deleted file mode 100755
index 88271d1..0000000
--- a/src/main/java/brachy/modularui/api/IJsonSerializable.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package brachy.modularui.api;
-
-import com.mojang.serialization.Codec;
-import com.mojang.serialization.DataResult;
-import com.mojang.serialization.Dynamic;
-import com.mojang.serialization.JsonOps;
-
-import com.google.gson.JsonObject;
-import org.jetbrains.annotations.ApiStatus;
-
-public interface IJsonSerializable> {
-
- /**
- * Override this
- *
- * @return the codec to serialize this object with
- */
- // TODO actually implement on subclasses
- @ApiStatus.OverrideOnly
- default Codec getCodec() {
- return Codec.PASSTHROUGH.flatComapMap(dynamic -> {
- loadFromJson(dynamic.cast(JsonOps.INSTANCE).getAsJsonObject());
- return (T) this;
- }, object -> {
- JsonObject jsonObject = new JsonObject();
- if (saveToJson(jsonObject)) {
- return DataResult.success(new Dynamic<>(JsonOps.INSTANCE, jsonObject));
- }
- return DataResult.error(() -> "Failed to serialize drawable %s".formatted(object));
- });
- }
-
- /**
- * Reads extra json data after this drawable is created.
- *
- * @param json json to read from
- */
- default void loadFromJson(JsonObject json) {}
-
- /**
- * Writes all json data necessary so that deserializing it results in the same drawable.
- *
- * @param json json to write to
- * @return if the drawable was serialized
- */
- default boolean saveToJson(JsonObject json) {
- return false;
- }
-}
diff --git a/src/main/java/brachy/modularui/api/ITheme.java b/src/main/java/brachy/modularui/api/ITheme.java
index 85660f9..41d171f 100644
--- a/src/main/java/brachy/modularui/api/ITheme.java
+++ b/src/main/java/brachy/modularui/api/ITheme.java
@@ -6,16 +6,47 @@
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.theme.WidgetThemeEntry;
import brachy.modularui.theme.WidgetThemeKey;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.DynamicOps;
+import com.mojang.serialization.Encoder;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.jetbrains.annotations.UnmodifiableView;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
- * A theme is parsed from json and contains style information like color or background texture.
+ * A theme is parsed from JSON and contains style information like color or background texture.
*/
public interface ITheme {
+ Encoder ENCODER = new Encoder<>() {
+ @Override
+ public DataResult encode(ITheme input, DynamicOps ops, T prefix) {
+ var mapBuilder = ops.mapBuilder();
+ mapBuilder.add("id", ops.createString(input.getId()));
+ mapBuilder.add("parent", ops.createString(input.getParentTheme().getId()));
+ input.getFallback().encode(ops, mapBuilder, true);
+ Map> encodedThemes = new Object2ObjectOpenHashMap<>();
+ for (WidgetThemeEntry> entry : input.getWidgetThemes()) {
+ if (entry.key() == IThemeApi.FALLBACK) continue;
+ var set = encodedThemes.computeIfAbsent(entry.key().getName(), k -> new ObjectOpenHashSet<>());
+ if (entry.key().isSubWidgetTheme() && set.contains(entry.theme()) && set.contains(entry.hoverTheme())) continue;
+ entry.encode(ops, mapBuilder, false);
+ set.add(entry.theme());
+ set.add(entry.hoverTheme());
+ }
+ return mapBuilder.build(prefix);
+ }
+ };
+
/**
* @return the master default theme.
*/
diff --git a/src/main/java/brachy/modularui/api/IThemeApi.java b/src/main/java/brachy/modularui/api/IThemeApi.java
index 6f27180..689c121 100644
--- a/src/main/java/brachy/modularui/api/IThemeApi.java
+++ b/src/main/java/brachy/modularui/api/IThemeApi.java
@@ -11,11 +11,15 @@
import brachy.modularui.theme.ThemeAPI;
import brachy.modularui.theme.ThemeBuilder;
import brachy.modularui.theme.WidgetTheme;
+import brachy.modularui.theme.WidgetThemeCodec;
import brachy.modularui.theme.WidgetThemeKey;
import brachy.modularui.theme.WidgetThemeKeyBuilder;
-import brachy.modularui.theme.WidgetThemeParser;
+import brachy.modularui.theme.WidgetThemeMerger;
+import brachy.modularui.utils.Color;
import brachy.modularui.utils.serialization.json.JsonBuilder;
+import com.mojang.serialization.Codec;
+
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -30,46 +34,85 @@
@ApiStatus.NonExtendable
public interface IThemeApi {
+ // properties
+ String PARENT = "parent";
+ String DEFAULT_WIDTH = "defaultWidth";
+ String DEFAULT_HEIGHT = "defaultHeight";
+ String BACKGROUND = "background";
+ String COLOR = "color";
+ String TEXT_COLOR = "textColor";
+ String TEXT_SHADOW = "textShadow";
+ String ICON_COLOR = "iconColor";
+ String SLOT_HOVER_COLOR = "slotHoverColor";
+ String MARKED_COLOR = "markedColor";
+ String HINT_COLOR = "hintColor";
+ String SELECTED_BACKGROUND = "selectedBackground";
+ String SELECTED_COLOR = "selectedColor";
+ String SELECTED_TEXT_COLOR = "selectedTextColor";
+ String SELECTED_TEXT_SHADOW = "selectedTextShadow";
+ String SELECTED_ICON_COLOR = "selectedIconColor";
+
// widget themes
WidgetThemeKey FALLBACK = get().widgetThemeKeyBuilder("default", WidgetTheme.class)
.defaultTheme(WidgetTheme.darkTextNoShadow(18, 18, null))
+ .field(DEFAULT_WIDTH, int.class, Codec.INT, WidgetTheme::getDefaultWidth)
+ .field(DEFAULT_HEIGHT, int.class, Codec.INT, WidgetTheme::getDefaultHeight)
+ .field(BACKGROUND, IDrawable.class, IDrawable.CODEC, WidgetTheme::getBackground)
+ .fallbackField(COLOR, int.class, Color.CODEC, WidgetTheme::getColor)
+ .fallbackField(TEXT_COLOR, int.class, Color.CODEC, WidgetTheme::getTextColor)
+ .fallbackField(TEXT_SHADOW, boolean.class, Codec.BOOL, WidgetTheme::isTextShadow)
+ .fallbackField(ICON_COLOR, int.class, Color.CODEC, WidgetTheme::getIconColor)
.register();
WidgetThemeKey PANEL = get().widgetThemeKeyBuilder("panel", WidgetTheme.class)
.defaultTheme(WidgetTheme.darkTextNoShadow(176, 166, GuiTextures.MC_BACKGROUND))
+ .fieldsOf(FALLBACK)
.register();
WidgetThemeKey BUTTON = get().widgetThemeKeyBuilder("button", WidgetTheme.class)
.defaultTheme(WidgetTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON))
.defaultHoverTheme(WidgetTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON_HOVERED))
+ .fieldsOf(FALLBACK)
.register();
WidgetThemeKey CLOSE_BUTTON = get().widgetThemeKeyBuilder("closeButton", WidgetTheme.class)
.defaultTheme(WidgetTheme.whiteTextShadow(10, 10, GuiTextures.MC_BUTTON))
.defaultHoverTheme(WidgetTheme.whiteTextShadow(10, 10, GuiTextures.MC_BUTTON_HOVERED))
+ .fieldsOf(FALLBACK)
.register();
WidgetThemeKey SCROLLBAR = get().widgetThemeKeyBuilder("scrollbar", WidgetTheme.class)
.defaultTheme(WidgetTheme.darkTextNoShadow(4, 4, Scrollbar.VANILLA))
+ .fieldsOf(FALLBACK)
.register();
WidgetThemeKey ITEM_SLOT = get().widgetThemeKeyBuilder("itemSlot", SlotTheme.class)
.defaultTheme(new SlotTheme(GuiTextures.SLOT_ITEM))
+ .fieldsOf(FALLBACK)
+ .field(SLOT_HOVER_COLOR, int.class, Color.CODEC, SlotTheme::getSlotHoverColor)
.register();
WidgetThemeKey FLUID_SLOT = get().widgetThemeKeyBuilder("fluidSlot", SlotTheme.class)
.defaultTheme(new SlotTheme(GuiTextures.SLOT_FLUID))
+ .fieldsOf(ITEM_SLOT)
.register();
WidgetThemeKey TEXT_FIELD = get().widgetThemeKeyBuilder("textField", TextFieldTheme.class)
.defaultTheme(new TextFieldTheme(0xFF2F72A8, 0xFF5F5F5F))
+ .fieldsOf(FALLBACK)
+ .field(MARKED_COLOR, int.class, Color.CODEC, TextFieldTheme::getMarkedColor)
+ .field(HINT_COLOR, int.class, Color.CODEC, TextFieldTheme::getHintColor)
.register();
WidgetThemeKey TOGGLE_BUTTON = get().widgetThemeKeyBuilder("toggleButton", SelectableTheme.class)
- .defaultTheme(
- SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON, GuiTextures.MC_BUTTON_DISABLED))
- .defaultHoverTheme(SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON_HOVERED,
- IDrawable.NONE))
+ .defaultTheme(SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON, GuiTextures.MC_BUTTON_DISABLED))
+ .defaultHoverTheme(SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON_HOVERED, IDrawable.NONE))
+ .fieldsOf(FALLBACK)
+ .field(SELECTED_BACKGROUND, IDrawable.class, IDrawable.CODEC, SelectableTheme::getSelectedBackground)
+ .field(SELECTED_COLOR, int.class, Color.CODEC, SelectableTheme::getSelectedColor)
+ .field(SELECTED_TEXT_COLOR, int.class, Color.CODEC, SelectableTheme::getSelectedTextColor)
+ .field(SELECTED_TEXT_SHADOW, boolean.class, Codec.BOOL, SelectableTheme::isSelectedTextShadow)
+ .field(SELECTED_ICON_COLOR, int.class, Color.CODEC, SelectableTheme::getSelectedIconColor)
.register();
// subwidget themes
@@ -81,24 +124,6 @@ public interface IThemeApi {
String HOVER_SUFFIX = ":hover";
- // properties
- String PARENT = "parent";
- String DEFAULT_WIDTH = "defaultWidth";
- String DEFAULT_HEIGHT = "defaultHeight";
- String BACKGROUND = "background";
- String HOVER_BACKGROUND = "hoverBackground";
- String COLOR = "color";
- String TEXT_COLOR = "textColor";
- String TEXT_SHADOW = "textShadow";
- String ICON_COLOR = "iconColor";
- String SLOT_HOVER_COLOR = "slotHoverColor";
- String MARKED_COLOR = "markedColor";
- String HINT_COLOR = "hintColor";
- String SELECTED_BACKGROUND = "selectedBackground";
- String SELECTED_COLOR = "selectedColor";
- String SELECTED_TEXT_COLOR = "selectedTextColor";
- String SELECTED_TEXT_SHADOW = "selectedTextShadow";
- String SELECTED_ICON_COLOR = "selectedIconColor";
/**
* @return the default api implementation
@@ -219,21 +244,12 @@ default void registerThemeForScreen(String owner, String name, String theme) {
*/
void registerThemeForScreen(String screen, String theme);
- /**
- * Registers a widget theme. It is recommended to store the resulting key in a static variable and make it
- * accessible by public methods.
- *
- * @param id id of the widget theme
- * @param defaultTheme the fallback widget theme
- * @param defaultHoverTheme the fallback hover widget theme
- * @param parser the widget theme json parser function. This is usually another constructor.
- * @return key to access the widget theme
- */
+ @Deprecated
WidgetThemeKey registerWidgetTheme(String id, T defaultTheme, T defaultHoverTheme,
- WidgetThemeParser parser);
+ WidgetThemeMerger merger, WidgetThemeCodec codec);
default WidgetThemeKeyBuilder widgetThemeKeyBuilder(String id, Class type) {
- return new WidgetThemeKeyBuilder<>(id);
+ return new WidgetThemeKeyBuilder<>(id, type);
}
@UnmodifiableView
diff --git a/src/main/java/brachy/modularui/api/MCHelper.java b/src/main/java/brachy/modularui/api/MCHelper.java
index 2f4391e..ff20a5f 100644
--- a/src/main/java/brachy/modularui/api/MCHelper.java
+++ b/src/main/java/brachy/modularui/api/MCHelper.java
@@ -11,18 +11,23 @@
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
+import org.jetbrains.annotations.Nullable;
+
import java.util.List;
public class MCHelper {
+ @SuppressWarnings("DataFlowIssue")
+ @Nullable
@SideOnly(Side.CLIENT)
public static Minecraft getMc() {
return Minecraft.getInstance();
}
+ @Nullable
@SideOnly(Side.CLIENT)
public static Player getPlayer() {
- return getMc().player;
+ return getMc() == null ? null : getMc().player;
}
@SideOnly(Side.CLIENT)
@@ -60,12 +65,12 @@ public static void setScreen(Screen screen) {
@SideOnly(Side.CLIENT)
public static Screen getCurrentScreen() {
- return getMc().screen;
+ return getMc() == null ? null : getMc().screen;
}
@SideOnly(Side.CLIENT)
public static Font getFont() {
- return getMc().font;
+ return getMc() == null ? null : getMc().font;
}
public static List getItemToolTip(ItemStack item) {
diff --git a/src/main/java/brachy/modularui/api/drawable/IDrawable.java b/src/main/java/brachy/modularui/api/drawable/IDrawable.java
index 04fcfe9..c535ccf 100644
--- a/src/main/java/brachy/modularui/api/drawable/IDrawable.java
+++ b/src/main/java/brachy/modularui/api/drawable/IDrawable.java
@@ -9,12 +9,19 @@
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.theme.WidgetThemeEntry;
import brachy.modularui.utils.Color;
+import brachy.modularui.utils.serialization.codec.CodecRegistry;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
import brachy.modularui.widget.Widget;
import brachy.modularui.widget.sizer.Area;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.MapCodec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
+import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
/**
@@ -34,6 +41,55 @@ static IDrawable of(IDrawable... drawables) {
}
}
+ /**
+ * An empty drawable. Does nothing.
+ */
+ IDrawable EMPTY = new IDrawable() {
+ @Override
+ public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {}
+
+ @Override
+ public String toString() {
+ return "IDrawable.EMPTY";
+ }
+ };
+
+ /**
+ * An empty drawable used to mark hover textures as "should not be used"!
+ */
+ IDrawable NONE = new IDrawable() {
+ @Override
+ public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {}
+
+ @Override
+ public String toString() {
+ return "IDrawable.NONE";
+ }
+ };
+
+ CodecRegistry CODECS = new CodecRegistry<>();
+ MapCodec CODEC_DISPATCH = CodecUtil.dispatchNullable(Codec.STRING, IDrawable::getTypeName, CODECS::getNullableCodec);
+ Codec CODEC_EMPTY_NONE = Codec.STRING.flatXmap(s -> {
+ if (s == null || s.equals("empty") || s.equals("null")) return DataResult.success(EMPTY);
+ if (s.equals("none")) return DataResult.success(NONE);
+ return DataResult.error(() -> "Only valid options are empty, null and none");
+ }, d -> {
+ if (d == EMPTY) return DataResult.success("empty");
+ if (d == NONE) return DataResult.success("none");
+ return DataResult.error(() -> "Only works for empty and none");
+ });
+ Codec CODEC = CodecUtil.chainedCodec(
+ CodecUtil.nullCodec(EMPTY), CODEC_EMPTY_NONE,
+ DrawableStack.CODEC, CODEC_DISPATCH.codec());
+
+ static DataResult toJson(IDrawable drawable) {
+ return CODEC.encodeStart(JsonOps.INSTANCE, drawable);
+ }
+
+ static JsonElement toJsonOrThrow(IDrawable drawable) {
+ return toJson(drawable).getOrThrow(false, s -> {});
+ }
+
/**
* Draws this drawable at the given position with the given size. It's the implementors responsibility to properly
* apply the widget theme by calling {@link #applyColor(int)} before drawing.
@@ -164,15 +220,9 @@ default IDrawable getSubArea(float u0, float v0, float u1, float v1) {
return new SubAreaDrawable(this).uv(u0, v0, u1, v1);
}
- /**
- * An empty drawable. Does nothing.
- */
- IDrawable EMPTY = (context, x, y, width, height, widgetTheme) -> {};
-
- /**
- * An empty drawable used to mark hover textures as "should not be used"!
- */
- IDrawable NONE = (context, x, y, width, height, widgetTheme) -> {};
+ default String getTypeName() {
+ return getClass().getSimpleName();
+ }
static boolean isVisible(@Nullable IDrawable drawable) {
if (drawable == null || drawable == EMPTY || drawable == NONE) return false;
diff --git a/src/main/java/brachy/modularui/api/drawable/Text.java b/src/main/java/brachy/modularui/api/drawable/Text.java
index 947745f..4a70acb 100644
--- a/src/main/java/brachy/modularui/api/drawable/Text.java
+++ b/src/main/java/brachy/modularui/api/drawable/Text.java
@@ -1,6 +1,5 @@
package brachy.modularui.api.drawable;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.drawable.text.DynamicComponent;
import brachy.modularui.drawable.text.KeyIcon;
import brachy.modularui.drawable.text.ModularComponent;
@@ -8,6 +7,7 @@
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Alignment;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
@@ -15,7 +15,6 @@
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,7 +24,9 @@
/**
* This represents a piece of text in a GUI.
*/
-public interface Text extends IDrawable, IJsonSerializable {
+public interface Text extends IDrawable {
+
+ MutableObjectCodec CODEC = ModularComponent.CODEC;
int TEXT_COLOR = 0xFF404040;
@@ -235,17 +236,7 @@ default KeyIcon asTextIcon() {
}
@Override
- default void loadFromJson(JsonObject json) {
- if (json.has("color") || json.has("shadow") || json.has("align") || json.has("alignment") ||
- json.has("scale")) {
- /*StyledText styledText = this instanceof StyledText styledText1 ? styledText1 : withStyle();
- if (json.has("color")) {
- styledText.color(JsonHelper.getInt(json, 0, "color"));
- }
- styledText.shadow(JsonHelper.getBoolean(json, false, "shadow"));
- styledText.alignment(
- JsonHelper.deserialize(json, Alignment.class, styledText.alignment(), "align", "alignment"));
- styledText.scale(JsonHelper.getFloat(json, 1, "scale"));*/
- }
+ default String getTypeName() {
+ return "text";
}
}
diff --git a/src/main/java/brachy/modularui/api/widget/IWidget.java b/src/main/java/brachy/modularui/api/widget/IWidget.java
index 93e85e0..85e5ea3 100644
--- a/src/main/java/brachy/modularui/api/widget/IWidget.java
+++ b/src/main/java/brachy/modularui/api/widget/IWidget.java
@@ -10,10 +10,15 @@
import brachy.modularui.utils.FormattingUtil;
import brachy.modularui.utils.ObjectList;
import brachy.modularui.utils.Stencil;
+import brachy.modularui.utils.serialization.codec.CodecRegistry;
import brachy.modularui.widget.sizer.Area;
import brachy.modularui.widget.sizer.StandardResizer;
+import com.google.common.base.CaseFormat;
import com.google.common.base.CharMatcher;
+
+import com.mojang.serialization.Codec;
+
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,6 +32,9 @@
*/
public interface IWidget extends ITreeNode {
+ CodecRegistry CODECS = new CodecRegistry<>();
+ Codec CODEC = Codec.STRING.dispatch("widget", IWidget::getTypeName, CODECS::getNullableCodec);
+
String WIDGET_TRANSLATION_KEY_FORMAT = "widget.%s.name";
/**
* This char matcher is used to remove any non-{@code [a-z0-9_.-]} characters in translation keys.
@@ -377,6 +385,15 @@ default boolean areAncestorsEnabled() {
@Nullable
String getName();
+ /**
+ * The type name of this widget. This is used for codecs.
+ *
+ * @return the simple class name or other fitting name
+ */
+ default String getTypeName() {
+ return getClass().getSimpleName();
+ }
+
default boolean isName(String name) {
return name.equals(getName());
}
diff --git a/src/main/java/brachy/modularui/core/mixins/common/ComponentSerializerMixin.java b/src/main/java/brachy/modularui/core/mixins/common/ComponentSerializerMixin.java
new file mode 100644
index 0000000..99050ba
--- /dev/null
+++ b/src/main/java/brachy/modularui/core/mixins/common/ComponentSerializerMixin.java
@@ -0,0 +1,55 @@
+package brachy.modularui.core.mixins.common;
+
+import brachy.modularui.ModularUI;
+import brachy.modularui.drawable.text.ModularComponent;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+
+import net.minecraft.network.chat.Component;
+
+import net.minecraft.network.chat.MutableComponent;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.lang.reflect.Type;
+
+/**
+ * We need this since nested components are potentially modular too
+ */
+@Mixin(Component.Serializer.class)
+public class ComponentSerializerMixin {
+
+ @WrapOperation(
+ method = "serialize(Lnet/minecraft/network/chat/Component;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;",
+ at = @At(value = "INVOKE",
+ target = "Lnet/minecraft/network/chat/Component$Serializer;serialize(Lnet/minecraft/network/chat/Component;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;"))
+ public JsonElement serialize(Component.Serializer instance, Component src, Type typeOfSrc, JsonSerializationContext context, Operation original) {
+ if (src instanceof ModularComponent mc) {
+ var d = ModularComponent.CODEC.mutableCodec().encodeJson(mc);
+ var res = d.result();
+ if (res.isPresent()) return res.get();
+ ModularUI.LOGGER.error("Error encoding nested ModularComponent: {}", d.error().orElseThrow().message());
+ }
+ return original.call(instance, src, typeOfSrc, context);
+ }
+
+ @WrapOperation(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/network/chat/MutableComponent;", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/Component$Serializer;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/network/chat/MutableComponent;"))
+ public MutableComponent deserialize(Component.Serializer instance, JsonElement json, Type typeOfT, JsonDeserializationContext context, Operation original) {
+ MutableComponent comp = original.call(instance, json, typeOfT, context);
+ if (comp.getClass() == MutableComponent.class && json instanceof JsonObject jsonObj && ModularComponent.CODEC.hasAnyField(jsonObj)) {
+ ModularComponent mc = comp.asModular();
+ var d = ModularComponent.CODEC.mutableCodec().parseJson(jsonObj, mc);
+ var res = d.result();
+ if (res.isEmpty()) throw new JsonParseException(d.error().orElseThrow().message());
+ return res.get();
+ }
+ return comp;
+ }
+}
diff --git a/src/main/java/brachy/modularui/core/mixins/common/TextColorMixin.java b/src/main/java/brachy/modularui/core/mixins/common/TextColorMixin.java
new file mode 100644
index 0000000..bf5109b
--- /dev/null
+++ b/src/main/java/brachy/modularui/core/mixins/common/TextColorMixin.java
@@ -0,0 +1,19 @@
+package brachy.modularui.core.mixins.common;
+
+import net.minecraft.network.chat.TextColor;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(TextColor.class)
+public class TextColorMixin {
+
+ /**
+ * @reason Minecraft uses Integer.parseInt which fails when alpha is specified because of signed vs. unsigned
+ */
+ @Redirect(method = "parseColor", at = @At(value = "INVOKE", target = "Ljava/lang/Integer;parseInt(Ljava/lang/String;I)I"))
+ private static int fixDecode(String s, int radix) {
+ return (int) Long.parseLong(s, radix);
+ }
+}
diff --git a/src/main/java/brachy/modularui/drawable/AdaptableUITexture.java b/src/main/java/brachy/modularui/drawable/AdaptableUITexture.java
index 7013503..b971b9a 100644
--- a/src/main/java/brachy/modularui/drawable/AdaptableUITexture.java
+++ b/src/main/java/brachy/modularui/drawable/AdaptableUITexture.java
@@ -2,11 +2,23 @@
import brachy.modularui.screen.viewport.GuiContext;
+import brachy.modularui.utils.Color;
+
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import lombok.Getter;
+
+import lombok.experimental.Accessors;
+
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.resources.ResourceLocation;
import com.mojang.blaze3d.systems.RenderSystem;
import com.google.gson.JsonObject;
+
+import net.minecraft.util.ExtraCodecs;
+
import org.joml.Matrix4f;
import java.util.Objects;
@@ -15,10 +27,11 @@
* This class is a 9-slice texture. It can be created using
* {@link UITexture.Builder#adaptable(int, int, int, int)}.
*/
+@Accessors(fluent = true)
public class AdaptableUITexture extends UITexture {
- private final int imageWidth, imageHeight, bl, bt, br, bb;
- private final boolean tiled;
+ @Getter private final int imageWidth, imageHeight, bl, bt, br, bb;
+ @Getter private final boolean tiled;
/**
* Use {@link UITexture#builder()} with {@link Builder#adaptable(int, int)}
@@ -203,18 +216,6 @@ public void drawTiled(GuiContext context, float x, float y, float width, float h
RenderSystem.disableBlend();
}
- @Override
- protected void saveTextureToJson(JsonObject json) {
- super.saveToJson(json);
- json.addProperty("imageWidth", this.imageWidth);
- json.addProperty("imageHeight", this.imageHeight);
- json.addProperty("bl", this.bl);
- json.addProperty("br", this.br);
- json.addProperty("bt", this.bt);
- json.addProperty("bb", this.bb);
- json.addProperty("tiled", this.tiled);
- }
-
@Override
protected AdaptableUITexture copy() {
return new AdaptableUITexture(location, u0, v0, u1, v1, colorType, nonOpaque,
@@ -226,6 +227,14 @@ public AdaptableUITexture withColorOverride(int color) {
return (AdaptableUITexture) super.withColorOverride(color);
}
+ @Override
+ public Builder toBuilder() {
+ return super.toBuilder()
+ .imageSize(this.imageWidth, this.imageHeight)
+ .adaptable(this.bl, this.bt, this.br, this.bb)
+ .tiled();
+ }
+
@Override
public boolean equals(Object o) {
return o != null && getClass() == o.getClass() && isEqual((AdaptableUITexture) o);
diff --git a/src/main/java/brachy/modularui/drawable/Circle.java b/src/main/java/brachy/modularui/drawable/Circle.java
index 56ffbbb..4a6ae82 100644
--- a/src/main/java/brachy/modularui/drawable/Circle.java
+++ b/src/main/java/brachy/modularui/drawable/Circle.java
@@ -1,24 +1,33 @@
package brachy.modularui.drawable;
import brachy.modularui.animation.IAnimatable;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Color;
import brachy.modularui.utils.Interpolations;
-import brachy.modularui.utils.serialization.json.JsonHelper;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
+import com.mojang.serialization.Codec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonObject;
+import lombok.Getter;
import lombok.Setter;
+import lombok.ToString;
import lombok.experimental.Accessors;
+@ToString
@Accessors(fluent = true, chain = true)
-public class Circle implements IDrawable, IJsonSerializable, IAnimatable {
+public class Circle implements IDrawable, IAnimatable {
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(Circle::new)
+ .addOpt("colorInner", Circle::colorInner, Circle::colorInner, Codec.INT, 0).alias("color")
+ .addOpt("colorOuter", Circle::colorOuter, Circle::colorOuter, Codec.INT, 0).alias("color")
+ .addOpt("segments", Circle::segments, Circle::segments, Codec.INT, 40)
+ .build();
+
+ @Getter
@Setter
private int colorInner, colorOuter, segments;
@@ -28,22 +37,6 @@ public Circle() {
this.segments = 40;
}
- public Circle setColorInner(int colorInner) {
- return colorInner(colorInner);
- }
-
- public Circle setColorOuter(int colorOuter) {
- return colorOuter(colorOuter);
- }
-
- public Circle setColor(int inner, int outer) {
- return color(inner, outer);
- }
-
- public Circle setSegments(int segments) {
- return segments(segments);
- }
-
public Circle color(int inner, int outer) {
this.colorInner = inner;
this.colorOuter = outer;
@@ -62,21 +55,6 @@ public void draw(GuiContext context, int x0, int y0, int width, int height, Widg
this.colorInner, this.colorOuter, this.segments);
}
- @Override
- public void loadFromJson(JsonObject json) {
- this.colorInner = JsonHelper.getColor(json, Color.WHITE.main, "colorInner", "color");
- this.colorOuter = JsonHelper.getColor(json, Color.WHITE.main, "colorOuter", "color");
- this.segments = JsonHelper.getInt(json, 40, "segments");
- }
-
- @Override
- public boolean saveToJson(JsonObject json) {
- json.addProperty("colorInner", this.colorInner);
- json.addProperty("colorOuter", this.colorOuter);
- json.addProperty("segments", this.segments);
- return true;
- }
-
@Override
public Circle interpolate(Circle start, Circle end, float t) {
this.colorInner = Color.lerp(start.colorInner, end.colorInner, t);
@@ -88,7 +66,12 @@ public Circle interpolate(Circle start, Circle end, float t) {
@Override
public Circle copyOrImmutable() {
return new Circle()
- .setColor(this.colorInner, this.colorOuter)
- .setSegments(this.segments);
+ .color(this.colorInner, this.colorOuter)
+ .segments(this.segments);
+ }
+
+ @Override
+ public String getTypeName() {
+ return "circle";
}
}
diff --git a/src/main/java/brachy/modularui/drawable/ColorType.java b/src/main/java/brachy/modularui/drawable/ColorType.java
index c26c67d..abb32d7 100755
--- a/src/main/java/brachy/modularui/drawable/ColorType.java
+++ b/src/main/java/brachy/modularui/drawable/ColorType.java
@@ -2,15 +2,21 @@
import brachy.modularui.theme.WidgetTheme;
+import com.mojang.serialization.Codec;
+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
+import net.minecraft.util.ExtraCodecs;
+
import java.util.Map;
import java.util.Objects;
import java.util.function.ToIntFunction;
public class ColorType {
+ public static final Codec CODEC = ExtraCodecs.stringResolverCodec(ColorType::getName, ColorType::get);
+
private static final Map COLOR_TYPES = new Object2ObjectOpenHashMap<>();
public static ColorType get(String name) {
diff --git a/src/main/java/brachy/modularui/drawable/DrawableSerialization.java b/src/main/java/brachy/modularui/drawable/DrawableSerialization.java
deleted file mode 100644
index 570b705..0000000
--- a/src/main/java/brachy/modularui/drawable/DrawableSerialization.java
+++ /dev/null
@@ -1,256 +0,0 @@
-package brachy.modularui.drawable;
-
-import brachy.modularui.ModularUI;
-import brachy.modularui.ModularUIConfig;
-import brachy.modularui.api.IJsonSerializable;
-import brachy.modularui.api.drawable.IDrawable;
-import brachy.modularui.api.drawable.Text;
-import brachy.modularui.drawable.text.ModularComponent;
-import brachy.modularui.utils.ObjectList;
-import brachy.modularui.utils.serialization.json.JsonHelper;
-
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.MutableComponent;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonNull;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.google.gson.JsonSyntaxException;
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.Nullable;
-
-import java.lang.reflect.Type;
-import java.util.Map;
-import java.util.function.Function;
-
-public class DrawableSerialization implements JsonSerializer, JsonDeserializer {
-
- private static final Map> DRAWABLE_TYPES = new Object2ObjectOpenHashMap<>();
- private static final Map, String> REVERSE_DRAWABLE_TYPES = new Object2ObjectOpenHashMap<>();
- private static final Map TEXTURES = new Object2ObjectOpenHashMap<>();
- private static final Map REVERSE_TEXTURES = new Object2ObjectOpenHashMap<>();
-
- private static synchronized void registerTextureInternal(String name, UITexture texture) {
- if (texture == null) return;
- UITexture current = TEXTURES.put(name, texture);
- REVERSE_TEXTURES.put(texture, name);
- if (current != null && (ModularUI.isDev() || ModularUIConfig.DEBUG_UI.get())) {
- ModularUI.LOGGER.warn("[DEBUG] Replacing texture with name '{}' and location '{}' with texture with location '{}'", name, current.location, texture.location);
- }
- }
-
- public static synchronized void registerTexture(String name, UITexture texture) {
- String current = REVERSE_TEXTURES.get(texture);
- if (current != null) {
- if (name != null && !current.equals(name)) {
- TEXTURES.put(name, texture);
- REVERSE_TEXTURES.put(texture, name);
- TEXTURES.remove(current);
- }
- return;
- }
- if (name == null) {
- registerTextureAutoName(texture);
- } else {
- registerTextureInternal(name, texture);
- }
- }
-
- public static void registerTextureAutoName(UITexture texture) {
- if (texture == null) return;
- String[] p = texture.location.getPath().split("/");
- p = p[p.length - 1].split("\\.");
- String baseName = texture.location.getNamespace() + ":" + p[0];
- String name = baseName;
- int number = 0;
- UITexture current;
- while ((current = TEXTURES.get(name)) != null) {
- if (current.equals(texture)) return;
- number++;
- name = baseName + "_" + number;
- if (number == 20 && (ModularUI.isDev() || ModularUIConfig.DEBUG_UI.get())) {
- ModularUI.LOGGER.warn("[DEBUG] Trying to register a UITexture with location '{}' for at least 20 times. This is likely a bug and should be fixed.", texture.location);
- } else if (number == 10000) {
- throw new IllegalStateException("Trying to register a UITexture with location '" + texture.location + "' 10000 times.");
- }
- }
- registerTexture(name, texture);
- }
-
- @Nullable
- public static UITexture getTexture(String s) {
- return TEXTURES.get(s);
- }
-
- @Nullable
- public static String getTextureId(UITexture texture) {
- return REVERSE_TEXTURES.get(texture);
- }
-
- public static > void registerDrawableType(String id, Class type,
- Function creator) {
- if (DRAWABLE_TYPES.containsKey(id)) {
- throw new IllegalArgumentException("Drawable type '" + id + "' already exists!");
- }
- DRAWABLE_TYPES.put(id, creator);
- if (type != null) {
- REVERSE_DRAWABLE_TYPES.put(type, id);
- }
- }
-
- @ApiStatus.Internal
- public static void init() {
- // empty, none and text are special cases
- registerDrawableType("texture", UITexture.class, UITexture::parseFromJson);
- registerDrawableType("color", Rectangle.class, json -> new Rectangle());
- registerDrawableType("rectangle", Rectangle.class, json -> new Rectangle());
- registerDrawableType("ellipse", Circle.class, json -> new Circle());
- registerDrawableType("item", ItemDrawable.class, ItemDrawable::ofJson);
- registerDrawableType("icon", Icon.class, Icon::ofJson);
- registerDrawableType("stack", DrawableStack.class, DrawableStack::parseJson);
- registerDrawableType("scrollbar", Scrollbar.class, Scrollbar::ofJson);
- }
-
- public static IDrawable deserialize(JsonElement json) {
- return JsonHelper.DESERIALIZER.deserialize(json, IDrawable.class);
- }
-
- public static JsonElement serialize(IDrawable drawable) {
- return JsonHelper.SERIALIZER.serialize(drawable, IDrawable.class);
- }
-
- @Override
- public IDrawable deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
- if (element.isJsonNull()) {
- return IDrawable.EMPTY;
- }
- if (element.isJsonPrimitive()) {
- if ("empty".equals(element.getAsString()) || "null".equals(element.getAsString())) {
- return IDrawable.EMPTY;
- }
- if ("none".equals(element.getAsString())) {
- return IDrawable.NONE;
- }
- }
- if (element.isJsonArray()) {
- return DrawableStack.parseJson(element.getAsJsonArray());
- }
- if (!element.isJsonObject()) {
- ModularUI.LOGGER.throwing(new JsonParseException("Drawable json should be an object or an array."));
- return IDrawable.EMPTY;
- }
- JsonObject json = element.getAsJsonObject();
- if (json.entrySet().isEmpty()) {
- return IDrawable.EMPTY;
- }
- String type = JsonHelper.getString(json, "empty", "type");
- if ("text".equals(type)) {
- ModularComponent key = parseText(json);
- key.loadFromJson(json);
- return key;
- }
- if (!DRAWABLE_TYPES.containsKey(type)) {
- ModularUI.LOGGER
- .throwing(new JsonParseException("Drawable type '" + type + "' is not json serializable!"));
- return IDrawable.EMPTY;
- }
- IDrawable drawable = DRAWABLE_TYPES.get(type).apply(json);
- ((IJsonSerializable>) drawable).loadFromJson(json);
- return drawable;
- }
-
- @Override
- public JsonElement serialize(IDrawable src, Type typeOfSrc, JsonSerializationContext context) {
- if (src == IDrawable.EMPTY) return JsonNull.INSTANCE;
- if (src == IDrawable.NONE) return new JsonPrimitive("none");
- if (src instanceof DrawableStack drawableStack) {
- JsonArray jsonArray = new JsonArray();
- for (IDrawable drawable : drawableStack.drawables()) {
- jsonArray.add(JsonHelper.serialize(drawable));
- }
- return jsonArray;
- }
- JsonObject json = new JsonObject();
- if (src instanceof Text key) {
- json.addProperty("type", "text");
- json.addProperty("text", Component.Serializer.toJson(key.getFormatted()));
- } else if (!(src instanceof IJsonSerializable> serializable)) {
- throw new IllegalArgumentException("Can't serialize IDrawable of type '" + src.getClass().getSimpleName() + "' which doesn't implement IJsonSerializable!");
- } else {
- Class> type = src.getClass();
- String key = REVERSE_DRAWABLE_TYPES.get(type);
- while (key == null && type != null && type != Object.class) {
- type = type.getSuperclass();
- key = REVERSE_DRAWABLE_TYPES.get(type);
- }
- if (key == null) {
- ModularUI.LOGGER.error("Serialization of drawable of type '{}' failed, because a key for the type could not be found!",
- src.getClass().getSimpleName());
- return JsonNull.INSTANCE;
- }
- json.addProperty("type", key);
- if (!serializable.saveToJson(json)) {
- ModularUI.LOGGER.error("Serialization of drawable of type '{}' failed!", src.getClass().getSimpleName());
- }
- }
- return json;
- }
-
- private static ModularComponent parseText(JsonObject json) throws JsonParseException {
- JsonParseException exception;
- try {
- return Component.Serializer.fromJson(json).asModular();
- } catch (JsonSyntaxException e) {
- exception = e;
- }
- JsonElement element = JsonHelper.getJsonElement(json, "text", "string", "key");
- if (element == null || element.isJsonNull()) {
- return Text.str("No text found!");
- } else if (element.isJsonPrimitive()) {
- String s = element.getAsString();
- if (s.startsWith("translate:")) {
- return Text.lang(s.substring(10));
- }
- return JsonHelper.getBoolean(json, false, "lang", "translate") ? Text.lang(s) : Text.str(s);
- } else if (element.isJsonArray()) {
- ObjectList strings = ObjectList.create();
- for (JsonElement element1 : element.getAsJsonArray()) {
- strings.add(parseText(element1));
- }
- strings.trim();
- return Text.comp(strings.elements());
- }
- throw exception;
- }
-
- private static Component parseText(JsonElement element) throws JsonParseException {
- JsonParseException exception = new JsonParseException("Could not parse IKey from %s".formatted(element));
- try {
- MutableComponent component = Component.Serializer.fromJson(element);
- if (component != null) {
- return component.asModular();
- }
- } catch (JsonSyntaxException e) {
- exception = e;
- }
- if (element.isJsonPrimitive()) {
- String s = element.getAsString();
- if (s.startsWith("translate:")) {
- return Text.lang(s.substring(10));
- }
- return Text.str(s);
- }
- if (element.isJsonObject()) {
- return parseText(element.getAsJsonObject());
- }
- throw exception;
- }
-}
diff --git a/src/main/java/brachy/modularui/drawable/DrawableStack.java b/src/main/java/brachy/modularui/drawable/DrawableStack.java
index 86a74cb..cf0ec2d 100644
--- a/src/main/java/brachy/modularui/drawable/DrawableStack.java
+++ b/src/main/java/brachy/modularui/drawable/DrawableStack.java
@@ -1,31 +1,43 @@
package brachy.modularui.drawable;
-import brachy.modularui.ModularUI;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
-import brachy.modularui.utils.serialization.json.JsonHelper;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
+import lombok.ToString;
+
+import net.minecraft.util.ExtraCodecs;
+import com.mojang.serialization.Codec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
* A stack of {@link IDrawable} backed by an array which are drawn on top of each other.
*/
-public record DrawableStack(IDrawable... drawables) implements IDrawable, IJsonSerializable {
+public record DrawableStack(IDrawable... drawables) implements IDrawable {
+
+ public static final Codec CODEC = ExtraCodecs.lazyInitializedCodec(() ->
+ CodecUtil.checkedEncoder(IDrawable.CODEC.listOf().xmap(DrawableStack::fromList, DrawableStack::toList),
+ d -> d instanceof DrawableStack));
public static final IDrawable[] EMPTY_BACKGROUND = {};
public static final DrawableStack EMPTY = new DrawableStack(EMPTY_BACKGROUND);
+ public static IDrawable fromList(List list) {
+ return IDrawable.of(list.toArray(IDrawable[]::new));
+ }
+
+ public static List toList(IDrawable drawable) {
+ if (drawable instanceof DrawableStack stack) {
+ return List.of(stack.drawables);
+ }
+ return Collections.singletonList(drawable);
+ }
+
public DrawableStack(IDrawable... drawables) {
this.drawables = drawables == null || drawables.length == 0 ? EMPTY_BACKGROUND : drawables;
}
@@ -47,42 +59,4 @@ public boolean canApplyTheme() {
}
return false;
}
-
- public static IDrawable parseJson(JsonObject json) {
- JsonElement drawables = JsonHelper.getJsonElement(json, "drawables", "children");
- if (drawables != null && drawables.isJsonArray()) {
- return parseJson(drawables.getAsJsonArray());
- }
- ModularUI.LOGGER.throwing(
- new JsonParseException("DrawableStack json should have an array named 'drawables' or 'children'."));
- return IDrawable.EMPTY;
- }
-
- public static IDrawable parseJson(JsonArray drawables) {
- List list = new ArrayList<>();
- for (JsonElement child : drawables) {
- IDrawable drawable = JsonHelper.deserialize(child, IDrawable.class);
- if (drawable != null) {
- list.add(drawable);
- }
- }
- if (list.isEmpty()) {
- return IDrawable.EMPTY;
- }
- if (list.size() == 1) {
- return list.get(0);
- }
- return new DrawableStack(list.toArray(IDrawable[]::new));
- }
-
- // this method should never be called, but the special casing code is copied here in case it does.
- @Override
- public boolean saveToJson(JsonObject json) {
- JsonArray jsonArray = new JsonArray();
- for (IDrawable drawable : this.drawables()) {
- jsonArray.add(JsonHelper.serialize(drawable));
- }
- json.add("drawables", jsonArray);
- return true;
- }
}
diff --git a/src/main/java/brachy/modularui/drawable/FluidDrawable.java b/src/main/java/brachy/modularui/drawable/FluidDrawable.java
index f0622eb..578112e 100644
--- a/src/main/java/brachy/modularui/drawable/FluidDrawable.java
+++ b/src/main/java/brachy/modularui/drawable/FluidDrawable.java
@@ -3,29 +3,71 @@
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
import brachy.modularui.widget.Widget;
+import lombok.ToString;
+
+import net.minecraft.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.material.Fluid;
+import com.mojang.serialization.Codec;
import net.minecraftforge.fluids.FluidStack;
-import org.jetbrains.annotations.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+@ToString
+@Accessors(fluent = true, chain = true)
public class FluidDrawable implements IDrawable {
- private FluidStack fluid = null;
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(FluidDrawable::new)
+ .add("fluids", FluidDrawable::fluids, FluidDrawable::getFluidList, CodecUtil.listLike(FluidStack.CODEC)).alias("fluid")
+ .addOpt("cycleTime", FluidDrawable::cycleTime, FluidDrawable::cycleTime, Codec.INT, 1000)
+ .build();
+
+ private FluidStack[] fluids = new FluidStack[0];
+ @Getter
+ @Setter
+ private int cycleTime;
+
+ public FluidDrawable() {
+ this(new FluidStack[0]);
+ }
+
+ public FluidDrawable(FluidStack... fluid) {
+ fluids(fluid);
+ }
+
+ public FluidDrawable(FluidStack fluid) {
+ fluid(fluid);
+ }
+
+ public FluidDrawable(Fluid fluid) {
+ fluid(fluid);
+ }
- public FluidDrawable() {}
+ public FluidDrawable(Fluid fluid, int amount) {
+ fluid(fluid, amount);
+ }
- /**
- * Takes a fluid stack, it can be null but will not draw anything
- *
- * @param fluid - fluid stack to draw
- */
- public FluidDrawable(@NotNull FluidStack fluid) {
- setFluid(fluid);
+ public FluidDrawable(Fluid fluid, int amount, @Nullable CompoundTag tag) {
+ fluid(fluid, amount, tag);
}
@Override
public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {
+ if (this.fluids.length == 0) return;
+ FluidStack fluid = this.fluids.length == 1 ? this.fluids[0] :
+ this.fluids[(int) (Util.getMillis() % (this.cycleTime * this.fluids.length)) / this.cycleTime];
GuiDraw.drawFluidTexture(context.getGraphics(), fluid, x, y, width, height, context.getCurrentDrawingZ());
}
@@ -44,8 +86,66 @@ public Widget> asWidget() {
return IDrawable.super.asWidget().size(16);
}
- public FluidDrawable setFluid(FluidStack fluid) {
- this.fluid = fluid;
+ public List getFluidList() {
+ return Arrays.asList(this.fluids);
+ }
+
+ public FluidStack[] getFluids() {
+ return this.fluids;
+ }
+
+ public FluidDrawable fluids(Collection fluids) {
+ return fluids(fluids.toArray(FluidStack[]::new));
+ }
+
+ public FluidDrawable fluids(FluidStack... fluids) {
+ this.fluids = fluids;
return this;
}
+
+ public FluidDrawable fluid(FluidStack fluid) {
+ if (this.fluids.length != 1) {
+ this.fluids = new FluidStack[1];
+ }
+ this.fluids[0] = fluid;
+ return this;
+ }
+
+ public FluidDrawable fluid(Fluid fluid) {
+ return fluid(fluid, 1, null);
+ }
+
+ public FluidDrawable fluid(Fluid fluid, int amount) {
+ return fluid(fluid, amount, null);
+ }
+
+ public FluidDrawable fluid(Fluid fluid, int amount, @Nullable CompoundTag tag) {
+ FluidStack fluidStack = new FluidStack(fluid, amount);
+ fluidStack.setTag(tag);
+ return fluid(fluidStack);
+ }
+
+ public FluidDrawable addFluid(FluidStack fluid) {
+ this.fluids = ArrayUtils.add(this.fluids, fluid);
+ return this;
+ }
+
+ public FluidDrawable addFluid(Fluid fluid) {
+ return addFluid(fluid, 1, null);
+ }
+
+ public FluidDrawable addFluid(Fluid fluid, int amount) {
+ return addFluid(fluid, amount, null);
+ }
+
+ public FluidDrawable addFluid(Fluid fluid, int amount, @Nullable CompoundTag tag) {
+ FluidStack fluidStack = new FluidStack(fluid, amount);
+ fluidStack.setTag(tag);
+ return addFluid(fluidStack);
+ }
+
+ @Override
+ public String getTypeName() {
+ return "fluid";
+ }
}
diff --git a/src/main/java/brachy/modularui/drawable/GuiDraw.java b/src/main/java/brachy/modularui/drawable/GuiDraw.java
index e1ffd07..250e18e 100644
--- a/src/main/java/brachy/modularui/drawable/GuiDraw.java
+++ b/src/main/java/brachy/modularui/drawable/GuiDraw.java
@@ -4,7 +4,6 @@
import brachy.modularui.api.drawable.IRichTextBuilder;
import brachy.modularui.client.GuiSpriteManager;
import brachy.modularui.drawable.text.TextRenderer;
-import brachy.modularui.screen.RichTooltip;
import brachy.modularui.screen.event.RichTooltipEvent;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.screen.viewport.ModularGuiContext;
@@ -601,8 +600,10 @@ public static void drawEntityLookingAtAngle(GuiGraphics graph
oldYHeadRotO = livingEntity.yHeadRotO;
oldYHeadRot = livingEntity.yHeadRot;
- livingEntity.yBodyRotO = livingEntity.yBodyRot = 180.0f + xAngle * 20.0f;
- livingEntity.yHeadRotO = livingEntity.yHeadRot = entity.getYRot();
+ livingEntity.yBodyRot = 180.0f + xAngle * 20.0f;
+ livingEntity.yHeadRot = entity.getYRot();
+ livingEntity.yBodyRotO = livingEntity.yBodyRot;
+ livingEntity.yHeadRotO = livingEntity.yHeadRot;
}
// skip rotating the render by 180° on the Z axis here, because we always do that in setupDrawEntity
diff --git a/src/main/java/brachy/modularui/drawable/Icon.java b/src/main/java/brachy/modularui/drawable/Icon.java
index ff8b2fa..3671138 100644
--- a/src/main/java/brachy/modularui/drawable/Icon.java
+++ b/src/main/java/brachy/modularui/drawable/Icon.java
@@ -1,34 +1,43 @@
package brachy.modularui.drawable;
import brachy.modularui.ModularUI;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.api.drawable.IIcon;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Alignment;
-import brachy.modularui.utils.serialization.json.JsonHelper;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
import brachy.modularui.widget.sizer.Box;
+import com.mojang.serialization.Codec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonObject;
import lombok.Getter;
/**
* A {@link IDrawable} wrapper with a fixed size and an alignment.
*/
-public class Icon implements IIcon, IJsonSerializable {
+public class Icon implements IIcon {
- private final IDrawable drawable;
- @Getter
- private int width = 0, height = 0;
- private float aspectRatio = 0;
- @Getter
- private Alignment alignment = Alignment.Center;
- @Getter
- private final Box margin = new Box();
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(Icon::new)
+ .add("drawable", Icon::drawable, Icon::getDrawable, IDrawable.CODEC)
+ .addOpt("width", Icon::width, Icon::getWidth, Codec.INT, 0)
+ .addOpt("height", Icon::height, Icon::getHeight, Codec.INT, 0)
+ .addOpt("aspectRatio", Icon::aspectRatio, Icon::getAspectRatio, Codec.FLOAT, 0f)
+ .addOpt("alignment", Icon::alignment, Icon::getAlignment, Alignment.CODEC, Alignment.Center)
+ .addOpt("margin", Icon::margin, Icon::getMargin, Box.CODEC, Box.ZERO)
+ .build();
+
+ @Getter private IDrawable drawable;
+ @Getter private int width = 0, height = 0;
+ @Getter private float aspectRatio = 0;
+ @Getter private Alignment alignment = Alignment.Center;
+ @Getter private final Box margin = new Box();
+
+ private Icon() {
+ this.drawable = IDrawable.EMPTY;
+ }
public Icon(IDrawable drawable) {
this.drawable = drawable;
@@ -85,6 +94,11 @@ public IDrawable getWrappedDrawable() {
return drawable;
}
+ public Icon drawable(IDrawable drawable) {
+ this.drawable = drawable;
+ return this;
+ }
+
public Icon expandWidth() {
return width(0);
}
@@ -160,32 +174,11 @@ public Icon marginBottom(int val) {
return this;
}
- @Override
- public void loadFromJson(JsonObject json) {
- this.width = (json.has("autoWidth") || json.has("autoSize")) &&
- JsonHelper.getBoolean(json, true, "autoWidth", "autoSize") ? 0 :
- JsonHelper.getInt(json, 0, "width", "w", "size");
- this.height = (json.has("autoHeight") || json.has("autoSize")) &&
- JsonHelper.getBoolean(json, true, "autoHeight", "autoSize") ? 0 :
- JsonHelper.getInt(json, 0, "height", "h", "size");
- this.aspectRatio = JsonHelper.getFloat(json, 0, "aspectRatio");
- this.alignment = JsonHelper.deserialize(json, Alignment.class, Alignment.Center, "alignment", "align");
- this.margin.fromJson(json);
- }
-
- public static Icon ofJson(JsonObject json) {
- return JsonHelper.deserialize(json, IDrawable.class, IDrawable.EMPTY, "drawable", "icon").asIcon();
- }
-
- @Override
- public boolean saveToJson(JsonObject json) {
- json.add("drawable", JsonHelper.serialize(this.drawable));
- json.addProperty("width", this.width);
- json.addProperty("height", this.height);
- json.addProperty("aspectRatio", this.aspectRatio);
- json.add("alignment", JsonHelper.serialize(this.alignment));
- this.margin.toJson(json);
- return true;
+ public Icon margin(Box box) {
+ if (box != null && box != this.margin) {
+ Box.CODEC.copyFields(box, this.margin);
+ }
+ return this;
}
@Override
diff --git a/src/main/java/brachy/modularui/drawable/IngredientDrawable.java b/src/main/java/brachy/modularui/drawable/IngredientDrawable.java
deleted file mode 100755
index f567cc0..0000000
--- a/src/main/java/brachy/modularui/drawable/IngredientDrawable.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package brachy.modularui.drawable;
-
-import brachy.modularui.api.IJsonSerializable;
-import brachy.modularui.api.drawable.IDrawable;
-import brachy.modularui.screen.viewport.GuiContext;
-import brachy.modularui.theme.WidgetTheme;
-
-import net.minecraft.Util;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.crafting.Ingredient;
-import net.minecraftforge.api.distmarker.Dist;
-import net.minecraftforge.api.distmarker.OnlyIn;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.experimental.Tolerate;
-
-public class IngredientDrawable implements IDrawable, IJsonSerializable {
-
- @Getter
- @Setter
- private ItemStack[] items;
- @Getter
- @Setter
- private int cycleTime = 1000;
-
- public IngredientDrawable(Ingredient ingredient) {
- this(ingredient.getItems());
- }
-
- public IngredientDrawable(ItemStack... items) {
- setItems(items);
- }
-
- @OnlyIn(Dist.CLIENT)
- @Override
- public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {
- if (this.items.length == 0) return;
- ItemStack item = this.items[(int) (Util.getMillis() % (this.cycleTime * this.items.length)) / this.cycleTime];
- if (item != null) {
- GuiDraw.drawItem(context.getGraphics(), item, x, y, width, height, context.getCurrentDrawingZ());
- }
- }
-
- /**
- * Sets how many milliseconds each item shows up
- *
- * @param cycleTime time per item in milliseconds
- * @return this
- */
- @Tolerate
- public IngredientDrawable cycleTime(int cycleTime) {
- this.cycleTime = cycleTime;
- return this;
- }
-
- @Tolerate
- public void setItems(Ingredient ingredient) {
- setItems(ingredient.getItems());
- }
-}
diff --git a/src/main/java/brachy/modularui/drawable/ItemDrawable.java b/src/main/java/brachy/modularui/drawable/ItemDrawable.java
index 000cf47..1237e64 100755
--- a/src/main/java/brachy/modularui/drawable/ItemDrawable.java
+++ b/src/main/java/brachy/modularui/drawable/ItemDrawable.java
@@ -1,140 +1,159 @@
package brachy.modularui.drawable;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
-import brachy.modularui.utils.serialization.json.JsonHelper;
-import brachy.modularui.widget.Widget;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
-import net.minecraft.core.registries.BuiltInRegistries;
+import lombok.ToString;
+
+import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.NbtOps;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.level.block.Block;
-import com.mojang.serialization.JsonOps;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.level.ItemLike;
+import com.mojang.serialization.Codec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
import lombok.Getter;
-import org.jetbrains.annotations.NotNull;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.apache.commons.lang3.ArrayUtils;
import org.jetbrains.annotations.Nullable;
-import java.util.NoSuchElementException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@ToString
+@Accessors(fluent = true, chain = true)
+public class ItemDrawable implements IDrawable {
-public class ItemDrawable implements IDrawable, IJsonSerializable {
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(ItemDrawable::new)
+ .add("items", ItemDrawable::items, ItemDrawable::getItemList, CodecUtil.listLike(ItemStack.CODEC)).alias("item")
+ .addOpt("cycleTime", ItemDrawable::cycleTime, ItemDrawable::cycleTime, Codec.INT, 1000)
+ .build();
+ private ItemStack[] items = new ItemStack[0];
@Getter
- private ItemStack item = ItemStack.EMPTY;
+ @Setter
+ private int cycleTime = 1000;
- public ItemDrawable() {}
+ private ItemDrawable() {
+ this(new ItemStack[0]);
+ }
- public ItemDrawable(@NotNull ItemStack item) {
- setItem(item);
+ public ItemDrawable(Ingredient ingredient) {
+ this(ingredient.getItems());
}
- public ItemDrawable(@NotNull Item item) {
- setItem(item);
+ public ItemDrawable(ItemStack... items) {
+ items(items);
}
- public ItemDrawable(@NotNull Item item, int amount) {
- setItem(item, amount);
+ public ItemDrawable(ItemStack item) {
+ item(item);
}
- public ItemDrawable(@NotNull Item item, int amount, @Nullable CompoundTag nbt) {
- setItem(item, amount, nbt);
+ public ItemDrawable(ItemLike item) {
+ item(item);
}
- public ItemDrawable(@NotNull Block item) {
- setItem(item);
+ public ItemDrawable(ItemLike item, int amount) {
+ item(item, amount);
}
- public ItemDrawable(@NotNull Block item, int amount) {
- setItem(new ItemStack(item, amount));
+ public ItemDrawable(ItemLike item, int amount, @Nullable CompoundTag tag) {
+ item(item, amount, tag);
}
@OnlyIn(Dist.CLIENT)
@Override
public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {
- applyColor(widgetTheme.getColor());
- GuiDraw.drawItem(context.getGraphics(), this.item, x, y, width, height, context.getCurrentDrawingZ());
+ if (this.items.length == 0) return;
+ ItemStack item = this.items.length == 1 ? this.items[0] :
+ this.items[(int) (Util.getMillis() % (this.cycleTime * this.items.length)) / this.cycleTime];
+ if (item != null) {
+ GuiDraw.drawItem(context.getGraphics(), item, x, y, width, height, context.getCurrentDrawingZ());
+ }
}
@Override
- public int getDefaultWidth() {
+ public int getDefaultHeight() {
return 16;
}
@Override
- public int getDefaultHeight() {
+ public int getDefaultWidth() {
return 16;
}
- @Override
- public Widget> asWidget() {
- return IDrawable.super.asWidget().size(16);
+ public ItemStack[] getItems() {
+ return this.items;
+ }
+
+ public void ingredient(Ingredient ingredient) {
+ items(ingredient.getItems());
}
- public ItemDrawable setItem(@NotNull ItemStack item) {
- this.item = item;
+ public ItemDrawable items(Collection items) {
+ return items(items.toArray(ItemStack[]::new));
+ }
+
+ public ItemDrawable items(ItemStack... items) {
+ this.items = items;
return this;
}
- public ItemDrawable setItem(@NotNull Item item) {
- return setItem(item, 1, null);
+ public ItemDrawable item(ItemStack item) {
+ if (this.items.length != 1) {
+ this.items = new ItemStack[1];
+ }
+ this.items[0] = item;
+ return this;
+ }
+
+ public ItemDrawable item(ItemLike item) {
+ return item(item.asItem(), 1, null);
}
- public ItemDrawable setItem(@NotNull Item item, int amount) {
- return setItem(item, amount, null);
+ public ItemDrawable item(ItemLike item, int amount) {
+ return item(item, amount, null);
}
- public ItemDrawable setItem(@NotNull Item item, int amount, @Nullable CompoundTag nbt) {
+ public ItemDrawable item(ItemLike item, int amount, @Nullable CompoundTag tag) {
ItemStack itemStack = new ItemStack(item, amount);
- itemStack.setTag(nbt);
- return setItem(itemStack);
+ itemStack.setTag(tag);
+ return item(itemStack);
}
- public ItemDrawable setItem(@NotNull Block item) {
- return setItem(item, 1);
+ public ItemDrawable addItem(ItemStack item) {
+ this.items = ArrayUtils.add(this.items, item);
+ return this;
}
- public ItemDrawable setItem(@NotNull Block item, int amount) {
- return setItem(new ItemStack(item, amount));
+ public ItemDrawable addItem(ItemLike item) {
+ return addItem(item.asItem(), 1, null);
}
- public static ItemDrawable ofJson(JsonObject json) {
- String itemName = JsonHelper.getString(json, null, "item");
- if (itemName == null) throw new JsonParseException("Item property not found!");
- if (itemName.isEmpty()) return new ItemDrawable();
- ItemStack stack;
- try {
- ResourceLocation id = new ResourceLocation(itemName);
- stack = new ItemStack(BuiltInRegistries.ITEM.get(id));
- } catch (NoSuchElementException e) {
- throw new JsonParseException(e);
- }
- if (json.has("nbt")) {
- CompoundTag nbt = (CompoundTag) JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE,
- JsonHelper.getObject(json, new JsonObject(), o -> o, "nbt"));
- stack.setTag(nbt);
- }
- return new ItemDrawable(stack);
+ public ItemDrawable addItem(ItemLike item, int amount) {
+ return addItem(item, amount, null);
+ }
+
+ public ItemDrawable addItem(ItemLike item, int amount, @Nullable CompoundTag tag) {
+ ItemStack itemStack = new ItemStack(item, amount);
+ itemStack.setTag(tag);
+ return addItem(itemStack);
+ }
+
+ public List getItemList() {
+ return Arrays.asList(this.items);
}
@Override
- public boolean saveToJson(JsonObject json) {
- if (this.item == null || this.item.isEmpty()) {
- json.addProperty("item", "");
- return true;
- }
- json.addProperty("item", this.item.getItemHolder().unwrapKey().get().location().toString());
- if (this.item.hasTag()) {
- json.addProperty("nbt", this.item.getTag().toString());
- }
- return true;
+ public String getTypeName() {
+ return "item";
}
}
diff --git a/src/main/java/brachy/modularui/drawable/Rectangle.java b/src/main/java/brachy/modularui/drawable/Rectangle.java
index a11c398..08e12e8 100644
--- a/src/main/java/brachy/modularui/drawable/Rectangle.java
+++ b/src/main/java/brachy/modularui/drawable/Rectangle.java
@@ -3,33 +3,55 @@
import brachy.modularui.GTRenderTypes;
import brachy.modularui.ModularUI;
import brachy.modularui.animation.IAnimatable;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Color;
import brachy.modularui.utils.Interpolations;
-import brachy.modularui.utils.serialization.json.JsonHelper;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
import com.mojang.blaze3d.vertex.VertexConsumer;
+import com.mojang.serialization.Codec;
+
+import lombok.ToString;
+
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.joml.Matrix4f;
-import java.util.function.IntConsumer;
-
+@ToString
@Accessors(fluent = true, chain = true)
-public class Rectangle implements IDrawable, IJsonSerializable, IAnimatable {
+public class Rectangle implements IDrawable, IAnimatable {
+
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(Rectangle::new)
+ .addOpt("colorTopLeft", Rectangle::colorTL, Rectangle::colorTL, Color.CODEC, Color.WHITE.main)
+ .alias("colorTL", "colorLeft", "colorTop", "color")
+ .addOpt("colorTopRight", Rectangle::colorTR, Rectangle::colorTR, Color.CODEC, Color.WHITE.main)
+ .alias("colorTR", "colorRight", "colorTop", "color")
+ .addOpt("colorBottomLeft", Rectangle::colorBL, Rectangle::colorBL, Color.CODEC, Color.WHITE.main)
+ .alias("colorBL", "colorLeft", "colorBottom", "color")
+ .addOpt("colorBottomRight", Rectangle::colorBR, Rectangle::colorBR, Color.CODEC, Color.WHITE.main)
+ .alias("colorBR", "colorRight", "colorBottom", "color")
+ .addOpt("cornerRadius", Rectangle::cornerRadius, Rectangle::cornerRadius, Codec.INT, 0)
+ .addOpt("cornerSegments", Rectangle::cornerSegments, Rectangle::cornerSegments, Codec.INT, 8)
+ .addOpt("borderThickness", Rectangle::borderThickness, Rectangle::borderThickness, Codec.FLOAT, 0f)
+ .addOpt("canApplyTheme", Rectangle::canApplyTheme, Rectangle::canApplyTheme, Codec.BOOL, false)
+ .build();
- private int cornerRadius, colorTL, colorTR, colorBL, colorBR;
+ @Getter
+ @Setter
+ private int colorTL, colorTR, colorBL, colorBR;
+ @Getter
+ private int cornerRadius;
+ @Getter
@Setter
private int cornerSegments;
+ @Getter
+ @Setter
private float borderThickness;
@Getter
@Setter
@@ -128,65 +150,6 @@ private static void v(Matrix4f pose, VertexConsumer buffer, float x, float y, in
.endVertex();
}
- @Override
- public void loadFromJson(JsonObject json) {
- if (json.has("color")) {
- color(Color.ofJson(json.get("color")));
- }
- if (json.has("colorTop")) {
- int c = Color.ofJson(json.get("colorTop"));
- this.colorTL = c;
- this.colorTR = c;
- }
- if (json.has("colorBottom")) {
- int c = Color.ofJson(json.get("colorBottom"));
- this.colorBL = c;
- this.colorBR = c;
- }
- if (json.has("colorLeft")) {
- int c = Color.ofJson(json.get("colorLeft"));
- this.colorTL = c;
- this.colorBL = c;
- }
- if (json.has("colorRight")) {
- int c = Color.ofJson(json.get("colorRight"));
- this.colorTR = c;
- this.colorBR = c;
- }
- setColor(json, val -> this.colorTL = val, "colorTopLeft", "colorTL");
- setColor(json, val -> this.colorTR = val, "colorTopRight", "colorTR");
- setColor(json, val -> this.colorBL = val, "colorBottomLeft", "colorBL");
- setColor(json, val -> this.colorBR = val, "colorBottomRight", "colorBR");
- this.cornerRadius = JsonHelper.getInt(json, 0, "cornerRadius");
- this.cornerSegments = JsonHelper.getInt(json, 10, "cornerSegments");
- if (JsonHelper.getBoolean(json, false, "solid")) {
- this.borderThickness = 0;
- } else if (JsonHelper.getBoolean(json, false, "hollow")) {
- this.borderThickness = 1;
- } else {
- this.borderThickness = JsonHelper.getFloat(json, 0, "borderThickness");
- }
- }
-
- @Override
- public boolean saveToJson(JsonObject json) {
- json.addProperty("colorTL", this.colorTL);
- json.addProperty("colorTR", this.colorTR);
- json.addProperty("colorBL", this.colorBL);
- json.addProperty("colorBR", this.colorBR);
- json.addProperty("cornerRadius", this.cornerRadius);
- json.addProperty("cornerSegments", this.cornerSegments);
- json.addProperty("borderThickness", this.borderThickness);
- return true;
- }
-
- private void setColor(JsonObject json, IntConsumer color, String... keys) {
- JsonElement element = JsonHelper.getJsonElement(json, keys);
- if (element != null) {
- color.accept(Color.ofJson(element));
- }
- }
-
@Override
public Rectangle interpolate(Rectangle start, Rectangle end, float t) {
this.cornerRadius = Interpolations.lerp(start.cornerRadius, end.cornerRadius, t);
@@ -206,4 +169,9 @@ public Rectangle copyOrImmutable() {
.cornerSegments(this.cornerSegments)
.canApplyTheme(this.canApplyTheme);
}
+
+ @Override
+ public String getTypeName() {
+ return "rectangle";
+ }
}
diff --git a/src/main/java/brachy/modularui/drawable/Scrollbar.java b/src/main/java/brachy/modularui/drawable/Scrollbar.java
index f3b35c4..e9c16ae 100755
--- a/src/main/java/brachy/modularui/drawable/Scrollbar.java
+++ b/src/main/java/brachy/modularui/drawable/Scrollbar.java
@@ -1,26 +1,26 @@
package brachy.modularui.drawable;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Color;
-import brachy.modularui.utils.serialization.json.JsonHelper;
-import com.google.gson.JsonObject;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
-public record Scrollbar(boolean striped) implements IDrawable, IJsonSerializable {
+public record Scrollbar(boolean striped) implements IDrawable {
public static final Scrollbar DEFAULT = new Scrollbar(false);
public static final Scrollbar VANILLA = new Scrollbar(true);
- public static Scrollbar ofJson(JsonObject json) {
- if (JsonHelper.getBoolean(json, false, "striped", "vanilla")) {
- return VANILLA;
- }
- return DEFAULT;
+ public static Scrollbar get(boolean striped) {
+ return striped ? VANILLA : DEFAULT;
}
+ public static final MapCodec CODEC = IDrawable.CODECS.register(
+ Codec.BOOL.fieldOf("striped").xmap(Scrollbar::get, Scrollbar::striped),
+ "scrollbar", "Scrollbar");
+
@Override
public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) {
GuiDraw.drawRect(context.getGraphics(), x, y, width, height, Color.mix(0xFFEEEEEE, widgetTheme.getColor()));
@@ -52,10 +52,4 @@ public void draw(GuiContext context, int x, int y, int width, int height, Widget
public boolean canApplyTheme() {
return true;
}
-
- @Override
- public boolean saveToJson(JsonObject json) {
- json.addProperty("striped", this.striped);
- return true;
- }
}
diff --git a/src/main/java/brachy/modularui/drawable/SubAreaDrawable.java b/src/main/java/brachy/modularui/drawable/SubAreaDrawable.java
index 76fcd5e..03f6cd9 100644
--- a/src/main/java/brachy/modularui/drawable/SubAreaDrawable.java
+++ b/src/main/java/brachy/modularui/drawable/SubAreaDrawable.java
@@ -1,19 +1,34 @@
package brachy.modularui.drawable;
import brachy.modularui.api.drawable.IDrawable;
-
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
-
import brachy.modularui.utils.Interpolations;
import brachy.modularui.utils.math.MathUtils;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
+
+import com.mojang.serialization.Codec;
+import lombok.Getter;
import org.jetbrains.annotations.Nullable;
public class SubAreaDrawable extends DelegateDrawable {
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(SubAreaDrawable::new)
+ .add("drawable", SubAreaDrawable::drawable, SubAreaDrawable::getWrappedDrawable, IDrawable.CODEC)
+ .addOpt("u0", SubAreaDrawable::u0, SubAreaDrawable::getU0, Codec.FLOAT, 0f).alias("uStart")
+ .addOpt("v0", SubAreaDrawable::v0, SubAreaDrawable::getV0, Codec.FLOAT, 0f).alias("vStart")
+ .addOpt("u1", SubAreaDrawable::u1, SubAreaDrawable::getU1, Codec.FLOAT, 1f).alias("uEnd")
+ .addOpt("v1", SubAreaDrawable::v1, SubAreaDrawable::getV1, Codec.FLOAT, 1f).alias("vEnd")
+ .build();
+
+ @Getter
private float u0 = 0, v0 = 0, u1 = 1, v1 = 1;
+ private SubAreaDrawable() {
+ super(IDrawable.EMPTY);
+ }
+
public SubAreaDrawable(@Nullable IDrawable drawable) {
super(drawable);
}
diff --git a/src/main/java/brachy/modularui/drawable/TextureRegistry.java b/src/main/java/brachy/modularui/drawable/TextureRegistry.java
new file mode 100644
index 0000000..1944fc8
--- /dev/null
+++ b/src/main/java/brachy/modularui/drawable/TextureRegistry.java
@@ -0,0 +1,72 @@
+package brachy.modularui.drawable;
+
+import brachy.modularui.ModularUI;
+import brachy.modularui.ModularUIConfig;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+
+public class TextureRegistry {
+
+ private static final Map TEXTURES = new Object2ObjectOpenHashMap<>();
+ private static final Map REVERSE_TEXTURES = new Object2ObjectOpenHashMap<>();
+
+ private static synchronized void registerTextureInternal(String name, UITexture texture) {
+ if (texture == null) return;
+ UITexture current = TEXTURES.put(name, texture);
+ REVERSE_TEXTURES.put(texture, name);
+ if (current != null && (ModularUI.isDev() || ModularUIConfig.DEBUG_UI.get())) {
+ ModularUI.LOGGER.warn("[DEBUG] Replacing texture with name '{}' and location '{}' with texture with location '{}'", name, current.location, texture.location);
+ }
+ }
+
+ public static synchronized void registerTexture(String name, UITexture texture) {
+ String current = REVERSE_TEXTURES.get(texture);
+ if (current != null) {
+ if (name != null && !current.equals(name)) {
+ TEXTURES.put(name, texture);
+ REVERSE_TEXTURES.put(texture, name);
+ TEXTURES.remove(current);
+ }
+ return;
+ }
+ if (name == null) {
+ registerTextureAutoName(texture);
+ } else {
+ registerTextureInternal(name, texture);
+ }
+ }
+
+ public static void registerTextureAutoName(UITexture texture) {
+ if (texture == null) return;
+ String[] p = texture.location.getPath().split("/");
+ p = p[p.length - 1].split("\\.");
+ String baseName = texture.location.getNamespace() + ":" + p[0];
+ String name = baseName;
+ int number = 0;
+ UITexture current;
+ while ((current = TEXTURES.get(name)) != null) {
+ if (current.equals(texture)) return;
+ number++;
+ name = baseName + "_" + number;
+ if (number == 20 && (ModularUI.isDev() || ModularUIConfig.DEBUG_UI.get())) {
+ ModularUI.LOGGER.warn("[DEBUG] Trying to register a UITexture with location '{}' for at least 20 times. This is likely a bug and should be fixed.", texture.location);
+ } else if (number == 10000) {
+ throw new IllegalStateException("Trying to register a UITexture with location '" + texture.location + "' 10000 times.");
+ }
+ }
+ registerTexture(name, texture);
+ }
+
+ @Nullable
+ public static UITexture getTexture(String s) {
+ return TEXTURES.get(s);
+ }
+
+ @Nullable
+ public static String getTextureId(UITexture texture) {
+ return REVERSE_TEXTURES.get(texture);
+ }
+}
diff --git a/src/main/java/brachy/modularui/drawable/TiledUITexture.java b/src/main/java/brachy/modularui/drawable/TiledUITexture.java
index d2c3393..55a0ae7 100644
--- a/src/main/java/brachy/modularui/drawable/TiledUITexture.java
+++ b/src/main/java/brachy/modularui/drawable/TiledUITexture.java
@@ -6,13 +6,15 @@
import net.minecraft.resources.ResourceLocation;
import com.mojang.blaze3d.systems.RenderSystem;
-import com.google.gson.JsonObject;
+import lombok.Getter;
+import lombok.experimental.Accessors;
import java.util.Objects;
+@Accessors(fluent = true)
public class TiledUITexture extends UITexture {
- private final int imageWidth, imageHeight;
+ @Getter private final int imageWidth, imageHeight;
/**
* Use {@link UITexture#builder()} with {@link Builder#tiled()}
@@ -41,14 +43,6 @@ public void draw(GuiContext context, float x, float y, float width, float height
this.imageWidth, this.imageHeight, 0);
}
- @Override
- protected void saveTextureToJson(JsonObject json) {
- super.saveToJson(json);
- json.addProperty("imageWidth", this.imageWidth);
- json.addProperty("imageHeight", this.imageHeight);
- json.addProperty("tiled", true);
- }
-
@Override
protected TiledUITexture copy() {
return new TiledUITexture(location, u0, v0, u1, v1, colorType, nonOpaque, colorOverride, imageWidth, imageHeight);
@@ -59,6 +53,11 @@ public TiledUITexture withColorOverride(int color) {
return (TiledUITexture) super.withColorOverride(color);
}
+ @Override
+ public Builder toBuilder() {
+ return super.toBuilder().tiled(this.imageWidth, this.imageHeight);
+ }
+
@Override
public boolean equals(Object o) {
return o != null && getClass() == o.getClass() && isEqual((TiledUITexture) o);
diff --git a/src/main/java/brachy/modularui/drawable/UITexture.java b/src/main/java/brachy/modularui/drawable/UITexture.java
index b17ef6c..eacc426 100644
--- a/src/main/java/brachy/modularui/drawable/UITexture.java
+++ b/src/main/java/brachy/modularui/drawable/UITexture.java
@@ -1,30 +1,40 @@
package brachy.modularui.drawable;
import brachy.modularui.ModularUI;
-import brachy.modularui.api.IJsonSerializable;
import brachy.modularui.api.drawable.IDrawable;
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Color;
import brachy.modularui.utils.Interpolations;
-import brachy.modularui.utils.serialization.json.JsonHelper;
-import brachy.modularui.widget.sizer.Area;
+import brachy.modularui.utils.serialization.codec.CodecUtil;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
+
+import lombok.ToString;
import net.minecraft.resources.FileToIdConverter;
import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.MapCodec;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
import lombok.Getter;
+import lombok.Setter;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
+@ToString
@Accessors(fluent = true, chain = true)
-public class UITexture implements IDrawable, IJsonSerializable {
+public class UITexture implements IDrawable {
+
+ public static final MapCodec CODEC_FROM_BUILDER = Builder.CODEC.xmap(Builder::buildForCodec, UITexture::toBuilder);
+ public static final Codec CODEC_FROM_NAME = ExtraCodecs.stringResolverCodec(TextureRegistry::getTextureId, TextureRegistry::getTexture);
+ public static final MapCodec CODEC = IDrawable.CODECS.register("texture",
+ CodecUtil.chainedMapCodec(CODEC_FROM_NAME.fieldOf("name"), CODEC_FROM_BUILDER));
public static final UITexture DEFAULT = fullImage("gui/options_background", ColorType.DEFAULT);
public static final FileToIdConverter GUI_TEXTURE_ID_CONVERTER = new FileToIdConverter("textures/gui", ".png");
@@ -49,15 +59,15 @@ static UITexture icon(String name, int x, int y) {
private static final String TEXTURES_PREFIX = "textures/";
private static final String PNG_SUFFIX = ".png";
- @Getter
- public final ResourceLocation location;
- public final float u0, v0, u1, v1;
+ @ToString.Include
+ @Getter public final ResourceLocation location;
+ @Getter public final float u0, v0, u1, v1;
@Getter
@Nullable
public final ColorType colorType;
- public final boolean nonOpaque;
+ @Getter public final boolean nonOpaque;
- protected int colorOverride = 0;
+ @Getter protected int colorOverride = 0;
/**
* Creates a drawable texture
@@ -147,7 +157,7 @@ public static UITexture fullImage(String mod, String location, ColorType colorTy
}
public UITexture register(String name) {
- DrawableSerialization.registerTexture(name, this);
+ TextureRegistry.registerTexture(name, this);
return this;
}
@@ -210,79 +220,17 @@ public void applyColor(int themeColor) {
}
}
- public static UITexture parseFromJson(JsonObject json) {
- String name = JsonHelper.getString(json, null, "name", "id");
- if (name != null) {
- UITexture drawable = DrawableSerialization.getTexture(name);
- if (drawable != null) return drawable;
- ModularUI.LOGGER.error("Tried to parse UITexture from json, but no texture with name '{}' is registered!", name);
- return GuiTextures.HELP;
- }
- Builder builder = builder();
- builder.location(JsonHelper.getString(json, ModularUI.MOD_ID + ":gui/widgets/error", "location"))
- .imageSize(JsonHelper.getInt(json, defaultImageWidth, "imageWidth", "iw"),
- JsonHelper.getInt(json, defaultImageHeight, "imageHeight", "ih"));
- boolean mode1 = json.has("x") || json.has("y") || json.has("w") || json.has("h") || json.has("width") ||
- json.has("height");
- boolean mode2 = json.has("u0") || json.has("v0") || json.has("u1") || json.has("u1");
- if (mode1) {
- if (mode2) {
- throw new JsonParseException("Tried to specify x, y, w, h and u0, v0, u1, v1!");
- }
- builder.subAreaXYWH(JsonHelper.getInt(json, 0, "x"),
- JsonHelper.getInt(json, 0, "y"),
- JsonHelper.getInt(json, builder.iw, "w", "width"),
- JsonHelper.getInt(json, builder.ih, "h", "height"));
- } else if (mode2) {
- builder.subAreaUV(JsonHelper.getFloat(json, 0, "u0"),
- JsonHelper.getFloat(json, 0, "v0"),
- JsonHelper.getFloat(json, 1, "u1"),
- JsonHelper.getFloat(json, 1, "v1"));
- }
- int bl = JsonHelper.getInt(json, 0, "bl", "borderLeft", "borderX", "border");
- int br = JsonHelper.getInt(json, 0, "br", "borderRight", "borderY", "border");
- int bt = JsonHelper.getInt(json, 0, "bt", "borderTop", "borderBottom", "border");
- int bb = JsonHelper.getInt(json, 0, "bb", "borderBottom", "borderTop", "border");
- if (bl > 0 || br > 0 || bt > 0 || bb > 0) {
- builder.adaptable(bl, bt, br, bb);
- }
- if (JsonHelper.getBoolean(json, false, "tiled")) {
- builder.tiled();
- }
- String colorTypeName = JsonHelper.getString(json, null, "colorType", "color");
- if (colorTypeName != null) {
- builder.colorType(ColorType.get(colorTypeName));
- } else if (JsonHelper.getBoolean(json, false, "canApplyTheme")) {
- builder.canApplyTheme();
- }
- if (JsonHelper.getBoolean(json, false, "nonOpaque")) {
- builder.nonOpaque();
- }
- UITexture uiTexture = builder.build();
- uiTexture.colorOverride = JsonHelper.getColor(json, 0, "colorOverride");
- return uiTexture;
- }
-
@Override
- public boolean saveToJson(JsonObject json) {
- String name = DrawableSerialization.getTextureId(this);
- if (name != null) {
- json.addProperty("id", name);
- return true;
- }
- saveTextureToJson(json);
- return true;
+ public String getTypeName() {
+ return "texture";
}
- protected void saveTextureToJson(JsonObject json) {
- json.addProperty("location", this.location.toString());
- json.addProperty("u0", this.u0);
- json.addProperty("v0", this.v0);
- json.addProperty("u1", this.u1);
- json.addProperty("v1", this.v1);
- if (this.colorType != null) json.addProperty("colorType", this.colorType.getName());
- json.addProperty("nonOpaque", this.nonOpaque);
- json.addProperty("colorOverride", this.colorOverride);
+ public Builder toBuilder() {
+ return builder()
+ .location(this.location)
+ .subAreaUV(this.u0, this.v0, this.u1, this.v1)
+ .colorType(this.colorType)
+ .nonOpaque(this.nonOpaque);
}
@Override
@@ -311,28 +259,48 @@ public UITexture withColorOverride(int color) {
return t;
}
- private static int defaultImageWidth = 16, defaultImageHeight = 16;
-
- public static void setDefaultImageSize(int w, int h) {
- defaultImageWidth = w;
- defaultImageHeight = h;
- }
-
/**
* A builder class to help create image textures.
*/
+ @Accessors(fluent = false)
public static class Builder {
- private ResourceLocation location;
- private int iw = defaultImageWidth, ih = defaultImageHeight;
- private int x, y, w, h;
- private float u0 = 0, v0 = 0, u1 = 1, v1 = 1;
- private Mode mode = Mode.FULL;
- private int bl = 0, bt = 0, br = 0, bb = 0;
- private String name;
- private boolean tiled = false;
- private ColorType colorType = null;
- private boolean nonOpaque = false;
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.builder(UITexture::builder)
+ .add("location", Builder::location, Builder::getLocation, ResourceLocation.CODEC)
+ .addOpt("imageWidth", Builder::setIw, Builder::getIw, Codec.INT, -1).alias("iw")
+ .addOpt("imageHeight", Builder::setIh, Builder::getIh, Codec.INT, -1).alias("ih")
+ .addOpt("x", Builder::setX, Builder::getX, Codec.INT, 0)
+ .addOpt("y", Builder::setY, Builder::getY, Codec.INT, 0)
+ .addOpt("w", Builder::setW, Builder::getW, Codec.INT, 0)
+ .addOpt("h", Builder::setH, Builder::getH, Codec.INT, 0)
+ .addOpt("u0", Builder::setU0, Builder::getU0, Codec.FLOAT, 0f).alias("uStart")
+ .addOpt("v0", Builder::setV0, Builder::getV0, Codec.FLOAT, 0f).alias("vStart")
+ .addOpt("u1", Builder::setU1, Builder::getU1, Codec.FLOAT, 1f).alias("uEnd")
+ .addOpt("v1", Builder::setV1, Builder::getV1, Codec.FLOAT, 1f).alias("vEnd")
+ .addOpt("bl", Builder::setBl, Builder::getBl, Codec.INT, 0).alias("borderLeft", "borderX", "border")
+ .addOpt("bt", Builder::setBt, Builder::getBt, Codec.INT, 0).alias("borderTop", "borderY", "border")
+ .addOpt("br", Builder::setBr, Builder::getBr, Codec.INT, 0).alias("borderRight", "borderX", "border")
+ .addOpt("bb", Builder::setBb, Builder::getBb, Codec.INT, 0).alias("borderBottom", "borderY", "border")
+ .addOpt("name", Builder::name, Builder::getName, Codec.STRING, null)
+ .addOpt("tiled", Builder::tiled, Builder::isTiled, Codec.BOOL, false)
+ .addOpt("colorType", Builder::colorType, Builder::getColorType, ColorType.CODEC, null)
+ .addOpt("nonOpaque", Builder::nonOpaque, Builder::isNonOpaque, Codec.BOOL, false)
+ .addOpt("colorOverride", Builder::colorOverride, Builder::getColorOverride, Codec.INT, 0)
+ .build();
+
+ @Getter private ResourceLocation location;
+ @Getter
+ @Setter
+ private int iw = -1, ih = -1;
+ @Getter private int x, y, w, h;
+ @Getter private float u0 = 0, v0 = 0, u1 = 1, v1 = 1;
+ @Getter private Mode mode = Mode.FULL;
+ @Getter private int bl = 0, bt = 0, br = 0, bb = 0;
+ @Getter private String name;
+ @Getter private boolean tiled = false;
+ @Getter private ColorType colorType = null;
+ @Getter private boolean nonOpaque = false;
+ @Getter private int colorOverride = 0;
/**
* @param loc location of the image to draw
@@ -386,7 +354,11 @@ public Builder tiled(int imageWidth, int imageHeight) {
* This will make the image be drawn tiled rather than stretched.
*/
public Builder tiled() {
- this.tiled = true;
+ return tiled(true);
+ }
+
+ public Builder tiled(boolean tiled) {
+ this.tiled = tiled;
return this;
}
@@ -563,7 +535,16 @@ public Builder name(String name) {
* Sets this texture as at least partially transparent, will not disable glBlend when drawing.
*/
public Builder nonOpaque() {
- this.nonOpaque = true;
+ return nonOpaque(true);
+ }
+
+ public Builder nonOpaque(boolean nonOpaque) {
+ this.nonOpaque = nonOpaque;
+ return this;
+ }
+
+ public Builder colorOverride(int colorOverride) {
+ this.colorOverride = colorOverride;
return this;
}
@@ -573,16 +554,28 @@ public Builder nonOpaque() {
* @return the created texture
*/
public UITexture build() {
- UITexture texture = create();
- DrawableSerialization.registerTexture(this.name, texture);
- return texture;
+ return create()
+ .resultOrPartial(s -> {
+ throw new IllegalArgumentException(s);
+ }).map(texture -> {
+ TextureRegistry.registerTexture(this.name, texture);
+ return texture;
+ }).map(texture -> this.colorOverride != 0 ? texture.withColorOverride(this.colorOverride) : texture)
+ .orElseThrow();
+ }
+
+ private UITexture buildForCodec() {
+ // no error throwing and no drawable registration
+ return create()
+ .resultOrPartial(ModularUI.LOGGER::error)
+ .map(texture -> this.colorOverride != 0 ? texture.withColorOverride(this.colorOverride) : texture)
+ .orElseThrow();
}
- private UITexture create() {
+ private DataResult create() {
if (this.location == null) {
- throw new NullPointerException("Location must not be null");
+ return DataResult.error(() -> "Location must not be null");
}
- if (this.iw <= 0 || this.ih <= 0) throw new IllegalArgumentException("Image size must be > 0");
if (this.mode == Mode.FULL) {
this.u0 = 0;
this.v0 = 0;
@@ -590,6 +583,10 @@ private UITexture create() {
this.v1 = 1;
this.mode = Mode.RELATIVE;
} else if (this.mode == Mode.PIXEL) {
+ if (this.iw <= 0 || this.ih <= 0) return DataResult.error(() -> "Image size must be > 0 for sub area via xywh or ltrb");
+ if (this.x < 0 || this.y < 0 || this.w > this.iw || this.h > this.ih) {
+ return DataResult.error(() -> "X and Y must be > 0 and W and H must by smaller than the specified image size");
+ }
float tw = 1f / this.iw, th = 1f / this.ih;
this.u0 = this.x * tw;
this.v0 = this.y * th;
@@ -598,19 +595,81 @@ private UITexture create() {
this.mode = Mode.RELATIVE;
}
if (this.mode == Mode.RELATIVE) {
- if (this.u0 < 0 || this.v0 < 0 || this.u1 > 1 || this.v1 > 1)
- throw new IllegalArgumentException("UV values must be 0 - 1");
+ if (this.u0 < 0 || this.v0 < 0 || this.u1 > 1 || this.v1 > 1) {
+ return DataResult.error(() -> "UV values must be 0 - 1");
+ }
if (this.bl > 0 || this.bt > 0 || this.br > 0 || this.bb > 0) {
- return new AdaptableUITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType,
- this.nonOpaque, 0, this.iw, this.ih, this.bl, this.bt, this.br, this.bb, this.tiled);
+ if (this.iw <= 0 || this.ih <= 0)
+ return DataResult.error(() -> "Image size must be > 0 for adaptable textures (border > 0)");
+ return DataResult.success(new AdaptableUITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType,
+ this.nonOpaque, 0, this.iw, this.ih, this.bl, this.bt, this.br, this.bb, this.tiled));
}
if (this.tiled) {
- return new TiledUITexture(this.location, this.u0, this.v0, this.u1, this.v1,
- this.colorType, this.nonOpaque, 0, this.iw, this.ih);
+ if (this.iw <= 0 || this.ih <= 0) return DataResult.error(() -> "Image size must be > 0 for tiled textures");
+ return DataResult.success(new TiledUITexture(this.location, this.u0, this.v0, this.u1, this.v1,
+ this.colorType, this.nonOpaque, 0, this.iw, this.ih));
}
- return new UITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType, this.nonOpaque);
+ return DataResult.success(new UITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType, this.nonOpaque));
}
- throw new IllegalStateException();
+ return DataResult.error(() -> "Unknown error");
+ }
+
+ // Setters for codec
+
+ private void setX(int x) {
+ this.x = x;
+ if (x > 0) this.mode = Mode.PIXEL;
+ }
+
+ private void setY(int y) {
+ this.y = y;
+ if (y > 0) this.mode = Mode.PIXEL;
+ }
+
+ private void setW(int w) {
+ this.w = w;
+ if (w > 0) this.mode = Mode.PIXEL;
+ }
+
+ private void setH(int h) {
+ this.h = h;
+ if (h > 0) this.mode = Mode.PIXEL;
+ }
+
+ private void setU0(float u0) {
+ this.u0 = u0;
+ if (u0 > 0) this.mode = Mode.RELATIVE;
+ }
+
+ private void setV0(float v0) {
+ this.v0 = v0;
+ if (v0 > 0) this.mode = Mode.RELATIVE;
+ }
+
+ private void setU1(float u1) {
+ this.u1 = u1;
+ if (u1 < 1) this.mode = Mode.RELATIVE;
+ }
+
+ private void setV1(float v1) {
+ this.v1 = v1;
+ if (v1 < 1) this.mode = Mode.RELATIVE;
+ }
+
+ private void setBl(int bl) {
+ this.bl = bl;
+ }
+
+ private void setBb(int bb) {
+ this.bb = bb;
+ }
+
+ private void setBr(int br) {
+ this.br = br;
+ }
+
+ private void setBt(int bt) {
+ this.bt = bt;
}
}
diff --git a/src/main/java/brachy/modularui/drawable/text/ModularComponent.java b/src/main/java/brachy/modularui/drawable/text/ModularComponent.java
index a7ee775..3ae26a2 100644
--- a/src/main/java/brachy/modularui/drawable/text/ModularComponent.java
+++ b/src/main/java/brachy/modularui/drawable/text/ModularComponent.java
@@ -4,7 +4,7 @@
import brachy.modularui.screen.viewport.GuiContext;
import brachy.modularui.theme.WidgetTheme;
import brachy.modularui.utils.Alignment;
-import brachy.modularui.utils.serialization.json.JsonHelper;
+import brachy.modularui.utils.serialization.codec.MutableObjectCodec;
import brachy.modularui.widgets.TextWidget;
import net.minecraft.ChatFormatting;
@@ -20,8 +20,9 @@
import net.minecraft.network.chat.contents.ScoreContents;
import net.minecraft.network.chat.contents.SelectorContents;
import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.util.ExtraCodecs;
+import com.mojang.serialization.Codec;
-import com.google.gson.JsonObject;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -34,6 +35,14 @@
public class ModularComponent extends MutableComponent implements Text {
+ public static final MutableObjectCodec CODEC = MutableObjectCodec.drawableBuilder(ModularComponent.class, "Text")
+ .wrapped(ExtraCodecs.COMPONENT.xmap(ModularComponent::of, mc -> mc))
+ .addOpt("alignment", ModularComponent::alignment, ModularComponent::getAlignment, Alignment.CODEC, Alignment.Center)
+ .addOpt("scale", ModularComponent::scale, ModularComponent::getScale, Codec.FLOAT, 1f)
+ .addOpt("shadow", ModularComponent::shadow, ModularComponent::getShadow, Codec.BOOL, null)
+ .addUnencodable("dynamicColor", ModularComponent::color, ModularComponent::getDynamicColor)
+ .build();
+
public static ModularComponent literal(String text) {
return ModularComponent.create(new LiteralContents(text));
}
@@ -79,6 +88,7 @@ public static ModularComponent create(@NotNull ComponentContents contents) {
}
public static ModularComponent of(Component component) {
+ if (component instanceof ModularComponent mc) return mc;
return new ModularComponent(component.getContents(), component.getSiblings(), component.getStyle());
}
@@ -163,6 +173,10 @@ public ModularComponent scale(float scale) {
return this;
}
+ public ModularComponent color(Integer color) {
+ return color != null ? color((int) color) : color((IntSupplier) null);
+ }
+
@Override
public ModularComponent color(@Nullable IntSupplier color) {
this.dynamicColor = color;
@@ -209,24 +223,4 @@ public ModularComponent asModular() {
public @NotNull ModularComponent withStyle(@NotNull UnaryOperator