реверт чатстака на время фиксов (#947)
* Revert "a (#939)" This reverts commit3239865364. * Revert "Chat Stack (#935)" This reverts commit79fd150bf0.
This commit is contained in:
parent
4252733127
commit
25b439a971
9 changed files with 13 additions and 1126 deletions
|
|
@ -27,28 +27,6 @@ namespace Content.Client.Chat.UI
|
|||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total time a speech bubble stays on screen.
|
||||
/// </summary>
|
||||
|
|
@ -228,17 +206,17 @@ namespace Content.Client.Chat.UI
|
|||
|
||||
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 @@ namespace Content.Client.Chat.UI
|
|||
{
|
||||
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 @@ namespace Content.Client.Chat.UI
|
|||
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 @@ namespace Content.Client.Chat.UI
|
|||
|
||||
//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,
|
||||
|
|
|
|||
|
|
@ -45,14 +45,6 @@ using Robust.Shared.Utility;
|
|||
|
||||
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 @@ public sealed class ChatUIController : UIController
|
|||
public ChatSelectChannel SelectableChannels { get; private set; }
|
||||
private ChatSelectChannel PreferredChannel { get; set; } = ChatSelectChannel.OOC;
|
||||
|
||||
public ChatMessage? LastMessage = null;
|
||||
|
||||
private readonly Dictionary<EntityUid, SpeechBubbleStack> _lastBubbles = new();
|
||||
|
||||
public event Action<ChatSelectChannel>? CanSendChannelsChanged;
|
||||
public event Action<ChatChannel>? FilterableChannelsChanged;
|
||||
public event Action<ChatSelectChannel>? SelectableChannelsChanged;
|
||||
|
|
@ -466,19 +454,9 @@ public sealed class ChatUIController : UIController
|
|||
|
||||
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 @@ public sealed class ChatUIController : UIController
|
|||
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 sealed class ChatUIController : UIController
|
|||
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 sealed class ChatUIController : UIController
|
|||
}
|
||||
}
|
||||
|
||||
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 sealed class ChatUIController : UIController
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<PanelContainer Name="ChatWindowPanel" Access="Public" HorizontalExpand="True" VerticalExpand="True"
|
||||
StyleClasses="StyleNano.StyleClassChatPanel">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True" VerticalExpand="True">
|
||||
<controls1:SunriseOutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" Margin="8 8 8 4" />
|
||||
<OutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" Margin="8 8 8 4" />
|
||||
<controls:ChatInputBox HorizontalExpand="True" Name="ChatInput" Access="Public" Margin="2"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
|
|
|||
|
|
@ -104,29 +104,6 @@ public partial class ChatBox : UIWidget
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<SunriseRichTextEntry> _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>("font", out var font))
|
||||
{
|
||||
return font;
|
||||
}
|
||||
|
||||
return UserInterfaceManager.ThemeDefaults.DefaultFont;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private StyleBox? _getStyleBox()
|
||||
{
|
||||
if (StyleBoxOverride != null)
|
||||
{
|
||||
return StyleBoxOverride;
|
||||
}
|
||||
|
||||
TryGetStyleProperty<StyleBox>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The vertical size of this entry, in pixels.
|
||||
/// </summary>
|
||||
public int Height;
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal size of this entry, in pixels.
|
||||
/// </summary>
|
||||
public int Width;
|
||||
|
||||
/// <summary>
|
||||
/// The combined text indices in the message's text tags to put line breaks.
|
||||
/// </summary>
|
||||
public ValueList<int> LineBreaks;
|
||||
|
||||
private readonly Dictionary<int, Control>? _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<int, Control>? 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<int, Control>();
|
||||
tagControls.Add(nodeIndex, control);
|
||||
}
|
||||
|
||||
_tagControls = tagControls;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculate line dimensions and where it has line breaks for word wrapping.
|
||||
/// </summary>
|
||||
/// <param name="defaultFont">The font being used for display.</param>
|
||||
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
|
||||
/// <param name="uiScale"></param>
|
||||
/// <param name="lineHeightScale"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T> : IList<T>
|
||||
{
|
||||
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<T>())
|
||||
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<T> 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<T>.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<T>())
|
||||
_items[_read] = default!;
|
||||
|
||||
_read = NextIndex(_read);
|
||||
}
|
||||
else if (index == length - 1)
|
||||
{
|
||||
_write = WrapInv(_write - 1);
|
||||
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
_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<T>.Default.Equals(origValue, result));
|
||||
|
||||
_write = WrapInv(_write - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static T ShiftDown(Span<T> 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<T>())
|
||||
toCopy.Clear();
|
||||
|
||||
_write += prevSize;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly SunriseRingBufferList<T> _ringBufferList;
|
||||
private int _readPos;
|
||||
|
||||
internal Enumerator(SunriseRingBufferList<T> 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<T>.Current => Current;
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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('-');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -37,7 +37,6 @@ namespace Content.Shared.Chat
|
|||
public Color? MessageColorOverride;
|
||||
public string? AudioPath;
|
||||
public float AudioVolume;
|
||||
public int repeat = 1;
|
||||
|
||||
[NonSerialized]
|
||||
public bool Read;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue