GUI Improvements

This commit is contained in:
William Blake Galbreath
2025-01-12 17:34:29 -08:00
committed by granny
parent c5617e1e66
commit 7e8af548ea
7 changed files with 392 additions and 421 deletions

View File

@@ -1,5 +1,13 @@
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -106,6 +_,7 @@
// CraftBukkit start
if (!org.bukkit.craftbukkit.Main.useConsole) return;
// Paper start - Use TerminalConsoleAppender
+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click)
new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
/*
jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
@@ -224,6 +_,15 @@
io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark

View File

@@ -0,0 +1,135 @@
--- a/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/net/minecraft/server/gui/MinecraftServerGui.java
@@ -39,6 +_,11 @@
private Thread logAppenderThread;
private final Collection<Runnable> finalizers = Lists.newArrayList();
final AtomicBoolean isClosing = new AtomicBoolean();
+ // Purpur start - GUI Improvements
+ private final CommandHistory history = new CommandHistory();
+ private String currentCommand = "";
+ private int historyIndex = 0;
+ // Purpur end - GUI Improvements
public static MinecraftServerGui showFrameFor(final DedicatedServer server) {
try {
@@ -46,7 +_,7 @@
} catch (Exception var3) {
}
- final JFrame jFrame = new JFrame("Minecraft server");
+ final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI
final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server);
jFrame.setDefaultCloseOperation(2);
jFrame.add(minecraftServerGui);
@@ -54,7 +_,7 @@
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
// Paper start - Improve ServerGUI
- jFrame.setName("Minecraft server");
+ jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI
try {
jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
} catch (java.io.IOException ignore) {
@@ -64,7 +_,7 @@
@Override
public void windowClosing(WindowEvent event) {
if (!minecraftServerGui.isClosing.getAndSet(true)) {
- jFrame.setTitle("Minecraft server - shutting down!");
+ jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI
server.halt(true);
minecraftServerGui.runFinalizers();
}
@@ -112,7 +_,7 @@
private JComponent buildChatPanel() {
JPanel jPanel = new JPanel(new BorderLayout());
- JTextArea jTextArea = new JTextArea();
+ org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements
JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30);
jTextArea.setEditable(false);
jTextArea.setFont(MONOSPACED);
@@ -121,10 +_,43 @@
String trimmed = jTextField.getText().trim();
if (!trimmed.isEmpty()) {
this.server.handleConsoleInput(trimmed, this.server.createCommandSourceStack());
+ // Purpur start - GUI Improvements
+ history.add(trimmed);
+ historyIndex = -1;
+ // Purpur end - GUI Improvements
}
jTextField.setText("");
});
+ // Purpur start - GUI Improvements
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
+ jTextField.getActionMap().put("up", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex < 0) {
+ currentCommand = jTextField.getText();
+ }
+ if (historyIndex < history.size() - 1) {
+ jTextField.setText(history.get(++historyIndex));
+ }
+ }
+ });
+ jTextField.getActionMap().put("down", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex >= 0) {
+ if (historyIndex == 0) {
+ --historyIndex;
+ jTextField.setText(currentCommand);
+ } else {
+ --historyIndex;
+ jTextField.setText(history.get(historyIndex));
+ }
+ }
+ }
+ });
+ // Purpur end - GUI Improvements
jTextArea.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent event) {
@@ -159,7 +_,7 @@
}
private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
- public void print(JTextArea textArea, JScrollPane scrollPane, String line) {
+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line));
} else {
@@ -170,16 +_,29 @@
flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum();
}
- try {
+ /*try { // Purpur - GUI Improvements
document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit
} catch (BadLocationException var8) {
- }
+ }*/ // Purpur - GUI Improvements
+ textArea.append(line); // Purpur - GUI Improvements
if (flag) {
verticalScrollBar.setValue(Integer.MAX_VALUE);
}
}
}
+
+ // Purpur start - GUI Improvements
+ public static class CommandHistory extends java.util.LinkedList<String> {
+ @Override
+ public boolean add(String command) {
+ if (size() > 1000) {
+ remove();
+ }
+ return super.offerFirst(command);
+ }
+ }
+ // Purpur end - GUI Improvements
// Paper start - Add onboarding message for initial server start
private JComponent buildOnboardingPanel() {

View File

@@ -0,0 +1,88 @@
--- /dev/null
+++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java
@@ -1,0 +_,85 @@
+package org.purpurmc.purpur.gui.util;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+import java.util.List;
+
+@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY)
+@ConverterKeys({"highlightGUIError"})
+@PerformanceSensitive("allocation")
+public final class HighlightErrorConverter extends LogEventPatternConverter {
+ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red
+ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow
+
+ private final List<PatternFormatter> formatters;
+
+ private HighlightErrorConverter(List<PatternFormatter> formatters) {
+ super("highlightGUIError", null);
+ this.formatters = formatters;
+ }
+
+ @Override
+ public void format(LogEvent event, StringBuilder toAppendTo) {
+ Level level = event.getLevel();
+ if (level.isMoreSpecificThan(Level.ERROR)) {
+ format(ERROR, event, toAppendTo);
+ return;
+ } else if (level.isMoreSpecificThan(Level.WARN)) {
+ format(WARN, event, toAppendTo);
+ return;
+ }
+ for (PatternFormatter formatter : formatters) {
+ formatter.format(event, toAppendTo);
+ }
+ }
+
+ private void format(String style, LogEvent event, StringBuilder toAppendTo) {
+ int start = toAppendTo.length();
+ toAppendTo.append(style);
+ int end = toAppendTo.length();
+
+ for (PatternFormatter formatter : formatters) {
+ formatter.format(event, toAppendTo);
+ }
+
+ if (toAppendTo.length() == end) {
+ toAppendTo.setLength(start);
+ }
+ }
+
+ @Override
+ public boolean handlesThrowable() {
+ for (final PatternFormatter formatter : formatters) {
+ if (formatter.handlesThrowable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static HighlightErrorConverter newInstance(Configuration config, String[] options) {
+ if (options.length != 1) {
+ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length);
+ return null;
+ }
+
+ if (options[0] == null) {
+ LOGGER.error("No pattern supplied on highlightGUIError");
+ return null;
+ }
+
+ PatternParser parser = PatternLayout.createPatternParser(config);
+ List<PatternFormatter> formatters = parser.parse(options[0]);
+ return new HighlightErrorConverter(formatters);
+ }
+}

View File

@@ -0,0 +1,20 @@
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -2,7 +_,16 @@
<Configuration status="WARN" shutdownHook="disable">
<Appenders>
<Queue name="ServerGuiConsole">
- <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
+ <!-- Purpur start - copied from TerminalConsole -->
+ <PatternLayout>
+ <LoggerNamePatternSelector defaultPattern="%highlightGUIError{[%d{HH:mm:ss} %level]: [%logger] %stripAnsi{%msg}%n%xEx{full}}">
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+ pattern="%highlightGUIError{[%d{HH:mm:ss} %level]: %stripAnsi{%msg}%n%xEx{full}}" />
+ </LoggerNamePatternSelector>
+ </PatternLayout>
+ <!-- Purpur end -->
</Queue>
<TerminalConsole name="TerminalConsole">
<PatternLayout>

View File

@@ -0,0 +1,58 @@
package org.purpurmc.purpur.gui;
import net.md_5.bungee.api.ChatColor;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
public enum GUIColor {
BLACK(ChatColor.BLACK, new Color(0x000000)),
DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)),
DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)),
DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)),
DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)),
DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)),
GOLD(ChatColor.GOLD, new Color(0xBB8800)),
GRAY(ChatColor.GRAY, new Color(0x888888)),
DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)),
BLUE(ChatColor.BLUE, new Color(0x5555FF)),
GREEN(ChatColor.GREEN, new Color(0x55FF55)),
AQUA(ChatColor.AQUA, new Color(0x55DDDD)),
RED(ChatColor.RED, new Color(0xFF5555)),
LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)),
YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)),
WHITE(ChatColor.WHITE, new Color(0xBBBBBB));
private final ChatColor chat;
private final Color color;
private static final Map<ChatColor, GUIColor> BY_CHAT = new HashMap<>();
GUIColor(ChatColor chat, Color color) {
this.chat = chat;
this.color = color;
}
public Color getColor() {
return color;
}
public ChatColor getChatColor() {
return chat;
}
public String getCode() {
return chat.toString();
}
public static GUIColor getColor(ChatColor chat) {
return BY_CHAT.get(chat);
}
static {
for (GUIColor color : values()) {
BY_CHAT.put(color.chat, color);
}
}
}

View File

@@ -0,0 +1,83 @@
package org.purpurmc.purpur.gui;
import com.google.common.collect.Sets;
import javax.swing.UIManager;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import javax.swing.JTextPane;
import javax.swing.Timer;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import java.util.Set;
public class JColorTextPane extends JTextPane {
private static final GUIColor DEFAULT_COLOR;
static {
DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel")
? GUIColor.WHITE : GUIColor.BLACK;
}
public void append(String msg) {
// TODO: update to use adventure instead
BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor());
for (BaseComponent component : components) {
String text = component.toPlainText();
if (text == null || text.isEmpty()) {
continue;
}
GUIColor guiColor = GUIColor.getColor(component.getColor());
StyleContext context = StyleContext.getDefaultStyleContext();
AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor());
attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR);
attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic());
attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined());
attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough());
//attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly
try {
int pos = getDocument().getLength();
getDocument().insertString(pos, text, attr);
if (component.isObfuscated()) {
// dirty hack to blink some text
Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground()));
BLINKS.add(blink);
}
} catch (BadLocationException ignore) {
}
}
}
private static final Set<Blink> BLINKS = Sets.newHashSet();
private static boolean SYNC_BLINK;
static {
new Timer(500, e -> {
SYNC_BLINK = !SYNC_BLINK;
BLINKS.forEach(Blink::blink);
}).start();
}
public class Blink {
private final int start, length;
private final AttributeSet attr1, attr2;
private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) {
this.start = start;
this.length = length;
this.attr1 = attr1;
this.attr2 = attr2;
}
private void blink() {
getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true);
}
}
}