Archived
forked from RDTKEditor/RDTKEditor
添加对 Core 的直接引用
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the interface for rich string styles.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
|
||||
public interface IRDRichStringStyle<TSelf> : IEqualityOperators<TSelf, TSelf, bool>, IEquatable<TSelf>
|
||||
where TSelf : IRDRichStringStyle<TSelf>
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare with the current object.</param>
|
||||
/// <returns>True if the specified object is equal to the current object; otherwise, false.</returns>
|
||||
bool Equals([NotNullWhen(true)] object? obj) => obj is TSelf e && Equals(e);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closing tag for the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag.</param>
|
||||
/// <returns>The closing tag for the specified name.</returns>
|
||||
static string GetCloseTag(string name) => $"</{name}>";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opening tag for the specified name and optional argument.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag.</param>
|
||||
/// <param name="arg">The optional argument for the tag.</param>
|
||||
/// <returns>The opening tag for the specified name and optional argument.</returns>
|
||||
static string GetOpenTag(string name, string? arg = null)=> arg is null ? $"<{name}>" : $"<{name}={arg}>";
|
||||
|
||||
/// <summary>
|
||||
/// Generates an XML tag representing the differences between two <see cref="RDDialoguePhraseStyle"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="before">The initial <see cref="RDDialoguePhraseStyle"/> instance.</param>
|
||||
/// <param name="after">The modified <see cref="RDDialoguePhraseStyle"/> instance.</param>
|
||||
/// <returns>A string containing the XML tag that represents the differences between the two instances.</returns>
|
||||
static abstract string GetXmlTag(TSelf before, TSelf after);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the property of the rich string style based on the provided name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the property to reset.</param>
|
||||
/// <returns>True if the property was successfully reset; otherwise, false.</returns>
|
||||
bool ResetProperty(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property of the rich string style based on the provided name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
/// <returns>True if the property was successfully set; otherwise, false.</returns>
|
||||
bool SetProperty(string name, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a tag to the specified string based on the provided name and boolean values.
|
||||
/// </summary>
|
||||
/// <param name="tag">The string to which the tag will be added.</param>
|
||||
/// <param name="name">The name of the tag.</param>
|
||||
/// <param name="before">A boolean value indicating whether the tag is before.</param>
|
||||
/// <param name="after">A boolean value indicating whether the tag is after.</param>
|
||||
static void TryAddTag(ref string tag, string name, bool before, bool after)
|
||||
{
|
||||
if (before != after)
|
||||
tag += after
|
||||
? GetOpenTag(name)
|
||||
: GetCloseTag(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a tag to the specified string based on the provided name and optional string values.
|
||||
/// </summary>
|
||||
/// <param name="tag">The string to which the tag will be added.</param>
|
||||
/// <param name="name">The name of the tag.</param>
|
||||
/// <param name="before">An optional string value indicating the tag before.</param>
|
||||
/// <param name="after">An optional string value indicating the tag after.</param>
|
||||
static void TryAddTag(ref string tag, string name, string? before, string? after)
|
||||
{
|
||||
if (before != after)
|
||||
tag += after is null
|
||||
? GetCloseTag(name)
|
||||
: before is null
|
||||
? GetOpenTag(name, after)
|
||||
: GetCloseTag(name) + GetOpenTag(name, after);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the style has a phrase.
|
||||
/// </summary>
|
||||
static abstract bool HasPhrase { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a line of rich text with a specific style.
|
||||
/// </summary>
|
||||
/// <typeparam name="TStyle">The type of the style applied to the rich text.</typeparam>
|
||||
public interface IRDRichTextLine<TStyle> where TStyle : IRDRichStringStyle<TStyle>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RDLine{TStyle}"/> at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the rich text line.</param>
|
||||
/// <returns>The rich text line at the specified index.</returns>
|
||||
RDLine<TStyle> this[Index index] { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RDLine{TStyle}"/> within the specified range.
|
||||
/// </summary>
|
||||
/// <param name="range">The range of the rich text lines.</param>
|
||||
/// <returns>The rich text lines within the specified range.</returns>
|
||||
RDLine<TStyle> this[Range range] { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the length of the rich text line.
|
||||
/// </summary>
|
||||
int Length { get; }
|
||||
/// <summary>
|
||||
/// Concatenates multiple <see cref="RDLine{TStyle}"/> instances into a single instance.
|
||||
/// </summary>
|
||||
/// <param name="lines">The rich text lines to concatenate.</param>
|
||||
/// <returns>A new <see cref="RDLine{TStyle}"/> containing the concatenated content.</returns>
|
||||
static abstract RDLine<TStyle> Concat(params RDLine<TStyle>[] lines);
|
||||
/// <summary>
|
||||
/// Deserializes a string into an <see cref="RDLine{TStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The string to deserialize.</param>
|
||||
/// <returns>A new <see cref="RDLine{TStyle}"/> containing the deserialized content.</returns>
|
||||
static abstract RDLine<TStyle> Deserialize(string text);
|
||||
/// <summary>
|
||||
/// Serializes the current <see cref="RDLine{TStyle}"/> instance to a string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the current <see cref="RDLine{TStyle}"/> instance.</returns>
|
||||
string Serialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Text;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a line of dialogue, which consists of multiple dialogue components.
|
||||
/// </summary>
|
||||
public class RDDialogueBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the character speaking the dialogue line.
|
||||
/// </summary>
|
||||
public string? Character { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the expression of the character.
|
||||
/// </summary>
|
||||
public string? Expression { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the content of the dialogue line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The content of the dialogue line, represented as an <see cref="RDLine{TStyle}"/>.
|
||||
/// </value>
|
||||
public RDLine<RDDialoguePhraseStyle> Content { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Serializes the dialogue line to a string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the dialogue line.</returns>
|
||||
public string Serialize()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (!string.IsNullOrWhiteSpace(Character))
|
||||
{
|
||||
sb.Append(Character);
|
||||
if (!string.IsNullOrWhiteSpace(Expression))
|
||||
{
|
||||
sb.Append('_').Append(Expression);
|
||||
}
|
||||
sb.Append(':');
|
||||
}
|
||||
sb.Append(Content.Serialize());
|
||||
return sb.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// Deserializes a string into a <see cref="RDDialogueBlock"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to deserialize.</param>
|
||||
/// <returns>A new <see cref="RDDialogueBlock"/> containing the deserialized content.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the input string is null.</exception>
|
||||
/// <exception cref="FormatException">Thrown when the input string has an invalid format.</exception>
|
||||
public static RDDialogueBlock Deserialize(string str)
|
||||
{
|
||||
str = str.Trim();
|
||||
RDDialogueBlock line = new();
|
||||
int mi = str.IndexOf(':');
|
||||
if (mi != -1)
|
||||
{
|
||||
string character = str[..mi];
|
||||
if (character.Contains('_'))
|
||||
{
|
||||
string[] parts = character.Split('_', 2);
|
||||
character = parts[0];
|
||||
line.Expression = parts[1];
|
||||
}
|
||||
line.Character = character;
|
||||
}
|
||||
line.Content = RDLine<RDDialoguePhraseStyle>.Deserialize(str[(mi + 1)..]);
|
||||
return line;
|
||||
}
|
||||
/// <summary>
|
||||
/// Deserializes a string into a <see cref="RDDialogueBlock"/> instance, using a lookup of valid expressions.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to deserialize.</param>
|
||||
/// <param name="expressions">A lookup of valid expressions for each character.</param>
|
||||
/// <returns>A new <see cref="RDDialogueBlock"/> containing the deserialized content.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the input string is null.</exception>
|
||||
/// <exception cref="FormatException">Thrown when the input string has an invalid format.</exception>
|
||||
public static RDDialogueBlock Deserialize(string str, ILookup<string, string> expressions)
|
||||
{
|
||||
str = str.Trim();
|
||||
RDDialogueBlock line = new();
|
||||
int mi = str.IndexOf(':');
|
||||
if (mi != -1)
|
||||
{
|
||||
string character = str[..mi];
|
||||
string expression = "";
|
||||
if (character.Contains('_'))
|
||||
{
|
||||
string[] parts = character.Split('_', 2);
|
||||
character = parts[0];
|
||||
expression = parts[1];
|
||||
}
|
||||
else
|
||||
character = str;
|
||||
if (!expressions.Contains(character))
|
||||
{
|
||||
line.Content = RDLine<RDDialoguePhraseStyle>.Deserialize(str);
|
||||
return line;
|
||||
}
|
||||
line.Character = character;
|
||||
if (expressions[character].Contains(expression))
|
||||
line.Expression = expression;
|
||||
}
|
||||
line.Content = RDLine<RDDialoguePhraseStyle>.Deserialize(str[(mi + 1)..]);
|
||||
return line;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{Character}({Expression}):{Content}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a list of dialogue lines.
|
||||
/// </summary>
|
||||
public class RDDialogueExchange : List<RDDialogueBlock>
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes the dialogue list to a string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the dialogue list.</returns>
|
||||
public string Serialize() => string.Join('\n', this.Select(i => i.Serialize()));
|
||||
/// <summary>
|
||||
/// Deserializes a string into a <see cref="RDDialogueExchange"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="text">The string to deserialize.</param>
|
||||
/// <returns>A new <see cref="RDDialogueExchange"/> containing the deserialized dialogue lines.</returns>
|
||||
public static RDDialogueExchange Deserialize(string text) => [.. text.Split("\r\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(RDDialogueBlock.Deserialize)];
|
||||
/// <summary>
|
||||
/// Deserializes a string into a <see cref="RDDialogueExchange"/> instance, using a lookup of valid expressions.
|
||||
/// </summary>
|
||||
/// <param name="text">The string to deserialize.</param>
|
||||
/// <param name="expressions">A lookup of valid expressions for each character.</param>
|
||||
/// <returns>A new <see cref="RDDialogueExchange"/> containing the deserialized dialogue lines.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the input string is null.</exception>
|
||||
/// <exception cref="FormatException">Thrown when the input string has an invalid format.</exception>
|
||||
public static RDDialogueExchange Deserialize(string text, ILookup<string, string> expressions) => [.. text.Split("\r\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(i => RDDialogueBlock.Deserialize(i, expressions))];
|
||||
///<inheritdoc/>
|
||||
public override string ToString() => string.Join('\n', this.Select(i => i.ToString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the style of a rich string.
|
||||
/// </summary>
|
||||
public struct RDDialoguePhraseStyle : IRDRichStringStyle<RDDialoguePhraseStyle>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text.
|
||||
/// </summary>
|
||||
public RDColor? Color { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the speed of the text animation.
|
||||
/// </summary>
|
||||
public float? Speed { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the volume of the text.
|
||||
/// </summary>
|
||||
public float? Volume { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the pitch of the text.
|
||||
/// </summary>
|
||||
public float? Pitch { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the pitch range of the text.
|
||||
/// </summary>
|
||||
public float? PitchRange { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should shake.
|
||||
/// </summary>
|
||||
public bool Shake { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the radius of the shake effect.
|
||||
/// </summary>
|
||||
public float? ShakeRadius { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should have a wave effect.
|
||||
/// </summary>
|
||||
public bool Wave { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the wave effect.
|
||||
/// </summary>
|
||||
public float? WaveHeight { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the speed of the wave effect.
|
||||
/// </summary>
|
||||
public float? WaveSpeed { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should have a swirl effect.
|
||||
/// </summary>
|
||||
public bool Swirl { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the radius of the swirl effect.
|
||||
/// </summary>
|
||||
public float? SwirlRadius { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the speed of the swirl effect.
|
||||
/// </summary>
|
||||
public float? SwirlSpeed { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should be sticky.
|
||||
/// </summary>
|
||||
public bool Sticky { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should be loud.
|
||||
/// </summary>
|
||||
public bool Loud { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should be bold.
|
||||
/// </summary>
|
||||
public bool Bold { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text should be whispered.
|
||||
/// </summary>
|
||||
public bool Whisper { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public static bool HasPhrase => true;
|
||||
/// <summary>
|
||||
/// Sets the property of the rich string style based on the provided name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
/// <returns>True if the property was successfully set; otherwise, false.</returns>
|
||||
public bool SetProperty(string name, string value)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "color":
|
||||
if (RDColor.TryFromName(value, out RDColor color))
|
||||
Color = color;
|
||||
else if (RDColor.TryFromRgba(value, out color))
|
||||
Color = color;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case "speed":
|
||||
Speed = float.Parse(value);
|
||||
break;
|
||||
case "volume":
|
||||
Volume = float.Parse(value);
|
||||
break;
|
||||
case "pitch":
|
||||
Pitch = float.Parse(value);
|
||||
break;
|
||||
case "pitchRange":
|
||||
PitchRange = float.Parse(value);
|
||||
break;
|
||||
case "shake":
|
||||
Shake = bool.Parse(value);
|
||||
break;
|
||||
case "shakeRadius":
|
||||
ShakeRadius = float.Parse(value);
|
||||
break;
|
||||
case "wave":
|
||||
Wave = bool.Parse(value);
|
||||
break;
|
||||
case "waveHeight":
|
||||
WaveHeight = float.Parse(value);
|
||||
break;
|
||||
case "waveSpeed":
|
||||
WaveSpeed = float.Parse(value);
|
||||
break;
|
||||
case "swirl":
|
||||
Swirl = bool.Parse(value);
|
||||
break;
|
||||
case "swirlRadius":
|
||||
SwirlRadius = float.Parse(value);
|
||||
break;
|
||||
case "swirlSpeed":
|
||||
SwirlSpeed = float.Parse(value);
|
||||
break;
|
||||
case "sticky":
|
||||
Sticky = bool.Parse(value);
|
||||
break;
|
||||
case "loud":
|
||||
Loud = bool.Parse(value);
|
||||
break;
|
||||
case "bold":
|
||||
Bold = bool.Parse(value);
|
||||
break;
|
||||
case "whisper":
|
||||
Whisper = bool.Parse(value);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes the property of the rich string style based on the provided name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the property to remove.</param>
|
||||
/// <returns>True if the property was successfully removed; otherwise, false.</returns>
|
||||
public bool ResetProperty(string name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "color":
|
||||
Color = null;
|
||||
break;
|
||||
case "speed":
|
||||
Speed = null;
|
||||
break;
|
||||
case "volume":
|
||||
Volume = null;
|
||||
break;
|
||||
case "pitch":
|
||||
Pitch = null;
|
||||
break;
|
||||
case "pitchRange":
|
||||
PitchRange = null;
|
||||
break;
|
||||
case "shake":
|
||||
Shake = false;
|
||||
break;
|
||||
case "shakeRadius":
|
||||
ShakeRadius = null;
|
||||
break;
|
||||
case "wave":
|
||||
Wave = false;
|
||||
break;
|
||||
case "waveHeight":
|
||||
WaveHeight = null;
|
||||
break;
|
||||
case "waveSpeed":
|
||||
WaveSpeed = null;
|
||||
break;
|
||||
case "swirl":
|
||||
Swirl = false;
|
||||
break;
|
||||
case "swirlRadius":
|
||||
SwirlRadius = null;
|
||||
break;
|
||||
case "swirlSpeed":
|
||||
SwirlSpeed = null;
|
||||
break;
|
||||
case "sticky":
|
||||
Sticky = false;
|
||||
break;
|
||||
case "loud":
|
||||
Loud = false;
|
||||
break;
|
||||
case "bold":
|
||||
Bold = false;
|
||||
break;
|
||||
case "whisper":
|
||||
Whisper = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static string GetXmlTag(RDDialoguePhraseStyle before, RDDialoguePhraseStyle after)
|
||||
{
|
||||
string tag = "";
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "color",
|
||||
before.Color?.TryGetName(out string[] namesbefore) == true
|
||||
? namesbefore[0].ToLower()
|
||||
: before.Color?.ToString(before.Color?.A == 255 ? "#RRGGBB" : "#RRGGBBAA"),
|
||||
after.Color?.TryGetName(out string[] namesafter) == true
|
||||
? namesafter[0].ToLower()
|
||||
: after.Color?.ToString(after.Color?.A == 255 ? "#RRGGBB" : "#RRGGBBAA"));
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "speed", before.Speed?.ToString(), after.Speed?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "volume", before.Volume?.ToString(), after.Volume?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "pitch", before.Pitch?.ToString(), after.Pitch?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "pitchRange", before.PitchRange?.ToString(), after.PitchRange?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "shake", before.Shake, after.Shake);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "shakeRadius", before.ShakeRadius?.ToString(), after.ShakeRadius?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "wave", before.Wave, after.Wave);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "waveHeight", before.WaveHeight?.ToString(), after.WaveHeight?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "waveSpeed", before.WaveSpeed?.ToString(), after.WaveSpeed?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "swirl", before.Swirl, after.Swirl);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "swirlRadius", before.SwirlRadius?.ToString(), after.SwirlRadius?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "swirlSpeed", before.SwirlSpeed?.ToString(), after.SwirlSpeed?.ToString());
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "sticky", before.Sticky, after.Sticky);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "loud", before.Loud, after.Loud);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "bold", before.Bold, after.Bold);
|
||||
IRDRichStringStyle<RDDialoguePhraseStyle>.TryAddTag(ref tag, "whisper", before.Whisper, after.Whisper);
|
||||
return tag;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static bool operator ==(RDDialoguePhraseStyle left, RDDialoguePhraseStyle right) =>
|
||||
left.Color == right.Color
|
||||
&& left.Speed == right.Speed
|
||||
&& left.Volume == right.Volume
|
||||
&& left.Pitch == right.Pitch
|
||||
&& left.PitchRange == right.PitchRange
|
||||
&& left.Shake == right.Shake
|
||||
&& left.ShakeRadius == right.ShakeRadius
|
||||
&& left.Wave == right.Wave
|
||||
&& left.WaveHeight == right.WaveHeight
|
||||
&& left.WaveSpeed == right.WaveSpeed
|
||||
&& left.Swirl == right.Swirl
|
||||
&& left.SwirlRadius == right.SwirlRadius
|
||||
&& left.SwirlSpeed == right.SwirlSpeed
|
||||
&& left.Sticky == right.Sticky
|
||||
&& left.Loud == right.Loud
|
||||
&& left.Bold == right.Bold
|
||||
&& left.Whisper == right.Whisper;
|
||||
/// <inheritdoc/>
|
||||
public static bool operator !=(RDDialoguePhraseStyle left, RDDialoguePhraseStyle right) => !(left == right);
|
||||
/// <inheritdoc/>
|
||||
public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is RDDialoguePhraseStyle e && base.Equals(e);
|
||||
/// <inheritdoc/>
|
||||
public readonly bool Equals(RDDialoguePhraseStyle other) => this == other;
|
||||
/// <inheritdoc/>
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum representing the different types of rich string events.
|
||||
/// </summary>
|
||||
public enum RDDialogueToneType
|
||||
{
|
||||
/// <summary>
|
||||
/// Static event type.
|
||||
/// </summary>
|
||||
Static,
|
||||
/// <summary>
|
||||
/// Flash event type.
|
||||
/// </summary>
|
||||
Flash,
|
||||
/// <summary>
|
||||
/// Very slow event type.
|
||||
/// </summary>
|
||||
VerySlow,
|
||||
/// <summary>
|
||||
/// Slow event type.
|
||||
/// </summary>
|
||||
Slow,
|
||||
/// <summary>
|
||||
/// Normal event type.
|
||||
/// </summary>
|
||||
Normal,
|
||||
/// <summary>
|
||||
/// Fast event type.
|
||||
/// </summary>
|
||||
Fast,
|
||||
/// <summary>
|
||||
/// Very fast event type.
|
||||
/// </summary>
|
||||
VeryFast,
|
||||
/// <summary>
|
||||
/// Very very fast event type.
|
||||
/// </summary>
|
||||
VeryVeryFast,
|
||||
/// <summary>
|
||||
/// Excited event type.
|
||||
/// </summary>
|
||||
Excited,
|
||||
/// <summary>
|
||||
/// Shout event type.
|
||||
/// </summary>
|
||||
Shout,
|
||||
/// <summary>
|
||||
/// Shake event type.
|
||||
/// </summary>
|
||||
Shake,
|
||||
/// <summary>
|
||||
/// Pause event type.
|
||||
/// </summary>
|
||||
Pause,
|
||||
}
|
||||
/// <summary>
|
||||
/// Class representing a rich string event.
|
||||
/// </summary>
|
||||
/// <param name="Type">Rich string event type.</param>
|
||||
/// <param name="Index">Rich string event index.</param>
|
||||
public record struct RDDialogueTone(RDDialogueToneType Type, int Index)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the pause duration for the dialogue event.
|
||||
/// </summary>
|
||||
public int? Pause { get; init; }
|
||||
/// <summary>
|
||||
/// Serializes the rich string event type to its corresponding string representation.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the rich string event type.</returns>
|
||||
/// <exception cref="NotImplementedException">Thrown when the event type is not implemented.</exception>
|
||||
public string Serialize() => "[" + Type switch
|
||||
{
|
||||
RDDialogueToneType.Static => "static",
|
||||
RDDialogueToneType.Flash => "flash",
|
||||
RDDialogueToneType.VerySlow => "vslow",
|
||||
RDDialogueToneType.Slow => "slow",
|
||||
RDDialogueToneType.Normal => "normal",
|
||||
RDDialogueToneType.Fast => "fast",
|
||||
RDDialogueToneType.VeryFast => "vfast",
|
||||
RDDialogueToneType.VeryVeryFast => "vvvfast",
|
||||
RDDialogueToneType.Excited => "excited",
|
||||
RDDialogueToneType.Shout => "shout",
|
||||
RDDialogueToneType.Shake => "shake",
|
||||
RDDialogueToneType.Pause => Pause?.ToString(),
|
||||
_ => throw new NotImplementedException(),
|
||||
} + "]";
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="RDDialogueTone"/> based on the provided type and index.
|
||||
/// </summary>
|
||||
/// <param name="type">The string representation of the event type.</param>
|
||||
/// <param name="index">The index of the event.</param>
|
||||
/// <param name="result">The created <see cref="RDDialogueTone"/> instance if successful, otherwise null.</param>
|
||||
/// <returns>True if the event was successfully created, otherwise false.</returns>
|
||||
public static bool Create(string type, int index, [NotNullWhen(true)] out RDDialogueTone? result)
|
||||
{
|
||||
RDDialogueToneType? eventType = type switch
|
||||
{
|
||||
"static" => RDDialogueToneType.Static,
|
||||
"flash" => RDDialogueToneType.Flash,
|
||||
"vslow" => RDDialogueToneType.VerySlow,
|
||||
"slow" => RDDialogueToneType.Slow,
|
||||
"normal" => RDDialogueToneType.Normal,
|
||||
"fast" => RDDialogueToneType.Fast,
|
||||
"vfast" => RDDialogueToneType.VeryFast,
|
||||
"vvvfast" => RDDialogueToneType.VeryVeryFast,
|
||||
"excited" => RDDialogueToneType.Excited,
|
||||
"shout" => RDDialogueToneType.Shout,
|
||||
"shake" => RDDialogueToneType.Shake,
|
||||
_ => null,
|
||||
};
|
||||
if (eventType is null)
|
||||
{
|
||||
if (int.TryParse(type, out int pause))
|
||||
{
|
||||
result = new(RDDialogueToneType.Pause, index) { Pause = pause };
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
result = new(eventType.Value, index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a list of rich text strings.
|
||||
/// </summary>
|
||||
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
|
||||
public struct RDLine<TStyle>()
|
||||
: IRDRichTextLine<TStyle>
|
||||
where TStyle : IRDRichStringStyle<TStyle>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the list of rich text strings.
|
||||
/// </summary>
|
||||
private RDPhrase<TStyle>[] texts = [];
|
||||
/// <summary>
|
||||
/// The length of the string.
|
||||
/// </summary>
|
||||
public readonly int Length => texts.Sum(i => i.Length);
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RDLine{RDPhraseStyle}"/> at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the <see cref="RDLine{RDPhraseStyle}"/> to get or set.</param>
|
||||
/// <returns>The <see cref="RDLine{RDPhraseStyle}"/> at the specified index.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
|
||||
public RDLine<TStyle> this[Index index]
|
||||
{
|
||||
get
|
||||
{
|
||||
int i = index.GetOffset(Length);
|
||||
if (Length <= i)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
int ti = 0;
|
||||
while (texts[ti].Length < i)
|
||||
{
|
||||
i -= texts[ti].Length;
|
||||
ti++;
|
||||
}
|
||||
RDLine<TStyle> line = new()
|
||||
{
|
||||
texts = [texts[ti][i]]
|
||||
};
|
||||
return line;
|
||||
}
|
||||
set
|
||||
{
|
||||
int i = index.GetOffset(Length);
|
||||
if (Length <= i)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
texts = Concat([this[..i], value, this[(i + 1)..]]).texts;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RDLine{RDPhraseStyle}"/> within the specified range.
|
||||
/// </summary>
|
||||
/// <param name="range">The range of the <see cref="RDLine{RDPhraseStyle}"/> to get or set.</param>
|
||||
/// <returns>The <see cref="RDLine{RDPhraseStyle}"/> within the specified range.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the range is out of bounds.</exception>
|
||||
public RDLine<TStyle> this[Range range]
|
||||
{
|
||||
get
|
||||
{
|
||||
int start = range.Start.GetOffset(Length);
|
||||
int end = range.End.GetOffset(Length);
|
||||
if (!(start <= end && end <= Length))
|
||||
throw new ArgumentOutOfRangeException(nameof(range));
|
||||
int ti = 0, tstart, tend;
|
||||
RDPhrase<TStyle>[] strings = [];
|
||||
while (texts[ti].Length <= start)
|
||||
{
|
||||
start -= texts[ti].Length;
|
||||
end -= texts[ti].Length;
|
||||
ti++;
|
||||
}
|
||||
tstart = ti;
|
||||
while (texts[ti].Length < end)
|
||||
{
|
||||
end -= texts[ti].Length;
|
||||
ti++;
|
||||
}
|
||||
tend = ti;
|
||||
if (tstart == tend)
|
||||
strings = [texts[tstart][start..end]];
|
||||
else
|
||||
{
|
||||
for (int i = tstart + 1; i < tend; i++)
|
||||
strings = [.. strings, texts[i]];
|
||||
strings = [texts[tstart][start..], .. strings, texts[tend][..end]];
|
||||
}
|
||||
RDLine<TStyle> line = new()
|
||||
{
|
||||
texts = strings
|
||||
};
|
||||
return line;
|
||||
}
|
||||
set
|
||||
{
|
||||
int start = range.Start.GetOffset(Length);
|
||||
int end = range.End.GetOffset(Length);
|
||||
if (!(start < end && end <= Length))
|
||||
throw new ArgumentOutOfRangeException(nameof(range));
|
||||
texts = Concat([this[..start], value, this[end..]]).texts;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static RDLine<TStyle> Concat(params RDLine<TStyle>[] lines)
|
||||
{
|
||||
RDPhrase<TStyle>[] texts = [.. lines[0].texts];
|
||||
foreach (RDLine<TStyle> line in lines[1..])
|
||||
{
|
||||
if (texts[^1].Style == line.texts[0].Style)
|
||||
{
|
||||
RDPhrase<TStyle> before = texts[^1], after = line.texts[0];
|
||||
RDPhrase<TStyle> richString = new(before.Text + after.Text)
|
||||
{
|
||||
Style = before.Style,
|
||||
Events = [.. before.Events, .. after.Events.Select(i => new RDDialogueTone(i.Type, i.Index + before.Length) { Pause = i.Pause })]
|
||||
};
|
||||
texts = [.. texts[..^1], richString, .. line.texts[1..]];
|
||||
}
|
||||
else
|
||||
texts = [.. texts, .. line.texts];
|
||||
}
|
||||
return new() { texts = texts };
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="RDPhrase{RDPhraseStyle}"/> to a <see cref="RDLine{RDPhraseStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The <see cref="RDPhrase{RDPhraseStyle}"/> to convert.</param>
|
||||
/// <returns>A new <see cref="RDLine{RDPhraseStyle}"/> containing the specified <see cref="RDPhrase{RDPhraseStyle}"/>.</returns>
|
||||
public static implicit operator RDLine<TStyle>(RDPhrase<TStyle> text) => new() { texts = [text] };
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="RDPhrase{RDPhraseStyle}"/> to a <see cref="RDLine{RDPhraseStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="texts">The <see cref="RDPhrase{RDPhraseStyle}"/> to convert.</param>
|
||||
/// <returns>A new <see cref="RDLine{RDPhraseStyle}"/> containing the specified <see cref="RDPhrase{RDPhraseStyle}"/>.</returns>
|
||||
public static implicit operator RDLine<TStyle>(RDPhrase<TStyle>[] texts) => new() { texts = texts };
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="string"/> to a <see cref="RDLine{RDPhraseStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The <see cref="string"/> to convert.</param>
|
||||
/// <returns>A new <see cref="RDLine{RDPhraseStyle}"/> containing the specified <see cref="string"/>.</returns>
|
||||
public static implicit operator RDLine<TStyle>(string text) => new() { texts = [new RDPhrase<TStyle>(text)] };
|
||||
/// <summary>
|
||||
/// Deserializes a string into an <see cref="RDLine{RDPhraseStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The string to deserialize.</param>
|
||||
/// <returns>A new <see cref="RDLine{RDPhraseStyle}"/> containing the deserialized content.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the input text is null.</exception>
|
||||
/// <exception cref="FormatException">Thrown when the input text has an invalid format.</exception>
|
||||
static public RDLine<TStyle> Deserialize(string text)
|
||||
{
|
||||
RDLine<TStyle> line = "";
|
||||
TStyle style = new();
|
||||
int start = 0;
|
||||
while (start < text.Length)
|
||||
{
|
||||
TStyle tempStyle = style;
|
||||
int end = text.IndexOf('<', start);
|
||||
if (end == -1)
|
||||
{
|
||||
line += DeserializeStringPart(text[start..], tempStyle);
|
||||
break;
|
||||
}
|
||||
int start2 = text.IndexOf('>', end);
|
||||
int end2 = text.IndexOf('<', end + 1);
|
||||
if (start2 == -1)
|
||||
break;
|
||||
if (end2 != -1 && end2 < start2)
|
||||
{
|
||||
line += DeserializeStringPart(text[start..end2], tempStyle);
|
||||
start = end2;
|
||||
continue;
|
||||
}
|
||||
string textpart = text[start..end];
|
||||
line += DeserializeStringPart(textpart, tempStyle);
|
||||
string[] keyvalue = text[(end + 1)..start2].Split('=', 2);
|
||||
if (keyvalue[0].StartsWith('/') && style.ResetProperty(keyvalue[0][1..]))
|
||||
start = start2 + 1;
|
||||
else if (style.SetProperty(keyvalue[0], keyvalue.Length == 2 ? keyvalue[1] : "true"))
|
||||
start = start2 + 1;
|
||||
else
|
||||
start = start2 + 1;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
private static RDPhrase<TStyle> DeserializeStringPart(string text, TStyle style)
|
||||
{
|
||||
if (!TStyle.HasPhrase)
|
||||
return new RDPhrase<TStyle>(text) { Style = style };
|
||||
int pstart = 0;
|
||||
RDDialogueTone[] events = [];
|
||||
while (pstart < text.Length)
|
||||
{
|
||||
int pend = text.IndexOf('[', pstart);
|
||||
if (pend == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int pstart2 = text.IndexOf(']', pend);
|
||||
int pend2 = text.IndexOf('[', pend + 1);
|
||||
if (pstart2 == -1)
|
||||
break;
|
||||
if (pend2 != -1 && pend2 < pstart2)
|
||||
{
|
||||
pstart = pend2 + 1;
|
||||
continue;
|
||||
}
|
||||
string btag = text[(pend + 1)..pstart2];
|
||||
if (RDDialogueTone.Create(btag, pend, out RDDialogueTone? e) && e is RDDialogueTone ei)
|
||||
events = [.. events, ei];
|
||||
text = text[..pend] + text[(pstart2 + 1)..];
|
||||
}
|
||||
return new RDPhrase<TStyle>(text) { Style = style, Events = events };
|
||||
}
|
||||
/// <summary>
|
||||
/// Serializes the current <see cref="RDLine{RDPhraseStyle}"/> instance to a string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the current <see cref="RDLine{RDPhraseStyle}"/> instance.</returns>
|
||||
/// <remarks>
|
||||
/// The serialization process converts the rich text line into a string format, including any styling information.
|
||||
/// </remarks>
|
||||
public readonly string Serialize()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
TStyle style = new();
|
||||
int ci = 0;
|
||||
foreach (RDPhrase<TStyle> str in texts)
|
||||
{
|
||||
sb.Append(TStyle.GetXmlTag(style, str.Style));
|
||||
string part = str.Text;
|
||||
int offset = 0;
|
||||
foreach (RDDialogueTone e in str.Events)
|
||||
{
|
||||
string serialized = e.Serialize();
|
||||
part = part.Insert(e.Index + offset, serialized);
|
||||
offset += serialized.Length;
|
||||
}
|
||||
sb.Append(part);
|
||||
ci += str.Length;
|
||||
style = str.Style;
|
||||
}
|
||||
sb.Append(TStyle.GetXmlTag(style, new()));
|
||||
return sb.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// Concatenates two <see cref="RDLine{RDPhraseStyle}"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The left <see cref="RDLine{RDPhraseStyle}"/>.</param>
|
||||
/// <param name="right">The right <see cref="RDLine{RDPhraseStyle}"/>.</param>
|
||||
/// <returns>A new <see cref="RDLine{RDPhraseStyle}"/> that is the result of concatenating the two specified instances.</returns>
|
||||
public static RDLine<TStyle> operator +(RDLine<TStyle> left, RDLine<TStyle> right) => Concat([.. left.texts, .. right.texts]);
|
||||
/// <inheritdoc/>
|
||||
public override readonly string ToString() => string.Join("", texts);
|
||||
/// <inheritdoc/>
|
||||
private readonly string GetDebuggerDisplay() => ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rich text string with various styling options.
|
||||
/// </summary>
|
||||
/// <param name="text">The text content of the rich string.</param>
|
||||
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
|
||||
public struct RDPhrase<TStyle>(string text)
|
||||
: IEqualityOperators<RDPhrase<TStyle>, RDPhrase<TStyle>, bool>,
|
||||
IEquatable<RDPhrase<TStyle>>
|
||||
where TStyle : IRDRichStringStyle<TStyle>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text content of the rich string.
|
||||
/// </summary>
|
||||
public string Text { get; internal set; } = text;
|
||||
/// <summary>
|
||||
/// Gets or sets the events associated with the rich string.
|
||||
/// </summary>
|
||||
public RDDialogueTone[] Events { get; init; } = [];
|
||||
/// <summary>
|
||||
/// Gets the length of the text content.
|
||||
/// </summary>
|
||||
/// <value>The number of characters in the text content.</value>
|
||||
public readonly int Length => Text.Length;
|
||||
/// <summary>
|
||||
/// Gets the rich string at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the character.</param>
|
||||
/// <returns>A new <see cref="RDPhrase{TStyle}"/> with the character at the specified index.</returns>
|
||||
public RDPhrase<TStyle> this[Index index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return new RDPhrase<TStyle>
|
||||
{
|
||||
Text = Text[index].ToString(),
|
||||
Style = Style,
|
||||
Events = GetEvents(index.GetOffset(Length), 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rich string within the specified range.
|
||||
/// </summary>
|
||||
/// <param name="range">The range of characters.</param>
|
||||
/// <returns>A new <see cref="RDPhrase{TStyle}"/> with the characters within the specified range.</returns>
|
||||
public RDPhrase<TStyle> this[Range range]
|
||||
{
|
||||
get
|
||||
{
|
||||
RDPhrase<TStyle> style = new()
|
||||
{
|
||||
Text = Text[range],
|
||||
Style = Style,
|
||||
Events = GetEvents(range.Start.GetOffset(Length), range.End.GetOffset(Length) - range.Start.GetOffset(Length))
|
||||
};
|
||||
return style;
|
||||
}
|
||||
}
|
||||
private readonly RDDialogueTone[] GetEvents(int start, int length) => Events
|
||||
.Where(e => e.Index >= start && e.Index < start + length)
|
||||
.Select(e => new RDDialogueTone(e.Type, e.Index - start))
|
||||
.ToArray();
|
||||
/// <summary>
|
||||
/// Gets a new <see cref="RDPhrase{TStyle}"/> with the same style as the current instance.
|
||||
/// </summary>
|
||||
public TStyle Style { get; init; } = new();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RDPhrase{TStyle}"/> struct with an empty text.
|
||||
/// </summary>
|
||||
public RDPhrase() : this("") { }
|
||||
/// <summary>
|
||||
/// Implicitly converts a string to an <see cref="RDPhrase{TStyle}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to convert.</param>
|
||||
/// <returns>A new <see cref="RDPhrase{TStyle}"/> instance with the specified text.</returns>
|
||||
public static implicit operator RDPhrase<TStyle>(string text) => new() { Text = text };
|
||||
/// <inheritdoc/>
|
||||
public static bool operator ==(RDPhrase<TStyle> left, RDPhrase<TStyle> right) => left.Text == right.Text && left.Style == right.Style;
|
||||
/// <inheritdoc/>
|
||||
public static bool operator !=(RDPhrase<TStyle> left, RDPhrase<TStyle> right) => !(left == right);
|
||||
/// <inheritdoc/>
|
||||
public readonly bool Equals(RDPhrase<TStyle> other) => this == other;
|
||||
/// <inheritdoc/>
|
||||
public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is RDPhrase<TStyle> && base.Equals(obj);
|
||||
/// <inheritdoc/>
|
||||
public override readonly int GetHashCode() => Text.GetHashCode();
|
||||
/// <inheritdoc/>
|
||||
public override readonly string ToString() => Text;
|
||||
/// <inheritdoc/>
|
||||
private readonly string GetDebuggerDisplay() => ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace RhythmBase.Components.RichText
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a rich string style.
|
||||
/// </summary>
|
||||
public struct RDRichStringStyle : IRDRichStringStyle<RDRichStringStyle>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置文本的颜色。
|
||||
/// </summary>
|
||||
public RDColor? Color { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public static bool HasPhrase => false;
|
||||
/// <inheritdoc/>
|
||||
public static string GetXmlTag(RDRichStringStyle before, RDRichStringStyle after)
|
||||
{
|
||||
string tag = "";
|
||||
IRDRichStringStyle<RDRichStringStyle>.TryAddTag(ref tag, "color",
|
||||
before.Color?.TryGetName(out string[] namesbefore) == true
|
||||
? namesbefore[0].ToLower()
|
||||
: before.Color?.ToString(before.Color?.A == 255 ? "#RRGGBB" : "#RRGGBBAA"),
|
||||
after.Color?.TryGetName(out string[] namesafter) == true
|
||||
? namesafter[0].ToLower()
|
||||
: after.Color?.ToString(after.Color?.A == 255 ? "#RRGGBB" : "#RRGGBBAA"));
|
||||
return tag;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public readonly bool Equals(RDRichStringStyle other) => this == other;
|
||||
/// <inheritdoc/>
|
||||
public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is RDRichStringStyle e && Equals(e);
|
||||
/// <inheritdoc/>
|
||||
public bool ResetProperty(string name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "color":
|
||||
Color = null;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool SetProperty(string name, string value)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "color":
|
||||
if (RDColor.TryFromName(value, out RDColor color))
|
||||
Color = color;
|
||||
else if (RDColor.TryFromRgba(value, out color))
|
||||
Color = color;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static bool operator ==(RDRichStringStyle left, RDRichStringStyle right) => left.Color == right.Color;
|
||||
/// <inheritdoc/>
|
||||
public static bool operator !=(RDRichStringStyle left, RDRichStringStyle right) => !(left == right);
|
||||
/// <inheritdoc/>
|
||||
public readonly override int GetHashCode() => Color.GetHashCode();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user