diff --git a/libraries/text-components/build.gradle b/libraries/text-components/build.gradle new file mode 100644 index 00000000..7bb43400 --- /dev/null +++ b/libraries/text-components/build.gradle @@ -0,0 +1,5 @@ +setUpLibrary(project) + +dependencies { + implementation 'com.google.code.gson:gson:2.8.0' +} diff --git a/libraries/text-components/gradle.properties b/libraries/text-components/gradle.properties new file mode 100644 index 00000000..1d71ad08 --- /dev/null +++ b/libraries/text-components/gradle.properties @@ -0,0 +1,6 @@ +library_id = text-components +library_name = Text Components +library_description = Versatile text components. +library_version = 0.1.0-alpha.1 + +osl_dependencies = core:>=0.7.0 diff --git a/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/ClickEvent.java b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/ClickEvent.java new file mode 100644 index 00000000..65e722df --- /dev/null +++ b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/ClickEvent.java @@ -0,0 +1,64 @@ +package net.ornithemc.osl.text.api; + +import java.util.Locale; +import java.util.Objects; + +public class ClickEvent { + + private final Action action; + private final String value; + + public ClickEvent(Action action, String value) { + this.action = action; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClickEvent)) { + return false; + } + ClickEvent event = (ClickEvent) o; + return this.action == event.action && Objects.equals(this.value, event.value); + } + + public Action getAction() { + return this.action; + } + + public String getValue() { + return this.value; + } + + public static ClickEvent openUrl(String url) { + return new ClickEvent(Action.OPEN_URL, url); + } + + public static ClickEvent runCommand(String command) { + return new ClickEvent(Action.RUN_COMMAND, command); + } + + public static ClickEvent suggestCommand(String command) { + return new ClickEvent(Action.SUGGEST_COMMAND, command); + } + + public static ClickEvent copyToClipboard(String text) { + return new ClickEvent(Action.COPY_TO_CLIPBOARD, text); + } + + public enum Action { + + OPEN_URL, RUN_COMMAND, SUGGEST_COMMAND, COPY_TO_CLIPBOARD; + + public String getName() { + return this.name().toLowerCase(Locale.ROOT); + } + + public static Action byName(String name) { + return Action.valueOf(name.toUpperCase()); + } + } +} diff --git a/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Formatting.java b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Formatting.java new file mode 100644 index 00000000..1a577845 --- /dev/null +++ b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Formatting.java @@ -0,0 +1,68 @@ +package net.ornithemc.osl.text.api; + +public enum Formatting { + + BLACK ('0', 0x000000), + DARK_BLUE ('1', 0x0000AA), + DARK_GREEN ('2', 0x00AA00), + DARK_AQUA ('3', 0x00AAAA), + DARK_RED ('4', 0xAA0000), + DARK_PURPLE ('5', 0xAA00AA), + GOLD ('6', 0xFFAA00), + GRAY ('7', 0xAAAAAA), + DARK_GRAY ('8', 0x555555), + BLUE ('9', 0x5555FF), + GREEN ('a', 0x55FF55), + AQUA ('b', 0x55FFFF), + RED ('c', 0xFF5555), + LIGHT_PURPLE ('d', 0xFF55FF), + YELLOW ('e', 0xFFFF55), + WHITE ('f', 0xFFFFFF), + OBFUSCATED ('k'), + BOLD ('l'), + STRIKETHROUGH('m'), + UNDERLINED ('n'), + ITALIC ('o'), + RESET ('r'); + + public static final char PREFIX = 'ยง'; + + final char code; + final Integer color; + + private Formatting(char code) { + this(code, null); + } + + private Formatting(char code, Integer color) { + this.code = code; + this.color = color; + } + + @Override + public String toString() { + return "" + PREFIX + this.code; + } + + public char getCode() { + return this.code; + } + + public boolean isColor() { + return this.color != null; + } + + public Integer getColor() { + return this.color; + } + + public static Formatting byCode(char code) { + for (Formatting f : Formatting.values()) { + if (f.code == code) { + return f; + } + } + + throw new IllegalStateException("unknown text formatting code " + code); + } +} diff --git a/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/HoverEvent.java b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/HoverEvent.java new file mode 100644 index 00000000..332d3a32 --- /dev/null +++ b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/HoverEvent.java @@ -0,0 +1,52 @@ +package net.ornithemc.osl.text.api; + +import java.util.Locale; +import java.util.Objects; + +public class HoverEvent { + + private final Action action; + private final Object value; + + public HoverEvent(Action action, Object value) { + this.action = action; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HoverEvent)) { + return false; + } + HoverEvent event = (HoverEvent) o; + return this.action == event.action && Objects.equals(this.value, event.value); + } + + public Action getAction() { + return this.action; + } + + public T getValue() { + return (T) this.value; + } + + public static HoverEvent showText(TextComponent text) { + return new HoverEvent(Action.SHOW_TEXT, text); + } + + public enum Action { + + SHOW_TEXT; + + public String getName() { + return this.name().toLowerCase(Locale.ROOT); + } + + public static Action byName(String name) { + return Action.valueOf(name.toUpperCase()); + } + } +} diff --git a/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Style.java b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Style.java new file mode 100644 index 00000000..48af6253 --- /dev/null +++ b/libraries/text-components/src/main/java/net/ornithemc/osl/text/api/Style.java @@ -0,0 +1,539 @@ +package net.ornithemc.osl.text.api; + +import java.lang.reflect.Type; +import java.util.Objects; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public final class Style { + + public static final Style EMPTY = new Style(null, null, null, null, null, null, null, null, null); + + private final TextColor color; + private final Boolean bold; + private final Boolean italic; + private final Boolean underlined; + private final Boolean strikethrough; + private final Boolean obfuscated; + private final String insertion; + private final ClickEvent clickEvent; + private final HoverEvent hoverEvent; + + private Style( + TextColor color, + Boolean bold, + Boolean italic, + Boolean underlined, + Boolean strikethrough, + Boolean obfuscated, + String insertion, + ClickEvent clickEvent, + HoverEvent hoverEvent + ) { + this.color = color; + this.bold = bold; + this.italic = italic; + this.underlined = underlined; + this.strikethrough = strikethrough; + this.obfuscated = obfuscated; + this.insertion = insertion; + this.clickEvent = clickEvent; + this.hoverEvent = hoverEvent; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Style)) { + return false; + } + Style style = (Style) o; + return this.color == style.color + && this.bold == style.bold + && this.italic == style.italic + && this.obfuscated == style.obfuscated + && this.strikethrough == style.strikethrough + && this.underlined == style.underlined + && Objects.equals(this.insertion, style.insertion) + && Objects.equals(this.clickEvent, style.clickEvent) + && Objects.equals(this.hoverEvent, style.hoverEvent); + } + + public TextColor getColor() { + return this.color; + } + + public boolean isBold() { + return this.bold == Boolean.TRUE; + } + + public boolean isItalic() { + return this.italic == Boolean.TRUE; + } + + public boolean isStrikethrough() { + return this.strikethrough == Boolean.TRUE; + } + + public boolean isUnderlined() { + return this.underlined == Boolean.TRUE; + } + + public boolean isObfuscated() { + return this.obfuscated == Boolean.TRUE; + } + + public String getInsertion() { + return this.insertion; + } + + public ClickEvent getClickEvent() { + return this.clickEvent; + } + + public HoverEvent getHoverEvent() { + return this.hoverEvent; + } + + public boolean isEmpty() { + return this == EMPTY; + } + + private static Style checkEmptyAfterChange(Style style, T oldValue, T newValue) { + return oldValue != null && newValue == null && style.equals(EMPTY) ? EMPTY : style; + } + + public Style withColor(int color) { + return this.withColor(TextColor.of(color)); + } + + public Style withColor(Formatting formatting) { + return this.withColor(TextColor.fromFormatting(formatting)); + } + + public Style withColor(TextColor color) { + return Objects.equals(this.color, color) + ? this + : checkEmptyAfterChange( + new Style( + color, + this.bold, + this.italic, + this.underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.color, + color + ); + } + + public Style withBold(Boolean bold) { + return Objects.equals(this.bold, bold) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + bold, + this.italic, + this.underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.bold, + bold + ); + } + + public Style withItalic(Boolean italic) { + return Objects.equals(this.italic, italic) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + italic, + this.underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.italic, + italic + ); + } + + public Style withUnderlined(Boolean underlined) { + return Objects.equals(this.underlined, underlined) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.underlined, + underlined + ); + } + + public Style withStrikethrough(Boolean strikethrough) { + return Objects.equals(this.strikethrough, strikethrough) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + this.underlined, + strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.strikethrough, + strikethrough + ); + } + + public Style withObfuscated(Boolean obfuscated) { + return Objects.equals(this.obfuscated, obfuscated) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + this.underlined, + this.strikethrough, + obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ), + this.obfuscated, + obfuscated + ); + } + + public Style withInsertion(String insertion) { + return Objects.equals(this.insertion, insertion) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + this.underlined, + this.strikethrough, + this.obfuscated, + insertion, + this.clickEvent, + this.hoverEvent + ), + this.insertion, + insertion + ); + } + + public Style withClickEvent(ClickEvent clickEvent) { + return Objects.equals(this.clickEvent, clickEvent) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + this.underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + clickEvent, + this.hoverEvent + ), + this.clickEvent, + clickEvent + ); + } + + public Style withHoverEvent(HoverEvent hoverEvent) { + return Objects.equals(this.hoverEvent, hoverEvent) + ? this + : checkEmptyAfterChange( + new Style( + this.color, + this.bold, + this.italic, + this.underlined, + this.strikethrough, + this.obfuscated, + this.insertion, + this.clickEvent, + hoverEvent + ), + this.hoverEvent, + hoverEvent + ); + } + + public Style withFormatting(Formatting... formattings) { + TextColor color = this.color; + Boolean bold = this.bold; + Boolean italic = this.italic; + Boolean strikethrough = this.strikethrough; + Boolean underlined = this.underlined; + Boolean obfuscated = this.obfuscated; + + for (Formatting formatting : formattings) { + switch (formatting) { + case RESET: + return EMPTY; + case OBFUSCATED: + obfuscated = true; + break; + case BOLD: + bold = true; + break; + case STRIKETHROUGH: + strikethrough = true; + break; + case UNDERLINED: + underlined = true; + break; + case ITALIC: + italic = true; + break; + default: + if (formatting.isColor()) { + color = TextColor.fromFormatting(formatting); + } + } + } + + return new Style( + color, + bold, + italic, + underlined, + strikethrough, + obfuscated, + this.insertion, + this.clickEvent, + this.hoverEvent + ); + } + + public Style withStyle(Style style) { + if (this == EMPTY) { + return style; + } + if (style == EMPTY) { + return this; + } + return new Style( + this.color != null ? this.color : style.color, + this.bold != null ? this.bold : style.bold, + this.italic != null ? this.italic : style.italic, + this.underlined != null ? this.underlined : style.underlined, + this.strikethrough != null ? this.strikethrough : style.strikethrough, + this.obfuscated != null ? this.obfuscated : style.obfuscated, + this.insertion != null ? this.insertion : style.insertion, + this.clickEvent != null ? this.clickEvent : style.clickEvent, + this.hoverEvent != null ? this.hoverEvent : style.hoverEvent + ); + } + + public void apply(StringBuilder sb) { + if (this.color != null) { + Formatting formatting = this.color.getFormatting(); + + if (formatting != null) { + sb.append(formatting); + } + } + if (this.bold != null) { + sb.append(Formatting.BOLD); + } + if (this.italic != null) { + sb.append(Formatting.ITALIC); + } + if (this.underlined != null) { + sb.append(Formatting.UNDERLINED); + } + if (this.strikethrough != null) { + sb.append(Formatting.STRIKETHROUGH); + } + if (this.obfuscated != null) { + sb.append(Formatting.OBFUSCATED); + } + } + + public static class Serializer implements JsonDeserializer