Rework chat line templates and logic

- Extract chat line templates and logic so they can be reused across widgets
- Make text notification styling entirely template driven (by removing chat color configuration and making color optional for `TextNotification`)
- Add a new TextNotificationsDisplay widget (based on and replacing ChatDisplayWidget)
- Add timestamp support to text notifications
This commit is contained in:
Ivaylo Draganov
2021-06-15 17:32:21 +03:00
committed by Matthias Mailänder
parent 8416dc3f2d
commit 9e92340ea7
20 changed files with 330 additions and 363 deletions

View File

@@ -21,16 +21,18 @@ namespace OpenRA
public readonly TextNotificationPool Pool;
public readonly string Prefix;
public readonly string Text;
public readonly Color PrefixColor;
public readonly Color TextColor;
public readonly Color? PrefixColor;
public readonly Color? TextColor;
public readonly DateTime Time;
public TextNotification(TextNotificationPool pool, string prefix, string text, Color prefixColor, Color textColor)
public TextNotification(TextNotificationPool pool, string prefix, string text, Color? prefixColor, Color? textColor)
{
Pool = pool;
Prefix = prefix;
Text = text;
PrefixColor = prefixColor;
TextColor = textColor;
Time = DateTime.Now;
}
public bool CanIncrementOnDuplicate()
@@ -38,19 +40,17 @@ namespace OpenRA
return Pool == TextNotificationPool.Feedback || Pool == TextNotificationPool.System;
}
public bool Equals(TextNotification other)
{
return other != null && other.GetHashCode() == GetHashCode();
}
public static bool operator ==(TextNotification me, TextNotification other) { return me.GetHashCode() == other.GetHashCode(); }
public override bool Equals(object obj)
{
return obj is TextNotification && Equals((TextNotification)obj);
}
public static bool operator !=(TextNotification me, TextNotification other) { return !(me == other); }
public bool Equals(TextNotification other) { return other == this; }
public override bool Equals(object obj) { return obj is TextNotification notification && Equals(notification); }
public override int GetHashCode()
{
return string.Format("{0}{1}{2}", Prefix, Text, Pool).GetHashCode();
return HashCode.Combine(Prefix, Text, (int)Pool);
}
}
}

View File

@@ -16,33 +16,29 @@ namespace OpenRA
{
public static class TextNotificationsManager
{
static Color systemMessageColor = Color.White;
static Color chatMessageColor = Color.White;
static string systemMessageLabel;
static readonly string SystemMessageLabel;
public static long ChatDisabledUntil { get; internal set; }
static TextNotificationsManager()
{
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
if (!ChromeMetrics.TryGet("SystemMessageLabel", out systemMessageLabel))
systemMessageLabel = "Battlefield Control";
if (!ChromeMetrics.TryGet("SystemMessageLabel", out SystemMessageLabel))
SystemMessageLabel = "Battlefield Control";
}
public static void AddFeedbackLine(string text)
{
AddTextNotification(TextNotificationPool.Feedback, systemMessageLabel, text, systemMessageColor, systemMessageColor);
AddTextNotification(TextNotificationPool.Feedback, SystemMessageLabel, text);
}
public static void AddSystemLine(string text)
{
AddSystemLine(systemMessageLabel, text);
AddSystemLine(SystemMessageLabel, text);
}
public static void AddSystemLine(string prefix, string text)
{
AddTextNotification(TextNotificationPool.System, prefix, text, systemMessageColor, systemMessageColor);
AddTextNotification(TextNotificationPool.System, prefix, text);
}
public static void AddChatLine(string prefix, string text, Color? prefixColor = null, Color? textColor = null)
@@ -58,7 +54,7 @@ namespace OpenRA
static void AddTextNotification(TextNotificationPool pool, string prefix, string text, Color? prefixColor = null, Color? textColor = null)
{
if (IsPoolEnabled(pool))
Game.OrderManager.AddTextNotification(new TextNotification(pool, prefix, text, prefixColor ?? chatMessageColor, textColor ?? chatMessageColor));
Game.OrderManager.AddTextNotification(new TextNotification(pool, prefix, text, prefixColor, textColor));
}
static bool IsPoolEnabled(TextNotificationPool pool)

View File

@@ -1,143 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets
{
public class ChatDisplayWidget : Widget
{
public readonly int RemoveTime = 0;
public readonly bool UseContrast = false;
public readonly bool UseShadow = false;
public readonly Color BackgroundColorDark = ChromeMetrics.Get<Color>("TextContrastColorDark");
public readonly Color BackgroundColorLight = ChromeMetrics.Get<Color>("TextContrastColorLight");
public string Notification = "";
public readonly int TextLineBoxHeight = 16;
public readonly int Space = 4;
const int LogLength = 9;
List<TextNotification> recentLines = new List<TextNotification>();
List<int> lineExpirations = new List<int>();
public override Rectangle EventBounds => Rectangle.Empty;
public override void Draw()
{
var pos = RenderOrigin;
var chatLogArea = new Rectangle(pos.X, pos.Y, Bounds.Width, Bounds.Height);
var chatPos = new int2(chatLogArea.X + 5, chatLogArea.Bottom - 8);
var font = Game.Renderer.Fonts["Regular"];
Game.Renderer.EnableScissor(chatLogArea);
foreach (var line in recentLines.AsEnumerable().Reverse())
{
var lineHeight = TextLineBoxHeight;
var inset = 0;
string name = null;
if (!string.IsNullOrEmpty(line.Prefix))
{
name = line.Prefix + ":";
inset = font.Measure(name).X + 5;
}
var text = WidgetUtils.WrapText(line.Text, chatLogArea.Width - inset - 6, font);
var textSize = font.Measure(text).Y;
var offset = font.TopOffset;
if (chatPos.Y - font.TopOffset < pos.Y)
break;
var textLineHeight = lineHeight;
var dh = textSize - textLineHeight;
if (dh > 0)
textLineHeight += dh;
var textOffset = textLineHeight - (textLineHeight - textSize - offset) / 2;
var textPos = new int2(chatPos.X + inset, chatPos.Y - textOffset);
if (name != null)
{
var nameSize = font.Measure(name).Y;
var namePos = chatPos.WithY(chatPos.Y - (textLineHeight - (lineHeight - nameSize - offset) / 2));
if (UseContrast)
font.DrawTextWithContrast(name, namePos,
line.PrefixColor, BackgroundColorDark, BackgroundColorLight, 1);
else if (UseShadow)
font.DrawTextWithShadow(name, namePos,
line.PrefixColor, BackgroundColorDark, BackgroundColorLight, 1);
else
font.DrawText(name, namePos, line.PrefixColor);
}
if (UseContrast)
font.DrawTextWithContrast(text, textPos,
line.TextColor, Color.Black, 1);
else if (UseShadow)
font.DrawTextWithShadow(text, textPos,
line.TextColor, Color.Black, 1);
else
font.DrawText(text, textPos, Color.White);
chatPos = chatPos.WithY(chatPos.Y - Space - textLineHeight);
}
Game.Renderer.DisableScissor();
}
public void AddLine(TextNotification chatLine)
{
recentLines.Add(chatLine);
lineExpirations.Add(Game.LocalTick + RemoveTime);
if (Notification != null)
Game.Sound.Play(SoundType.UI, Notification);
while (recentLines.Count > LogLength)
RemoveLine();
}
public void RemoveMostRecentLine()
{
if (recentLines.Count == 0)
return;
recentLines.RemoveAt(recentLines.Count - 1);
lineExpirations.RemoveAt(lineExpirations.Count - 1);
}
public void RemoveLine()
{
if (recentLines.Count == 0)
return;
recentLines.RemoveAt(0);
lineExpirations.RemoveAt(0);
}
public override void Tick()
{
if (RemoveTime == 0)
return;
// This takes advantage of the fact that recentLines is ordered by expiration, from sooner to later
while (recentLines.Count > 0 && Game.LocalTick >= lineExpirations[0])
RemoveLine();
}
}
}

View File

@@ -25,15 +25,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
readonly OrderManager orderManager;
readonly Ruleset modRules;
readonly World world;
readonly ContainerWidget chatOverlay;
readonly ChatDisplayWidget chatOverlayDisplay;
readonly TextNotificationsDisplayWidget chatOverlayDisplay;
readonly ContainerWidget chatChrome;
readonly ScrollPanelWidget chatScrollPanel;
readonly ContainerWidget chatTemplate;
readonly TextFieldWidget chatText;
readonly CachedTransform<int, string> chatDisabledLabel;
readonly Dictionary<TextNotificationPool, Widget> templates = new Dictionary<TextNotificationPool, Widget>();
readonly TabCompletionLogic tabCompletion = new TabCompletionLogic();
@@ -43,11 +44,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
int repetitions;
bool chatEnabled;
readonly bool isMenuChat;
[ObjectCreator.UseCtor]
public IngameChatLogic(Widget widget, OrderManager orderManager, World world, ModData modData, bool isMenuChat, Dictionary<string, MiniYaml> logicArgs)
{
this.orderManager = orderManager;
modRules = modData.DefaultRules;
this.isMenuChat = isMenuChat;
this.world = world;
var chatTraits = world.WorldActor.TraitsImplementing<INotifyChat>().ToArray();
var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot);
@@ -59,11 +64,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic
tabCompletion.Commands = chatTraits.OfType<ChatCommands>().SelectMany(x => x.Commands.Keys).ToList();
tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
if (logicArgs.TryGetValue("Templates", out var templateIds))
{
foreach (var item in templateIds.Nodes)
{
var key = FieldLoader.GetValue<TextNotificationPool>("key", item.Key);
templates[key] = Ui.LoadWidget(item.Value.Value, null, new WidgetArgs());
}
}
var chatPanel = (ContainerWidget)widget;
chatOverlay = chatPanel.GetOrNull<ContainerWidget>("CHAT_OVERLAY");
if (chatOverlay != null)
{
chatOverlayDisplay = chatOverlay.Get<ChatDisplayWidget>("CHAT_DISPLAY");
chatOverlayDisplay = chatOverlay.Get<TextNotificationsDisplayWidget>("CHAT_DISPLAY");
chatOverlay.Visible = false;
}
@@ -194,14 +208,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
chatScrollPanel = chatChrome.Get<ScrollPanelWidget>("CHAT_SCROLLPANEL");
chatTemplate = chatScrollPanel.Get<ContainerWidget>("CHAT_TEMPLATE");
chatScrollPanel.RemoveChildren();
chatScrollPanel.ScrollToBottom();
foreach (var chatLine in orderManager.NotificationsCache)
AddChatLine(chatLine, true);
orderManager.AddTextNotification += AddChatLineWrapper;
orderManager.AddTextNotification += AddNotificationWrapper;
chatText.IsDisabled = () => !chatEnabled || (world.IsReplay && !Game.Settings.Debug.EnableDebugCommandsInReplays);
@@ -248,7 +261,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Ui.ResetTooltips();
}
public void AddChatLineWrapper(TextNotification chatLine)
public void AddNotificationWrapper(TextNotification chatLine)
{
var chatLineToDisplay = chatLine;
@@ -263,53 +276,27 @@ namespace OpenRA.Mods.Common.Widgets.Logic
chatLine.TextColor);
chatScrollPanel.RemoveChild(chatScrollPanel.Children[chatScrollPanel.Children.Count - 1]);
chatOverlayDisplay?.RemoveMostRecentLine();
chatOverlayDisplay?.RemoveMostRecentNotification();
}
else
repetitions = 0;
lastLine = chatLine;
chatOverlayDisplay?.AddLine(chatLineToDisplay);
chatOverlayDisplay?.AddNotification(chatLineToDisplay);
// HACK: Force disable the chat notification sound for the in-menu chat dialog
// This works around our inability to disable the sounds for the in-game dialog when it is hidden
AddChatLine(chatLineToDisplay, chatOverlay == null);
}
void AddChatLine(TextNotification chatLine, bool suppressSound)
void AddChatLine(TextNotification notification, bool suppressSound)
{
var template = chatTemplate.Clone();
var nameLabel = template.Get<LabelWidget>("NAME");
var textLabel = template.Get<LabelWidget>("TEXT");
var name = "";
if (!string.IsNullOrEmpty(chatLine.Prefix))
name = chatLine.Prefix + ":";
var font = Game.Renderer.Fonts[nameLabel.Font];
var nameSize = font.Measure(chatLine.Prefix);
nameLabel.GetColor = () => chatLine.PrefixColor;
nameLabel.GetText = () => name;
nameLabel.Bounds.Width = nameSize.X;
textLabel.GetColor = () => chatLine.TextColor;
textLabel.Bounds.X += nameSize.X;
textLabel.Bounds.Width -= nameSize.X;
// Hack around our hacky wordwrap behavior: need to resize the widget to fit the text
var text = WidgetUtils.WrapText(chatLine.Text, textLabel.Bounds.Width, font);
textLabel.GetText = () => text;
var dh = font.Measure(text).Y - textLabel.Bounds.Height;
if (dh > 0)
{
textLabel.Bounds.Height += dh;
template.Bounds.Height += dh;
}
var chatLine = templates[notification.Pool].Clone();
WidgetUtils.SetupTextNotification(chatLine, notification, chatScrollPanel.Bounds.Width - chatScrollPanel.ScrollbarWidth, isMenuChat && !world.IsReplay);
var scrolledToBottom = chatScrollPanel.ScrolledToBottom;
chatScrollPanel.AddChild(template);
chatScrollPanel.AddChild(chatLine);
if (scrolledToBottom)
chatScrollPanel.ScrollToBottom(smooth: true);
@@ -343,7 +330,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
if (!disposed)
{
orderManager.AddTextNotification -= AddChatLineWrapper;
orderManager.AddTextNotification -= AddNotificationWrapper;
disposed = true;
}

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Widget newSpectatorTemplate;
readonly ScrollPanelWidget lobbyChatPanel;
readonly Widget chatTemplate;
readonly Dictionary<TextNotificationPool, Widget> chatTemplates = new Dictionary<TextNotificationPool, Widget>();
readonly TextFieldWidget chatTextField;
readonly CachedTransform<int, string> chatDisabledLabel;
@@ -398,6 +398,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (skirmishMode)
disconnectButton.Text = "Back";
if (logicArgs.TryGetValue("ChatTemplates", out var templateIds))
{
foreach (var item in templateIds.Nodes)
{
var key = FieldLoader.GetValue<TextNotificationPool>("key", item.Key);
chatTemplates[key] = Ui.LoadWidget(item.Value.Value, null, new WidgetArgs());
}
}
var chatMode = lobby.Get<ButtonWidget>("CHAT_MODE");
chatMode.GetText = () => teamChat ? "Team" : "All";
chatMode.OnClick = () => teamChat ^= true;
@@ -442,7 +451,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
chatDisabledLabel = new CachedTransform<int, string>(x => x > 0 ? $"Chat available in {x} seconds..." : "Chat Disabled");
lobbyChatPanel = lobby.Get<ScrollPanelWidget>("CHAT_DISPLAY");
chatTemplate = lobbyChatPanel.Get("CHAT_TEMPLATE");
lobbyChatPanel.RemoveChildren();
var settingsButton = lobby.GetOrNull<ButtonWidget>("SETTINGS_BUTTON");
@@ -510,13 +518,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
}
void AddChatLine(TextNotification chatLine)
void AddChatLine(TextNotification notification)
{
var template = (ContainerWidget)chatTemplate.Clone();
LobbyUtils.SetupChatLine(template, DateTime.Now, chatLine);
var chatLine = chatTemplates[notification.Pool].Clone();
WidgetUtils.SetupTextNotification(chatLine, notification, lobbyChatPanel.Bounds.Width - lobbyChatPanel.ScrollbarWidth, true);
var scrolledToBottom = lobbyChatPanel.ScrolledToBottom;
lobbyChatPanel.AddChild(template);
lobbyChatPanel.AddChild(chatLine);
if (scrolledToBottom)
lobbyChatPanel.ScrollToBottom(smooth: true);

View File

@@ -648,37 +648,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
HideChildWidget(parent, "STATUS_IMAGE");
}
public static void SetupChatLine(ContainerWidget template, DateTime time, TextNotification chatLine)
{
var nameLabel = template.Get<LabelWidget>("NAME");
var timeLabel = template.Get<LabelWidget>("TIME");
var textLabel = template.Get<LabelWidget>("TEXT");
var nameText = chatLine.Prefix + ":";
var font = Game.Renderer.Fonts[nameLabel.Font];
var nameSize = font.Measure(nameText);
timeLabel.GetText = () => $"{time.Hour:D2}:{time.Minute:D2}";
nameLabel.GetColor = () => chatLine.PrefixColor;
nameLabel.GetText = () => nameText;
nameLabel.Bounds.Width = nameSize.X;
textLabel.GetColor = () => chatLine.TextColor;
textLabel.Bounds.X += nameSize.X;
textLabel.Bounds.Width -= nameSize.X;
// Hack around our hacky wordwrap behavior: need to resize the widget to fit the text
var text = WidgetUtils.WrapText(chatLine.Text, textLabel.Bounds.Width, font);
textLabel.GetText = () => text;
var dh = font.Measure(text).Y - textLabel.Bounds.Height;
if (dh > 0)
{
textLabel.Bounds.Height += dh;
template.Bounds.Height += dh;
}
}
static void HideChildWidget(Widget parent, string widgetId)
{
var widget = parent.GetOrNull(widgetId);

View File

@@ -0,0 +1,136 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets
{
public class TextNotificationsDisplayWidget : Widget
{
public readonly int RemoveTime = 0;
public readonly int ItemSpacing = 4;
public readonly int BottomSpacing = 0;
public readonly int LogLength = 8;
public readonly bool HideOverflow = true;
public string ChatTemplate = "CHAT_LINE_TEMPLATE";
public string SystemTemplate = "SYSTEM_LINE_TEMPLATE";
public string MissionTemplate = "SYSTEM_LINE_TEMPLATE";
public string FeedbackTemplate = "SYSTEM_LINE_TEMPLATE";
readonly Dictionary<TextNotificationPool, Widget> templates = new Dictionary<TextNotificationPool, Widget>();
readonly List<int> expirations = new List<int>();
Rectangle overflowDrawBounds = Rectangle.Empty;
public override Rectangle EventBounds => Rectangle.Empty;
public override void Initialize(WidgetArgs args)
{
base.Initialize(args);
templates.Add(TextNotificationPool.Chat, Ui.LoadWidget(ChatTemplate, null, new WidgetArgs()));
templates.Add(TextNotificationPool.System, Ui.LoadWidget(SystemTemplate, null, new WidgetArgs()));
templates.Add(TextNotificationPool.Mission, Ui.LoadWidget(MissionTemplate, null, new WidgetArgs()));
templates.Add(TextNotificationPool.Feedback, Ui.LoadWidget(FeedbackTemplate, null, new WidgetArgs()));
// HACK: Assume that all templates use the same font
var lineHeight = Game.Renderer.Fonts[templates[TextNotificationPool.Chat].Get<LabelWidget>("TEXT").Font].Measure("").Y;
var wholeLines = (int)Math.Floor((double)((Bounds.Height - BottomSpacing) / lineHeight));
var visibleChildrenHeight = wholeLines * lineHeight;
overflowDrawBounds = new Rectangle(RenderOrigin.X, RenderOrigin.Y, Bounds.Width, Bounds.Height);
overflowDrawBounds.Y += Bounds.Height - visibleChildrenHeight;
overflowDrawBounds.Height = visibleChildrenHeight;
}
public override void DrawOuter()
{
if (!IsVisible() || Children.Count == 0)
return;
var mostRecentMessageOverflows = Bounds.Height < Children[Children.Count - 1].Bounds.Height;
if (mostRecentMessageOverflows && HideOverflow)
Game.Renderer.EnableScissor(overflowDrawBounds);
for (var i = Children.Count - 1; i >= 0; i--)
{
if (Bounds.Contains(Children[i].Bounds) || !HideOverflow || mostRecentMessageOverflows)
Children[i].DrawOuter();
if (mostRecentMessageOverflows)
break;
}
if (mostRecentMessageOverflows && HideOverflow)
Game.Renderer.DisableScissor();
}
public void AddNotification(TextNotification notification)
{
var notificationWidget = templates[notification.Pool].Clone();
WidgetUtils.SetupTextNotification(notificationWidget, notification, Bounds.Width, false);
if (Children.Count == 0)
notificationWidget.Bounds.Y = Bounds.Bottom - notificationWidget.Bounds.Height - BottomSpacing;
else
{
foreach (var line in Children)
line.Bounds.Y -= notificationWidget.Bounds.Height + ItemSpacing;
var lastLine = Children[Children.Count - 1];
notificationWidget.Bounds.Y = lastLine.Bounds.Bottom + ItemSpacing;
}
AddChild(notificationWidget);
expirations.Add(Game.LocalTick + RemoveTime);
while (Children.Count > LogLength)
RemoveNotification();
}
public void RemoveMostRecentNotification()
{
if (Children.Count == 0)
return;
var mostRecentChild = Children[Children.Count - 1];
RemoveChild(mostRecentChild);
expirations.RemoveAt(expirations.Count - 1);
for (var i = Children.Count - 1; i >= 0; i--)
Children[i].Bounds.Y += mostRecentChild.Bounds.Height + ItemSpacing;
}
private void RemoveNotification()
{
if (Children.Count == 0)
return;
RemoveChild(Children[0]);
expirations.RemoveAt(0);
}
public override void Tick()
{
if (RemoveTime == 0)
return;
// This takes advantage of the fact that recentLines is ordered by expiration, from sooner to later
while (Children.Count > 0 && Game.LocalTick >= expirations[0])
RemoveNotification();
}
}
}

View File

@@ -364,6 +364,62 @@ namespace OpenRA.Mods.Common.Widgets
return name.Update((p.PlayerName, p.WinState, clientState));
};
}
public static void SetupTextNotification(Widget notificationWidget, TextNotification notification, int boxWidth, bool withTimestamp)
{
var timeLabel = notificationWidget.GetOrNull<LabelWidget>("TIME");
var prefixLabel = notificationWidget.GetOrNull<LabelWidget>("PREFIX");
var textLabel = notificationWidget.Get<LabelWidget>("TEXT");
var textFont = Game.Renderer.Fonts[textLabel.Font];
var textWidth = boxWidth - notificationWidget.Bounds.X - textLabel.Bounds.X;
var hasPrefix = !string.IsNullOrEmpty(notification.Prefix) && prefixLabel != null;
var timeOffset = 0;
if (withTimestamp && timeLabel != null)
{
var time = $"{notification.Time.Hour:D2}:{notification.Time.Minute:D2}";
timeOffset = timeLabel.Bounds.Width + timeLabel.Bounds.X;
timeLabel.GetText = () => time;
textWidth -= timeOffset;
textLabel.Bounds.X += timeOffset;
if (hasPrefix)
prefixLabel.Bounds.X += timeOffset;
}
if (hasPrefix)
{
var prefix = notification.Prefix + ":";
var prefixSize = Game.Renderer.Fonts[prefixLabel.Font].Measure(prefix);
var prefixOffset = prefixSize.X + prefixLabel.Bounds.X;
prefixLabel.GetColor = () => notification.PrefixColor ?? prefixLabel.TextColor;
prefixLabel.GetText = () => prefix;
prefixLabel.Bounds.Width = prefixSize.X;
textWidth -= prefixOffset;
textLabel.Bounds.X += prefixOffset - timeOffset;
}
textLabel.GetColor = () => notification.TextColor ?? textLabel.TextColor;
textLabel.Bounds.Width = textWidth;
// Hack around our hacky wordwrap behavior: need to resize the widget to fit the text
var text = WrapText(notification.Text, textLabel.Bounds.Width, textFont);
textLabel.GetText = () => text;
var dh = textFont.Measure(text).Y - textLabel.Bounds.Height;
if (dh > 0)
{
textLabel.Bounds.Height += dh;
notificationWidget.Bounds.Height += dh;
}
notificationWidget.Bounds.Width = boxWidth - notificationWidget.Bounds.X;
}
}
public class CachedTransform<T, U>

View File

@@ -6,17 +6,22 @@ Container@CHAT_PANEL:
Logic: IngameChatLogic
OpenTeamChatKey: OpenTeamChat
OpenGeneralChatKey: OpenGeneralChat
Templates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
Children:
Container@CHAT_OVERLAY:
Width: PARENT_RIGHT - 24
Height: PARENT_BOTTOM - 25
Height: PARENT_BOTTOM - 30
Visible: false
Children:
ChatDisplay@CHAT_DISPLAY:
TextNotificationsDisplay@CHAT_DISPLAY:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
RemoveTime: 250
UseShadow: True
BottomSpacing: 3
Container@CHAT_CHROME:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
@@ -59,20 +64,3 @@ Container@CHAT_PANEL:
TopBottomSpacing: 3
ItemSpacing: 4
Align: Bottom
Children:
Container@CHAT_TEMPLATE:
X: 2
Width: PARENT_RIGHT - 27
Height: 16
Children:
Label@NAME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 12
Width: PARENT_RIGHT - 17
Height: 16
WordWrap: true
Shadow: True

View File

@@ -3,6 +3,11 @@ Container@CHAT_CONTAINER:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM - 20
Logic: IngameChatLogic
Templates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
Children:
Container@CHAT_CHROME:
X: 15
@@ -28,20 +33,3 @@ Container@CHAT_CONTAINER:
Height: PARENT_BOTTOM - 30
TopBottomSpacing: 3
ItemSpacing: 2
Children:
Container@CHAT_TEMPLATE:
X: 2
Width: PARENT_RIGHT - 27
Height: 16
Children:
Label@NAME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 12
Width: PARENT_RIGHT - 17
Height: 16
WordWrap: true
Shadow: True

View File

@@ -1,5 +1,10 @@
Container@SERVER_LOBBY:
Logic: LobbyLogic
ChatTemplates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - 560) / 2
Width: 900
@@ -99,29 +104,6 @@ Container@SERVER_LOBBY:
Height: PARENT_BOTTOM - 30
TopBottomSpacing: 3
ItemSpacing: 2
Children:
Container@CHAT_TEMPLATE:
Width: PARENT_RIGHT - 27
Height: 16
X: 2
Y: 0
Children:
Label@TIME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@NAME:
X: 45
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 55
Width: PARENT_RIGHT - 60
Height: 16
WordWrap: true
Shadow: True
Button@CHAT_MODE:
Y: PARENT_BOTTOM - HEIGHT
Width: 50

View File

@@ -138,6 +138,7 @@ ChromeLayout:
cnc|chrome/assetbrowser.yaml
cnc|chrome/missionbrowser.yaml
cnc|chrome/editor.yaml
common|chrome/text-notifications.yaml
Voices:
cnc|audio/voices.yaml

View File

@@ -6,17 +6,22 @@ Container@CHAT_PANEL:
Logic: IngameChatLogic
OpenTeamChatKey: OpenTeamChat
OpenGeneralChatKey: OpenGeneralChat
Templates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
Children:
Container@CHAT_OVERLAY:
Width: PARENT_RIGHT - 24
Height: PARENT_BOTTOM - 25
Height: PARENT_BOTTOM - 30
Visible: false
Children:
ChatDisplay@CHAT_DISPLAY:
TextNotificationsDisplay@CHAT_DISPLAY:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
RemoveTime: 250
UseShadow: True
BottomSpacing: 3
Container@CHAT_CHROME:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
@@ -54,20 +59,3 @@ Container@CHAT_PANEL:
TopBottomSpacing: 3
ItemSpacing: 4
Align: Bottom
Children:
Container@CHAT_TEMPLATE:
X: 2
Width: PARENT_RIGHT - 27
Height: 16
Children:
Label@NAME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 12
Width: PARENT_RIGHT - 17
Height: 16
WordWrap: true
Shadow: True

View File

@@ -3,6 +3,11 @@ Container@CHAT_CONTAINER:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM - 100
Logic: IngameChatLogic
Templates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
Children:
Container@CHAT_CHROME:
X: 20
@@ -28,20 +33,3 @@ Container@CHAT_CONTAINER:
Height: PARENT_BOTTOM - 30
TopBottomSpacing: 3
ItemSpacing: 2
Children:
Container@CHAT_TEMPLATE:
X: 2
Width: PARENT_RIGHT - 27
Height: 16
Children:
Label@NAME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 12
Width: PARENT_RIGHT - 17
Height: 16
WordWrap: true
Shadow: True

View File

@@ -1,5 +1,10 @@
Background@SERVER_LOBBY:
Logic: LobbyLogic
ChatTemplates:
Chat: CHAT_LINE_TEMPLATE
System: SYSTEM_LINE_TEMPLATE
Mission: SYSTEM_LINE_TEMPLATE
Feedback: SYSTEM_LINE_TEMPLATE
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - HEIGHT) / 2
Width: 900
@@ -103,28 +108,6 @@ Background@SERVER_LOBBY:
Height: PARENT_BOTTOM - 30
TopBottomSpacing: 2
ItemSpacing: 2
Children:
Container@CHAT_TEMPLATE:
X: 2
Width: PARENT_RIGHT - 27
Height: 16
Children:
Label@TIME:
X: 3
Width: 50
Height: 16
Shadow: True
Label@NAME:
X: 45
Width: 50
Height: 16
Shadow: True
Label@TEXT:
X: 55
Width: PARENT_RIGHT - 60
Height: 16
WordWrap: true
Shadow: True
Button@CHAT_MODE:
Y: PARENT_BOTTOM - HEIGHT
Width: 50

View File

@@ -0,0 +1,39 @@
Container@CHAT_LINE_TEMPLATE:
Width: PARENT_RIGHT
Height: 16
Children:
Label@TIME:
X: 5
Width: 37
Height: 16
Shadow: True
Label@PREFIX:
X: 5
Height: 16
Shadow: True
Label@TEXT:
X: 5
Height: 16
WordWrap: True
Shadow: True
Container@SYSTEM_LINE_TEMPLATE:
Width: PARENT_RIGHT
Height: 16
Children:
Label@TIME:
X: 5
Width: 37
Height: 16
Shadow: True
Label@PREFIX:
X: 5
Height: 16
Shadow: True
TextColor: FFFF00
Label@TEXT:
X: 5
Height: 16
WordWrap: True
Shadow: True
TextColor: FFFF00

View File

@@ -49,8 +49,6 @@ Metrics:
ChatLineSound: ChatLine
ClickDisabledSound: ClickDisabledSound
ClickSound: ClickSound
ChatMessageColor: FFFFFF
SystemMessageColor: FFFF00
NormalSelectionColor: FFFFFF
AltSelectionColor: 00FFFF
CtrlSelectionColor: FFFF00

View File

@@ -117,6 +117,7 @@ ChromeLayout:
common|chrome/replaybrowser.yaml
common|chrome/gamesave-browser.yaml
common|chrome/gamesave-loading.yaml
common|chrome/text-notifications.yaml
Weapons:
d2k|weapons/debris.yaml

View File

@@ -134,6 +134,7 @@ ChromeLayout:
common|chrome/confirmation-dialogs.yaml
common|chrome/editor.yaml
common|chrome/playerprofile.yaml
common|chrome/text-notifications.yaml
Weapons:
ra|weapons/explosions.yaml

View File

@@ -180,6 +180,7 @@ ChromeLayout:
common|chrome/missionbrowser.yaml
common|chrome/confirmation-dialogs.yaml
common|chrome/editor.yaml
common|chrome/text-notifications.yaml
Voices:
ts|audio/voices.yaml