diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index cbd22739cec..aa61e73e31c 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -27,28 +27,6 @@ public enum SpeechType : byte
Looc
}
- protected RichTextLabel? ContentLabel;
-
- public void UpdateText(ChatMessage message, int repeat)
- {
- if (ContentLabel == null)
- return;
-
- if (this is TextSpeechBubble)
- {
- var updatedMessage = $"{message.WrappedMessage} x{repeat}";
- ContentLabel.SetMessage(FormatSpeech(updatedMessage));
- }
- else if (this is FancyTextSpeechBubble)
- {
- var bubbleContent = SharedChatSystem.GetStringInsideTag(message, "BubbleContent");
- var updatedMessage = $"{bubbleContent} x{repeat}";
- ContentLabel.SetMessage(FormatSpeech(updatedMessage));
- }
-
- _timeLeft = TotalTime;
- }
-
///
/// The total time a speech bubble stays on screen.
///
@@ -228,17 +206,17 @@ public TextSpeechBubble(ChatMessage message, EntityUid senderEntity, string spee
protected override Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null)
{
- ContentLabel = new RichTextLabel
+ var label = new RichTextLabel
{
MaxWidth = SpeechMaxWidth,
};
- ContentLabel.SetMessage(FormatSpeech(message.WrappedMessage, fontColor));
+ label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor));
var panel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
- Children = { ContentLabel },
+ Children = { label },
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
};
@@ -258,17 +236,17 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
{
if (!ConfigManager.GetCVar(CCVars.ChatEnableFancyBubbles))
{
- ContentLabel = new RichTextLabel
+ var label = new RichTextLabel
{
MaxWidth = SpeechMaxWidth
};
- ContentLabel.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
+ label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
var unfanciedPanel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
- Children = { ContentLabel },
+ Children = { label },
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
};
return unfanciedPanel;
@@ -279,7 +257,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
Margin = new Thickness(1, 1, 1, 1)
};
- ContentLabel = new RichTextLabel
+ var bubbleContent = new RichTextLabel
{
MaxWidth = SpeechMaxWidth,
Margin = new Thickness(2, 6, 2, 2),
@@ -288,13 +266,13 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
//We'll be honest. *Yes* this is hacky. Doing this in a cleaner way would require a bottom-up refactor of how saycode handles sending chat messages. -Myr
bubbleHeader.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleHeader", fontColor));
- ContentLabel.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
+ bubbleContent.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
//As for below: Some day this could probably be converted to xaml. But that is not today. -Myr
var mainPanel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
- Children = { ContentLabel },
+ Children = { bubbleContent },
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Bottom,
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
index a12fc5a7bf9..e13fd8f3bec 100644
--- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
+++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
@@ -45,14 +45,6 @@
namespace Content.Client.UserInterface.Systems.Chat;
-public class SpeechBubbleStack
-{
- public required SpeechBubble Bubble { get; set; }
- public required string Message { get; set; }
- public int RepeatCount { get; set; } = 1;
- public SpeechBubble.SpeechType Type { get; set; }
-}
-
public sealed class ChatUIController : UIController
{
[Dependency] private readonly IClientAdminManager _admin = default!;
@@ -180,10 +172,6 @@ private readonly Dictionary _queuedSpeechBubbl
public ChatSelectChannel SelectableChannels { get; private set; }
private ChatSelectChannel PreferredChannel { get; set; } = ChatSelectChannel.OOC;
- public ChatMessage? LastMessage = null;
-
- private readonly Dictionary _lastBubbles = new();
-
public event Action? CanSendChannelsChanged;
public event Action? FilterableChannelsChanged;
public event Action? SelectableChannelsChanged;
@@ -466,19 +454,9 @@ private void AddSpeechBubble(ChatMessage msg, SpeechBubble.SpeechType speechType
private void CreateSpeechBubble(EntityUid entity, SpeechBubbleData speechData)
{
- var message = speechData.Message;
- var type = speechData.Type;
+ var bubble =
+ SpeechBubble.CreateSpeechBubble(speechData.Type, speechData.Message, entity);
- if (_lastBubbles.TryGetValue(entity, out var stack) &&
- stack.Message == message.WrappedMessage &&
- stack.Type == type)
- {
- stack.RepeatCount++;
- stack.Bubble.UpdateText(message, stack.RepeatCount);
- return;
- }
-
- var bubble = SpeechBubble.CreateSpeechBubble(type, message, entity);
bubble.OnDied += SpeechBubbleDied;
if (_activeSpeechBubbles.TryGetValue(entity, out var existing))
@@ -498,13 +476,6 @@ private void CreateSpeechBubble(EntityUid entity, SpeechBubbleData speechData)
existing.Add(bubble);
_speechBubbleRoot.AddChild(bubble);
- _lastBubbles[entity] = new SpeechBubbleStack
- {
- Bubble = bubble,
- Message = message.WrappedMessage,
- Type = type
- };
-
if (existing.Count > SpeechBubbleCap)
{
// Get the oldest to start fading fast.
@@ -540,9 +511,6 @@ public void RemoveSpeechBubble(EntityUid entityUid, SpeechBubble bubble)
var list = _activeSpeechBubbles[entityUid];
list.Remove(bubble);
- if (_lastBubbles.TryGetValue(entityUid, out var stack) && stack.Bubble == bubble)
- _lastBubbles.Remove(entityUid);
-
if (list.Count == 0)
{
_activeSpeechBubbles.Remove(entityUid);
@@ -912,22 +880,6 @@ public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
}
}
- if (LastMessage != null && msg.Message == LastMessage.Message)
- {
- LastMessage.repeat++;
- LastMessage.WrappedMessage = msg.WrappedMessage + $" x{LastMessage.repeat}";
-
- foreach (var chat in _chats)
- {
- chat.UpdateMessage(chat.GetHistoryLength() - 1, LastMessage);
- }
-
- TryBuble(msg, speechBubble);
- return;
- }
-
- LastMessage = msg;
-
// Log all incoming chat to repopulate when filter is un-toggled
if (!msg.HideChat)
{
@@ -946,11 +898,7 @@ public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
}
}
- TryBuble(msg, speechBubble);
- }
-
- private void TryBuble(ChatMessage msg, bool speechBubble)
- {
+ // Local messages that have an entity attached get a speech bubble.
if (!speechBubble || msg.SenderEntity == default)
return;
diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml
index 9b3a9840ddf..47286fbc26e 100644
--- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml
+++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml
@@ -2,7 +2,6 @@
xmlns="https://spacestation14.io"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls"
- xmlns:controls1="clr-namespace:Content.Client._Sunrise.UI.Controls"
MouseFilter="Stop"
HorizontalExpand="True"
VerticalExpand="True"
@@ -10,7 +9,7 @@
-
+
diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
index 54b372b546a..d55b3f01dfa 100644
--- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
@@ -104,29 +104,6 @@ private void OnChannelFilter(ChatChannel channel, bool active)
}
}
- public int GetHistoryLength()
- {
- return _controller.History.Count;
- }
-
- public void UpdateMessage(int index, ChatMessage message)
- {
- var updatedTuple = (_controller.History[index].Item1, message);
- _controller.History.Pop();
- _controller.History.Add(updatedTuple);
-
- var color = message.MessageColorOverride != null
- ? message.MessageColorOverride.Value
- : message.Channel.TextColor();
-
- var formatted = new FormattedMessage(3);
-
- formatted.AddMarkup(message.WrappedMessage);
- formatted.PushColor(color);
-
- Contents.UpdateLastMessage(formatted);
- }
-
public void AddLine(string message, Color color)
{
var formatted = new FormattedMessage(3);
diff --git a/Content.Client/_Sunrise/UI/Controls/SunriseOutputPanel.cs b/Content.Client/_Sunrise/UI/Controls/SunriseOutputPanel.cs
deleted file mode 100644
index 01ab0107360..00000000000
--- a/Content.Client/_Sunrise/UI/Controls/SunriseOutputPanel.cs
+++ /dev/null
@@ -1,289 +0,0 @@
-using System.Numerics;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.RichText;
-using Robust.Shared.Utility;
-
-namespace Content.Client._Sunrise.UI.Controls;
-
-[Virtual]
-public sealed class SunriseOutputPanel : Control
-{
- [Dependency] private readonly MarkupTagManager _tagManager = default!;
-
- public const string StylePropertyStyleBox = "stylebox";
-
- private readonly SunriseRingBufferList _entries = new();
- private bool _isAtBottom = true;
-
- private int _totalContentHeight;
- private bool _firstLine = true;
- private StyleBox? _styleBoxOverride;
- private VScrollBar _scrollBar;
-
- public bool ScrollFollowing { get; set; } = true;
-
- private bool _invalidOnVisible;
-
- public SunriseOutputPanel()
- {
- IoCManager.InjectDependencies(this);
- MouseFilter = Control.MouseFilterMode.Pass;
- RectClipContent = true;
-
- _scrollBar = new VScrollBar
- {
- Name = "_v_scroll",
- HorizontalAlignment = Control.HAlignment.Right
- };
- AddChild(_scrollBar);
- _scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
- }
-
- public int EntryCount => _entries.Count;
-
- public void UpdateLastMessage(FormattedMessage message)
- {
- var newEnt = new SunriseRichTextEntry(message, this, _tagManager, null);
- newEnt.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
- _entries[_entries.Count - 1] = newEnt;
- }
-
- public StyleBox? StyleBoxOverride
- {
- get => _styleBoxOverride;
- set
- {
- _styleBoxOverride = value;
- InvalidateMeasure();
- _invalidateEntries();
- }
- }
-
- public void Clear()
- {
- _firstLine = true;
- _entries.Clear();
- _totalContentHeight = 0;
- _scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
- _scrollBar.Value = 0;
- }
-
- public void RemoveEntry(Index index)
- {
- var entry = _entries[index];
- _entries.RemoveAt(index.GetOffset(_entries.Count));
-
- var font = _getFont();
- _totalContentHeight -= entry.Height + font.GetLineSeparation(UIScale);
- if (_entries.Count == 0)
- {
- Clear();
- }
-
- _scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
- }
-
- public void AddText(string text)
- {
- var msg = new FormattedMessage();
- msg.AddText(text);
- AddMessage(msg);
- }
-
- public void AddMessage(FormattedMessage message)
- {
- var entry = new SunriseRichTextEntry(message, this, _tagManager, null);
-
- entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
-
- _entries.Add(entry);
- var font = _getFont();
- _totalContentHeight += entry.Height;
- if (_firstLine)
- {
- _firstLine = false;
- }
- else
- {
- _totalContentHeight += font.GetLineSeparation(UIScale);
- }
-
- _scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
- if (_isAtBottom && ScrollFollowing)
- {
- _scrollBar.MoveToEnd();
- }
- }
-
- public void ScrollToBottom()
- {
- _scrollBar.MoveToEnd();
- _isAtBottom = true;
- }
-
- protected override void Draw(DrawingHandleScreen handle)
- {
- base.Draw(handle);
-
- var style = _getStyleBox();
- var font = _getFont();
- var lineSeparation = font.GetLineSeparation(UIScale);
- style?.Draw(handle, PixelSizeBox, UIScale);
- var contentBox = _getContentBox();
-
- var entryOffset = -_scrollBar.Value;
-
- // A stack for format tags.
- // This stack contains the format tag to RETURN TO when popped off.
- // So when a new color tag gets hit this stack gets the previous color pushed on.
- var context = new MarkupDrawingContext(2);
-
- foreach (ref var entry in _entries)
- {
- if (entryOffset + entry.Height < 0)
- {
- // Controls within the entry are the children of this control, which means they are drawn separately
- // after this Draw call, so we have to mark them as invisible to prevent them from being drawn.
- //
- // An alternative option is to ensure that the control position updating logic in entry.Draw is always
- // run, and then setting RectClipContent = true to use scissor box testing to handle the controls
- // visibility
- entry.HideControls();
- entryOffset += entry.Height + lineSeparation;
- continue;
- }
-
- if (entryOffset > contentBox.Height)
- {
- entry.HideControls();
- continue;
- }
-
- entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale);
-
- entryOffset += entry.Height + lineSeparation;
- }
- }
-
- protected override void MouseWheel(GUIMouseWheelEventArgs args)
- {
- base.MouseWheel(args);
-
- if (MathHelper.CloseToPercent(0, args.Delta.Y))
- {
- return;
- }
-
- _scrollBar.ValueTarget -= _getScrollSpeed() * args.Delta.Y;
- }
-
- protected override void Resized()
- {
- base.Resized();
-
- var styleBoxSize = _getStyleBox()?.MinimumSize.Y ?? 0;
-
- _scrollBar.Page = UIScale * (Height - styleBoxSize);
- _invalidateEntries();
- }
-
- protected override Vector2 MeasureOverride(Vector2 availableSize)
- {
- return _getStyleBox()?.MinimumSize ?? Vector2.Zero;
- }
-
- private void _invalidateEntries()
- {
- _totalContentHeight = 0;
- var font = _getFont();
- var sizeX = _getContentBox().Width;
- foreach (ref var entry in _entries)
- {
- entry.Update(_tagManager, font, sizeX, UIScale);
- _totalContentHeight += entry.Height + font.GetLineSeparation(UIScale);
- }
-
- _scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
- if (_isAtBottom && ScrollFollowing)
- {
- _scrollBar.MoveToEnd();
- }
- }
-
- [System.Diagnostics.Contracts.Pure]
- private Font _getFont()
- {
- if (TryGetStyleProperty("font", out var font))
- {
- return font;
- }
-
- return UserInterfaceManager.ThemeDefaults.DefaultFont;
- }
-
- [System.Diagnostics.Contracts.Pure]
- private StyleBox? _getStyleBox()
- {
- if (StyleBoxOverride != null)
- {
- return StyleBoxOverride;
- }
-
- TryGetStyleProperty(StylePropertyStyleBox, out var box);
- return box;
- }
-
- [System.Diagnostics.Contracts.Pure]
- private float _getScrollSpeed()
- {
- // The scroll speed depends on the UI scale because the scroll bar is working with physical pixels.
- return GetScrollSpeed(_getFont(), UIScale);
- }
-
- [System.Diagnostics.Contracts.Pure]
- private UIBox2 _getContentBox()
- {
- var style = _getStyleBox();
- var box = style?.GetContentBox(PixelSizeBox, UIScale) ?? PixelSizeBox;
- box.Right = Math.Max(box.Left, box.Right - _scrollBar.DesiredPixelSize.X);
- return box;
- }
-
- protected override void UIScaleChanged()
- {
- // If this control isn't visible, don't invalidate entries immediately.
- // This saves invalidating the debug console if it's hidden,
- // which is a huge boon as auto-scaling changes UI scale a lot in that scenario.
- if (!VisibleInTree)
- _invalidOnVisible = true;
- else
- _invalidateEntries();
-
- base.UIScaleChanged();
- }
-
- internal static float GetScrollSpeed(Font font, float scale)
- {
- return font.GetLineHeight(scale) * 2;
- }
-
- protected override void EnteredTree()
- {
- base.EnteredTree();
- // Due to any number of reasons the entries may be invalidated if added when not visible in the tree.
- // e.g. the control has not had its UI scale set and the messages were added, but the
- // existing ones were valid when the UI scale was set.
- _invalidateEntries();
- }
-
- protected override void VisibilityChanged(bool newVisible)
- {
- if (newVisible && _invalidOnVisible)
- {
- _invalidateEntries();
- _invalidOnVisible = false;
- }
- }
-}
diff --git a/Content.Client/_Sunrise/UI/Controls/SunriseRichTextEntry.cs b/Content.Client/_Sunrise/UI/Controls/SunriseRichTextEntry.cs
deleted file mode 100644
index e84cfce9ed1..00000000000
--- a/Content.Client/_Sunrise/UI/Controls/SunriseRichTextEntry.cs
+++ /dev/null
@@ -1,260 +0,0 @@
-using System.Numerics;
-using System.Text;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.RichText;
-using Robust.Shared.Collections;
-using Robust.Shared.Utility;
-
-namespace Content.Client._Sunrise.UI.Controls;
-
-internal struct SunriseRichTextEntry
-{
- private readonly Color _defaultColor;
- private readonly Type[]? _tagsAllowed;
-
- public readonly FormattedMessage Message;
-
- ///
- /// The vertical size of this entry, in pixels.
- ///
- public int Height;
-
- ///
- /// The horizontal size of this entry, in pixels.
- ///
- public int Width;
-
- ///
- /// The combined text indices in the message's text tags to put line breaks.
- ///
- public ValueList LineBreaks;
-
- private readonly Dictionary? _tagControls;
-
- public SunriseRichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
- {
- Message = message;
- Height = 0;
- Width = 0;
- LineBreaks = default;
- _defaultColor = defaultColor ?? new(200, 200, 200);
- _tagsAllowed = tagsAllowed;
- Dictionary? tagControls = null;
-
- var nodeIndex = -1;
- foreach (var node in Message)
- {
- nodeIndex++;
-
- if (node.Name == null)
- continue;
-
- if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
- continue;
-
- parent.Children.Add(control);
- tagControls ??= new Dictionary();
- tagControls.Add(nodeIndex, control);
- }
-
- _tagControls = tagControls;
- }
-
- ///
- /// Recalculate line dimensions and where it has line breaks for word wrapping.
- ///
- /// The font being used for display.
- /// The maximum horizontal size of the container of this entry.
- ///
- ///
- public void Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
- {
- // This method is gonna suck due to complexity.
- // Bear with me here.
- // I am so deeply sorry for the person adding stuff to this in the future.
-
- Height = defaultFont.GetHeight(uiScale);
- LineBreaks.Clear();
-
- int? breakLine;
- var wordWrap = new SunriseWordWrap(maxSizeX);
- var context = new MarkupDrawingContext();
- context.Font.Push(defaultFont);
- context.Color.Push(_defaultColor);
-
- // Go over every node.
- // Nodes can change the markup drawing context and return additional text.
- // It's also possible for nodes to return inline controls. They get treated as one large rune.
- var nodeIndex = -1;
- foreach (var node in Message)
- {
- nodeIndex++;
- var text = ProcessNode(tagManager, node, context);
-
- if (!context.Font.TryPeek(out var font))
- font = defaultFont;
-
- // And go over every character.
- foreach (var rune in text.EnumerateRunes())
- {
- if (ProcessRune(ref this, rune, out breakLine))
- continue;
-
- // Uh just skip unknown characters I guess.
- if (!font.TryGetCharMetrics(rune, uiScale, out var metrics))
- continue;
-
- if (ProcessMetric(ref this, metrics, out breakLine))
- return;
- }
-
- if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
- continue;
-
- control.Measure(new Vector2(Width, Height));
-
- var desiredSize = control.DesiredPixelSize;
- var controlMetrics = new CharMetrics(
- 0, 0,
- desiredSize.X,
- desiredSize.X,
- desiredSize.Y);
-
- if (ProcessMetric(ref this, controlMetrics, out breakLine))
- return;
- }
-
- Width = wordWrap.FinalizeText(out breakLine);
- CheckLineBreak(ref this, breakLine);
-
- bool ProcessRune(ref SunriseRichTextEntry src, Rune rune, out int? outBreakLine)
- {
- wordWrap.NextRune(rune, out breakLine, out var breakNewLine, out var skip);
- CheckLineBreak(ref src, breakLine);
- CheckLineBreak(ref src, breakNewLine);
- outBreakLine = breakLine;
- return skip;
- }
-
- bool ProcessMetric(ref SunriseRichTextEntry src, CharMetrics metrics, out int? outBreakLine)
- {
- wordWrap.NextMetrics(metrics, out breakLine, out var abort);
- CheckLineBreak(ref src, breakLine);
- outBreakLine = breakLine;
- return abort;
- }
-
- void CheckLineBreak(ref SunriseRichTextEntry src, int? line)
- {
- if (line is { } l)
- {
- src.LineBreaks.Add(l);
- if (!context.Font.TryPeek(out var font))
- font = defaultFont;
-
- src.Height += GetLineHeight(font, uiScale, lineHeightScale);
- }
- }
- }
-
- internal readonly void HideControls()
- {
- if (_tagControls == null)
- return;
- foreach (var control in _tagControls.Values)
- {
- control.Visible = false;
- }
- }
-
- public readonly void Draw(
- MarkupTagManager tagManager,
- DrawingHandleBase handle,
- Font defaultFont,
- UIBox2 drawBox,
- float verticalOffset,
- MarkupDrawingContext context,
- float uiScale,
- float lineHeightScale = 1)
- {
- context.Clear();
- context.Color.Push(_defaultColor);
- context.Font.Push(defaultFont);
-
- var globalBreakCounter = 0;
- var lineBreakIndex = 0;
- var baseLine = drawBox.TopLeft + new Vector2(0, defaultFont.GetAscent(uiScale) + verticalOffset);
- var controlYAdvance = 0f;
-
- var nodeIndex = -1;
- foreach (var node in Message)
- {
- nodeIndex++;
- var text = ProcessNode(tagManager, node, context);
- if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
- {
- color = _defaultColor;
- font = defaultFont;
- }
-
- foreach (var rune in text.EnumerateRunes())
- {
- if (lineBreakIndex < LineBreaks.Count &&
- LineBreaks[lineBreakIndex] == globalBreakCounter)
- {
- baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance);
- controlYAdvance = 0;
- lineBreakIndex += 1;
- }
-
- var advance = font.DrawChar(handle, rune, baseLine, uiScale, color);
- baseLine += new Vector2(advance, 0);
-
- globalBreakCounter += 1;
- }
-
- if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
- continue;
-
- // Controls may have been previously hidden via HideControls due to being "out-of frame".
- // If this ever gets replaced with RectClipContents / scissor box testing, this can be removed.
- control.Visible = true;
-
- var invertedScale = 1f / uiScale;
- var pos = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
- LayoutContainer.SetPosition(control, pos);
- control.Measure(new Vector2(Width, Height));
- var advanceX = control.DesiredPixelSize.X;
- controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale);
- baseLine += new Vector2(advanceX, 0);
- }
- }
-
- private readonly string ProcessNode(MarkupTagManager tagManager, MarkupNode node, MarkupDrawingContext context)
- {
- // If a nodes name is null it's a text node.
- if (node.Name == null)
- return node.Value.StringValue ?? "";
-
- //Skip the node if there is no markup tag for it.
- if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
- return "";
-
- if (!node.Closing)
- {
- tag.PushDrawContext(node, context);
- return tag.TextBefore(node);
- }
-
- tag.PopDrawContext(node, context);
- return tag.TextAfter(node);
- }
-
- private static int GetLineHeight(Font font, float uiScale, float lineHeightScale)
- {
- var height = font.GetLineHeight(uiScale);
- return (int)(height * lineHeightScale);
- }
-}
diff --git a/Content.Client/_Sunrise/UI/Controls/SunriseRingBufferList.cs b/Content.Client/_Sunrise/UI/Controls/SunriseRingBufferList.cs
deleted file mode 100644
index 33d068f992d..00000000000
--- a/Content.Client/_Sunrise/UI/Controls/SunriseRingBufferList.cs
+++ /dev/null
@@ -1,295 +0,0 @@
-using System.Collections;
-using System.Runtime.CompilerServices;
-using Robust.Shared.Utility;
-
-namespace Content.Client._Sunrise.UI.Controls;
-
-public sealed class SunriseRingBufferList : IList
-{
- private T[] _items;
- private int _read;
- private int _write;
-
- public SunriseRingBufferList(int capacity)
- {
- _items = new T[capacity];
- }
-
- public SunriseRingBufferList()
- {
- _items = [];
- }
-
- public int Capacity => _items.Length;
-
- private bool IsFull => _items.Length == 0 || NextIndex(_write) == _read;
-
- public void Add(T item)
- {
- if (IsFull)
- Expand();
-
- DebugTools.Assert(!IsFull);
-
- _items[_write] = item;
- _write = NextIndex(_write);
- }
-
- public void Clear()
- {
- _read = 0;
- _write = 0;
- if (RuntimeHelpers.IsReferenceOrContainsReferences())
- Array.Clear(_items);
- }
-
- public bool Contains(T item)
- {
- return IndexOf(item) >= 0;
- }
-
- public void CopyTo(T[] array, int arrayIndex)
- {
- ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
-
- CopyTo(array.AsSpan(arrayIndex));
- }
-
- private void CopyTo(Span dest)
- {
- if (dest.Length < Count)
- throw new ArgumentException("Not enough elements in destination!");
-
- var i = 0;
- foreach (var item in this)
- {
- dest[i++] = item;
- }
- }
-
- public bool Remove(T item)
- {
- var index = IndexOf(item);
- if (index < 0)
- return false;
-
- RemoveAt(index);
- return true;
- }
-
- public int Count
- {
- get
- {
- var length = _write - _read;
- if (length >= 0)
- return length;
-
- return length + _items.Length;
- }
- }
-
- public bool IsReadOnly => false;
-
- public int IndexOf(T item)
- {
- var i = 0;
- foreach (var containedItem in this)
- {
- if (EqualityComparer.Default.Equals(item, containedItem))
- return i;
-
- i += 1;
- }
-
- return -1;
- }
-
- public void Insert(int index, T item)
- {
- throw new NotSupportedException();
- }
-
- public void RemoveAt(int index)
- {
- var length = Count;
- ArgumentOutOfRangeException.ThrowIfNegative(index);
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, length);
-
- if (index == 0)
- {
- if (RuntimeHelpers.IsReferenceOrContainsReferences())
- _items[_read] = default!;
-
- _read = NextIndex(_read);
- }
- else if (index == length - 1)
- {
- _write = WrapInv(_write - 1);
-
- if (RuntimeHelpers.IsReferenceOrContainsReferences())
- _items[_write] = default!;
- }
- else
- {
- // If past me had better foresight I wouldn't be spending so much effort writing this right now.
-
- var realIdx = RealIndex(index);
- var origValue = _items[realIdx];
- T result;
-
- if (realIdx < _read)
- {
- // Scenario one: to-remove index is after break.
- // One shift is needed.
- // v
- // X X X O X X
- // W R
- DebugTools.Assert(_write < _read);
-
- result = ShiftDown(_items.AsSpan()[realIdx.._write], default!);
- }
- else if (_write < _read)
- {
- // Scenario two: to-remove index is before break, but write is after.
- // Two shifts are needed.
- // v
- // X O X X X X
- // W R
-
- var fromEnd = ShiftDown(_items.AsSpan(0, _write), default!);
- result = ShiftDown(_items.AsSpan(realIdx), fromEnd);
- }
- else
- {
- // Scenario two: array is contiguous.
- // One shift is needed.
- // v
- // X X X X O O
- // R W
-
- result = ShiftDown(_items.AsSpan()[realIdx.._write], default!);
- }
-
- // Just make sure we didn't bulldozer something.
- DebugTools.Assert(EqualityComparer.Default.Equals(origValue, result));
-
- _write = WrapInv(_write - 1);
- }
- }
-
- private static T ShiftDown(Span span, T substitution)
- {
- if (span.Length == 0)
- return substitution;
-
- var first = span[0];
- span[1..].CopyTo(span[..^1]);
- span[^1] = substitution!;
- return first;
- }
-
- private T GetSlot(int index)
- {
- ArgumentOutOfRangeException.ThrowIfNegative(index);
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count);
-
- return _items[RealIndex(index)];
- }
-
- public T this[int index]
- {
- get => GetSlot(index);
- set => _items[RealIndex(index)] = value;
- }
-
- private int RealIndex(int index)
- {
- return Wrap(index + _read);
- }
-
- private int NextIndex(int index) => Wrap(index + 1);
-
- private int Wrap(int index)
- {
- if (index >= _items.Length)
- index -= _items.Length;
-
- return index;
- }
-
- private int WrapInv(int index)
- {
- if (index < 0)
- index = _items.Length - 1;
-
- return index;
- }
-
- private void Expand()
- {
- var prevSize = _items.Length;
- var newSize = Math.Max(4, prevSize * 2);
- Array.Resize(ref _items, newSize);
-
- if (_write >= _read)
- return;
-
- // Write is behind read pointer, so we need to copy the items to be after the read pointer.
- var toCopy = _items.AsSpan(0, _write);
- var copyDest = _items.AsSpan(prevSize);
- toCopy.CopyTo(copyDest);
-
- if (RuntimeHelpers.IsReferenceOrContainsReferences())
- toCopy.Clear();
-
- _write += prevSize;
- }
-
- public Enumerator GetEnumerator()
- {
- return new Enumerator(this);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public struct Enumerator : IEnumerator
- {
- private readonly SunriseRingBufferList _ringBufferList;
- private int _readPos;
-
- internal Enumerator(SunriseRingBufferList ringBufferList)
- {
- _ringBufferList = ringBufferList;
- _readPos = _ringBufferList._read - 1;
- }
-
- public bool MoveNext()
- {
- _readPos = _ringBufferList.NextIndex(_readPos);
- return _readPos != _ringBufferList._write;
- }
-
- public void Reset()
- {
- this = new Enumerator(_ringBufferList);
- }
-
- public ref T Current => ref _ringBufferList._items[_readPos];
-
- T IEnumerator.Current => Current;
- object? IEnumerator.Current => Current;
-
- void IDisposable.Dispose()
- {
- }
- }
-}
diff --git a/Content.Client/_Sunrise/UI/Controls/SunriseWordWrap.cs b/Content.Client/_Sunrise/UI/Controls/SunriseWordWrap.cs
deleted file mode 100644
index da14e701edd..00000000000
--- a/Content.Client/_Sunrise/UI/Controls/SunriseWordWrap.cs
+++ /dev/null
@@ -1,170 +0,0 @@
-using System.Diagnostics.Contracts;
-using System.Text;
-using Robust.Client.Graphics;
-using Robust.Shared.Utility;
-
-namespace Content.Client._Sunrise.UI.Controls;
-
-internal struct SunriseWordWrap
-{
- private readonly float _maxSizeX;
-
- public float MaxUsedWidth;
- // Index we put into the LineBreaks list when a line break should occur.
- public int BreakIndexCounter;
- public int NextBreakIndexCounter;
- // If the CURRENT processing word ends up too long, this is the index to put a line break.
- public (int index, float lineSize)? WordStartBreakIndex;
- // Word size in pixels.
- public int WordSizePixels;
- // The horizontal position of the text cursor.
- public int PosX;
- public Rune LastRune;
- // If a word is larger than maxSizeX, we split it.
- // We need to keep track of some data to split it into two words.
- public (int breakIndex, int wordSizePixels)? ForceSplitData = null;
-
- public SunriseWordWrap(float maxSizeX)
- {
- this = default;
- _maxSizeX = maxSizeX;
- LastRune = new Rune('A');
- }
-
- public void NextRune(Rune rune, out int? breakLine, out int? breakNewLine, out bool skip)
- {
- BreakIndexCounter = NextBreakIndexCounter;
- NextBreakIndexCounter += rune.Utf16SequenceLength;
-
- breakLine = null;
- breakNewLine = null;
- skip = false;
-
- if (IsWordBoundary(LastRune, rune) || rune == new Rune('\n'))
- {
- // Word boundary means we know where the word ends.
- if (PosX > _maxSizeX && LastRune != new Rune(' '))
- {
- DebugTools.Assert(WordStartBreakIndex.HasValue,
- "wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
- //Ensure the assert had a chance to run and then just return
- if (!WordStartBreakIndex.HasValue)
- return;
-
- // We ran into a word boundary and the word is too big to fit the previous line.
- // So we insert the line break BEFORE the last word.
- breakLine = WordStartBreakIndex!.Value.index;
- MaxUsedWidth = Math.Max(MaxUsedWidth, WordStartBreakIndex.Value.lineSize);
- PosX = WordSizePixels;
- }
-
- // Start a new word since we hit a word boundary.
- //wordSize = 0;
- WordSizePixels = 0;
- WordStartBreakIndex = (BreakIndexCounter, PosX);
- ForceSplitData = null;
-
- // Just manually handle newlines.
- if (rune == new Rune('\n'))
- {
- MaxUsedWidth = Math.Max(MaxUsedWidth, PosX);
- PosX = 0;
- WordStartBreakIndex = null;
- skip = true;
- breakNewLine = BreakIndexCounter;
- }
- }
-
- LastRune = rune;
- }
-
- public void NextMetrics(in CharMetrics metrics, out int? breakLine, out bool abort)
- {
- abort = false;
- breakLine = null;
-
- // Increase word size and such with the current character.
- var oldWordSizePixels = WordSizePixels;
- WordSizePixels += metrics.Advance;
- // TODO: Theoretically, does it make sense to break after the glyph's width instead of its advance?
- // It might result in some more tight packing but I doubt it'd be noticeable.
- // Also definitely even more complex to implement.
- PosX += metrics.Advance;
-
- if (PosX <= _maxSizeX)
- return;
-
- if (!ForceSplitData.HasValue)
- {
- ForceSplitData = (BreakIndexCounter, oldWordSizePixels);
- }
-
- // Oh hey we get to break a word that doesn't fit on a single line.
- if (WordSizePixels > _maxSizeX)
- {
- var (breakIndex, splitWordSize) = ForceSplitData.Value;
- if (splitWordSize == 0)
- {
- // Happens if there's literally not enough space for a single character so uh...
- // Yeah just don't.
- abort = true;
- return;
- }
-
- // Reset forceSplitData so that we can split again if necessary.
- ForceSplitData = null;
- breakLine = breakIndex;
- WordSizePixels -= splitWordSize;
- WordStartBreakIndex = null;
- MaxUsedWidth = Math.Max(MaxUsedWidth, _maxSizeX);
- PosX = WordSizePixels;
- }
- }
-
- public int FinalizeText(out int? breakLine)
- {
- // This needs to happen because word wrapping doesn't get checked for the last word.
- if (PosX > _maxSizeX)
- {
- if (!WordStartBreakIndex.HasValue)
- {
- Logger.Error(
- "Assert fail inside RichTextEntry.Update, " +
- "wordStartBreakIndex is null on method end w/ word wrap required. " +
- "Dumping relevant stuff. Send this to PJB.");
- // Logger.Error($"Message: {Message}");
- Logger.Error($"maxSizeX: {_maxSizeX}");
- Logger.Error($"maxUsedWidth: {MaxUsedWidth}");
- Logger.Error($"breakIndexCounter: {BreakIndexCounter}");
- Logger.Error("wordStartBreakIndex: null (duh)");
- Logger.Error($"wordSizePixels: {WordSizePixels}");
- Logger.Error($"posX: {PosX}");
- Logger.Error($"lastChar: {LastRune}");
- Logger.Error($"forceSplitData: {ForceSplitData}");
- // Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
-
- throw new Exception(
- "wordStartBreakIndex can only be null if the word begins at a new line," +
- "in which case this branch shouldn't be reached as" +
- "the word would be split due to being longer than a single line.");
- }
-
- breakLine = WordStartBreakIndex.Value.index;
- MaxUsedWidth = Math.Max(MaxUsedWidth, WordStartBreakIndex.Value.lineSize);
- }
- else
- {
- breakLine = null;
- MaxUsedWidth = Math.Max(MaxUsedWidth, PosX);
- }
-
- return (int)MaxUsedWidth;
- }
-
- [Pure]
- private static bool IsWordBoundary(Rune a, Rune b)
- {
- return a == new Rune(' ') || b == new Rune(' ') || a == new Rune('-') || b == new Rune('-');
- }
-
-}
diff --git a/Content.Shared/Chat/MsgChatMessage.cs b/Content.Shared/Chat/MsgChatMessage.cs
index 5bf6ff45718..85367ffb739 100644
--- a/Content.Shared/Chat/MsgChatMessage.cs
+++ b/Content.Shared/Chat/MsgChatMessage.cs
@@ -37,7 +37,6 @@ public sealed class ChatMessage
public Color? MessageColorOverride;
public string? AudioPath;
public float AudioVolume;
- public int repeat = 1;
[NonSerialized]
public bool Read;