diff --git a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java index cbc2e1ee61f..35fa5c41d6d 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java +++ b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java @@ -6,6 +6,7 @@ import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; + import javax.swing.ScrollPaneConstants; import net.miginfocom.swing.MigLayout; @@ -15,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import forge.Singletons; import forge.gui.framework.SDisplayUtil; import forge.model.FModel; +import forge.net.ChatMessage; import forge.net.IOnlineChatInterface; import forge.net.IRemote; import forge.net.event.MessageEvent; @@ -132,7 +134,7 @@ public enum FNetOverlay implements IOnlineChatInterface { public void show() { show(null); } - public void show(final String message) { + public void show(final ChatMessage message) { if (!hasBeenShown) { hasBeenShown = true; loadLocation(); @@ -144,7 +146,7 @@ public enum FNetOverlay implements IOnlineChatInterface { }); } if (message != null) { - txtLog.setText(message); + txtLog.setText(message.getFormattedMessage()); } window.setVisible(true); } @@ -203,7 +205,7 @@ public enum FNetOverlay implements IOnlineChatInterface { } @Override - public void addMessage(final String message) { - txtLog.append(message); + public void addMessage(final ChatMessage message) { + txtLog.append(message.getFormattedMessage()); } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java index 22f231879da..c4069d1fbe3 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java @@ -13,6 +13,7 @@ import forge.gui.framework.EDocID; import forge.gui.framework.ICDoc; import forge.menus.IMenuProvider; import forge.menus.MenuUtil; +import forge.net.ChatMessage; import forge.net.NetConnectUtil; import forge.screens.home.CHomeUI; import forge.screens.home.CLobby; @@ -55,7 +56,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { } }); - final String result = NetConnectUtil.host(VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); + final ChatMessage result = NetConnectUtil.host(VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); SwingUtilities.invokeLater(new Runnable() { @Override @@ -79,7 +80,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { } }); - final String result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); + final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); SwingUtilities.invokeLater(new Runnable() { @Override diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineChatScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineChatScreen.java index f3b52774a88..44681e52793 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineChatScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineChatScreen.java @@ -1,19 +1,25 @@ package forge.screens.online; -import java.util.ArrayList; - import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment; +import forge.Graphics; +import forge.assets.FSkinColor; +import forge.assets.FSkinFont; +import forge.assets.TextRenderer; +import forge.assets.FSkinColor.Colors; import forge.model.FModel; +import forge.net.ChatMessage; import forge.net.IOnlineChatInterface; import forge.net.IRemote; import forge.net.event.MessageEvent; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.FScreen; +import forge.toolbox.FDisplayObject; import forge.toolbox.FEvent; import forge.toolbox.FEvent.FEventHandler; -import forge.toolbox.FChoiceList; +import forge.toolbox.FScrollPane; import forge.toolbox.FTextField; import forge.util.Utils; @@ -22,7 +28,7 @@ public class OnlineChatScreen extends FScreen implements IOnlineChatInterface { private IRemote gameClient; private final ForgePreferences prefs = FModel.getPreferences(); - private final FChoiceList lstLog = add(new FChoiceList(new ArrayList())); + private final ChatLog lstLog = add(new ChatLog()); private final FTextField txtSendMessage = add(new FTextField()); public OnlineChatScreen() { @@ -44,7 +50,11 @@ public class OnlineChatScreen extends FScreen implements IOnlineChatInterface { txtSendMessage.setText(""); if (gameClient != null) { - gameClient.send(new MessageEvent(prefs.getPref(FPref.PLAYER_NAME), message)); + String source = prefs.getPref(FPref.PLAYER_NAME); + if (lstLog.getChildCount() % 2 == 1) { + source = "RemoteGuy"; //TODO: Remove this + } + gameClient.send(new MessageEvent(source, message)); } } @@ -65,9 +75,103 @@ public class OnlineChatScreen extends FScreen implements IOnlineChatInterface { } @Override - public void addMessage(String message) { - lstLog.addItem(message); - lstLog.scrollToBottom(); + public void addMessage(ChatMessage message) { + lstLog.addMessage(message); Gdx.graphics.requestRendering(); } + + private static class ChatLog extends FScrollPane { + @Override + protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) { + float x = 0; + float y = 0; + float inset = 6 * PADDING; + float bubbleWidth = visibleWidth - inset; + + for (FDisplayObject obj : getChildren()) { + ChatMessageBubble bubble = (ChatMessageBubble)obj; + if (bubble.isLocal) { //right-align local messages + bubble.setBounds(x + inset, y, bubbleWidth, bubble.getPreferredHeight(bubbleWidth)); + } + else { + bubble.setBounds(x, y, bubbleWidth, bubble.getPreferredHeight(bubbleWidth)); + } + y += bubble.getHeight() + PADDING; + } + return new ScrollBounds(visibleWidth, y - PADDING); + } + + private void addMessage(ChatMessage message) { + add(new ChatMessageBubble(message)); + revalidate(); + scrollToBottom(); + } + } + + private static class ChatMessageBubble extends FDisplayObject { + private static final FSkinFont FONT = FSkinFont.get(12); + private static final FSkinColor LOCAL_COLOR = FSkinColor.get(Colors.CLR_ZEBRA); + private static final FSkinColor REMOTE_COLOR = LOCAL_COLOR.getContrastColor(-20); + private static final FSkinColor MESSAGE_COLOR = FSkinColor.get(Colors.CLR_TEXT); + private static final FSkinColor SOURCE_COLOR = MESSAGE_COLOR.alphaColor(0.75f); + private static final FSkinColor TIMESTAMP_COLOR = MESSAGE_COLOR.alphaColor(0.5f); + private static final FSkinColor BORDER_COLOR = FSkinColor.get(Colors.CLR_BORDERS); + private static final float BORDER_THICKNESS = Utils.scale(1); + private static final float TEXT_INSET = Utils.scale(5); + private static final float TRIANGLE_WIDTH = Utils.scale(5); + + private final ChatMessage message; + private final boolean isLocal; + private final String header; + private final TextRenderer textRenderer = new TextRenderer(); + + private ChatMessageBubble(ChatMessage message0) { + message = message0; + isLocal = message.isLocal(); + if (isLocal || message.getSource() == null) { + header = null; + } + else { + header = message.getSource() + ":"; + } + } + + public float getPreferredHeight(float width) { + float height = FONT.getCapHeight() + 4 * TEXT_INSET + TRIANGLE_WIDTH; + if (header != null) { + height += FONT.getLineHeight() + TEXT_INSET; + } + height += textRenderer.getWrappedBounds(message.getMessage(), FONT, width - 2 * TEXT_INSET).height; + return height; + } + + @Override + public void draw(Graphics g) { + float x = isLocal ? 0 : TRIANGLE_WIDTH; + float y = TEXT_INSET; + float w = getWidth() - TRIANGLE_WIDTH; + float h = getHeight() - TEXT_INSET; + FSkinColor color = isLocal ? LOCAL_COLOR : REMOTE_COLOR; + HAlignment horzAlignment = isLocal ? HAlignment.RIGHT : HAlignment.LEFT; + + g.fillRect(color, x, y, w, h); + g.drawRect(BORDER_THICKNESS, BORDER_COLOR, x, y, w, h); + + x += TEXT_INSET; + y += TEXT_INSET; + w -= 2 * TEXT_INSET; + + if (!isLocal && message.getSource() != null) { + float sourceHeight = FONT.getLineHeight(); + g.drawText(message.getSource() + ":", FONT, SOURCE_COLOR, x, y, w, sourceHeight, false, horzAlignment, true); + y += sourceHeight + TEXT_INSET; + } + + float timestampHeight = FONT.getCapHeight(); + g.drawText(message.getTimestamp(), FONT, TIMESTAMP_COLOR, x, h - timestampHeight, w, timestampHeight, false, horzAlignment, true); + + h -= y + timestampHeight + TEXT_INSET; + textRenderer.drawText(g, message.getMessage(), FONT, MESSAGE_COLOR, x, y, w, h, y, h, true, horzAlignment, true); + } + } } diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index 10f92d9ad7f..598e3034cd9 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -4,6 +4,7 @@ import forge.FThreads; import forge.Forge; import forge.interfaces.ILobbyView; import forge.match.GameLobby; +import forge.net.ChatMessage; import forge.net.IOnlineChatInterface; import forge.net.IOnlineLobby; import forge.net.NetConnectUtil; @@ -51,7 +52,7 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { LoadingOverlay.show(caption, new Runnable() { @Override public void run() { - final String result; + final ChatMessage result; final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); if (joinServer) { result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); diff --git a/forge-gui/src/main/java/forge/net/IOnlineChatInterface.java b/forge-gui/src/main/java/forge/net/IOnlineChatInterface.java index cde366e82c5..93219cd3f36 100644 --- a/forge-gui/src/main/java/forge/net/IOnlineChatInterface.java +++ b/forge-gui/src/main/java/forge/net/IOnlineChatInterface.java @@ -2,5 +2,5 @@ package forge.net; public interface IOnlineChatInterface { void setGameClient(IRemote iRemote); - void addMessage(String message); + void addMessage(ChatMessage message); } diff --git a/forge-gui/src/main/java/forge/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/net/NetConnectUtil.java index 3b7759f8170..6f044e868e6 100644 --- a/forge-gui/src/main/java/forge/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/net/NetConnectUtil.java @@ -1,8 +1,5 @@ package forge.net; -import java.text.SimpleDateFormat; -import java.util.Date; - import org.apache.commons.lang3.StringUtils; import forge.GuiBase; @@ -41,7 +38,7 @@ public class NetConnectUtil { return url; } - public static String host(final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { + public static ChatMessage host(final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { final int port = ForgeProfileProperties.getServerPort(); final FServerManager server = FServerManager.getInstance(); final ServerGameLobby lobby = new ServerGameLobby(); @@ -72,7 +69,7 @@ public class NetConnectUtil { } @Override public final void message(final String source, final String message) { - chatInterface.addMessage(formatMessage(source, message)); + chatInterface.addMessage(new ChatMessage(source, message)); } @Override public final void close() { @@ -84,7 +81,7 @@ public class NetConnectUtil { public final void send(final NetEvent event) { if (event instanceof MessageEvent) { final MessageEvent message = (MessageEvent) event; - chatInterface.addMessage(formatMessage(message.getSource(), message.getMessage())); + chatInterface.addMessage(new ChatMessage(message.getSource(), message.getMessage())); server.broadcast(event); } } @@ -97,7 +94,7 @@ public class NetConnectUtil { view.update(true); - return String.format("Hosting on port %d.", port); + return new ChatMessage(null, String.format("Hosting on port %d.", port)); } public static void copyHostedServerUrl() { @@ -107,7 +104,7 @@ public class NetConnectUtil { SOptionPane.showMessageDialog("Share the following URL with anyone who wishes to join your server. It has been copied to your clipboard for convenience.\n\n" + url, "Server URL", SOptionPane.INFORMATION_ICON); } - public static String join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { + public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui); onlineLobby.setClient(client); @@ -118,7 +115,7 @@ public class NetConnectUtil { client.addLobbyListener(new ILobbyListener() { @Override public final void message(final String source, final String message) { - chatInterface.addMessage(formatMessage(source, message)); + chatInterface.addMessage(new ChatMessage(source, message)); } @Override public final void update(final GameLobbyData state, final int slot) { @@ -154,20 +151,6 @@ public class NetConnectUtil { client.connect(hostname, port); - return String.format("Connected to %s:%d", hostname, port); - } - - private final static SimpleDateFormat inFormat = new SimpleDateFormat("HH:mm:ss"); - - public static String formatMessage(final String source, final String message) { - final String now = inFormat.format(new Date()); - final String toAdd; - if (source == null) { - toAdd = String.format("%n[%s] %s", now, message); - } - else { - toAdd = String.format("%n[%s] %s: %s", now, source, message); - } - return toAdd; + return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port)); } }