diff --git a/build.gradle.kts b/build.gradle.kts index 2a481711e..70c106c95 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(libs.bundles.log4j) implementation(libs.jline) implementation(libs.jansi) + implementation(libs.bundles.ansi4j) implementation(libs.terminalconsoleappender) api(libs.slf4j) implementation(libs.disruptor) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56e033c88..a1fda4c48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ via-loader = "2.2.13-SNAPSHOT" via-bedrock = "0.0.5-SNAPSHOT" flatlaf = "3.3" flatlaf-inter-version = "4.0" +flatlaf-jetbrains-mono-version = "2.304" [plugins] blossom = "net.kyori.blossom:2.1.0" @@ -33,6 +34,8 @@ log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } jline = "org.jline:jline-terminal-jansi:3.25.1" jansi = "org.fusesource.jansi:jansi:2.4.1" +ansi4j-core-api = "com.github.PavelKastornyy.ansi4j:ansi4j-core-api:ansi4j-1.1.0" +ansi4j-core-impl = "com.github.PavelKastornyy.ansi4j:ansi4j-core-impl:ansi4j-1.1.0" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" slf4j = "org.slf4j:slf4j-api:2.0.12" disruptor = "com.lmax:disruptor:3.4.4" @@ -43,6 +46,7 @@ flatlaf = { module = "com.formdev:flatlaf", version.ref = "flatlaf" } flatlaf-extras = { module = "com.formdev:flatlaf-extras", version.ref = "flatlaf" } flatlaf-intellij-themes = { module = "com.formdev:flatlaf-intellij-themes", version.ref = "flatlaf" } flatlaf-fonts-inter = { module = "com.formdev:flatlaf-fonts-inter", version.ref = "flatlaf-inter-version" } +flatlaf-fonts-jetbrains-mono = { module = "com.formdev:flatlaf-fonts-jetbrains-mono", version.ref = "flatlaf-jetbrains-mono-version" } xchart = "org.knowm.xchart:xchart:3.8.7" miglayout-swing = "com.miglayout:miglayout-swing:11.3" mcprotocollib = "com.github.GeyserMC:MCProtocolLib:2a76b37" @@ -86,5 +90,6 @@ junit = "org.junit.jupiter:junit-jupiter:5.10.2" log4j = ["log4j-api", "log4j-core", "log4j-slf4j2-impl", "log4j-iostreams", "log4j-jul"] grpc = ["grpc-proto", "grpc-services", "grpc-stub", "grpc-netty"] mixins = ["classtransform-mixinstranslator", "classtransform-mixinsdummy", "classtransform-additionalclassprovider"] -flatlaf = ["flatlaf", "flatlaf-extras", "flatlaf-intellij-themes", "flatlaf-fonts-inter"] +flatlaf = ["flatlaf", "flatlaf-extras", "flatlaf-intellij-themes", "flatlaf-fonts-inter", "flatlaf-fonts-jetbrains-mono"] kyori = ["kyori-plain", "kyori-gson"] +ansi4j = ["ansi4j-core-api", "ansi4j-core-impl"] diff --git a/src/main/java/net/pistonmaster/soulfire/client/gui/ThemeUtil.java b/src/main/java/net/pistonmaster/soulfire/client/gui/ThemeUtil.java index 26889dfb9..239613bd0 100644 --- a/src/main/java/net/pistonmaster/soulfire/client/gui/ThemeUtil.java +++ b/src/main/java/net/pistonmaster/soulfire/client/gui/ThemeUtil.java @@ -23,7 +23,7 @@ import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.extras.FlatUIDefaultsInspector; import com.formdev.flatlaf.fonts.inter.FlatInterFont; -import com.formdev.flatlaf.util.FontUtils; +import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont; import com.formdev.flatlaf.util.SystemInfo; import javax.swing.JDialog; import javax.swing.JFrame; @@ -62,11 +62,6 @@ public static void setLookAndFeel() { UIManager.setLookAndFeel(theme); - var font = UIManager.getFont("defaultFont"); - var newFont = - FontUtils.getCompositeFont(FlatInterFont.FAMILY, font.getStyle(), font.getSize()); - UIManager.put("defaultFont", newFont); - FlatLaf.updateUI(); FlatAnimatedLafChange.hideSnapshotWithAnimation(); @@ -76,12 +71,19 @@ public static void setLookAndFeel() { } public static void initFlatLaf() { + FlatInterFont.install(); + FlatLaf.setPreferredFontFamily(FlatInterFont.FAMILY); + FlatLaf.setPreferredLightFontFamily(FlatInterFont.FAMILY_LIGHT); + FlatLaf.setPreferredSemiboldFontFamily(FlatInterFont.FAMILY_SEMIBOLD); + + FlatJetBrainsMonoFont.install(); + FlatLaf.setPreferredMonospacedFontFamily(FlatJetBrainsMonoFont.FAMILY); + FlatInspector.install("ctrl shift I"); FlatUIDefaultsInspector.install("ctrl shift O"); ToolTipManager.sharedInstance().setInitialDelay(100); ToolTipManager.sharedInstance().setDismissDelay(10_000); UIManager.put("PasswordField.showRevealButton", true); - FlatInterFont.install(); if (SystemInfo.isMacOS) { // Use top screen menu bar on macOS diff --git a/src/main/java/net/pistonmaster/soulfire/client/gui/libs/MessageLogPanel.java b/src/main/java/net/pistonmaster/soulfire/client/gui/libs/MessageLogPanel.java index 22097118e..26ad32642 100644 --- a/src/main/java/net/pistonmaster/soulfire/client/gui/libs/MessageLogPanel.java +++ b/src/main/java/net/pistonmaster/soulfire/client/gui/libs/MessageLogPanel.java @@ -17,7 +17,10 @@ */ package net.pistonmaster.soulfire.client.gui.libs; +import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont; import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; @@ -31,7 +34,7 @@ import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; -import javax.swing.JTextArea; +import javax.swing.JTextPane; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.text.AbstractDocument; @@ -40,8 +43,26 @@ import javax.swing.text.DefaultCaret; import javax.swing.text.DocumentFilter; import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import pk.ansi4j.core.DefaultFunctionFinder; +import pk.ansi4j.core.DefaultParserFactory; +import pk.ansi4j.core.DefaultTextHandler; +import pk.ansi4j.core.api.Environment; +import pk.ansi4j.core.api.Fragment; +import pk.ansi4j.core.api.FragmentType; +import pk.ansi4j.core.api.FunctionFragment; +import pk.ansi4j.core.api.ParserFactory; +import pk.ansi4j.core.api.TextFragment; +import pk.ansi4j.core.api.iso6429.C0ControlFunction; +import pk.ansi4j.core.api.iso6429.ControlSequenceFunction; +import pk.ansi4j.core.iso6429.C0ControlFunctionHandler; +import pk.ansi4j.core.iso6429.C1ControlFunctionHandler; +import pk.ansi4j.core.iso6429.ControlSequenceHandler; +import pk.ansi4j.core.iso6429.ControlStringHandler; +import pk.ansi4j.core.iso6429.IndependentControlFunctionHandler; /** * Modified version of: toInsert = Collections.synchronizedList(new ArrayList<>()); - private final JTextArea textComponent; - private final AbstractDocument document; + private final JTextPane textComponent; + private final StyledDocument document; + private final ParserFactory factory = + new DefaultParserFactory.Builder() + .environment(Environment._7_BIT) + .textHandler(new DefaultTextHandler()) + .functionFinder(new DefaultFunctionFinder()) + .functionHandlers( + new C0ControlFunctionHandler(), + new C1ControlFunctionHandler(), + new ControlSequenceHandler(), + new IndependentControlFunctionHandler(), + new ControlStringHandler()) + .build(); private boolean clearText; public MessageLogPanel(int numLines) { setLayout(new BorderLayout()); - this.textComponent = new JTextArea(); + this.textComponent = new JTextPane(); - textComponent.setLineWrap(true); - textComponent.setWrapStyleWord(true); - - textComponent.setFont(new JLabel().getFont()); + textComponent.setFont( + new Font(FlatJetBrainsMonoFont.FAMILY, Font.PLAIN, new JLabel().getFont().getSize())); textComponent.setEditable(true); + textComponent.setBackground(Color.decode("#090300")); + var caret = (DefaultCaret) textComponent.getCaret(); caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - document = (AbstractDocument) textComponent.getDocument(); + document = textComponent.getStyledDocument(); document.addDocumentListener(new LimitLinesDocumentListener(numLines, true)); - document.setDocumentFilter(noopDocumentFilter); + ((AbstractDocument) document).setDocumentFilter(noopDocumentFilter); updatePopup(); @@ -112,8 +145,34 @@ private void updateTextComponent() { clearText = false; } else { try { - var offset = document.getLength(); - document.insertString(offset, String.join("", toInsert), defaultAttributes); + var parser = factory.createParser(String.join("", toInsert)); + + Fragment fragment; + while ((fragment = parser.parse()) != null) { + if (fragment.getType() == FragmentType.TEXT) { + var textFragment = (TextFragment) fragment; + document.insertString( + document.getLength(), textFragment.getText(), defaultAttributes); + } else if (fragment.getType() == FragmentType.FUNCTION) { + var functionFragment = (FunctionFragment) fragment; + if (functionFragment.getFunction() + == ControlSequenceFunction.SGR_SELECT_GRAPHIC_RENDITION) { + StyleConstants.setForeground(defaultAttributes, switch ((int) functionFragment.getArguments().getFirst().getValue()) { + case 30 -> Color.decode("#090300"); + case 31 -> Color.decode("#FF0000"); + case 32 -> Color.decode("#00FF00"); + case 33 -> Color.decode("#FFFF00"); + case 34 -> Color.decode("#0000FF"); + case 35 -> Color.decode("#FF00FF"); + case 36 -> Color.decode("#00FFFF"); + case 37 -> Color.decode("#FFFFFF"); + default -> Color.decode("#FFFFFF"); + }); + } else if (functionFragment.getFunction() == C0ControlFunction.LF_LINE_FEED) { + document.insertString(document.getLength(), "\n", defaultAttributes); + } + } + } } catch (BadLocationException e) { log.debug("Failed to insert text!", e); } diff --git a/src/main/java/net/pistonmaster/soulfire/server/util/SFLogAppender.java b/src/main/java/net/pistonmaster/soulfire/server/util/SFLogAppender.java index 2f2f59879..03aed569d 100644 --- a/src/main/java/net/pistonmaster/soulfire/server/util/SFLogAppender.java +++ b/src/main/java/net/pistonmaster/soulfire/server/util/SFLogAppender.java @@ -29,16 +29,16 @@ public class SFLogAppender extends AbstractAppender { private final AbstractStringLayout.Serializer formatter = new PatternLayout.SerializerBuilder() .setAlwaysWriteExceptions(true) - .setDisableAnsi(true) - .setNoConsoleNoAnsi(true) - .setDefaultPattern("[%d{HH:mm:ss} %level] [%logger{1.*}]: %minecraftFormatting{%msg}%xEx") + .setDisableAnsi(false) + .setNoConsoleNoAnsi(false) + .setDefaultPattern("%highlight{[%d{HH:mm:ss} %level] [%logger{1.*}]: %minecraftFormatting{%msg}%xEx}{FATAL=red, ERROR=red, WARN=yellow, INFO=normal, DEBUG=cyan, TRACE=black}") .build(); private final AbstractStringLayout.Serializer builtInFormatter = new PatternLayout.SerializerBuilder() .setAlwaysWriteExceptions(true) - .setDisableAnsi(true) - .setNoConsoleNoAnsi(true) - .setDefaultPattern("[%d{HH:mm:ss} %level] [%logger{1}]: %minecraftFormatting{%msg}%xEx") + .setDisableAnsi(false) + .setNoConsoleNoAnsi(false) + .setDefaultPattern("%highlight{[%d{HH:mm:ss} %level] [%logger{1}]: %minecraftFormatting{%msg}%xEx}{FATAL=red, ERROR=red, WARN=yellow, INFO=normal, DEBUG=cyan, TRACE=black}") .build(); public SFLogAppender() {