перенос файлов клиента из папки White в _White

This commit is contained in:
Remuchi
2024-01-28 17:32:55 +07:00
parent c80b10a688
commit 21dbccfec9
139 changed files with 345 additions and 434 deletions

View File

@@ -0,0 +1,265 @@
using System.Numerics;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Collections;
using Robust.Shared.Utility;
namespace Content.Client._White.UserInterface.Controls;
internal struct ChatRichTextEntry
{
private static readonly Color DefaultColor = new(200, 200, 200);
private readonly MarkupTagManager _tagManager;
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 = new();
public ChatRichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager)
{
Message = message;
Height = 0;
Width = 0;
LineBreaks = default;
_tagManager = tagManager;
var nodeIndex = -1;
foreach (var node in Message.Nodes)
{
nodeIndex++;
if (node.Name == null)
continue;
if (!_tagManager.TryGetMarkupTag(node.Name, null, out var tag) || !tag.TryGetControl(node, out var control))
continue;
parent.Children.Add(control);
_tagControls.Add(nodeIndex, control);
}
}
/// <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>
public void Update(Font defaultFont, float maxSizeX, float uiScale)
{
// 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 WordWrap(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.Nodes)
{
nodeIndex++;
var text = ProcessNode(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.TryGetValue(nodeIndex, out var control))
continue;
if (ProcessRune(ref this, new Rune(' '), out breakLine))
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 ChatRichTextEntry 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 ChatRichTextEntry 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 ChatRichTextEntry src, int? line)
{
if (line is { } l)
{
src.LineBreaks.Add(l);
if (!context.Font.TryPeek(out var font))
font = defaultFont;
src.Height += font.GetLineHeight(uiScale);
}
}
}
public readonly void Draw(
DrawingHandleScreen handle,
Font defaultFont,
UIBox2 drawBox,
float verticalOffset,
MarkupDrawingContext context,
float uiScale)
{
context.Clear();
context.Color.Push(DefaultColor);
context.Font.Push(defaultFont);
//handle.UseShader(MakeNewShader(false, 16));
var globalBreakCounter = 0;
var lineBreakIndex = 0;
var baseLine = drawBox.TopLeft + new Vector2(0, defaultFont.GetAscent(uiScale) + verticalOffset);
var controlYAdvance = 0;
var nodeIndex = -1;
foreach (var node in Message.Nodes)
{
nodeIndex++;
var text = ProcessNode(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 + font.GetLineHeight(uiScale) + controlYAdvance);
controlYAdvance = 0;
lineBreakIndex += 1;
}
// Костыльно
// Пока поставлю на 0.4. Потом надо адаптировать на 0.5
// MENTION: Это старый вариант обводки с добавлением задней буквы
//var baseLineBackground = new Vector2(
// baseLine.X - (font.GetLineHeight(uiScale+0.4f)/font.GetLineHeight(uiScale)),
// baseLine.Y + (font.GetLineHeight(uiScale+0.4f)/(font.GetLineHeight(uiScale)/2.5f))
//);
//font.DrawChar(handle, rune, baseLineBackground, uiScale+0.5f, new Color(0,0,0)); // или 0.4f
// MENTION2: Новый варик с шейдером. Шейдер кривой, а код грязный
//handle.UseShader(MakeNewShader()); // Ах да, я же шейдеры оставил в ебанинах ахах
// Также именно в этом файле пишем приколы со шрифтами.
// на уровне отрисовки лол.
//var sprite_icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "NoId");
//var _iconTexture = IoCManager.Resolve<IEntityManager>().EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite_icon);
var advance = font.DrawChar(handle, rune, baseLine, uiScale, color, true);
baseLine += new Vector2(advance, 0);
//handle.DrawTextureRect(_iconTexture, new UIBox2(baseLine += new Vector2(0, 5), new Vector2(6, 6)));
//baseLine += new Vector2(10, 0);
globalBreakCounter += 1;
}
if (!_tagControls.TryGetValue(nodeIndex, out var control))
continue;
// Почему-то не работает лол
//control.Position = new Vector2(baseLine.X, baseLine.Y - defaultFont.GetAscent(uiScale));
control.Measure(new Vector2(Width, Height));
var advanceX = control.DesiredPixelSize.X;
controlYAdvance = Math.Max(0, control.DesiredPixelSize.Y - font.GetLineHeight(uiScale));
baseLine += new Vector2(advanceX, 0);
}
}
// Unused shader maker
//private ShaderInstance MakeNewShader()
//{
// var shaderName = "SelectionOutlineBlack";
//
// var instance = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>(shaderName).InstanceUnique();
// //instance.SetParameter("outline_width", 1f);
// //instance.SetParameter("SCREEN_TEXTURE", viewport.RenderTarget.Texture);
// return instance;
//}
private readonly string ProcessNode(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, null, out var tag))
return "";
if (!node.Closing)
{
tag.PushDrawContext(node, context);
return tag.TextBefore(node);
}
tag.PopDrawContext(node, context);
return tag.TextAfter(node);
}
}

View File

@@ -0,0 +1,73 @@
using System.Diagnostics.Contracts;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Utility;
namespace Content.Client._White.UserInterface.Controls;
public class ChatRichTextLabel : Control
{
[Dependency] private readonly MarkupTagManager _tagManager = default!;
private FormattedMessage? _message;
private ChatRichTextEntry _entry;
public ChatRichTextLabel()
{
IoCManager.InjectDependencies(this);
}
public void SetMessage(FormattedMessage message)
{
_message = message;
_entry = new ChatRichTextEntry(_message, this, _tagManager);
InvalidateMeasure();
}
public void SetMessage(string message)
{
var msg = new FormattedMessage();
msg.AddText(message);
SetMessage(msg);
}
public string? GetMessage() => _message?.ToMarkup();
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (_message == null)
{
return Vector2.Zero;
}
var font = _getFont();
_entry.Update(font, availableSize.X * UIScale, UIScale);
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_message == null)
{
return;
}
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale);
}
[Pure]
private Font _getFont()
{
if (TryGetStyleProperty<Font>("font", out var font))
{
return font;
}
return UserInterfaceManager.ThemeDefaults.DefaultFont;
}
}

View File

@@ -0,0 +1,170 @@
using System.Diagnostics.Contracts;
using System.Text;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
namespace Content.Client._White.UserInterface.Controls;
internal struct WordWrap
{
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 WordWrap(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('-');
}
}

View File

@@ -1,7 +1,4 @@
<Control
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Radial">
<Control xmlns="https://spacestation14.io">
<BoxContainer>
<TextureButton
Name="Controller"

View File

@@ -1,10 +1,10 @@
<controls:RadialContainer
<radial1:RadialContainer
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Radial"
xmlns:radial1="clr-namespace:Content.Client._White.UserInterface.Radial"
Visible="False"
MaxSize="0 0">
<BoxContainer>
<controls:RadialButton
<radial1:RadialButton
Name="CloseButton"
Access="Public"
VerticalExpand="True"
@@ -14,4 +14,4 @@
<LayoutContainer Access="Public" Name="Layout" HorizontalExpand="True" VerticalExpand="True"></LayoutContainer>
<RichTextLabel Access="Public" Name="ActionLabel" HorizontalExpand="True" Visible="False"/>
</BoxContainer>
</controls:RadialContainer>
</radial1:RadialContainer>

View File

@@ -1,7 +1,6 @@
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Content.Client.Message;
using Content.Client.Resources;
using Robust.Client.Animations;
@@ -222,7 +221,7 @@ public partial class RadialContainer : Control
button.Controller.OnMouseEntered += (_) =>
{
PlaySizeAnimation(button, _focusSize, OutSizeAnimationKey, InSizeAnimationKey);
ActionLabel.SetMarkup(button.Content ?? string.Empty);
RichTextLabelExt.SetMarkup(ActionLabel, button.Content ?? string.Empty);
ActionLabel.Visible = IsAction;
};
button.Controller.OnMouseExited += (_) =>
@@ -237,7 +236,7 @@ public partial class RadialContainer : Control
CloseButton.Controller.OnMouseEntered += (_) =>
{
PlaySizeAnimation(CloseButton, _focusSize, OutSizeAnimationKey, InSizeAnimationKey);
ActionLabel.SetMarkup(CloseButton.Content ?? string.Empty);
RichTextLabelExt.SetMarkup(ActionLabel, CloseButton.Content ?? string.Empty);
ActionLabel.Visible = true;
};
@@ -405,10 +404,10 @@ public partial class RadialContainer : Control
{
var distance = FocusSize * 1.2f;
if (Layout.Children.Count() <= MaxButtons)
if (Enumerable.Count<Control>(Layout.Children) <= MaxButtons)
return distance + offset;
for (var i = 0; i < (Layout.Children.Count() - MaxButtons); i++)
for (var i = 0; i < (Enumerable.Count<Control>(Layout.Children) - MaxButtons); i++)
{
distance += (NormalSize/3);
}