275 lines
8.0 KiB
C#
275 lines
8.0 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
|
using Dalamud.Interface.Utility;
|
|
using Lumina.Text;
|
|
using System;
|
|
using System.Numerics;
|
|
using System.Threading;
|
|
using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
|
using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
|
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
|
|
|
namespace LightlessSync.Utils;
|
|
|
|
public static class SeStringUtils
|
|
{
|
|
private static int _seStringHitboxCounter;
|
|
private static int _iconHitboxCounter;
|
|
|
|
public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
|
|
{
|
|
var b = new DalamudSeStringBuilder();
|
|
|
|
if (glowColor is Vector4 glow)
|
|
b.Add(new GlowPayload(glow));
|
|
|
|
if (textColor is Vector4 color)
|
|
b.Add(new ColorPayload(color));
|
|
|
|
b.AddText(text ?? string.Empty);
|
|
|
|
if (textColor is not null)
|
|
b.Add(new ColorEndPayload());
|
|
|
|
if (glowColor is not null)
|
|
b.Add(new GlowEndPayload());
|
|
|
|
return b.Build();
|
|
}
|
|
|
|
public static DalamudSeString BuildPlain(string text)
|
|
{
|
|
var b = new DalamudSeStringBuilder();
|
|
b.AddText(text ?? string.Empty);
|
|
return b.Build();
|
|
}
|
|
|
|
public static DalamudSeString BuildRichText(ReadOnlySpan<RichTextEntry> fragments)
|
|
{
|
|
var builder = new LuminaSeStringBuilder();
|
|
|
|
foreach (var fragment in fragments)
|
|
{
|
|
if (string.IsNullOrEmpty(fragment.Text))
|
|
continue;
|
|
|
|
var hasColor = fragment.Color.HasValue;
|
|
if (hasColor)
|
|
{
|
|
Vector4 color = fragment.Color!.Value;
|
|
builder.PushColorRgba(color);
|
|
}
|
|
|
|
if (fragment.Bold)
|
|
builder.AppendSetBold(true);
|
|
|
|
builder.Append(fragment.Text.AsSpan());
|
|
|
|
if (fragment.Bold)
|
|
builder.AppendSetBold(false);
|
|
|
|
if (hasColor)
|
|
builder.PopColor();
|
|
}
|
|
|
|
return DalamudSeString.Parse(builder.ToArray());
|
|
}
|
|
|
|
public static DalamudSeString BuildRichText(params RichTextEntry[] fragments) => BuildRichText(fragments.AsSpan());
|
|
public static void RenderSeString(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
|
{
|
|
drawList ??= ImGui.GetWindowDrawList();
|
|
|
|
var drawParams = new SeStringDrawParams
|
|
{
|
|
Font = font ?? UiBuilder.MonoFont,
|
|
Color = 0xFFFFFFFF,
|
|
WrapWidth = float.MaxValue,
|
|
TargetDrawList = drawList
|
|
};
|
|
|
|
ImGui.SetCursorScreenPos(position);
|
|
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
|
|
|
var textSize = ImGui.CalcTextSize(seString.TextValue);
|
|
if (textSize.Y <= 0f)
|
|
textSize.Y = ImGui.GetTextLineHeight();
|
|
|
|
ImGui.Dummy(new Vector2(0f, textSize.Y));
|
|
}
|
|
|
|
public static void RenderSeStringWrapped(DalamudSeString seString, float wrapWidth, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
|
{
|
|
drawList ??= ImGui.GetWindowDrawList();
|
|
|
|
var drawParams = new SeStringDrawParams
|
|
{
|
|
Font = font ?? ImGui.GetFont(),
|
|
Color = ImGui.GetColorU32(ImGuiCol.Text),
|
|
WrapWidth = wrapWidth,
|
|
TargetDrawList = drawList
|
|
};
|
|
|
|
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
|
|
|
var calcWrapWidth = wrapWidth > 0f ? wrapWidth : -1f;
|
|
var textSize = ImGui.CalcTextSize(seString.TextValue, wrapWidth: calcWrapWidth);
|
|
if (textSize.Y <= 0f)
|
|
textSize.Y = ImGui.GetTextLineHeight();
|
|
|
|
ImGui.Dummy(new Vector2(0f, textSize.Y));
|
|
}
|
|
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null)
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
var drawParams = new SeStringDrawParams
|
|
{
|
|
Font = font ?? UiBuilder.MonoFont,
|
|
Color = 0xFFFFFFFF,
|
|
WrapWidth = float.MaxValue,
|
|
TargetDrawList = drawList
|
|
};
|
|
|
|
ImGui.SetCursorScreenPos(position);
|
|
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
|
|
|
var textSize = ImGui.CalcTextSize(seString.TextValue);
|
|
|
|
ImGui.SetCursorScreenPos(position);
|
|
if (id is not null)
|
|
{
|
|
ImGui.PushID(id);
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushID(Interlocked.Increment(ref _seStringHitboxCounter));
|
|
}
|
|
|
|
try
|
|
{
|
|
ImGui.InvisibleButton("##hitbox", textSize);
|
|
}
|
|
finally
|
|
{
|
|
ImGui.PopID();
|
|
}
|
|
|
|
return textSize;
|
|
}
|
|
|
|
public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null)
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
var drawParams = new SeStringDrawParams
|
|
{
|
|
Font = font ?? UiBuilder.MonoFont,
|
|
Color = 0xFFFFFFFF,
|
|
WrapWidth = float.MaxValue,
|
|
TargetDrawList = drawList
|
|
};
|
|
|
|
var iconMacro = $"<icon({iconId})>";
|
|
var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams);
|
|
|
|
ImGui.SetCursorScreenPos(position);
|
|
if (id is not null)
|
|
{
|
|
ImGui.PushID(id);
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushID(Interlocked.Increment(ref _iconHitboxCounter));
|
|
}
|
|
|
|
try
|
|
{
|
|
ImGui.InvisibleButton("##iconHitbox", drawResult.Size);
|
|
}
|
|
finally
|
|
{
|
|
ImGui.PopID();
|
|
}
|
|
|
|
return drawResult.Size;
|
|
}
|
|
|
|
#region Internal Payloads
|
|
|
|
public readonly record struct RichTextEntry(string Text, Vector4? Color = null, bool Bold = false);
|
|
|
|
private abstract class AbstractColorPayload : Payload
|
|
{
|
|
protected byte Red { get; init; }
|
|
protected byte Green { get; init; }
|
|
protected byte Blue { get; init; }
|
|
|
|
protected override byte[] EncodeImpl()
|
|
{
|
|
return new byte[] { 0x02, ChunkType, 0x05, 0xF6, Red, Green, Blue, 0x03 };
|
|
}
|
|
|
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream) { }
|
|
|
|
public override PayloadType Type => PayloadType.Unknown;
|
|
protected abstract byte ChunkType { get; }
|
|
}
|
|
|
|
private abstract class AbstractColorEndPayload : Payload
|
|
{
|
|
protected override byte[] EncodeImpl()
|
|
{
|
|
return new byte[] { 0x02, ChunkType, 0x02, 0xEC, 0x03 };
|
|
}
|
|
|
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream) { }
|
|
|
|
public override PayloadType Type => PayloadType.Unknown;
|
|
protected abstract byte ChunkType { get; }
|
|
}
|
|
|
|
private sealed class ColorPayload : AbstractColorPayload
|
|
{
|
|
protected override byte ChunkType => 0x13;
|
|
|
|
public ColorPayload(Vector3 color)
|
|
{
|
|
Red = Math.Max((byte)1, (byte)(color.X * 255f));
|
|
Green = Math.Max((byte)1, (byte)(color.Y * 255f));
|
|
Blue = Math.Max((byte)1, (byte)(color.Z * 255f));
|
|
}
|
|
|
|
public ColorPayload(Vector4 color) : this(new Vector3(color.X, color.Y, color.Z)) { }
|
|
}
|
|
|
|
private sealed class ColorEndPayload : AbstractColorEndPayload
|
|
{
|
|
protected override byte ChunkType => 0x13;
|
|
}
|
|
|
|
private sealed class GlowPayload : AbstractColorPayload
|
|
{
|
|
protected override byte ChunkType => 0x14;
|
|
|
|
public GlowPayload(Vector3 color)
|
|
{
|
|
Red = Math.Max((byte)1, (byte)(color.X * 255f));
|
|
Green = Math.Max((byte)1, (byte)(color.Y * 255f));
|
|
Blue = Math.Max((byte)1, (byte)(color.Z * 255f));
|
|
}
|
|
|
|
public GlowPayload(Vector4 color) : this(new Vector3(color.X, color.Y, color.Z)) { }
|
|
}
|
|
|
|
private sealed class GlowEndPayload : AbstractColorEndPayload
|
|
{
|
|
protected override byte ChunkType => 0x14;
|
|
}
|
|
|
|
#endregion
|
|
}
|