mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 12:44:16 +01:00
SeString renderer: fix colors, add link support (#1983)
* Add coloring options * Add link support * simplify * fixes * Prevent EncodeString from causing crashes * Fix link range application and add link example * Fix test widget * Make DalamudLinkPayload backward compatible * make it better to use * make it better to use * Mark SeString rendering functions experimental via comments * rename * Simplify * Make sestring draw functions take in draw params * Improvements
This commit is contained in:
parent
5fdd88b488
commit
b6eb18d550
25 changed files with 2009 additions and 766 deletions
|
|
@ -115,10 +115,10 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Interface\Internal\ImGuiSeStringRenderer\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
|
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\DerivedGeneralCategory.txt" LogicalName="DerivedGeneralCategory.txt" />
|
||||||
<EmbeddedResource Include="Interface\Internal\ImGuiSeStringRenderer\TextProcessing\EastAsianWidth.txt" LogicalName="EastAsianWidth.txt" />
|
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\EastAsianWidth.txt" LogicalName="EastAsianWidth.txt" />
|
||||||
<EmbeddedResource Include="Interface\Internal\ImGuiSeStringRenderer\TextProcessing\DerivedGeneralCategory.txt" LogicalName="DerivedGeneralCategory.txt" />
|
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\emoji-data.txt" LogicalName="emoji-data.txt" />
|
||||||
<EmbeddedResource Include="Interface\Internal\ImGuiSeStringRenderer\TextProcessing\emoji-data.txt" LogicalName="emoji-data.txt" />
|
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ using System.IO;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -13,32 +15,39 @@ public class DalamudLinkPayload : Payload
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override PayloadType Type => PayloadType.DalamudLink;
|
public override PayloadType Type => PayloadType.DalamudLink;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Gets the plugin command ID to be linked.</summary>
|
||||||
/// Gets the plugin command ID to be linked.
|
public uint CommandId { get; internal set; }
|
||||||
/// </summary>
|
|
||||||
public uint CommandId { get; internal set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Gets an optional extra integer value 1.</summary>
|
||||||
/// Gets the plugin name to be linked.
|
public int Extra1 { get; internal set; }
|
||||||
/// </summary>
|
|
||||||
|
/// <summary>Gets an optional extra integer value 2.</summary>
|
||||||
|
public int Extra2 { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the plugin name to be linked.</summary>
|
||||||
public string Plugin { get; internal set; } = string.Empty;
|
public string Plugin { get; internal set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets an optional extra string.</summary>
|
||||||
|
public string ExtraString { get; internal set; } = string.Empty;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override string ToString() =>
|
||||||
{
|
$"{this.Type} - {this.Plugin} ({this.CommandId}/{this.Extra1}/{this.Extra2}/{this.ExtraString})";
|
||||||
return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override byte[] EncodeImpl()
|
protected override byte[] EncodeImpl()
|
||||||
{
|
{
|
||||||
return new Lumina.Text.SeStringBuilder()
|
return new Lumina.Text.SeStringBuilder()
|
||||||
.BeginMacro(MacroCode.Link)
|
.BeginMacro(MacroCode.Link)
|
||||||
.AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1)
|
.AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1)
|
||||||
.AppendStringExpression(this.Plugin)
|
.AppendUIntExpression(this.CommandId)
|
||||||
.AppendUIntExpression(this.CommandId)
|
.AppendIntExpression(this.Extra1)
|
||||||
.EndMacro()
|
.AppendIntExpression(this.Extra2)
|
||||||
.ToArray();
|
.BeginStringExpression()
|
||||||
|
.Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString }))
|
||||||
|
.EndExpression()
|
||||||
|
.EndMacro()
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -49,16 +58,53 @@ public class DalamudLinkPayload : Payload
|
||||||
var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position));
|
var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position));
|
||||||
var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan());
|
var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan());
|
||||||
|
|
||||||
if (!rosps.TryGetExpression(out var pluginExpression, out var commandIdExpression))
|
if (!rosps.TryGetExpression(
|
||||||
return;
|
out var commandIdExpression,
|
||||||
|
out var extra1Expression,
|
||||||
|
out var extra2Expression,
|
||||||
|
out var compositeExpression))
|
||||||
|
{
|
||||||
|
if (!rosps.TryGetExpression(out var pluginExpression, out commandIdExpression))
|
||||||
|
return;
|
||||||
|
|
||||||
if (!pluginExpression.TryGetString(out var pluginString))
|
if (!pluginExpression.TryGetString(out var pluginString))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!commandIdExpression.TryGetUInt(out var commandId))
|
if (!commandIdExpression.TryGetUInt(out var commandId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.Plugin = pluginString.ExtractText();
|
this.Plugin = pluginString.ExtractText();
|
||||||
this.CommandId = commandId;
|
this.CommandId = commandId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!commandIdExpression.TryGetUInt(out var commandId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!extra1Expression.TryGetInt(out var extra1))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!extra2Expression.TryGetInt(out var extra2))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!compositeExpression.TryGetString(out var compositeString))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string[] extraData;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
extraData = JsonConvert.DeserializeObject<string[]>(compositeString.ExtractText());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.CommandId = commandId;
|
||||||
|
this.Extra1 = extra1;
|
||||||
|
this.Extra2 = extra2;
|
||||||
|
this.Plugin = extraData[0];
|
||||||
|
this.ExtraString = extraData[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
|
||||||
/// <summary>Game font data file.</summary>
|
/// <summary>Game font data file.</summary>
|
||||||
internal sealed unsafe class GfdFile : FileResource
|
internal sealed unsafe class GfdFile : FileResource
|
||||||
1149
Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs
Normal file
1149
Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,11 +2,11 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeEastAsianWidthClass;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass;
|
||||||
using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeGeneralCategory;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory;
|
||||||
using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeLineBreakClass;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary>Enumerates line break offsets.</summary>
|
/// <summary>Enumerates line break offsets.</summary>
|
||||||
internal ref struct LineBreakEnumerator
|
internal ref struct LineBreakEnumerator
|
||||||
|
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary>Stores unicode data.</summary>
|
/// <summary>Stores unicode data.</summary>
|
||||||
internal static class UnicodeData
|
internal static class UnicodeData
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary><a href="https://www.unicode.org/reports/tr11/">Unicode east asian width</a>.</summary>
|
/// <summary><a href="https://www.unicode.org/reports/tr11/">Unicode east asian width</a>.</summary>
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary><a href="https://www.unicode.org/reports/tr51/#Emoji_Characters">Unicode emoji property</a>.</summary>
|
/// <summary><a href="https://www.unicode.org/reports/tr51/#Emoji_Characters">Unicode emoji property</a>.</summary>
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary><a href="https://www.unicode.org/reports/tr44/#General_Category_Values">Unicode general category.</a>.
|
/// <summary><a href="https://www.unicode.org/reports/tr44/#General_Category_Values">Unicode general category.</a>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary><a href="https://unicode.org/reports/tr14/#Table1">Unicode line break class</a>.</summary>
|
/// <summary><a href="https://unicode.org/reports/tr14/#Table1">Unicode line break class</a>.</summary>
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")]
|
||||||
|
|
@ -8,7 +8,7 @@ using Lumina.Text;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary>Enumerates a UTF-N byte sequence by codepoint.</summary>
|
/// <summary>Enumerates a UTF-N byte sequence by codepoint.</summary>
|
||||||
[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")]
|
[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")]
|
||||||
|
|
@ -257,7 +257,11 @@ internal ref struct UtfEnumerator
|
||||||
/// <summary>Gets the effective <c>char</c> value, with invalid or non-representable codepoints replaced.
|
/// <summary>Gets the effective <c>char</c> value, with invalid or non-representable codepoints replaced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
||||||
public char EffectiveChar => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue;
|
public char EffectiveChar
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Gets the effective <c>int</c> value, with invalid codepoints replaced.</summary>
|
/// <summary>Gets the effective <c>int</c> value, with invalid codepoints replaced.</summary>
|
||||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
||||||
|
|
@ -268,6 +272,14 @@ internal ref struct UtfEnumerator
|
||||||
? 0xFFFD
|
? 0xFFFD
|
||||||
: rune.Value;
|
: rune.Value;
|
||||||
|
|
||||||
|
/// <summary>Gets the effective <see cref="Rune"/> value, with invalid codepoints replaced.</summary>
|
||||||
|
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
||||||
|
public Rune EffectiveRune
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => new(this.EffectiveInt);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right);
|
public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right);
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary>Flags on enumerating a unicode sequence.</summary>
|
/// <summary>Flags on enumerating a unicode sequence.</summary>
|
||||||
[Flags]
|
[Flags]
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
|
|
||||||
/// <summary>Represents a single value to be used in a UTF-N byte sequence.</summary>
|
/// <summary>Represents a single value to be used in a UTF-N byte sequence.</summary>
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 4)]
|
[StructLayout(LayoutKind.Explicit, Size = 4)]
|
||||||
168
Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs
Normal file
168
Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Lumina.Text.Payloads;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
|
/// <summary>Render styles for a SeString.</summary>
|
||||||
|
public record struct SeStringDrawParams
|
||||||
|
{
|
||||||
|
/// <summary>Gets or sets the target draw list.</summary>
|
||||||
|
/// <value>Target draw list, <c>default(ImDrawListPtr)</c> to not draw, or <c>null</c> to use
|
||||||
|
/// <see cref="ImGui.GetWindowDrawList"/> (the default).</value>
|
||||||
|
/// <remarks>If this value is set, <see cref="ImGui.Dummy"/> will not be called, and ImGui ID will be ignored.
|
||||||
|
/// </remarks>
|
||||||
|
public ImDrawListPtr? TargetDrawList { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the font to use.</summary>
|
||||||
|
/// <value>Font to use, or <c>null</c> to use <see cref="ImGui.GetFont"/> (the default).</value>
|
||||||
|
public ImFontPtr? Font { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
||||||
|
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos"/>.</value>
|
||||||
|
public Vector2? ScreenOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the font size.</summary>
|
||||||
|
/// <value>Font size in pixels, or <c>0</c> to use the current ImGui font size <see cref="ImGui.GetFontSize"/>.
|
||||||
|
/// </value>
|
||||||
|
public float? FontSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the line height ratio.</summary>
|
||||||
|
/// <value><c>1</c> or <c>null</c> (the default) will use <see cref="FontSize"/> as the line height.
|
||||||
|
/// <c>2</c> will make line height twice the <see cref="FontSize"/>.</value>
|
||||||
|
public float? LineHeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the wrapping width.</summary>
|
||||||
|
/// <value>Width in pixels, or <c>null</c> to wrap at the end of available content region from
|
||||||
|
/// <see cref="ImGui.GetContentRegionAvail"/> (the default).</value>
|
||||||
|
public float? WrapWidth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the thickness of underline under links.</summary>
|
||||||
|
public float? LinkUnderlineThickness { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the opacity, commonly called "alpha".</summary>
|
||||||
|
/// <value>Opacity value ranging from 0(invisible) to 1(fully visible), or <c>null</c> to use the current ImGui
|
||||||
|
/// opacity from <see cref="ImGuiStyle.Alpha"/> accessed using <see cref="ImGui.GetStyle"/>.</value>
|
||||||
|
public float? Opacity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the strength of the edge, which will have effects on the edge opacity.</summary>
|
||||||
|
/// <value>Strength value ranging from 0(invisible) to 1(fully visible), or <c>null</c> to use the default value
|
||||||
|
/// of <c>0.25f</c> that might be subject to change in the future.</value>
|
||||||
|
public float? EdgeStrength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the color of the rendered text.</summary>
|
||||||
|
/// <value>Color in RGBA, or <c>null</c> to use <see cref="ImGuiCol.Text"/> (the default).</value>
|
||||||
|
public uint? Color { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the color of the rendered text edge.</summary>
|
||||||
|
/// <value>Color in RGBA, or <c>null</c> to use opaque black (the default).</value>
|
||||||
|
public uint? EdgeColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the color of the rendered text shadow.</summary>
|
||||||
|
/// <value>Color in RGBA, or <c>null</c> to use opaque black (the default).</value>
|
||||||
|
public uint? ShadowColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the background color of a link when hovered.</summary>
|
||||||
|
/// <value>Color in RGBA, or <c>null</c> to use <see cref="ImGuiCol.ButtonHovered"/> (the default).</value>
|
||||||
|
public uint? LinkHoverBackColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the background color of a link when active.</summary>
|
||||||
|
/// <value>Color in RGBA, or <c>null</c> to use <see cref="ImGuiCol.ButtonActive"/> (the default).</value>
|
||||||
|
public uint? LinkActiveBackColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets a value indicating whether to force the color of the rendered text edge.</summary>
|
||||||
|
/// <remarks>If set, then <see cref="MacroCode.EdgeColor"/> and <see cref="MacroCode.EdgeColorType"/> will be
|
||||||
|
/// ignored.</remarks>
|
||||||
|
public bool ForceEdgeColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets a value indicating whether the text is rendered bold.</summary>
|
||||||
|
public bool Bold { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets a value indicating whether the text is rendered italic.</summary>
|
||||||
|
public bool Italic { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets a value indicating whether the text is rendered with edge.</summary>
|
||||||
|
public bool Edge { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets a value indicating whether the text is rendered with shadow.</summary>
|
||||||
|
public bool Shadow { get; set; }
|
||||||
|
|
||||||
|
private readonly unsafe ImFont* EffectiveFont =>
|
||||||
|
(this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null
|
||||||
|
? f.NativePtr
|
||||||
|
: throw new ArgumentException("Specified font is empty.");
|
||||||
|
|
||||||
|
private readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f);
|
||||||
|
|
||||||
|
private readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha;
|
||||||
|
|
||||||
|
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
||||||
|
[SuppressMessage(
|
||||||
|
"StyleCop.CSharp.OrderingRules",
|
||||||
|
"SA1214:Readonly fields should appear before non-readonly fields",
|
||||||
|
Justification = "Matching the above order.")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe struct Resolved(in SeStringDrawParams ssdp)
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
||||||
|
public readonly ImDrawList* DrawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
||||||
|
public readonly ImFont* Font = ssdp.EffectiveFont;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ScreenOffset"/>
|
||||||
|
public readonly Vector2 ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
||||||
|
public readonly float FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LineHeight"/>
|
||||||
|
public readonly float LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.WrapWidth"/>
|
||||||
|
public readonly float WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkUnderlineThickness"/>
|
||||||
|
public readonly float LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Opacity"/>
|
||||||
|
public readonly float Opacity = ssdp.EffectiveOpacity;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.EdgeStrength"/>
|
||||||
|
public readonly float EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Color"/>
|
||||||
|
public uint Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.EdgeColor"/>
|
||||||
|
public uint EdgeColor = ssdp.EdgeColor ?? 0xFF000000;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ShadowColor"/>
|
||||||
|
public uint ShadowColor = ssdp.ShadowColor ?? 0xFF000000;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkHoverBackColor"/>
|
||||||
|
public readonly uint LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkActiveBackColor"/>
|
||||||
|
public readonly uint LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ForceEdgeColor"/>
|
||||||
|
public readonly bool ForceEdgeColor = ssdp.ForceEdgeColor;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Bold"/>
|
||||||
|
public bool Bold = ssdp.Bold;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Italic"/>
|
||||||
|
public bool Italic = ssdp.Italic;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Edge"/>
|
||||||
|
public bool Edge = ssdp.Edge;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Shadow"/>
|
||||||
|
public bool Shadow = ssdp.Shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
|
/// <summary>Represents the result of n rendered interactable SeString.</summary>
|
||||||
|
public ref struct SeStringDrawResult
|
||||||
|
{
|
||||||
|
private Payload? lazyPayload;
|
||||||
|
|
||||||
|
/// <summary>Gets the visible size of the text rendered/to be rendered.</summary>
|
||||||
|
public Vector2 Size { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether a payload or the whole text has been clicked.</summary>
|
||||||
|
public bool Clicked { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the offset of the interacted payload, or <c>-1</c> if none.</summary>
|
||||||
|
public int InteractedPayloadOffset { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the interacted payload envelope, or <see cref="ReadOnlySpan{T}.Empty"/> if none.</summary>
|
||||||
|
public ReadOnlySpan<byte> InteractedPayloadEnvelope { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the interacted payload, or <c>null</c> if none.</summary>
|
||||||
|
public Payload? InteractedPayload =>
|
||||||
|
this.lazyPayload ??=
|
||||||
|
this.InteractedPayloadEnvelope.IsEmpty
|
||||||
|
? default
|
||||||
|
: SeString.Parse(this.InteractedPayloadEnvelope).Payloads.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
@ -1,693 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using BitFaster.Caching.Lru;
|
|
||||||
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Game.Config;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing;
|
|
||||||
using Dalamud.Interface.Utility;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|
||||||
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
using Lumina.Excel.GeneratedSheets2;
|
|
||||||
using Lumina.Text.Expressions;
|
|
||||||
using Lumina.Text.Payloads;
|
|
||||||
using Lumina.Text.ReadOnly;
|
|
||||||
|
|
||||||
using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer;
|
|
||||||
|
|
||||||
/// <summary>Draws SeString.</summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
internal unsafe class SeStringRenderer : IInternalDisposableService
|
|
||||||
{
|
|
||||||
private const int ChannelShadow = 0;
|
|
||||||
private const int ChannelEdge = 1;
|
|
||||||
private const int ChannelFore = 2;
|
|
||||||
private const int ChannelCount = 3;
|
|
||||||
|
|
||||||
private const char SoftHyphen = '\u00AD';
|
|
||||||
private const char ObjectReplacementCharacter = '\uFFFC';
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly GameConfig gameConfig = Service<GameConfig>.Get();
|
|
||||||
|
|
||||||
private readonly ConcurrentLru<string, ReadOnlySeString> cache = new(1024);
|
|
||||||
|
|
||||||
private readonly GfdFile gfd;
|
|
||||||
private readonly uint[] colorTypes;
|
|
||||||
private readonly uint[] edgeColorTypes;
|
|
||||||
|
|
||||||
private readonly List<TextFragment> words = [];
|
|
||||||
|
|
||||||
private readonly List<uint> colorStack = [];
|
|
||||||
private readonly List<uint> edgeColorStack = [];
|
|
||||||
private readonly List<uint> shadowColorStack = [];
|
|
||||||
private bool bold;
|
|
||||||
private bool italic;
|
|
||||||
private Vector2 edge;
|
|
||||||
private Vector2 shadow;
|
|
||||||
|
|
||||||
private ImDrawListSplitterPtr splitter = new(ImGuiNative.ImDrawListSplitter_ImDrawListSplitter());
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private SeStringRenderer(DataManager dm)
|
|
||||||
{
|
|
||||||
var uiColor = dm.Excel.GetSheet<UIColor>()!;
|
|
||||||
var maxId = 0;
|
|
||||||
foreach (var row in uiColor)
|
|
||||||
maxId = (int)Math.Max(row.RowId, maxId);
|
|
||||||
|
|
||||||
this.colorTypes = new uint[maxId + 1];
|
|
||||||
this.edgeColorTypes = new uint[maxId + 1];
|
|
||||||
foreach (var row in uiColor)
|
|
||||||
{
|
|
||||||
this.colorTypes[row.RowId] = BgraToRgba((row.UIForeground >> 8) | (row.UIForeground << 24));
|
|
||||||
this.edgeColorTypes[row.RowId] = BgraToRgba((row.UIGlow >> 8) | (row.UIGlow << 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
static uint BgraToRgba(uint x)
|
|
||||||
{
|
|
||||||
var buf = (byte*)&x;
|
|
||||||
(buf[0], buf[2]) = (buf[2], buf[0]);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Finalizes an instance of the <see cref="SeStringRenderer"/> class.</summary>
|
|
||||||
~SeStringRenderer() => this.ReleaseUnmanagedResources();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources();
|
|
||||||
|
|
||||||
/// <summary>Creates and caches a SeString from a text macro representation, and then draws it.</summary>
|
|
||||||
/// <param name="text">SeString text macro representation.</param>
|
|
||||||
/// <param name="wrapWidth">Wrapping width. If a non-positive number is provided, then the remainder of the width
|
|
||||||
/// will be used.</param>
|
|
||||||
public void CompileAndDrawWrapped(string text, float wrapWidth = 0)
|
|
||||||
{
|
|
||||||
ThreadSafety.AssertMainThread();
|
|
||||||
|
|
||||||
this.DrawWrapped(
|
|
||||||
this.cache.GetOrAdd(
|
|
||||||
text,
|
|
||||||
static text =>
|
|
||||||
{
|
|
||||||
var outstr = default(Utf8String);
|
|
||||||
outstr.Ctor();
|
|
||||||
RaptureTextModule.Instance()->MacroEncoder.EncodeString(&outstr, text.ReplaceLineEndings("<br>"));
|
|
||||||
var res = new ReadOnlySeString(outstr.AsSpan().ToArray());
|
|
||||||
outstr.Dtor();
|
|
||||||
return res;
|
|
||||||
}).AsSpan(),
|
|
||||||
wrapWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="DrawWrapped(ReadOnlySeStringSpan, float)"/>
|
|
||||||
public void DrawWrapped(in Utf8String utf8String, float wrapWidth = 0) =>
|
|
||||||
this.DrawWrapped(utf8String.AsSpan(), wrapWidth);
|
|
||||||
|
|
||||||
/// <summary>Draws a SeString.</summary>
|
|
||||||
/// <param name="sss">SeString to draw.</param>
|
|
||||||
/// <param name="wrapWidth">Wrapping width. If a non-positive number is provided, then the remainder of the width
|
|
||||||
/// will be used.</param>
|
|
||||||
public void DrawWrapped(ReadOnlySeStringSpan sss, float wrapWidth = 0)
|
|
||||||
{
|
|
||||||
ThreadSafety.AssertMainThread();
|
|
||||||
|
|
||||||
if (wrapWidth <= 0)
|
|
||||||
wrapWidth = ImGui.GetContentRegionAvail().X;
|
|
||||||
|
|
||||||
this.words.Clear();
|
|
||||||
this.colorStack.Clear();
|
|
||||||
this.edgeColorStack.Clear();
|
|
||||||
this.shadowColorStack.Clear();
|
|
||||||
|
|
||||||
this.colorStack.Add(ImGui.GetColorU32(ImGuiCol.Text));
|
|
||||||
this.edgeColorStack.Add(0);
|
|
||||||
this.shadowColorStack.Add(0);
|
|
||||||
this.bold = this.italic = false;
|
|
||||||
this.edge = Vector2.One;
|
|
||||||
this.shadow = Vector2.Zero;
|
|
||||||
|
|
||||||
var state = new DrawState(
|
|
||||||
sss,
|
|
||||||
ImGui.GetWindowDrawList(),
|
|
||||||
this.splitter,
|
|
||||||
ImGui.GetFont(),
|
|
||||||
ImGui.GetFontSize(),
|
|
||||||
ImGui.GetCursorScreenPos());
|
|
||||||
this.CreateTextFragments(ref state, wrapWidth);
|
|
||||||
|
|
||||||
var size = Vector2.Zero;
|
|
||||||
for (var i = 0; i < this.words.Count; i++)
|
|
||||||
{
|
|
||||||
var word = this.words[i];
|
|
||||||
this.DrawWord(
|
|
||||||
ref state,
|
|
||||||
word.Offset,
|
|
||||||
state.Raw.Data[word.From..word.To],
|
|
||||||
i == 0
|
|
||||||
? '\0'
|
|
||||||
: this.words[i - 1].IsSoftHyphenVisible
|
|
||||||
? this.words[i - 1].LastRuneRepr
|
|
||||||
: this.words[i - 1].LastRuneRepr2);
|
|
||||||
|
|
||||||
if (word.IsSoftHyphenVisible && i > 0)
|
|
||||||
{
|
|
||||||
this.DrawWord(
|
|
||||||
ref state,
|
|
||||||
word.Offset + new Vector2(word.AdvanceWidthWithoutLastRune, 0),
|
|
||||||
"-"u8,
|
|
||||||
this.words[i - 1].LastRuneRepr);
|
|
||||||
}
|
|
||||||
|
|
||||||
size = Vector2.Max(size, word.Offset + new Vector2(word.VisibleWidth, state.FontSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Splitter.Merge(state.DrawList);
|
|
||||||
|
|
||||||
ImGui.Dummy(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets the printable char for the given char, or null(\0) if it should not be handled at all.</summary>
|
|
||||||
/// <param name="c">Character to determine.</param>
|
|
||||||
/// <returns>Character to print, or null(\0) if none.</returns>
|
|
||||||
private static Rune? ToPrintableRune(int c) => c switch
|
|
||||||
{
|
|
||||||
char.MaxValue => null,
|
|
||||||
SoftHyphen => new('-'),
|
|
||||||
_ when UnicodeData.LineBreak[c]
|
|
||||||
is UnicodeLineBreakClass.BK
|
|
||||||
or UnicodeLineBreakClass.CR
|
|
||||||
or UnicodeLineBreakClass.LF
|
|
||||||
or UnicodeLineBreakClass.NL => new(0),
|
|
||||||
_ => new(c),
|
|
||||||
};
|
|
||||||
|
|
||||||
private void ReleaseUnmanagedResources()
|
|
||||||
{
|
|
||||||
if (this.splitter.NativePtr is not null)
|
|
||||||
this.splitter.Destroy();
|
|
||||||
this.splitter = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateTextFragments(ref DrawState state, float wrapWidth)
|
|
||||||
{
|
|
||||||
var prev = 0;
|
|
||||||
var runningOffset = Vector2.Zero;
|
|
||||||
var runningWidth = 0f;
|
|
||||||
foreach (var (curr, mandatory) in new LineBreakEnumerator(state.Raw, UtfEnumeratorFlags.Utf8SeString))
|
|
||||||
{
|
|
||||||
var fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset);
|
|
||||||
var nextRunningWidth = Math.Max(runningWidth, runningOffset.X + fragment.VisibleWidth);
|
|
||||||
if (nextRunningWidth <= wrapWidth)
|
|
||||||
{
|
|
||||||
// New fragment fits in the current line.
|
|
||||||
if (this.words.Count > 0)
|
|
||||||
{
|
|
||||||
char lastFragmentEnd;
|
|
||||||
if (this.words[^1].EndsWithSoftHyphen)
|
|
||||||
{
|
|
||||||
runningOffset.X += this.words[^1].AdvanceWidthWithoutLastRune - this.words[^1].AdvanceWidth;
|
|
||||||
lastFragmentEnd = this.words[^1].LastRuneRepr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lastFragmentEnd = this.words[^1].LastRuneRepr2;
|
|
||||||
}
|
|
||||||
|
|
||||||
runningOffset.X += MathF.Round(
|
|
||||||
state.Font.GetDistanceAdjustmentForPair(lastFragmentEnd, fragment.FirstRuneRepr) *
|
|
||||||
state.FontSizeScale);
|
|
||||||
fragment = fragment with { Offset = runningOffset };
|
|
||||||
}
|
|
||||||
|
|
||||||
this.words.Add(fragment);
|
|
||||||
runningWidth = nextRunningWidth;
|
|
||||||
runningOffset.X += fragment.AdvanceWidth;
|
|
||||||
prev = curr;
|
|
||||||
}
|
|
||||||
else if (fragment.VisibleWidth <= wrapWidth)
|
|
||||||
{
|
|
||||||
// New fragment does not fit in the current line, but it will fit in the next line.
|
|
||||||
// Implicit conditions: runningWidth > 0, this.words.Count > 0
|
|
||||||
runningWidth = fragment.VisibleWidth;
|
|
||||||
runningOffset.X = fragment.AdvanceWidth;
|
|
||||||
runningOffset.Y += state.FontSize;
|
|
||||||
prev = curr;
|
|
||||||
this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true };
|
|
||||||
this.words.Add(fragment with { Offset = runningOffset with { X = 0 } });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// New fragment does not fit in the given width, and it needs to be broken down.
|
|
||||||
while (prev < curr)
|
|
||||||
{
|
|
||||||
if (runningOffset.X > 0)
|
|
||||||
{
|
|
||||||
runningOffset.X = 0;
|
|
||||||
runningOffset.Y += state.FontSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset, wrapWidth);
|
|
||||||
runningWidth = fragment.VisibleWidth;
|
|
||||||
runningOffset.X = fragment.AdvanceWidth;
|
|
||||||
prev = fragment.To;
|
|
||||||
if (this.words.Count > 0)
|
|
||||||
this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true };
|
|
||||||
this.words.Add(fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragment.MandatoryBreakAfter)
|
|
||||||
{
|
|
||||||
runningOffset.X = runningWidth = 0;
|
|
||||||
runningOffset.Y += state.FontSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawWord(ref DrawState state, Vector2 offset, ReadOnlySpan<byte> span, char lastRuneRepr)
|
|
||||||
{
|
|
||||||
var gfdTextureSrv =
|
|
||||||
(nint)UIModule.Instance()->GetRaptureAtkModule()->AtkModule.AtkFontManager.Gfd->Texture->
|
|
||||||
D3D11ShaderResourceView;
|
|
||||||
var x = 0f;
|
|
||||||
var width = 0f;
|
|
||||||
foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString))
|
|
||||||
{
|
|
||||||
if (c.IsSeStringPayload)
|
|
||||||
{
|
|
||||||
var enu = new ReadOnlySeStringSpan(span[c.ByteOffset..]).GetEnumerator();
|
|
||||||
if (!enu.MoveNext())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var payload = enu.Current;
|
|
||||||
switch (payload.MacroCode)
|
|
||||||
{
|
|
||||||
case MacroCode.Color:
|
|
||||||
TouchColorStack(this.colorStack, payload);
|
|
||||||
continue;
|
|
||||||
case MacroCode.EdgeColor:
|
|
||||||
TouchColorStack(this.edgeColorStack, payload);
|
|
||||||
continue;
|
|
||||||
case MacroCode.ShadowColor:
|
|
||||||
TouchColorStack(this.shadowColorStack, payload);
|
|
||||||
continue;
|
|
||||||
case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
|
||||||
this.bold = u != 0;
|
|
||||||
continue;
|
|
||||||
case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
|
||||||
this.italic = u != 0;
|
|
||||||
continue;
|
|
||||||
case MacroCode.Edge when payload.TryGetExpression(out var e1, out var e2) &&
|
|
||||||
e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2):
|
|
||||||
this.edge = new(v1, v2);
|
|
||||||
continue;
|
|
||||||
case MacroCode.Shadow when payload.TryGetExpression(out var e1, out var e2) &&
|
|
||||||
e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2):
|
|
||||||
this.shadow = new(v1, v2);
|
|
||||||
continue;
|
|
||||||
case MacroCode.ColorType:
|
|
||||||
TouchColorTypeStack(this.colorStack, this.colorTypes, payload);
|
|
||||||
continue;
|
|
||||||
case MacroCode.EdgeColorType:
|
|
||||||
TouchColorTypeStack(this.edgeColorStack, this.edgeColorTypes, payload);
|
|
||||||
continue;
|
|
||||||
case MacroCode.Icon:
|
|
||||||
case MacroCode.Icon2:
|
|
||||||
{
|
|
||||||
if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is not (var icon and not None) ||
|
|
||||||
!this.gfd.TryGetEntry((uint)icon, out var gfdEntry) ||
|
|
||||||
gfdEntry.IsEmpty)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var useHq = state.FontSize > 19;
|
|
||||||
var sizeScale = (state.FontSize + 1) / gfdEntry.Height;
|
|
||||||
state.SetCurrentChannel(ChannelFore);
|
|
||||||
state.Draw(
|
|
||||||
offset + new Vector2(x, 0),
|
|
||||||
gfdTextureSrv,
|
|
||||||
Vector2.Zero,
|
|
||||||
gfdEntry.Size * sizeScale,
|
|
||||||
Vector2.Zero,
|
|
||||||
useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0,
|
|
||||||
useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1);
|
|
||||||
width = Math.Max(width, x + (gfdEntry.Width * sizeScale));
|
|
||||||
x += MathF.Round(gfdEntry.Width * sizeScale);
|
|
||||||
lastRuneRepr = '\0';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ToPrintableRune(c.EffectiveChar) is not { } rune)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE';
|
|
||||||
if (runeRepr != 0)
|
|
||||||
{
|
|
||||||
var dist = state.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr);
|
|
||||||
ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)state.Font.FindGlyph(runeRepr).NativePtr;
|
|
||||||
|
|
||||||
var dyItalic = this.italic
|
|
||||||
? new Vector2(state.Font.FontSize - g.Y0, state.Font.FontSize - g.Y1) / 6
|
|
||||||
: Vector2.Zero;
|
|
||||||
|
|
||||||
if (this.shadow != Vector2.Zero && this.shadowColorStack[^1] >= 0x1000000)
|
|
||||||
{
|
|
||||||
state.SetCurrentChannel(ChannelShadow);
|
|
||||||
state.Draw(
|
|
||||||
offset + this.shadow + new Vector2(x + dist, 0),
|
|
||||||
g,
|
|
||||||
dyItalic,
|
|
||||||
this.shadowColorStack[^1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.edge != Vector2.Zero && this.edgeColorStack[^1] >= 0x1000000)
|
|
||||||
{
|
|
||||||
state.SetCurrentChannel(ChannelEdge);
|
|
||||||
for (var dx = -this.edge.X; dx <= this.edge.X; dx++)
|
|
||||||
{
|
|
||||||
for (var dy = -this.edge.Y; dy <= this.edge.Y; dy++)
|
|
||||||
{
|
|
||||||
if (dx == 0 && dy == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
state.Draw(offset + new Vector2(x + dist + dx, dy), g, dyItalic, this.edgeColorStack[^1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetCurrentChannel(ChannelFore);
|
|
||||||
for (var dx = this.bold ? 1 : 0; dx >= 0; dx--)
|
|
||||||
state.Draw(offset + new Vector2(x + dist + dx, 0), g, dyItalic, this.colorStack[^1]);
|
|
||||||
|
|
||||||
width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale));
|
|
||||||
x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastRuneRepr = runeRepr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
static void TouchColorStack(List<uint> stack, ReadOnlySePayloadSpan payload)
|
|
||||||
{
|
|
||||||
if (!payload.TryGetExpression(out var expr))
|
|
||||||
return;
|
|
||||||
if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor && stack.Count > 1)
|
|
||||||
stack.RemoveAt(stack.Count - 1);
|
|
||||||
else if (expr.TryGetUInt(out var u))
|
|
||||||
stack.Add(u);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void TouchColorTypeStack(List<uint> stack, uint[] colorTypes, ReadOnlySePayloadSpan payload)
|
|
||||||
{
|
|
||||||
if (!payload.TryGetExpression(out var expr))
|
|
||||||
return;
|
|
||||||
if (!expr.TryGetUInt(out var u))
|
|
||||||
return;
|
|
||||||
if (u != 0)
|
|
||||||
stack.Add(u < colorTypes.Length ? colorTypes[u] : 0u);
|
|
||||||
else if (stack.Count > 1)
|
|
||||||
stack.RemoveAt(stack.Count - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan<byte> sss)
|
|
||||||
{
|
|
||||||
var e = new ReadOnlySeStringSpan(sss).GetEnumerator();
|
|
||||||
if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2)
|
|
||||||
return None;
|
|
||||||
|
|
||||||
var payload = e.Current;
|
|
||||||
switch (payload.MacroCode)
|
|
||||||
{
|
|
||||||
case MacroCode.Icon
|
|
||||||
when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId):
|
|
||||||
return (BitmapFontIcon)iconId;
|
|
||||||
case MacroCode.Icon2
|
|
||||||
when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId):
|
|
||||||
var configName = (BitmapFontIcon)iconId switch
|
|
||||||
{
|
|
||||||
ControllerShoulderLeft => SystemConfigOption.PadButton_L1,
|
|
||||||
ControllerShoulderRight => SystemConfigOption.PadButton_R1,
|
|
||||||
ControllerTriggerLeft => SystemConfigOption.PadButton_L2,
|
|
||||||
ControllerTriggerRight => SystemConfigOption.PadButton_R2,
|
|
||||||
ControllerButton3 => SystemConfigOption.PadButton_Triangle,
|
|
||||||
ControllerButton1 => SystemConfigOption.PadButton_Cross,
|
|
||||||
ControllerButton0 => SystemConfigOption.PadButton_Circle,
|
|
||||||
ControllerButton2 => SystemConfigOption.PadButton_Square,
|
|
||||||
ControllerStart => SystemConfigOption.PadButton_Start,
|
|
||||||
ControllerBack => SystemConfigOption.PadButton_Select,
|
|
||||||
ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS,
|
|
||||||
ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS,
|
|
||||||
ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS,
|
|
||||||
ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS,
|
|
||||||
ControllerAnalogRightStick => SystemConfigOption.PadButton_RS,
|
|
||||||
ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS,
|
|
||||||
ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS,
|
|
||||||
ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS,
|
|
||||||
_ => (SystemConfigOption?)null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb))
|
|
||||||
return (BitmapFontIcon)iconId;
|
|
||||||
|
|
||||||
return pb switch
|
|
||||||
{
|
|
||||||
PadButtonValue.Autorun_Support => ControllerShoulderLeft,
|
|
||||||
PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight,
|
|
||||||
PadButtonValue.XHB_Left_Start => ControllerTriggerLeft,
|
|
||||||
PadButtonValue.XHB_Right_Start => ControllerTriggerRight,
|
|
||||||
PadButtonValue.Jump => ControllerButton3,
|
|
||||||
PadButtonValue.Accept => ControllerButton1,
|
|
||||||
PadButtonValue.Cancel => ControllerButton0,
|
|
||||||
PadButtonValue.Map_Sub => ControllerButton2,
|
|
||||||
PadButtonValue.MainCommand => ControllerStart,
|
|
||||||
PadButtonValue.HUD_Select => ControllerBack,
|
|
||||||
PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch
|
|
||||||
{
|
|
||||||
ControllerAnalogLeftStick => ControllerAnalogLeftStick,
|
|
||||||
ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn,
|
|
||||||
ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown,
|
|
||||||
ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight,
|
|
||||||
ControllerAnalogRightStick => ControllerAnalogLeftStick,
|
|
||||||
ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn,
|
|
||||||
ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown,
|
|
||||||
ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight,
|
|
||||||
_ => (BitmapFontIcon)iconId,
|
|
||||||
},
|
|
||||||
PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch
|
|
||||||
{
|
|
||||||
ControllerAnalogLeftStick => ControllerAnalogRightStick,
|
|
||||||
ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn,
|
|
||||||
ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown,
|
|
||||||
ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight,
|
|
||||||
ControllerAnalogRightStick => ControllerAnalogRightStick,
|
|
||||||
ControllerAnalogRightStickIn => ControllerAnalogRightStickIn,
|
|
||||||
ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown,
|
|
||||||
ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight,
|
|
||||||
_ => (BitmapFontIcon)iconId,
|
|
||||||
},
|
|
||||||
_ => (BitmapFontIcon)iconId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly record struct TextFragment(
|
|
||||||
int From,
|
|
||||||
int To,
|
|
||||||
Vector2 Offset,
|
|
||||||
float VisibleWidth,
|
|
||||||
float AdvanceWidth,
|
|
||||||
float AdvanceWidthWithoutLastRune,
|
|
||||||
bool MandatoryBreakAfter,
|
|
||||||
bool EndsWithSoftHyphen,
|
|
||||||
char FirstRuneRepr,
|
|
||||||
char LastRuneRepr,
|
|
||||||
char LastRuneRepr2)
|
|
||||||
{
|
|
||||||
public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.MandatoryBreakAfter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ref struct DrawState
|
|
||||||
{
|
|
||||||
public readonly ReadOnlySeStringSpan Raw;
|
|
||||||
public readonly float FontSize;
|
|
||||||
public readonly float FontSizeScale;
|
|
||||||
public readonly Vector2 ScreenOffset;
|
|
||||||
|
|
||||||
public ImDrawListPtr DrawList;
|
|
||||||
public ImDrawListSplitterPtr Splitter;
|
|
||||||
public ImFontPtr Font;
|
|
||||||
|
|
||||||
public DrawState(
|
|
||||||
ReadOnlySeStringSpan raw,
|
|
||||||
ImDrawListPtr drawList,
|
|
||||||
ImDrawListSplitterPtr splitter,
|
|
||||||
ImFontPtr font,
|
|
||||||
float fontSize,
|
|
||||||
Vector2 screenOffset)
|
|
||||||
{
|
|
||||||
this.Raw = raw;
|
|
||||||
this.DrawList = drawList;
|
|
||||||
this.Splitter = splitter;
|
|
||||||
this.Font = font;
|
|
||||||
this.FontSize = fontSize;
|
|
||||||
this.FontSizeScale = fontSize / font.FontSize;
|
|
||||||
this.ScreenOffset = screenOffset;
|
|
||||||
|
|
||||||
splitter.Split(drawList, ChannelCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCurrentChannel(int channelIndex) => this.Splitter.SetCurrentChannel(this.DrawList, channelIndex);
|
|
||||||
|
|
||||||
public void Draw(Vector2 offset, in ImGuiHelpers.ImFontGlyphReal g, Vector2 dyItalic, uint color) =>
|
|
||||||
this.Draw(
|
|
||||||
offset,
|
|
||||||
this.Font.ContainerAtlas.Textures[g.TextureIndex].TexID,
|
|
||||||
g.XY0 * this.FontSizeScale,
|
|
||||||
g.XY1 * this.FontSizeScale,
|
|
||||||
dyItalic * this.FontSizeScale,
|
|
||||||
g.UV0,
|
|
||||||
g.UV1,
|
|
||||||
color);
|
|
||||||
|
|
||||||
public void Draw(
|
|
||||||
Vector2 offset,
|
|
||||||
nint igTextureId,
|
|
||||||
Vector2 xy0,
|
|
||||||
Vector2 xy1,
|
|
||||||
Vector2 dyItalic,
|
|
||||||
Vector2 uv0,
|
|
||||||
Vector2 uv1,
|
|
||||||
uint color = uint.MaxValue)
|
|
||||||
{
|
|
||||||
offset += this.ScreenOffset;
|
|
||||||
this.DrawList.AddImageQuad(
|
|
||||||
igTextureId,
|
|
||||||
offset + new Vector2(xy0.X + dyItalic.X, xy0.Y),
|
|
||||||
offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y),
|
|
||||||
offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y),
|
|
||||||
offset + new Vector2(xy1.X + dyItalic.X, xy0.Y),
|
|
||||||
new(uv0.X, uv0.Y),
|
|
||||||
new(uv0.X, uv1.Y),
|
|
||||||
new(uv1.X, uv1.Y),
|
|
||||||
new(uv1.X, uv0.Y),
|
|
||||||
color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextFragment CreateFragment(
|
|
||||||
SeStringRenderer renderer,
|
|
||||||
int from,
|
|
||||||
int to,
|
|
||||||
bool mandatoryBreakAfter,
|
|
||||||
Vector2 offset,
|
|
||||||
float wrapWidth = float.PositiveInfinity)
|
|
||||||
{
|
|
||||||
var lastNonSpace = from;
|
|
||||||
|
|
||||||
var x = 0f;
|
|
||||||
var w = 0f;
|
|
||||||
var visibleWidth = 0f;
|
|
||||||
var advanceWidth = 0f;
|
|
||||||
var prevAdvanceWidth = 0f;
|
|
||||||
var firstRuneRepr = char.MaxValue;
|
|
||||||
var lastRuneRepr = default(char);
|
|
||||||
var lastRuneRepr2 = default(char);
|
|
||||||
var endsWithSoftHyphen = false;
|
|
||||||
foreach (var c in UtfEnumerator.From(this.Raw.Data[from..to], UtfEnumeratorFlags.Utf8SeString))
|
|
||||||
{
|
|
||||||
prevAdvanceWidth = x;
|
|
||||||
lastRuneRepr2 = lastRuneRepr;
|
|
||||||
endsWithSoftHyphen = c.EffectiveChar == SoftHyphen;
|
|
||||||
|
|
||||||
var byteOffset = from + c.ByteOffset;
|
|
||||||
var isBreakableWhitespace = false;
|
|
||||||
if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } &&
|
|
||||||
renderer.GetBitmapFontIconFor(this.Raw.Data[byteOffset..]) is var icon and not None &&
|
|
||||||
renderer.gfd.TryGetEntry((uint)icon, out var gfdEntry) &&
|
|
||||||
!gfdEntry.IsEmpty)
|
|
||||||
{
|
|
||||||
var sizeScale = (this.FontSize + 1) / gfdEntry.Height;
|
|
||||||
w = Math.Max(w, x + (gfdEntry.Width * sizeScale));
|
|
||||||
x += MathF.Round(gfdEntry.Width * sizeScale);
|
|
||||||
lastRuneRepr = default;
|
|
||||||
}
|
|
||||||
else if (ToPrintableRune(c.EffectiveChar) is { } rune)
|
|
||||||
{
|
|
||||||
var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE';
|
|
||||||
if (runeRepr != 0)
|
|
||||||
{
|
|
||||||
var dist = this.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr);
|
|
||||||
ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)this.Font.FindGlyph(runeRepr).NativePtr;
|
|
||||||
w = Math.Max(w, x + ((dist + g.X1) * this.FontSizeScale));
|
|
||||||
x += MathF.Round((dist + g.AdvanceX) * this.FontSizeScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
isBreakableWhitespace = Rune.IsWhiteSpace(rune) &&
|
|
||||||
UnicodeData.LineBreak[rune.Value] is not UnicodeLineBreakClass.GL;
|
|
||||||
lastRuneRepr = runeRepr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstRuneRepr == char.MaxValue)
|
|
||||||
firstRuneRepr = lastRuneRepr;
|
|
||||||
|
|
||||||
if (isBreakableWhitespace)
|
|
||||||
{
|
|
||||||
advanceWidth = x;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (w > wrapWidth && lastNonSpace != from && !endsWithSoftHyphen)
|
|
||||||
{
|
|
||||||
to = byteOffset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
advanceWidth = x;
|
|
||||||
visibleWidth = w;
|
|
||||||
lastNonSpace = byteOffset + c.ByteLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
offset,
|
|
||||||
visibleWidth,
|
|
||||||
advanceWidth,
|
|
||||||
prevAdvanceWidth,
|
|
||||||
mandatoryBreakAfter,
|
|
||||||
endsWithSoftHyphen,
|
|
||||||
firstRuneRepr,
|
|
||||||
lastRuneRepr,
|
|
||||||
lastRuneRepr2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface.Internal.ImGuiSeStringRenderer;
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
// Customised version of https://github.com/aers/FFXIVUIDebug
|
// Customised version of https://github.com/aers/FFXIVUIDebug
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal;
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
@ -204,10 +209,22 @@ internal unsafe class UiDebug
|
||||||
var textNode = (AtkTextNode*)node;
|
var textNode = (AtkTextNode*)node;
|
||||||
ImGui.Text("text: ");
|
ImGui.Text("text: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textNode->NodeText);
|
Service<SeStringRenderer>.Get().Draw(textNode->NodeText);
|
||||||
|
|
||||||
ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize);
|
ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button($"Encode##{(ulong)textNode:X}"))
|
||||||
|
{
|
||||||
|
using var tmp = new Utf8String();
|
||||||
|
RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, textNode->NodeText.StringPtr);
|
||||||
|
textNode->NodeText.Copy(&tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button($"Decode##{(ulong)textNode:X}"))
|
||||||
|
textNode->NodeText.SetString(new ReadOnlySeStringSpan(textNode->NodeText.StringPtr).ToString());
|
||||||
|
|
||||||
ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}");
|
ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}");
|
||||||
int b = textNode->AlignmentFontType;
|
int b = textNode->AlignmentFontType;
|
||||||
if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1))
|
if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1))
|
||||||
|
|
@ -233,7 +250,7 @@ internal unsafe class UiDebug
|
||||||
var counterNode = (AtkCounterNode*)node;
|
var counterNode = (AtkCounterNode*)node;
|
||||||
ImGui.Text("text: ");
|
ImGui.Text("text: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(counterNode->NodeText);
|
Service<SeStringRenderer>.Get().Draw(counterNode->NodeText);
|
||||||
break;
|
break;
|
||||||
case NodeType.Image:
|
case NodeType.Image:
|
||||||
var imageNode = (AtkImageNode*)node;
|
var imageNode = (AtkImageNode*)node;
|
||||||
|
|
@ -372,31 +389,31 @@ internal unsafe class UiDebug
|
||||||
var textInputComponent = (AtkComponentTextInput*)compNode->Component;
|
var textInputComponent = (AtkComponentTextInput*)compNode->Component;
|
||||||
ImGui.Text("InputBase Text1: ");
|
ImGui.Text("InputBase Text1: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText1);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText1);
|
||||||
|
|
||||||
ImGui.Text("InputBase Text2: ");
|
ImGui.Text("InputBase Text2: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText2);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText2);
|
||||||
|
|
||||||
ImGui.Text("Text1: ");
|
ImGui.Text("Text1: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->UnkText01);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText01);
|
||||||
|
|
||||||
ImGui.Text("Text2: ");
|
ImGui.Text("Text2: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->UnkText02);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText02);
|
||||||
|
|
||||||
ImGui.Text("Text3: ");
|
ImGui.Text("Text3: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->UnkText03);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText03);
|
||||||
|
|
||||||
ImGui.Text("Text4: ");
|
ImGui.Text("Text4: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->UnkText04);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText04);
|
||||||
|
|
||||||
ImGui.Text("Text5: ");
|
ImGui.Text("Text5: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(textInputComponent->UnkText05);
|
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText05);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,9 @@ using System.Text.Unicode;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
@ -16,10 +19,6 @@ using Dalamud.Interface.Utility.Raii;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
||||||
using Lumina.Text.ReadOnly;
|
|
||||||
|
|
||||||
using SeStringRenderer = Dalamud.Interface.Internal.ImGuiSeStringRenderer.SeStringRenderer;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Utility;
|
namespace Dalamud.Interface.Utility;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -181,13 +180,57 @@ public static class ImGuiHelpers
|
||||||
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}");
|
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringRenderer.DrawWrapped(ReadOnlySeStringSpan, float)"/>
|
/// <summary>Draws a SeString.</summary>
|
||||||
|
/// <param name="sss">SeString to draw.</param>
|
||||||
|
/// <param name="wrapWidth">Wrapping width. If a non-positive number is provided, then the remainder of the width
|
||||||
|
/// will be used.</param>
|
||||||
|
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
|
||||||
|
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
|
||||||
public static void SeStringWrapped(ReadOnlySpan<byte> sss, float wrapWidth = 0) =>
|
public static void SeStringWrapped(ReadOnlySpan<byte> sss, float wrapWidth = 0) =>
|
||||||
Service<SeStringRenderer>.Get().DrawWrapped(sss, wrapWidth);
|
Service<SeStringRenderer>.Get().Draw(sss, new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null });
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringRenderer.CompileAndDrawWrapped"/>
|
/// <summary>Creates and caches a SeString from a text macro representation, and then draws it.</summary>
|
||||||
|
/// <param name="text">SeString text macro representation.
|
||||||
|
/// Newline characters will be normalized to <see cref="NewLinePayload"/>.</param>
|
||||||
|
/// <param name="wrapWidth">Wrapping width. If a non-positive number is provided, then the remainder of the width
|
||||||
|
/// will be used.</param>
|
||||||
|
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
|
||||||
|
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
|
||||||
public static void CompileSeStringWrapped(string text, float wrapWidth = 0) =>
|
public static void CompileSeStringWrapped(string text, float wrapWidth = 0) =>
|
||||||
Service<SeStringRenderer>.Get().CompileAndDrawWrapped(text, wrapWidth);
|
Service<SeStringRenderer>.Get().CompileAndDrawWrapped(
|
||||||
|
text,
|
||||||
|
new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null });
|
||||||
|
|
||||||
|
/// <summary>Draws a SeString.</summary>
|
||||||
|
/// <param name="sss">SeString to draw.</param>
|
||||||
|
/// <param name="style">Initial rendering style.</param>
|
||||||
|
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
|
||||||
|
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
|
||||||
|
/// <returns>Interaction result of the rendered text.</returns>
|
||||||
|
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
|
||||||
|
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
|
||||||
|
public static SeStringDrawResult SeStringWrapped(
|
||||||
|
ReadOnlySpan<byte> sss,
|
||||||
|
in SeStringDrawParams style = default,
|
||||||
|
ImGuiId imGuiId = default,
|
||||||
|
ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) =>
|
||||||
|
Service<SeStringRenderer>.Get().Draw(sss, style, imGuiId, buttonFlags);
|
||||||
|
|
||||||
|
/// <summary>Creates and caches a SeString from a text macro representation, and then draws it.</summary>
|
||||||
|
/// <param name="text">SeString text macro representation.
|
||||||
|
/// Newline characters will be normalized to <see cref="NewLinePayload"/>.</param>
|
||||||
|
/// <param name="style">Initial rendering style.</param>
|
||||||
|
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
|
||||||
|
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
|
||||||
|
/// <returns>Interaction result of the rendered text.</returns>
|
||||||
|
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
|
||||||
|
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
|
||||||
|
public static SeStringDrawResult CompileSeStringWrapped(
|
||||||
|
string text,
|
||||||
|
in SeStringDrawParams style,
|
||||||
|
ImGuiId imGuiId = default,
|
||||||
|
ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) =>
|
||||||
|
Service<SeStringRenderer>.Get().CompileAndDrawWrapped(text, style, imGuiId, buttonFlags);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write unformatted text wrapped.
|
/// Write unformatted text wrapped.
|
||||||
|
|
|
||||||
176
Dalamud/Interface/Utility/ImGuiId.cs
Normal file
176
Dalamud/Interface/Utility/ImGuiId.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
/// <summary>Represents any type of ImGui ID.</summary>
|
||||||
|
public readonly ref struct ImGuiId
|
||||||
|
{
|
||||||
|
/// <summary>Type of the ID.</summary>
|
||||||
|
public readonly Type IdType;
|
||||||
|
|
||||||
|
/// <summary>Numeric ID. Valid if <see cref="IdType"/> is <see cref="Type.Numeric"/>.</summary>
|
||||||
|
public readonly nint Numeric;
|
||||||
|
|
||||||
|
/// <summary>UTF-16 string ID. Valid if <see cref="IdType"/> is <see cref="Type.U16"/>.</summary>
|
||||||
|
public readonly ReadOnlySpan<char> U16;
|
||||||
|
|
||||||
|
/// <summary>UTF-8 string ID. Valid if <see cref="IdType"/> is <see cref="Type.U8"/>.</summary>
|
||||||
|
public readonly ReadOnlySpan<byte> U8;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ImGuiId"/> struct.</summary>
|
||||||
|
/// <param name="id">A numeric ID, or 0 to not provide an ID.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ImGuiId(nint id)
|
||||||
|
{
|
||||||
|
if (id != 0)
|
||||||
|
(this.IdType, this.Numeric) = (Type.Numeric, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ImGuiId"/> struct.</summary>
|
||||||
|
/// <param name="id">A UTF-16 string ID, or <see cref="ReadOnlySpan{T}.Empty"/> to not provide an ID.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ImGuiId(ReadOnlySpan<char> id)
|
||||||
|
{
|
||||||
|
if (!id.IsEmpty)
|
||||||
|
{
|
||||||
|
this.IdType = Type.U16;
|
||||||
|
this.U16 = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ImGuiId"/> struct.</summary>
|
||||||
|
/// <param name="id">A UTF-8 string ID, or <see cref="ReadOnlySpan{T}.Empty"/> to not provide an ID.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ImGuiId(ReadOnlySpan<byte> id)
|
||||||
|
{
|
||||||
|
if (!id.IsEmpty)
|
||||||
|
{
|
||||||
|
this.IdType = Type.U8;
|
||||||
|
this.U8 = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Possible types for an ImGui ID.</summary>
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
/// <summary>No ID is specified.</summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary><see cref="ImGuiId.Numeric"/> field is used.</summary>
|
||||||
|
Numeric,
|
||||||
|
|
||||||
|
/// <summary><see cref="ImGuiId.U16"/> field is used.</summary>
|
||||||
|
U16,
|
||||||
|
|
||||||
|
/// <summary><see cref="ImGuiId.U8"/> field is used.</summary>
|
||||||
|
U8,
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static unsafe implicit operator ImGuiId(void* id) => new((nint)id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static unsafe implicit operator ImGuiId(float id) => new(*(int*)&id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static unsafe implicit operator ImGuiId(double id) => new(*(nint*)&id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(sbyte id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(byte id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(char id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(short id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(ushort id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(int id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(uint id) => new((nint)id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(nint id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(nuint id) => new((nint)id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(Span<char> id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(ReadOnlySpan<char> id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(Memory<char> id) => new(id.Span);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(ReadOnlyMemory<char> id) => new(id.Span);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(char[] id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(string id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(Span<byte> id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(ReadOnlySpan<byte> id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(Memory<byte> id) => new(id.Span);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(ReadOnlyMemory<byte> id) => new(id.Span);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ImGuiId(byte[] id) => new(id);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator bool(ImGuiId id) => !id.IsEmpty();
|
||||||
|
|
||||||
|
/// <summary>Determines if no ID is stored.</summary>
|
||||||
|
/// <returns><c>true</c> if no ID is stored.</returns>
|
||||||
|
public bool IsEmpty() => this.IdType switch
|
||||||
|
{
|
||||||
|
Type.None => true,
|
||||||
|
Type.Numeric => this.Numeric == 0,
|
||||||
|
Type.U16 => this.U16.IsEmpty,
|
||||||
|
Type.U8 => this.U8.IsEmpty,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Pushes ID if any is stored.</summary>
|
||||||
|
/// <returns><c>true</c> if any ID is pushed.</returns>
|
||||||
|
public unsafe bool PushId()
|
||||||
|
{
|
||||||
|
switch (this.IdType)
|
||||||
|
{
|
||||||
|
case Type.Numeric:
|
||||||
|
ImGuiNative.igPushID_Ptr((void*)this.Numeric);
|
||||||
|
return true;
|
||||||
|
case Type.U16:
|
||||||
|
fixed (void* p = this.U16)
|
||||||
|
ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + (this.U16.Length * 2));
|
||||||
|
return true;
|
||||||
|
case Type.U8:
|
||||||
|
fixed (void* p = this.U8)
|
||||||
|
ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + this.U8.Length);
|
||||||
|
return true;
|
||||||
|
case Type.None:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
|
||||||
|
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
|
using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
||||||
|
using LSeString = Lumina.Text.SeString;
|
||||||
|
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||||
|
|
||||||
namespace Dalamud.Utility;
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
|
@ -13,7 +18,51 @@ public static class SeStringExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="originalString">The original Lumina SeString.</param>
|
/// <param name="originalString">The original Lumina SeString.</param>
|
||||||
/// <returns>The re-parsed Dalamud SeString.</returns>
|
/// <returns>The re-parsed Dalamud SeString.</returns>
|
||||||
public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData);
|
public static DSeString ToDalamudString(this LSeString originalString) => DSeString.Parse(originalString.RawData);
|
||||||
|
|
||||||
|
/// <summary>Compiles and appends a macro string.</summary>
|
||||||
|
/// <param name="ssb">Target SeString builder.</param>
|
||||||
|
/// <param name="macroString">Macro string in UTF-8 to compile and append to <paramref name="ssb"/>.</param>
|
||||||
|
/// <returns><c>this</c> for method chaining.</returns>
|
||||||
|
/// <remarks>Must be called from the main thread.</remarks>
|
||||||
|
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<byte> macroString)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
return ssb.Append(Service<SeStringRenderer>.Get().Compile(macroString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Compiles and appends a macro string.</summary>
|
||||||
|
/// <param name="ssb">Target SeString builder.</param>
|
||||||
|
/// <param name="macroString">Macro string in UTF-16 to compile and append to <paramref name="ssb"/>.</param>
|
||||||
|
/// <returns><c>this</c> for method chaining.</returns>
|
||||||
|
/// <remarks>Must be called from the main thread.</remarks>
|
||||||
|
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<char> macroString)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
return ssb.Append(Service<SeStringRenderer>.Get().Compile(macroString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Compiles and appends a macro string.</summary>
|
||||||
|
/// <param name="ssb">Target SeString builder.</param>
|
||||||
|
/// <param name="macroString">Macro string in UTF-8 to compile and append to <paramref name="ssb"/>.</param>
|
||||||
|
/// <returns><c>this</c> for method chaining.</returns>
|
||||||
|
/// <remarks>Must be called from the main thread.</remarks>
|
||||||
|
public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan<byte> macroString)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
return ssb.Append(DSeString.Parse(Service<SeStringRenderer>.Get().Compile(macroString)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Compiles and appends a macro string.</summary>
|
||||||
|
/// <param name="ssb">Target SeString builder.</param>
|
||||||
|
/// <param name="macroString">Macro string in UTF-16 to compile and append to <paramref name="ssb"/>.</param>
|
||||||
|
/// <returns><c>this</c> for method chaining.</returns>
|
||||||
|
/// <remarks>Must be called from the main thread.</remarks>
|
||||||
|
public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan<char> macroString)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
return ssb.Append(DSeString.Parse(Service<SeStringRenderer>.Get().Compile(macroString)));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validate if character name is valid.
|
/// Validate if character name is valid.
|
||||||
|
|
@ -24,8 +73,5 @@ public static class SeStringExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">character name to validate.</param>
|
/// <param name="value">character name to validate.</param>
|
||||||
/// <returns>indicator if character is name is valid.</returns>
|
/// <returns>indicator if character is name is valid.</returns>
|
||||||
public static bool IsValidCharacterName(this SeString value)
|
public static bool IsValidCharacterName(this DSeString value) => value.ToString().IsValidCharacterName();
|
||||||
{
|
|
||||||
return value.ToString().IsValidCharacterName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue