renamed modules dir
This commit is contained in:
57
pandalib-chat/pom.xml
Normal file
57
pandalib-chat/pom.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>pandalib-parent</artifactId>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pandalib-chat</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-platform-bungeecord</artifactId>
|
||||
<version>4.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>${bungeecord.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
456
pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java
Normal file
456
pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java
Normal file
@@ -0,0 +1,456 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.BlockNBTComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentBuilder;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.EntityNBTComponent;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.kyori.adventure.text.SelectorComponent;
|
||||
import net.kyori.adventure.text.StorageNBTComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.event.HoverEventSource;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextDecoration.State;
|
||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public abstract sealed class Chat extends ChatStatic implements HoverEventSource<Component>, ComponentLike {
|
||||
|
||||
protected final ComponentBuilder<?, ?> builder;
|
||||
protected boolean console = false;
|
||||
|
||||
/* package */ Chat(ComponentBuilder<?, ?> b) {
|
||||
Objects.requireNonNull(b, "Provided component builder must not be null");
|
||||
builder = b;
|
||||
}
|
||||
|
||||
|
||||
public Component getAdv() {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public BaseComponent get() {
|
||||
return toBungee(getAdv());
|
||||
}
|
||||
|
||||
public BaseComponent[] getAsArray() {
|
||||
return toBungeeArray(getAdv());
|
||||
}
|
||||
|
||||
private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FIENDLY = LegacyComponentSerializer.builder()
|
||||
.hexColors()
|
||||
.useUnusualXRepeatedCharacterHexFormat()
|
||||
.build();
|
||||
public String getLegacyText() {
|
||||
return LEGACY_SERIALIZER_BUNGEE_FIENDLY.serialize(getAdv());
|
||||
}
|
||||
|
||||
public String getPlainText() {
|
||||
return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public Chat then(Component comp) {
|
||||
if (comp instanceof TextComponent txtComp) {
|
||||
if (!txtComp.hasStyling() && (txtComp.content().isEmpty())) {
|
||||
// no need to add the provided component to the current component.
|
||||
// but eventual child component must be added
|
||||
if (!txtComp.children().isEmpty()) {
|
||||
for (Component child : txtComp.children())
|
||||
then(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
builder.append(comp);
|
||||
return this;
|
||||
}
|
||||
public Chat then(BaseComponent subComponent) {
|
||||
return then(toAdventure(subComponent));
|
||||
}
|
||||
public Chat then(Chat comp) {
|
||||
return then(comp.getAdv());
|
||||
}
|
||||
public Chat then(BaseComponent[] components) {
|
||||
return then(toAdventure(components));
|
||||
}
|
||||
|
||||
public Chat thenText(Object plainText) { return then(text(plainText)); }
|
||||
public Chat thenInfo(Object plainText) { return then(infoText(plainText)); }
|
||||
public Chat thenWarning(Object plainText) { return then(warningText(plainText)); }
|
||||
public Chat thenSuccess(Object plainText) { return then(successText(plainText)); }
|
||||
public Chat thenFailure(Object plainText) { return then(failureText(plainText)); }
|
||||
public Chat thenData(Object plainText) { return then(dataText(plainText)); }
|
||||
public Chat thenDecoration(Object plainText) { return then(decorationText(plainText)); }
|
||||
public Chat thenPlayerName(String legacyText) { return then(playerNameText(legacyText)); }
|
||||
public Chat thenPlayerName(Component comp) { return then(playerNameComponent(comp)); }
|
||||
public Chat thenNewLine() { return then(Component.newline()); }
|
||||
public Chat thenLegacyText(Object legacyText) { return then(legacyText(legacyText)); }
|
||||
public Chat thenTranslation(String key, Object... with) { return then(translation(key, with)); }
|
||||
public Chat thenKeyBind(String key) { return then(keybind(key)); }
|
||||
public Chat thenScore(String name, String objective) { return then(score(name, objective)); }
|
||||
|
||||
public Chat thenURLLink(Chat inner, String url, Chat hover) { return then(ChatUtil.createURLLink(inner, url, hover)); }
|
||||
public Chat thenURLLink(Chat inner, String url) { return thenURLLink(inner, url, null); }
|
||||
public Chat thenURLLink(String url, Chat hover) { return thenURLLink(text(url), url, hover); }
|
||||
public Chat thenURLLink(String url) { return thenURLLink(text(url), url); }
|
||||
|
||||
public Chat thenCommandLink(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandLink(inner, cmdWithSlash, hover)); }
|
||||
public Chat thenCommandLink(Chat inner, String cmdWithSlash) { return thenCommandLink(inner, cmdWithSlash, null); }
|
||||
public Chat thenCommandLink(String cmdWithSlash, Chat hover) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash, hover); }
|
||||
public Chat thenCommandLink(String cmdWithSlash) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash); }
|
||||
|
||||
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandSuggest(inner, cmdWithSlash, hover)); }
|
||||
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash) { return thenCommandSuggest(inner, cmdWithSlash, null); }
|
||||
public Chat thenCommandSuggest(String cmdWithSlash, Chat hover) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash, hover); }
|
||||
public Chat thenCommandSuggest(String cmdWithSlash) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash); }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenEmptyCharLine() {
|
||||
return then(ChatUtil.emptyLine(config.decorationChar, config.decorationColor, console));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided Chat left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenLeftTextCharLine(Chat leftText) {
|
||||
return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" "), config.decorationChar,
|
||||
config.decorationColor, config.nbCharMargin, console));
|
||||
}
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided component left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenLeftTextCharLine(BaseComponent leftText) {
|
||||
return thenLeftTextCharLine(chatComponent(leftText));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided Chat right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenRightTextCharLine(Chat rightText) {
|
||||
return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" "), config.decorationChar,
|
||||
config.decorationColor, config.nbCharMargin, console));
|
||||
}
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided component right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenRightTextCharLine(BaseComponent leftText) {
|
||||
return thenRightTextCharLine(chatComponent(leftText));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided Chat centered on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenCenterTextCharLine(Chat centerText) {
|
||||
return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" "), config.decorationChar,
|
||||
config.decorationColor, console));
|
||||
}
|
||||
/**
|
||||
* Draws a full line with the default decoration char, colored with the default decoration color,
|
||||
* and with the provided component centered on the line, default to the decoration color, and surrounded with 1 space on each side.
|
||||
* @return this, for method chaining
|
||||
*/
|
||||
public Chat thenCenterTextCharLine(BaseComponent leftText) {
|
||||
return thenCenterTextCharLine(chatComponent(leftText));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static final class FormatableChat extends Chat {
|
||||
/* package */ FormatableChat(ComponentBuilder<?, ?> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public FormatableChat console(boolean c) { console = c; return this; }
|
||||
|
||||
public FormatableChat color(TextColor c) { builder.color(c); return this; }
|
||||
public FormatableChat color(ChatColor c) { return color(c == null ? null : TextColor.color(c.getColor().getRGB())); }
|
||||
public FormatableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); }
|
||||
public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); }
|
||||
|
||||
public FormatableChat black() { return color(NamedTextColor.BLACK); }
|
||||
public FormatableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); }
|
||||
public FormatableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); }
|
||||
public FormatableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); }
|
||||
public FormatableChat darkRed() { return color(NamedTextColor.DARK_RED); }
|
||||
public FormatableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); }
|
||||
public FormatableChat gold() { return color(NamedTextColor.GOLD); }
|
||||
public FormatableChat gray() { return color(NamedTextColor.GRAY); }
|
||||
public FormatableChat darkGray() { return color(NamedTextColor.DARK_GRAY); }
|
||||
public FormatableChat blue() { return color(NamedTextColor.BLUE); }
|
||||
public FormatableChat green() { return color(NamedTextColor.GREEN); }
|
||||
public FormatableChat aqua() { return color(NamedTextColor.AQUA); }
|
||||
public FormatableChat red() { return color(NamedTextColor.RED); }
|
||||
public FormatableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); }
|
||||
public FormatableChat yellow() { return color(NamedTextColor.YELLOW); }
|
||||
public FormatableChat white() { return color(NamedTextColor.WHITE); }
|
||||
|
||||
public FormatableChat successColor() { return color(config.successColor); }
|
||||
public FormatableChat failureColor() { return color(config.failureColor); }
|
||||
public FormatableChat infoColor() { return color(config.infoColor); }
|
||||
public FormatableChat warningColor() { return color(config.warningColor); }
|
||||
public FormatableChat dataColor() { return color(config.dataColor); }
|
||||
public FormatableChat decorationColor() { return color(config.decorationColor); }
|
||||
public FormatableChat urlColor() { return color(config.urlColor); }
|
||||
public FormatableChat commandColor() { return color(config.commandColor); }
|
||||
public FormatableChat highlightedCommandColor() { return color(config.highlightedCommandColor); }
|
||||
public FormatableChat broadcastColor() { return color(config.broadcastColor); }
|
||||
|
||||
private FormatableChat setStyle(Consumer<Style.Builder> styleOp) { builder.style(styleOp); return this; }
|
||||
|
||||
private FormatableChat setDecoration(TextDecoration deco, Boolean state) {
|
||||
return setStyle(b -> b.decoration(deco, State.byBoolean(state)));
|
||||
}
|
||||
|
||||
public FormatableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); }
|
||||
public FormatableChat bold() { return bold(true); }
|
||||
|
||||
public FormatableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); }
|
||||
public FormatableChat italic() { return italic(true); }
|
||||
|
||||
public FormatableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); }
|
||||
public FormatableChat underlined() { return underlined(true); }
|
||||
|
||||
public FormatableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); }
|
||||
public FormatableChat strikethrough() { return strikethrough(true); }
|
||||
|
||||
public FormatableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); }
|
||||
public FormatableChat obfuscated() { return obfuscated(true); }
|
||||
|
||||
public FormatableChat font(Key f) { return setStyle(s -> s.font(f)); }
|
||||
|
||||
public FormatableChat shiftClickInsertion(String i) { builder.insertion(i); return this; }
|
||||
|
||||
private FormatableChat click(ClickEvent e) { builder.clickEvent(e); return this; }
|
||||
public FormatableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); }
|
||||
public FormatableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); }
|
||||
public FormatableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); }
|
||||
public FormatableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); }
|
||||
public FormatableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); }
|
||||
|
||||
public FormatableChat hover(HoverEventSource<?> e) { builder.hoverEvent(e); return this; }
|
||||
public FormatableChat hover(Chat v) { return hover(v.getAdv()); }
|
||||
public FormatableChat hover(BaseComponent v) { return hover(toAdventure(v)); }
|
||||
public FormatableChat hover(BaseComponent[] v) { return hover(toAdventure(v)); }
|
||||
public FormatableChat hover(String legacyText) { return hover(legacyText(legacyText)); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public HoverEvent<Component> asHoverEvent(UnaryOperator<Component> op) {
|
||||
return HoverEvent.showText(op.apply(getAdv()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component asComponent() {
|
||||
return getAdv();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Chat c
|
||||
&& getAdv().equals(c.getAdv());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getAdv().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPlainText();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static ComponentLike[] filterObjToComponentLike(Object[] values) {
|
||||
if (values == null)
|
||||
return null;
|
||||
ComponentLike[] ret = new ComponentLike[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Object v = values[i];
|
||||
if (v instanceof BaseComponent[])
|
||||
ret[i] = toAdventure((BaseComponent[]) v);
|
||||
else if (v instanceof BaseComponent)
|
||||
ret[i] = toAdventure((BaseComponent) v);
|
||||
else if (v instanceof ComponentLike)
|
||||
ret[i] = (ComponentLike) v;
|
||||
else
|
||||
ret[i] = Component.text(Objects.toString(v));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public static Component toAdventure(BaseComponent[] components) {
|
||||
return BungeeComponentSerializer.get().deserialize(components);
|
||||
}
|
||||
public static Component toAdventure(BaseComponent component) {
|
||||
return toAdventure(new BaseComponent[] { component });
|
||||
}
|
||||
|
||||
public static BaseComponent[] toBungeeArray(Component component) {
|
||||
return BungeeComponentSerializer.get().serialize(component);
|
||||
}
|
||||
public static BaseComponent toBungee(Component component) {
|
||||
BaseComponent[] arr = toBungeeArray(component);
|
||||
return arr.length == 1 ? arr[0] : new net.md_5.bungee.api.chat.TextComponent(arr);
|
||||
}
|
||||
|
||||
|
||||
public static ComponentBuilder<?, ?> componentToBuilder(Component c) {
|
||||
ComponentBuilder<?, ?> builder;
|
||||
if (c instanceof TextComponent) {
|
||||
builder = Component.text()
|
||||
.content(((TextComponent) c).content());
|
||||
}
|
||||
else if (c instanceof TranslatableComponent) {
|
||||
builder = Component.translatable()
|
||||
.key(((TranslatableComponent) c).key())
|
||||
.args(((TranslatableComponent) c).args());
|
||||
}
|
||||
else if (c instanceof SelectorComponent) {
|
||||
builder = Component.selector()
|
||||
.pattern(((SelectorComponent) c).pattern());
|
||||
}
|
||||
else if (c instanceof ScoreComponent) {
|
||||
builder = Component.score()
|
||||
.name(((ScoreComponent) c).name())
|
||||
.objective(((ScoreComponent) c).objective());
|
||||
}
|
||||
else if (c instanceof KeybindComponent) {
|
||||
builder = Component.keybind()
|
||||
.keybind(((KeybindComponent) c).keybind());
|
||||
}
|
||||
else if (c instanceof BlockNBTComponent) {
|
||||
builder = Component.blockNBT()
|
||||
.interpret(((BlockNBTComponent) c).interpret())
|
||||
.nbtPath(((BlockNBTComponent) c).nbtPath())
|
||||
.pos(((BlockNBTComponent) c).pos());
|
||||
}
|
||||
else if (c instanceof EntityNBTComponent) {
|
||||
builder = Component.entityNBT()
|
||||
.interpret(((EntityNBTComponent) c).interpret())
|
||||
.nbtPath(((EntityNBTComponent) c).nbtPath())
|
||||
.selector(((EntityNBTComponent) c).selector());
|
||||
}
|
||||
else if (c instanceof StorageNBTComponent) {
|
||||
builder = Component.storageNBT()
|
||||
.interpret(((StorageNBTComponent) c).interpret())
|
||||
.nbtPath(((StorageNBTComponent) c).nbtPath())
|
||||
.storage(((StorageNBTComponent) c).storage());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknows component type " + c.getClass());
|
||||
}
|
||||
return builder.style(c.style()).append(c.children());
|
||||
}
|
||||
|
||||
|
||||
public static Chat italicFalseIfNotSet(Chat c) {
|
||||
c.builder.style(b -> {
|
||||
if (b.build().decoration(TextDecoration.ITALIC) == State.NOT_SET) {
|
||||
((FormatableChat) c).italic(false);
|
||||
}
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected static final Config config = new Config();
|
||||
|
||||
public static Config getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
public TextColor decorationColor = NamedTextColor.YELLOW;
|
||||
public char decorationChar = '-';
|
||||
public int nbCharMargin = 1;
|
||||
public TextColor successColor = NamedTextColor.GREEN;
|
||||
public TextColor failureColor = NamedTextColor.RED;
|
||||
public TextColor infoColor = NamedTextColor.GOLD;
|
||||
public TextColor warningColor = NamedTextColor.GOLD;
|
||||
public TextColor dataColor = NamedTextColor.GRAY;
|
||||
public TextColor urlColor = NamedTextColor.GREEN;
|
||||
public TextColor commandColor = NamedTextColor.GRAY;
|
||||
public TextColor highlightedCommandColor = NamedTextColor.WHITE;
|
||||
public TextColor broadcastColor = NamedTextColor.YELLOW;
|
||||
public Supplier<Chat> prefix;
|
||||
|
||||
public int getPrefixWidth(boolean console) {
|
||||
Chat c = prefix == null ? null : prefix.get();
|
||||
return c == null ? 0 : ChatUtil.componentWidth(c.getAdv(), console);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,303 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
public class ChatColorUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoPpRr";
|
||||
public static final String ALL_COLORS = "0123456789AaBbCcDdEeFf";
|
||||
|
||||
|
||||
private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("§x(?>§[\\da-f]){6}", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ESS_COLOR_PATTERN = Pattern.compile("§#[\\da-f]{6}", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* Return the legacy format needed to reproduce the format at the end of the provided legacy text.
|
||||
* Supports standard chat colors and formats, BungeeCord Chat rgb format and EssentialsX rgb format.
|
||||
* The RGB value from EssentialsX format is converted to BungeeCord Chat when included in the returned value.
|
||||
*/
|
||||
public static String getLastColors(String legacyText) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int length = legacyText.length();
|
||||
|
||||
for (int index = length - 2; index >= 0; index--) {
|
||||
if (legacyText.charAt(index) == ChatColor.COLOR_CHAR) {
|
||||
|
||||
// detection of rgb color §x§0§1§2§3§4§5
|
||||
String rgb;
|
||||
if (index > 11
|
||||
&& legacyText.charAt(index - 12) == ChatColor.COLOR_CHAR
|
||||
&& (legacyText.charAt(index - 11) == 'x'
|
||||
|| legacyText.charAt(index - 11) == 'X')
|
||||
&& HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
|
||||
result.insert(0, rgb);
|
||||
break;
|
||||
}
|
||||
|
||||
// detection of rgb color §#012345 (and converting it to bungee chat format)
|
||||
if (index < length - 7
|
||||
&& legacyText.charAt(index + 1) == '#'
|
||||
&& ESS_COLOR_PATTERN.matcher(rgb = legacyText.substring(index, index + 8)).matches()) {
|
||||
rgb = "§x§" + rgb.charAt(2) + "§" + rgb.charAt(3)
|
||||
+ "§" + rgb.charAt(4) + "§" + rgb.charAt(5)
|
||||
+ "§" + rgb.charAt(6) + "§" + rgb.charAt(7);
|
||||
result.insert(0, rgb);
|
||||
break;
|
||||
}
|
||||
|
||||
// try detect non-rgb format
|
||||
char colorChar = legacyText.charAt(index + 1);
|
||||
ChatColor legacyColor = getChatColorByChar(colorChar);
|
||||
|
||||
if (legacyColor != null) {
|
||||
result.insert(0, legacyColor);
|
||||
|
||||
// Once we find a color or reset we can stop searching
|
||||
char col = legacyColor.toString().charAt(1);
|
||||
if ((col >= '0' && col <= '9')
|
||||
|| (col >= 'a' && col <= 'f')
|
||||
|| col == 'r') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static ChatColor getChatColorByChar(char code) {
|
||||
return ChatColor.getByChar(Character.toLowerCase(code));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Translate the color code of the provided string, that uses the the color char, to
|
||||
* the {@code §} color code format.
|
||||
* <p>
|
||||
* This method is the improved version of {@link ChatColor#translateAlternateColorCodes(char, String)},
|
||||
* because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
|
||||
* Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
|
||||
* to component (see {@link Chat#legacyText(Object)}).
|
||||
* <p>
|
||||
* This method should be used for user input (no permission check) or string configuration, but not string
|
||||
* from another API or containing URLs.
|
||||
*/
|
||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
||||
{
|
||||
char colorChar = ChatColor.COLOR_CHAR;
|
||||
StringBuilder acc = new StringBuilder();
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for ( int i = 0; i < b.length; i++ )
|
||||
{
|
||||
if (i < b.length - 1 // legacy chat format
|
||||
&& b[i] == altColorChar && ALL_CODES.indexOf(b[i + 1]) > -1)
|
||||
{
|
||||
acc.append(colorChar);
|
||||
acc.append(lowerCase(b[i + 1]));
|
||||
i++;
|
||||
}
|
||||
else if (i < b.length - 13 // bungee chat RGB format
|
||||
&& b[i] == altColorChar
|
||||
&& lowerCase(b[i + 1]) == 'x'
|
||||
&& b[i + 2] == altColorChar && ALL_COLORS.indexOf(b[i + 3]) > -1
|
||||
&& b[i + 4] == altColorChar && ALL_COLORS.indexOf(b[i + 5]) > -1
|
||||
&& b[i + 6] == altColorChar && ALL_COLORS.indexOf(b[i + 7]) > -1
|
||||
&& b[i + 8] == altColorChar && ALL_COLORS.indexOf(b[i + 9]) > -1
|
||||
&& b[i + 10] == altColorChar && ALL_COLORS.indexOf(b[i + 11]) > -1
|
||||
&& b[i + 12] == altColorChar && ALL_COLORS.indexOf(b[i + 13]) > -1) {
|
||||
acc.append(colorChar).append(lowerCase(b[i + 1]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 3]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 5]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 7]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 9]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 11]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 13]));
|
||||
i+=13;
|
||||
}
|
||||
else if (i < b.length - 7 // Essentials chat RGB format
|
||||
&& b[i] == altColorChar
|
||||
&& b[i + 1] == '#'
|
||||
&& ALL_COLORS.indexOf(b[i + 2]) > -1 && ALL_COLORS.indexOf(b[i + 3]) > -1
|
||||
&& ALL_COLORS.indexOf(b[i + 4]) > -1 && ALL_COLORS.indexOf(b[i + 5]) > -1
|
||||
&& ALL_COLORS.indexOf(b[i + 6]) > -1 && ALL_COLORS.indexOf(b[i + 7]) > -1) {
|
||||
acc.append(colorChar).append('x');
|
||||
acc.append(colorChar).append(lowerCase(b[i + 2]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 3]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 4]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 5]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 6]));
|
||||
acc.append(colorChar).append(lowerCase(b[i + 7]));
|
||||
i+=7;
|
||||
}
|
||||
else if (i < b.length - 1 && b[i] == altColorChar && b[i + 1] == altColorChar) {
|
||||
acc.append(altColorChar);
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
acc.append(b[i]);
|
||||
}
|
||||
}
|
||||
return acc.toString();
|
||||
}
|
||||
|
||||
private static char lowerCase(char c) { return Character.toLowerCase(c); }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Force a text to be italic, while keeping other formatting and colors.
|
||||
* The text is prefixed with the ITALIC tag, but is not reset at the end.
|
||||
* @param legacyText the original text
|
||||
* @return the text fully italic
|
||||
*/
|
||||
public static String forceItalic(String legacyText) {
|
||||
return forceFormat(legacyText, ChatColor.ITALIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a text to be bold, while keeping other formatting and colors.
|
||||
* The text is prefixed with the BOLD tag, but is not reset at the end.
|
||||
* @param legacyText the original text
|
||||
* @return the text fully bold
|
||||
*/
|
||||
public static String forceBold(String legacyText) {
|
||||
return forceFormat(legacyText, ChatColor.BOLD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a text to be underlined, while keeping other formatting and colors.
|
||||
* The text is prefixed with the UNDERLINE tag, but is not reset at the end.
|
||||
* @param legacyText the original text
|
||||
* @return the text fully underlined
|
||||
*/
|
||||
public static String forceUnderline(String legacyText) {
|
||||
return forceFormat(legacyText, ChatColor.UNDERLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a text to be stroked through, while keeping other formatting and colors.
|
||||
* The text is prefixed with the STRIKETHROUGH tag, but is not reset at the end.
|
||||
* @param legacyText the original text
|
||||
* @return the text fully stroked through
|
||||
*/
|
||||
public static String forceStrikethrough(String legacyText) {
|
||||
return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a text to be obfuscated, while keeping other formatting and colors.
|
||||
* The text is prefixed with the MAGIC tag, but is not reset at the end.
|
||||
* @param legacyText the original text
|
||||
* @return the text fully obfuscated
|
||||
*/
|
||||
public static String forceObfuscated(String legacyText) {
|
||||
return forceFormat(legacyText, ChatColor.MAGIC);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static String forceFormat(String legacyText, ChatColor format) {
|
||||
return format + legacyText
|
||||
.replace(format.toString(), "") // remove previous tag to make the result cleaner
|
||||
.replaceAll("§([a-frA-FR\\d])", "§$1" + format);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Replace the RESET tag of the input string to the specified color tag.
|
||||
* @param legacyText the original text
|
||||
* @param color the color to used to replace the RESET tag
|
||||
* (can be a combination of a color tag followed by multiple format tag)
|
||||
* @return the resulting text
|
||||
*/
|
||||
public static String resetToColor(String legacyText, String color) {
|
||||
return legacyText.replace(ChatColor.RESET.toString(), color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static TextColor toAdventure(ChatColor bungee) {
|
||||
if (bungee == null)
|
||||
return null;
|
||||
if (bungee.getColor() == null)
|
||||
throw new IllegalArgumentException("The provided Bungee ChatColor must be an actual color (not format nor reset).");
|
||||
return TextColor.color(bungee.getColor().getRGB());
|
||||
}
|
||||
|
||||
public static ChatColor toBungee(TextColor col) {
|
||||
if (col == null)
|
||||
return null;
|
||||
if (col instanceof NamedTextColor) {
|
||||
return ChatColor.of(((NamedTextColor) col).toString());
|
||||
}
|
||||
return ChatColor.of(col.asHexString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static TextColor interpolateColor(float v0, float v1, float v, TextColor cc0, TextColor cc1) {
|
||||
float normV = (v - v0) / (v1 - v0);
|
||||
return TextColor.lerp(normV, cc0, cc1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class ChatValueGradient {
|
||||
private record GradientValueColor(float value, TextColor color) { } // Java 16
|
||||
|
||||
final List<GradientValueColor> colors = new ArrayList<>();
|
||||
|
||||
public synchronized ChatValueGradient add(float v, TextColor col) {
|
||||
colors.add(new GradientValueColor(v, col));
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized TextColor pickColorAt(float v) {
|
||||
if (colors.isEmpty())
|
||||
throw new IllegalStateException("Must define at least one color in this ChatValueGradient instance.");
|
||||
if (colors.size() == 1)
|
||||
return colors.get(0).color();
|
||||
|
||||
colors.sort((p1, p2) -> Float.compare(p1.value(), p2.value()));
|
||||
|
||||
if (v <= colors.get(0).value())
|
||||
return colors.get(0).color();
|
||||
if (v >= colors.get(colors.size() - 1).value())
|
||||
return colors.get(colors.size() - 1).color();
|
||||
|
||||
int p1 = 1;
|
||||
for (; p1 < colors.size(); p1++) {
|
||||
if (colors.get(p1).value() >= v)
|
||||
break;
|
||||
}
|
||||
int p0 = p1 - 1;
|
||||
float v0 = colors.get(p0).value(), v1 = colors.get(p1).value();
|
||||
TextColor cc0 = colors.get(p0).color(), cc1 = colors.get(p1).color();
|
||||
return interpolateColor(v0, v1, v, cc0, cc1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
public abstract class ChatStatic {
|
||||
|
||||
|
||||
|
||||
public static FormatableChat chatComponent(Component c) {
|
||||
return new FormatableChat(Chat.componentToBuilder(c));
|
||||
}
|
||||
|
||||
public static FormatableChat chatComponent(BaseComponent c) {
|
||||
return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
|
||||
}
|
||||
|
||||
public static FormatableChat chatComponent(Chat c) {
|
||||
return chatComponent(c.getAdv());
|
||||
}
|
||||
|
||||
public static FormatableChat chat() {
|
||||
return new FormatableChat(Component.text());
|
||||
}
|
||||
|
||||
public static FormatableChat chatComponent(BaseComponent[] c) {
|
||||
return chatComponent(Chat.toAdventure(c));
|
||||
}
|
||||
|
||||
public static FormatableChat text(Object plainText) {
|
||||
if (plainText instanceof Chat) {
|
||||
Log.warning("Using Chat instance as plain text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
||||
return (FormatableChat) plainText;
|
||||
}
|
||||
if (plainText instanceof Component) {
|
||||
Log.warning("Using Component instance as plain text. Please use proper API method. I’ll properly use your Component this time...", new Throwable());
|
||||
return chatComponent((Component) plainText);
|
||||
}
|
||||
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
|
||||
}
|
||||
|
||||
public static FormatableChat legacyText(Object legacyText) {
|
||||
if (legacyText instanceof Chat) {
|
||||
Log.warning("Using Chat instance as legacy text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
||||
return (FormatableChat) legacyText;
|
||||
}
|
||||
if (legacyText instanceof Component) {
|
||||
Log.warning("Using Component instance as legacy text. Please use proper API method. I’ll properly use your Component this time...", new Throwable());
|
||||
return chatComponent((Component) legacyText);
|
||||
}
|
||||
return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
|
||||
}
|
||||
|
||||
public static FormatableChat infoText(Object plainText) {
|
||||
return text(plainText).infoColor();
|
||||
}
|
||||
|
||||
public static FormatableChat warningText(Object plainText) {
|
||||
return text(plainText).warningColor();
|
||||
}
|
||||
|
||||
public static FormatableChat dataText(Object plainText) {
|
||||
return text(plainText).dataColor();
|
||||
}
|
||||
|
||||
public static FormatableChat decorationText(Object plainText) {
|
||||
return text(plainText).decorationColor();
|
||||
}
|
||||
|
||||
public static FormatableChat successText(Object plainText) {
|
||||
return text(plainText).successColor();
|
||||
}
|
||||
|
||||
public static FormatableChat failureText(Object plainText) {
|
||||
return text(plainText).failureColor();
|
||||
}
|
||||
|
||||
public static FormatableChat playerNameText(String legacyText) {
|
||||
FormatableChat fc = legacyText(legacyText);
|
||||
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
|
||||
return fc;
|
||||
}
|
||||
|
||||
public static FormatableChat playerNameComponent(Component c) {
|
||||
FormatableChat fc = chatComponent(c);
|
||||
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
|
||||
return fc;
|
||||
}
|
||||
|
||||
public static FormatableChat translation(String key, Object... with) {
|
||||
return new FormatableChat(Component.translatable()
|
||||
.key(key)
|
||||
.args(Chat.filterObjToComponentLike(with))
|
||||
);
|
||||
}
|
||||
|
||||
public static FormatableChat keybind(String key) {
|
||||
return new FormatableChat(Component.keybind()
|
||||
.keybind(key)
|
||||
);
|
||||
}
|
||||
|
||||
public static FormatableChat score(String name, String objective) {
|
||||
return new FormatableChat(Component.score()
|
||||
.name(name)
|
||||
.objective(objective)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
709
pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java
Normal file
709
pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java
Normal file
@@ -0,0 +1,709 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextDecoration.State;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
|
||||
public class ChatUtil {
|
||||
|
||||
public static final int DEFAULT_CHAR_SIZE = 6;
|
||||
public static final Map<Integer, String> CHARS_SIZE = Map.ofEntries(
|
||||
Map.entry(-6, "§"),
|
||||
Map.entry(2, "!.,:;i|¡'"),
|
||||
Map.entry(3, "`lìí’‘"),
|
||||
Map.entry(4, " I[]tï×"),
|
||||
Map.entry(5, "\"()*<>fk{}"),
|
||||
Map.entry(7, "@~®©«»"),
|
||||
Map.entry(9, "├└")
|
||||
);
|
||||
|
||||
|
||||
|
||||
public static final int DEFAULT_CHAT_WIDTH = 320;
|
||||
public static final int SIGN_WIDTH = 90;
|
||||
public static final int BOOK_WIDTH = 116;
|
||||
public static final int MOTD_WIDTH = 270;
|
||||
public static final int BEDROCK_FORM_WIDE_BUTTON = 178;
|
||||
|
||||
public static final int CONSOLE_NB_CHAR_DEFAULT = 50;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static FormatableChat createURLLink(String text, String url) {
|
||||
return createURLLink(ChatStatic.legacyText(text), url, null);
|
||||
}
|
||||
|
||||
public static FormatableChat createURLLink(String text, String url, String hoverText) {
|
||||
return createURLLink(ChatStatic.legacyText(text), url, hoverText != null ? ChatStatic.legacyText(hoverText) : null);
|
||||
}
|
||||
|
||||
/* package */ static FormatableChat createURLLink(Chat element, String url, Chat hover) {
|
||||
String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
|
||||
return (FormatableChat) ChatStatic.chat()
|
||||
.clickURL(url)
|
||||
.urlColor()
|
||||
.hover(
|
||||
hover != null ? hover : Chat.text(dispURL)
|
||||
)
|
||||
.then(element);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static FormatableChat createCommandLink(String text, String commandWithSlash, String hoverText) {
|
||||
return createCommandLink(text, commandWithSlash, hoverText == null ? null : ChatStatic.legacyText(hoverText));
|
||||
}
|
||||
public static FormatableChat createCommandLink(String text, String commandWithSlash, Chat hoverText) {
|
||||
return createCommandLink(ChatStatic.legacyText(text), commandWithSlash, hoverText);
|
||||
}
|
||||
|
||||
/* package */ static FormatableChat createCommandLink(Chat d, String commandWithSlash, Chat hoverText) {
|
||||
FormatableChat c = ChatStatic.chat()
|
||||
.clickCommand(commandWithSlash)
|
||||
.commandColor();
|
||||
if (hoverText != null)
|
||||
c.hover(hoverText);
|
||||
return (FormatableChat) c.then(d);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static FormatableChat createCommandSuggest(String text, String commandWithSlash, String hoverText) {
|
||||
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : ChatStatic.legacyText(hoverText));
|
||||
}
|
||||
public static FormatableChat createCommandSuggest(String text, String commandWithSlash, Chat hoverText) {
|
||||
return createCommandSuggest(ChatStatic.legacyText(text), commandWithSlash, hoverText);
|
||||
}
|
||||
|
||||
/* package */ static FormatableChat createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) {
|
||||
FormatableChat c = ChatStatic.chat()
|
||||
.clickSuggest(commandWithSlash)
|
||||
.commandColor();
|
||||
if (hoverText != null)
|
||||
c.hover(hoverText);
|
||||
return (FormatableChat) c.then(d);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param cmdFormat the command with %d inside to be replaced with the page number (must start with slash)
|
||||
*/
|
||||
public static Chat createPagination(String prefix, String cmdFormat, int currentPage, int nbPages, int nbPagesToDisplay) {
|
||||
Set<Integer> pagesToDisplay = new TreeSet<>();
|
||||
|
||||
for (int i = 0; i < nbPagesToDisplay && i < nbPages && nbPages - i > 0; i++) {
|
||||
pagesToDisplay.add(i + 1);
|
||||
pagesToDisplay.add(nbPages - i);
|
||||
}
|
||||
|
||||
for (int i = currentPage - nbPagesToDisplay + 1; i < currentPage + nbPagesToDisplay; i++) {
|
||||
if (i > 0 && i <= nbPages)
|
||||
pagesToDisplay.add(i);
|
||||
}
|
||||
|
||||
Chat d = ChatStatic.chat().thenLegacyText(prefix);
|
||||
boolean first = true;
|
||||
int previous = 0;
|
||||
|
||||
for (int page : pagesToDisplay) {
|
||||
if (!first) {
|
||||
if (page == previous + 1) {
|
||||
d.thenText(" ");
|
||||
}
|
||||
else {
|
||||
if (cmdFormat.endsWith("%d")) {
|
||||
d.thenText(" ");
|
||||
d.then(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page"));
|
||||
d.thenText(" ");
|
||||
}
|
||||
else
|
||||
d.thenText(" ... ");
|
||||
}
|
||||
}
|
||||
else
|
||||
first = false;
|
||||
|
||||
FormatableChat pDisp = createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page);
|
||||
if (page == currentPage) {
|
||||
pDisp.highlightedCommandColor();
|
||||
}
|
||||
d.then(pDisp);
|
||||
|
||||
previous = page;
|
||||
}
|
||||
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param decorationColor support null values
|
||||
*/
|
||||
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console) {
|
||||
return centerText(text, repeatedChar, decorationColor, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
|
||||
}
|
||||
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console, int maxWidth) {
|
||||
return centerText(text, repeatedChar, decorationColor, false, console, maxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param decorationColor support null values
|
||||
*/
|
||||
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
|
||||
|
||||
int textWidth = componentWidth(text.getAdv(), console);
|
||||
|
||||
if (textWidth > maxWidth)
|
||||
return text;
|
||||
|
||||
int repeatedCharWidth = charW(repeatedChar, console, decorationBold);
|
||||
int sideWidth = (maxWidth - textWidth) / 2;
|
||||
int sideNbChar = sideWidth / repeatedCharWidth;
|
||||
|
||||
if (sideNbChar == 0)
|
||||
return text;
|
||||
|
||||
String sideChars = repeatedChar(repeatedChar, sideNbChar);
|
||||
FormatableChat side = ChatStatic.text(sideChars).color(decorationColor);
|
||||
if (decorationBold)
|
||||
side.bold();
|
||||
|
||||
Chat d = Chat.chat()
|
||||
.then(side)
|
||||
.then(text);
|
||||
if (repeatedChar != ' ')
|
||||
d.then(side);
|
||||
|
||||
return d;
|
||||
|
||||
}
|
||||
|
||||
public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console) {
|
||||
return leftText(text, repeatedChar, decorationColor, nbLeft, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
|
||||
}
|
||||
|
||||
public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console, int maxWidth) {
|
||||
|
||||
int textWidth = componentWidth(text.getAdv(), console);
|
||||
int repeatedCharWidth = charW(repeatedChar, console, false);
|
||||
int leftWidth = nbLeft * repeatedCharWidth;
|
||||
|
||||
if (textWidth + leftWidth > maxWidth)
|
||||
return text;
|
||||
|
||||
int rightNbChar = (maxWidth - (textWidth + leftWidth)) / repeatedCharWidth;
|
||||
|
||||
Chat d = ChatStatic.chat()
|
||||
.then(ChatStatic.text(repeatedChar(repeatedChar, nbLeft)).color(decorationColor))
|
||||
.then(text);
|
||||
if (repeatedChar != ' ') {
|
||||
d.then(ChatStatic.text(repeatedChar(repeatedChar, rightNbChar)).color(decorationColor));
|
||||
}
|
||||
return d;
|
||||
|
||||
}
|
||||
|
||||
public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight, boolean console) {
|
||||
return rightText(text, repeatedChar, decorationColor, nbRight, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
|
||||
}
|
||||
|
||||
public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight,
|
||||
boolean console, int maxWidth) {
|
||||
|
||||
int textWidth = componentWidth(text.getAdv(), console);
|
||||
int repeatedCharWidth = charW(repeatedChar, console, false);
|
||||
int rightWidth = nbRight * repeatedCharWidth;
|
||||
|
||||
if (textWidth + rightWidth > maxWidth)
|
||||
return text;
|
||||
|
||||
int leftNbChar = (maxWidth - (textWidth + rightWidth)) / repeatedCharWidth;
|
||||
|
||||
Chat d = ChatStatic.chat()
|
||||
.then(ChatStatic.text(repeatedChar(repeatedChar, leftNbChar)).color(decorationColor))
|
||||
.then(text);
|
||||
if (repeatedChar != ' ') {
|
||||
d.then(ChatStatic.text(repeatedChar(repeatedChar, nbRight)).color(decorationColor));
|
||||
}
|
||||
return d;
|
||||
|
||||
}
|
||||
|
||||
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean console) {
|
||||
return emptyLine(repeatedChar, decorationColor, false, console);
|
||||
}
|
||||
|
||||
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console) {
|
||||
return emptyLine(repeatedChar, decorationColor, decorationBold, console, (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
|
||||
}
|
||||
|
||||
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
|
||||
int count = maxWidth / charW(repeatedChar, console, decorationBold);
|
||||
FormatableChat line = ChatStatic.text(repeatedChar(repeatedChar, count)).color(decorationColor);
|
||||
if (decorationBold)
|
||||
line.bold();
|
||||
return line;
|
||||
}
|
||||
|
||||
private static String repeatedChar(char repeatedChar, int count) {
|
||||
char[] c = new char[count];
|
||||
Arrays.fill(c, repeatedChar);
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static int componentWidth(Component component, boolean console) {
|
||||
return componentWidth(component, console, false);
|
||||
}
|
||||
|
||||
public static int componentWidth(Component component, boolean console, boolean parentBold) {
|
||||
if (component == null)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
|
||||
State currentBold = component.style().decoration(TextDecoration.BOLD);
|
||||
boolean actuallyBold = childBold(parentBold, currentBold);
|
||||
|
||||
if (component instanceof TextComponent) {
|
||||
count += strWidth(((TextComponent)component).content(), console, actuallyBold);
|
||||
}
|
||||
else if (component instanceof TranslatableComponent) {
|
||||
for (Component c : ((TranslatableComponent)component).args())
|
||||
count += componentWidth(c, console, actuallyBold);
|
||||
}
|
||||
|
||||
for (Component c : component.children())
|
||||
count += componentWidth(c, console, actuallyBold);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static boolean childBold(boolean parent, TextDecoration.State child) {
|
||||
return (parent && child != State.FALSE) || (!parent && child == State.TRUE);
|
||||
}
|
||||
|
||||
public static int strWidth(String str, boolean console, boolean bold) {
|
||||
int count = 0;
|
||||
for (char c : str.toCharArray())
|
||||
count += charW(c, console, bold);
|
||||
return Math.max(count, 0);
|
||||
}
|
||||
|
||||
public static int charW(char c, boolean console, boolean bold) {
|
||||
if (console) return (c == '§') ? -1 : 1;
|
||||
for (int px : CHARS_SIZE.keySet())
|
||||
if (CHARS_SIZE.get(px).indexOf(c) >= 0) return px + (bold ? 1 : 0);
|
||||
return 6 + (bold ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<Chat> wrapInLimitedPixelsToChat(String legacyText, int pixelWidth) {
|
||||
return wrapInLimitedPixels(legacyText, pixelWidth).stream()
|
||||
.map(ChatStatic::legacyText)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<String> wrapInLimitedPixels(String legacyText, int pixelWidth) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
legacyText += "\n"; // workaround to force algorithm to compute last lines;
|
||||
|
||||
String currentLine = "";
|
||||
int currentLineSize = 0;
|
||||
int index = 0;
|
||||
|
||||
StringBuilder currentWord = new StringBuilder();
|
||||
int currentWordSize = 0;
|
||||
boolean bold = false;
|
||||
boolean firstCharCurrentWordBold = false;
|
||||
|
||||
do {
|
||||
char c = legacyText.charAt(index);
|
||||
if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
|
||||
currentWord.append(c);
|
||||
c = legacyText.charAt(++index);
|
||||
currentWord.append(c);
|
||||
|
||||
if (c == 'l' || c == 'L') // bold
|
||||
bold = true;
|
||||
if ((c >= '0' && c <= '9') // reset bold
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F')
|
||||
|| c == 'r' || c == 'R'
|
||||
|| c == 'x' || c == 'X')
|
||||
bold = false;
|
||||
|
||||
}
|
||||
else if (c == ' ') {
|
||||
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
|
||||
lines.add(currentLine);
|
||||
String lastStyle = ChatColorUtil.getLastColors(currentLine);
|
||||
if (currentWord.charAt(0) == ' ') {
|
||||
currentWord = new StringBuilder(currentWord.substring(1));
|
||||
currentWordSize -= charW(' ', false, firstCharCurrentWordBold);
|
||||
}
|
||||
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
|
||||
currentLineSize = currentWordSize;
|
||||
}
|
||||
else {
|
||||
currentLine += currentWord;
|
||||
currentLineSize += currentWordSize;
|
||||
}
|
||||
currentWord = new StringBuilder("" + c);
|
||||
currentWordSize = charW(c, false, bold);
|
||||
firstCharCurrentWordBold = bold;
|
||||
}
|
||||
else if (c == '\n') {
|
||||
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
|
||||
lines.add(currentLine);
|
||||
String lastStyle = ChatColorUtil.getLastColors(currentLine);
|
||||
if (currentWord.charAt(0) == ' ') {
|
||||
currentWord = new StringBuilder(currentWord.substring(1));
|
||||
}
|
||||
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
|
||||
}
|
||||
else {
|
||||
currentLine += currentWord;
|
||||
}
|
||||
// wrap after
|
||||
lines.add(currentLine);
|
||||
String lastStyle = ChatColorUtil.getLastColors(currentLine);
|
||||
|
||||
currentLine = lastStyle.equals("§r") ? "" : lastStyle;
|
||||
currentLineSize = 0;
|
||||
currentWord = new StringBuilder();
|
||||
currentWordSize = 0;
|
||||
firstCharCurrentWordBold = bold;
|
||||
}
|
||||
else {
|
||||
currentWord.append(c);
|
||||
currentWordSize += charW(c, false, bold);
|
||||
}
|
||||
|
||||
} while(++index < legacyText.length());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<Component> renderTable(List<List<Chat>> rows, String space, boolean console) {
|
||||
List<List<Component>> compRows = new ArrayList<>(rows.size());
|
||||
for (List<Chat> row : rows) {
|
||||
List<Component> compRow = new ArrayList<>(row.size());
|
||||
for (Chat c : row) {
|
||||
compRow.add(c.getAdv());
|
||||
}
|
||||
compRows.add(compRow);
|
||||
}
|
||||
return renderTableComp(compRows, space, console);
|
||||
}
|
||||
|
||||
|
||||
public static List<Component> renderTableComp(List<List<Component>> rows, String space, boolean console) {
|
||||
// determine columns width
|
||||
List<Integer> nbPixelPerColumn = new ArrayList<>();
|
||||
for (List<Component> row : rows) {
|
||||
for (int i = 0; i < row.size(); i++) {
|
||||
int w = componentWidth(row.get(i), console);
|
||||
if (nbPixelPerColumn.size() <= i)
|
||||
nbPixelPerColumn.add(w);
|
||||
else if (nbPixelPerColumn.get(i) < w)
|
||||
nbPixelPerColumn.set(i, w);
|
||||
}
|
||||
}
|
||||
|
||||
// create the lines with appropriate spacing
|
||||
List<Component> spacedRows = new ArrayList<>(rows.size());
|
||||
for (List<Component> row : rows) {
|
||||
Chat spacedRow = Chat.chat();
|
||||
for (int i = 0; i < row.size() - 1; i++) {
|
||||
int w = componentWidth(row.get(i), console);
|
||||
int padding = nbPixelPerColumn.get(i) - w;
|
||||
spacedRow.then(row.get(i));
|
||||
spacedRow.then(customWidthSpace(padding, console));
|
||||
spacedRow.thenText(space);
|
||||
}
|
||||
if (!row.isEmpty())
|
||||
spacedRow.then(row.get(row.size() - 1));
|
||||
spacedRows.add(spacedRow.getAdv());
|
||||
}
|
||||
|
||||
return spacedRows;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static Component customWidthSpace(int width, boolean console) {
|
||||
if (console)
|
||||
return Chat.text(" ".repeat(width)).getAdv();
|
||||
return switch (width) {
|
||||
case 0, 1 -> Component.empty();
|
||||
case 2 -> Chat.text(".").black().getAdv();
|
||||
case 3 -> Chat.text("`").black().getAdv();
|
||||
case 6 -> Chat.text(". ").black().getAdv();
|
||||
case 7 -> Chat.text("` ").black().getAdv();
|
||||
case 11 -> Chat.text("` ").black().getAdv();
|
||||
default -> {
|
||||
int nbSpace = width / 4;
|
||||
int nbBold = width % 4;
|
||||
int nbNotBold = nbSpace - nbBold;
|
||||
if (nbNotBold > 0) {
|
||||
if (nbBold > 0) {
|
||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false)
|
||||
.then(Chat.text(" ".repeat(nbBold)).bold(true))
|
||||
.getAdv();
|
||||
}
|
||||
else
|
||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
|
||||
}
|
||||
else if (nbBold > 0) {
|
||||
yield Chat.text(" ".repeat(nbBold)).bold(true).getAdv();
|
||||
}
|
||||
throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
|
||||
}
|
||||
};
|
||||
// "." is 2 px
|
||||
// "`" is 3 px
|
||||
// " " is 4 px
|
||||
// 0 ""
|
||||
// 1 ""
|
||||
// 2 "."
|
||||
// 3 "`"
|
||||
// 4 " "
|
||||
// 5 "§l "
|
||||
// 6 ". "
|
||||
// 7 "` "
|
||||
// 8 " "
|
||||
// 9 " §l "
|
||||
// 10 "§l "
|
||||
// 11 "` "
|
||||
// 12 " "
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static final String PROGRESS_BAR_START = "[";
|
||||
private static final String PROGRESS_BAR_END = "]";
|
||||
private static final TextColor PROGRESS_BAR_EMPTY_COLOR = NamedTextColor.DARK_GRAY;
|
||||
private static final char PROGRESS_BAR_EMPTY_CHAR = '.';
|
||||
private static final char PROGRESS_BAR_FULL_CHAR = '|';
|
||||
|
||||
public static Chat progressBar(double[] values, TextColor[] colors, double total, int pixelWidth, boolean console) {
|
||||
|
||||
// 1. Compute char size for each values
|
||||
int progressPixelWidth = pixelWidth - strWidth(PROGRESS_BAR_START + PROGRESS_BAR_END, console, false);
|
||||
int charPixelWidth = charW(PROGRESS_BAR_EMPTY_CHAR, console, false);
|
||||
|
||||
assert charPixelWidth == charW(PROGRESS_BAR_FULL_CHAR, console, false) : "PROGRESS_BAR_EMPTY_CHAR and PROGRESS_BAR_FULL_CHAR should have the same pixel width according to #charW(...)";
|
||||
|
||||
int progressCharWidth = progressPixelWidth / charPixelWidth;
|
||||
|
||||
int[] sizes = new int[values.length];
|
||||
double sumValuesBefore = 0;
|
||||
int sumSizesBefore = 0;
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
sumValuesBefore += values[i];
|
||||
int charPosition = Math.min((int) Math.round(progressCharWidth * sumValuesBefore / total), progressCharWidth);
|
||||
sizes[i] = charPosition - sumSizesBefore;
|
||||
sumSizesBefore += sizes[i];
|
||||
}
|
||||
|
||||
// 2. Generate rendered text
|
||||
Chat c = ChatStatic.text(PROGRESS_BAR_START);
|
||||
|
||||
int sumSizes = 0;
|
||||
for (int i = 0; i < sizes.length; i++) {
|
||||
sumSizes += sizes[i];
|
||||
|
||||
FormatableChat subC = ChatStatic.text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
|
||||
|
||||
if (colors != null && i < colors.length && colors[i] != null)
|
||||
subC.color(colors[i]);
|
||||
|
||||
c.then(subC);
|
||||
}
|
||||
|
||||
return c
|
||||
.then(ChatStatic.text(repeatedChar(PROGRESS_BAR_EMPTY_CHAR, progressCharWidth - sumSizes))
|
||||
.color(PROGRESS_BAR_EMPTY_COLOR))
|
||||
.thenText(PROGRESS_BAR_END);
|
||||
}
|
||||
|
||||
public static Chat progressBar(double value, TextColor color, double total, int pixelWidth, boolean console) {
|
||||
return progressBar(new double[] { value }, new TextColor[] { color }, total, pixelWidth, console);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static String truncatePrefix(String prefix, int maxLength) {
|
||||
if (prefix.length() > maxLength) {
|
||||
String lastColor = ChatColorUtil.getLastColors(prefix);
|
||||
prefix = truncateAtLengthWithoutReset(prefix, maxLength);
|
||||
if (!ChatColorUtil.getLastColors(prefix).equals(lastColor))
|
||||
prefix = truncateAtLengthWithoutReset(prefix, maxLength - lastColor.length()) + lastColor;
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
public static String truncateAtLengthWithoutReset(String prefix, int l) {
|
||||
if (prefix.length() > l) {
|
||||
prefix = prefix.substring(0, l);
|
||||
if (prefix.endsWith("§"))
|
||||
prefix = prefix.substring(0, prefix.length()-1);
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static final String TREE_MIDDLE_CONNECTED = "├";
|
||||
private static final String TREE_END_CONNECTED = "└";
|
||||
private static final String TREE_MIDDLE_OPEN = "│§0`§r";
|
||||
private static final String TREE_END_OPEN = "§0```§r";
|
||||
private static final String TREE_MIDDLE_OPEN_CONSOLE = "│";
|
||||
private static final String TREE_END_OPEN_CONSOLE = " "; // nbsp
|
||||
|
||||
/**
|
||||
* Generate a tree view based on the tree structure {@code node}.
|
||||
*
|
||||
* Each element in the returned list represent 1 line of the tree view.
|
||||
* Thus, the caller may send each line separately or at once depending of the quantity of data.
|
||||
* @return A array of component, each element being a single line.
|
||||
*/
|
||||
public static List<Chat> treeView(DisplayTreeNode node, boolean console) {
|
||||
List<Chat> ret = new ArrayList<>();
|
||||
|
||||
ret.add(ChatStatic.chat()
|
||||
.then(node.component));
|
||||
|
||||
for (int i = 0; i < node.children.size(); i++) {
|
||||
List<Chat> childComponents = treeView(node.children.get(i), console);
|
||||
boolean last = i == node.children.size() - 1;
|
||||
for (int j = 0; j < childComponents.size(); j++) {
|
||||
|
||||
String prefix = last ? (j == 0 ? TREE_END_CONNECTED : (console ? TREE_END_OPEN_CONSOLE : TREE_END_OPEN))
|
||||
: (j == 0 ? TREE_MIDDLE_CONNECTED : (console ? TREE_MIDDLE_OPEN_CONSOLE : TREE_MIDDLE_OPEN));
|
||||
|
||||
ret.add(ChatStatic.text(prefix)
|
||||
.then(childComponents.get(j)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class DisplayTreeNode {
|
||||
public final Chat component;
|
||||
public final List<DisplayTreeNode> children = new ArrayList<>();
|
||||
|
||||
public DisplayTreeNode(Chat cmp) {
|
||||
component = cmp;
|
||||
}
|
||||
|
||||
public DisplayTreeNode addChild(DisplayTreeNode child) {
|
||||
children.add(child);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user