Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions Framework/Intersect.Framework.Core/Color.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using Intersect.Framework;
using Intersect.Localization;
using MessagePack;
Expand All @@ -7,9 +8,19 @@
namespace Intersect;

[MessagePackObject]
public partial class Color : IEquatable<Color>

Check warning on line 11 in Framework/Intersect.Framework.Core/Color.cs

View workflow job for this annotation

GitHub Actions / build

'Color' overrides Object.Equals(object o) but does not override Object.GetHashCode()

Check warning on line 11 in Framework/Intersect.Framework.Core/Color.cs

View workflow job for this annotation

GitHub Actions / build

'Color' defines operator == or operator != but does not override Object.GetHashCode()
{

private static readonly Dictionary<string, Func<Color>> KnownColors;
static Color()
{
KnownColors = typeof(Color)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.PropertyType == typeof(Color) && p.GetMethod != null)
.ToDictionary(
p => p.Name.ToLowerInvariant(),
p => (Func<Color>)(() => p.GetMethod?.Invoke(null, null) as Color ?? Color.White)
);
}
public enum ChatColor
{

Expand Down Expand Up @@ -289,7 +300,7 @@
return new Color(a: a, r: r, g: g, b: b);
}

public static Color? FromString(string val, Color? defaultColor = default)
public static Color? FromCsv(string val, Color? defaultColor = default)
{
if (string.IsNullOrEmpty(val))
{
Expand All @@ -306,6 +317,27 @@
return new Color(parts[0], parts[1], parts[2], parts[3]);
}

public static Color? FromString(string val, Color? defaultColor = default)
{
if (string.IsNullOrWhiteSpace(val))
{
return defaultColor;
}

val = val.Trim().ToLowerInvariant();

if (KnownColors.TryGetValue(val, out Func<Color>? colorFunc))
{
return colorFunc();
}

Color? parsedColor = Color.FromHex(val);
parsedColor ??= Color.FromCsv(val);

return parsedColor ?? defaultColor;
}


public static implicit operator Color(string colorString) => FromString(colorString);

public static Color operator *(Color left, Color right) => new Color(
Expand Down
16 changes: 12 additions & 4 deletions Intersect.Client.Core/Interface/Game/EventWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Intersect.Client.Interface.Game.Typewriting;
using Intersect.Client.Localization;
using Intersect.Client.Networking;
using Intersect.Client.Utilities;
using Intersect.Configuration;
using Intersect.Enums;
using Intersect.Utilities;
Expand Down Expand Up @@ -160,18 +161,25 @@ private EventWindow(Canvas gameCanvas, Dialog dialog) : base(gameCanvas, nameof(
SkipRender();

_promptLabel.ClearText();
_promptLabel.AddText(_dialog.Prompt ?? string.Empty, _promptTemplateLabel);
_promptLabel.ForceImmediateRebuild();
var parsedText = TextColorParser.Parse(_dialog.Prompt ?? string.Empty, Color.White);

foreach (var segment in parsedText)
{
_promptLabel.AddText(segment.Text, segment.Color, Alignments.Left, _promptTemplateLabel.Font);
}

_ = _promptLabel.SizeToChildren();

_typewriting = ClientConfiguration.Instance.TypewriterEnabled &&
Globals.Database.TypewriterBehavior != TypewriterBehavior.Off;
if (_typewriting)
{
_promptLabel.ClearText();
_writer = new Typewriter(_dialog.Prompt ?? string.Empty, text => _promptLabel.AppendText(text, _promptTemplateLabel));
_writer = new Typewriter(parsedText.ToArray(), (text, color) =>
{
_promptLabel.AppendText(text, color, Alignments.Left, _promptTemplateLabel.Font);
});
}

Defer(
() =>
{
Expand Down
56 changes: 39 additions & 17 deletions Intersect.Client.Core/Interface/Game/Typewriting/Typewriter.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
using Intersect.Client.Core;
using Intersect.Client.Utilities;
using Intersect.Configuration;
using Intersect.Utilities;

namespace Intersect.Client.Interface.Game.Typewriting;

internal sealed class Typewriter
{
public delegate void TextWrittenHandler(string text);

public delegate void TextWrittenHandler(string text, Color color);
private static HashSet<string> FullStops => ClientConfiguration.Instance.TypewriterFullStops;
private static long FullStopSpeed => ClientConfiguration.Instance.TypewriterFullStopDelay;
private static HashSet<string> PartialStops => ClientConfiguration.Instance.TypewriterPauses;
private static long PartialStopSpeed => ClientConfiguration.Instance.TypewriterPauseDelay;
private static long TypingSpeed => ClientConfiguration.Instance.TypewriterPartDelay;

private int _offset;
private int _segmentIndex;
private string? _lastText;
private long _nextUpdateTime;

private readonly TextWrittenHandler _textWrittenHandler;
private readonly string _text;
private readonly ColorizedText[] _segments;

public bool IsDone { get; private set; }
public long DoneAtMilliseconds { get; private set; }

public Typewriter(string text, TextWrittenHandler textWrittenHandler)
public Typewriter(ColorizedText[] segments, TextWrittenHandler textWrittenHandler)
{
_text = text.ReplaceLineEndings("\n");
_segments = segments;
_textWrittenHandler = textWrittenHandler;
_nextUpdateTime = Timing.Global.MillisecondsUtc;

_segmentIndex = 0;
_offset = 0;
_lastText = null;

IsDone = false;
}

Expand All @@ -43,7 +43,7 @@ public void Write(string? soundName)
return;
}

if (_offset >= _text.Length)
if (_segmentIndex >= _segments.Length)
{
End();
return;
Expand All @@ -57,28 +57,44 @@ public void Write(string? soundName)
var emitSound = false;
while (_nextUpdateTime <= Timing.Global.MillisecondsUtc)
{
if (_offset >= _text.Length)
if (_segmentIndex >= _segments.Length)
{
End();
break;
return;
}

emitSound |= _offset % ClientConfiguration.Instance.TypewriterSoundFrequency == 0;

var segment = _segments[_segmentIndex];

if (_offset >= segment.Text.Length)
{
_offset = 0;
_segmentIndex++;

if (_segmentIndex >= _segments.Length)
{
End();
continue;
}

segment = _segments[_segmentIndex];
}

string nextText;
if (char.IsSurrogatePair(_text, _offset))
if (char.IsSurrogatePair(segment.Text, _offset))
{
nextText = _text[_offset..(_offset + 2)];
nextText = segment.Text[_offset..(_offset + 2)];
_offset += 2;
}
else
{
nextText = _text[_offset..(_offset + 1)];
nextText = segment.Text[_offset..(_offset + 1)];
++_offset;
}

_nextUpdateTime += GetTypingDelayFor(nextText, _lastText);
_textWrittenHandler(nextText);
_textWrittenHandler(nextText, segment.Color);
_lastText = nextText;
}

Expand Down Expand Up @@ -118,14 +134,20 @@ private static long GetTypingDelayFor(string next, string? last)

public void End()
{
if (IsDone || _text.Length < 1)
if (IsDone)
{
return;
}

if (_offset < _text.Length)
while (_segmentIndex < _segments.Length)
{
_textWrittenHandler(_text[_offset..]);
var segment = _segments[_segmentIndex];
if (_offset < segment.Text.Length)
{
_textWrittenHandler(segment.Text[_offset..], segment.Color);
}
_segmentIndex++;
_offset = 0;
}

IsDone = true;
Expand Down
47 changes: 47 additions & 0 deletions Intersect.Client.Core/Utilities/TextColorParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Reflection;
using System.Text.RegularExpressions;

namespace Intersect.Client.Utilities
{
public static class TextColorParser
{
private static readonly Regex ColorTagRegex = new(@"(\\c{[^}]*})", RegexOptions.Compiled);

public static List<ColorizedText> Parse(string text, Color defaultColor)
{
var segments = ColorTagRegex.Split(text)
.Where(s => !string.IsNullOrWhiteSpace(s))
.ToArray();

var output = new List<ColorizedText>(segments.Length);
var currentColor = defaultColor;

foreach (var segment in segments)
{
if (segment.StartsWith("\\c{") && segment.EndsWith("}"))
{
string colorCode = segment[3..^1].ToLowerInvariant();
currentColor = Color.FromString(colorCode, defaultColor);
}
else
{
output.Add(new ColorizedText(segment, currentColor));
}
}

return output;
}
}

public class ColorizedText
{
public string Text { get; }
public Color Color { get; }

public ColorizedText(string text, Color color)
{
Text = text;
Color = color;
}
}
}
4 changes: 2 additions & 2 deletions Intersect.Client.Framework/Gwen/Control/RichLabel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ public void AppendText(string text, Color? color, Alignments alignment, GameFont
var appendAlignment = alignment;
var appendFont = font ?? lastTextBlock.Font;

if (appendAlignment != lastTextBlock.Alignment &&
appendColor != lastTextBlock.Color &&
if (appendAlignment != lastTextBlock.Alignment ||
appendColor != lastTextBlock.Color ||
appendFont != lastTextBlock.Font)
{
AddText(text, color, alignment, font);
Expand Down
Loading