mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #2102 from goatcorp/net9-rollup
[net9] Rollup changes from master
This commit is contained in:
commit
df3900f571
40 changed files with 537 additions and 1448 deletions
|
|
@ -25,8 +25,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lumina" Version="5.4.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
||||
<PackageReference Include="Lumina" Version="5.6.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of muted works.
|
||||
/// Gets or sets a list of muted words.
|
||||
/// </summary>
|
||||
public List<string>? BadWords { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>11.0.0.0</DalamudVersion>
|
||||
<DalamudVersion>11.0.1.0</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
@ -67,8 +67,8 @@
|
|||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Include="Lumina" Version="5.4.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
||||
<PackageReference Include="Lumina" Version="5.6.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
public bool IsPvP { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPvPExcludingDen { get; private set; }
|
||||
public bool IsPvPExcludingDen => this.IsPvP && this.TerritoryType != 250;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGPosing => GameMain.IsInGPose();
|
||||
|
|
@ -195,7 +195,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
if (isPvP != this.IsPvP)
|
||||
{
|
||||
this.IsPvP = isPvP;
|
||||
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
||||
|
||||
if (this.IsPvP)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
|
|
@ -20,10 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
|||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType;
|
||||
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType;
|
||||
using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||
using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
||||
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
|
|
@ -107,6 +111,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
this.handleLinkClickHook.Dispose();
|
||||
}
|
||||
|
||||
#region DalamudSeString
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(XivChatEntry chat)
|
||||
{
|
||||
|
|
@ -137,45 +143,74 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LuminaSeString
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue()
|
||||
{
|
||||
while (this.chatQueue.Count > 0)
|
||||
{
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
var replacedMessage = new SeStringBuilder();
|
||||
if (this.chatQueue.Count == 0)
|
||||
return;
|
||||
|
||||
// Normalize Unicode NBSP to the built-in one, as the former won't renderl
|
||||
foreach (var payload in chat.Message.Payloads)
|
||||
var sb = LSeStringBuilder.SharedPool.Get();
|
||||
Span<byte> namebuf = stackalloc byte[256];
|
||||
using var sender = new Utf8String();
|
||||
using var message = new Utf8String();
|
||||
while (this.chatQueue.TryDequeue(out var chat))
|
||||
{
|
||||
if (payload is TextPayload { Text: not null } textPayload)
|
||||
sb.Clear();
|
||||
foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString))
|
||||
{
|
||||
var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE
|
||||
for (var i = 0; i < split.Length; i++)
|
||||
{
|
||||
replacedMessage.AddText(split[i]);
|
||||
if (i + 1 < split.Length)
|
||||
replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03]));
|
||||
if (c.IsSeStringPayload)
|
||||
sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength));
|
||||
else if (c.Value.IntValue == 0x202F)
|
||||
sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro();
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
if (chat.NameBytes.Length + 1 < namebuf.Length)
|
||||
{
|
||||
chat.NameBytes.AsSpan().CopyTo(namebuf);
|
||||
namebuf[chat.NameBytes.Length] = 0;
|
||||
sender.SetString(namebuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
replacedMessage.Add(payload);
|
||||
}
|
||||
sender.SetString(chat.NameBytes.NullTerminate());
|
||||
}
|
||||
|
||||
var sender = Utf8String.FromSequence(chat.Name.Encode());
|
||||
var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode());
|
||||
message.SetString(sb.GetViewAsSpan());
|
||||
|
||||
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
||||
|
||||
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0));
|
||||
|
||||
sender->Dtor(true);
|
||||
message->Dtor(true);
|
||||
this.HandlePrintMessageDetour(
|
||||
RaptureLogModule.Instance(),
|
||||
targetChannel,
|
||||
&sender,
|
||||
&message,
|
||||
chat.Timestamp,
|
||||
(byte)(chat.Silent ? 1 : 0));
|
||||
}
|
||||
|
||||
LSeStringBuilder.SharedPool.Return(sb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -228,29 +263,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
}
|
||||
}
|
||||
|
||||
private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color)
|
||||
{
|
||||
var builder = new SeStringBuilder();
|
||||
|
||||
if (!tag.IsNullOrEmpty())
|
||||
{
|
||||
if (color is not null)
|
||||
{
|
||||
builder.AddUiForeground($"[{tag}] ", color.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddText($"[{tag}] ");
|
||||
}
|
||||
}
|
||||
|
||||
this.Print(new XivChatEntry
|
||||
{
|
||||
Message = builder.AddText(message).Build(),
|
||||
Type = channel,
|
||||
});
|
||||
}
|
||||
|
||||
private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color)
|
||||
{
|
||||
var builder = new SeStringBuilder();
|
||||
|
|
@ -274,6 +286,33 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
});
|
||||
}
|
||||
|
||||
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
|
||||
{
|
||||
var sb = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
if (!tag.IsNullOrEmpty())
|
||||
{
|
||||
if (color is not null)
|
||||
{
|
||||
sb.PushColorType(color.Value);
|
||||
sb.Append($"[{tag}] ");
|
||||
sb.PopColorType();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($"[{tag}] ");
|
||||
}
|
||||
}
|
||||
|
||||
this.Print(new XivChatEntry
|
||||
{
|
||||
MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(),
|
||||
Type = channel,
|
||||
});
|
||||
|
||||
LSeStringBuilder.SharedPool.Return(sb);
|
||||
}
|
||||
|
||||
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
||||
{
|
||||
this.inventoryItemCopyHook.Original(thisPtr, otherPtr);
|
||||
|
|
@ -373,7 +412,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
|
||||
private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData)
|
||||
{
|
||||
if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink)
|
||||
if (linkData == null || linkData->Payload == null || (Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.handleLinkClickHook.Original(thisPtr, linkData);
|
||||
return;
|
||||
|
|
@ -381,7 +420,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var sb = LuminaSeStringBuilder.SharedPool.Get();
|
||||
var sb = LSeStringBuilder.SharedPool.Get();
|
||||
try
|
||||
{
|
||||
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
||||
|
|
@ -392,7 +431,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
sb.Append(payload);
|
||||
|
||||
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
||||
payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link &&
|
||||
payload.MacroCode == MacroCode.Link &&
|
||||
payload.TryGetExpression(out var expr1) &&
|
||||
expr1.TryGetInt(out var expr1Val) &&
|
||||
expr1Val == (int)LinkMacroPayloadType.Terminator)
|
||||
|
|
@ -421,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
}
|
||||
finally
|
||||
{
|
||||
LuminaSeStringBuilder.SharedPool.Return(sb);
|
||||
LSeStringBuilder.SharedPool.Return(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -505,6 +544,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
|||
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.Print(message, messageTag, tagColor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
|
||||
|
||||
private void OnMessageForward(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
=> this.ChatMessage?.Invoke(type, timestamp, ref sender, ref message, ref isHandled);
|
||||
|
||||
|
|
|
|||
|
|
@ -328,9 +328,9 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler
|
|||
public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.CreateObjectReference(
|
||||
(nint)this.context.Ui3DModule->NamePlateObjectInfoPointers[
|
||||
this.ArrayIndex].Value->GameObject);
|
||||
public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable[
|
||||
this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex]
|
||||
.Value->GameObject->ObjectIndex];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBattleChara? BattleChara => this.GameObject as IBattleChara;
|
||||
|
|
|
|||
|
|
@ -102,12 +102,19 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
var uploadPath = "/upload";
|
||||
var uploadData = JsonConvert.SerializeObject(uploadObject);
|
||||
Log.Verbose("{ListingPath}: {ListingUpload}", uploadPath, uploadData);
|
||||
await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json"));
|
||||
|
||||
// ====================================================================================
|
||||
var response = await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json"));
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Log.Warning("Universalis data upload for item#{CatalogId} returned status code {StatusCode}.\n" +
|
||||
" Response Body: {Body}", request.CatalogId, response.StatusCode, body);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task UploadTax(MarketTaxRates taxRates)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using Dalamud.Data;
|
|||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -84,50 +87,32 @@ public class PlayerPayload : Payload
|
|||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.playerName.Length + 7;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE,
|
||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName,
|
||||
/* unk */ 0x01,
|
||||
(byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually
|
||||
/* unk */ 0x01,
|
||||
/* unk */ 0xFF, // these sometimes vary but are frequently this
|
||||
(byte)(this.playerName.Length + 1),
|
||||
};
|
||||
|
||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName));
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
// TODO: should these really be here? additional payloads should come in separately already...
|
||||
|
||||
// encoded names are followed by the name in plain text again
|
||||
// use the payload parsing for consistency, as this is technically a new chunk
|
||||
bytes.AddRange(new TextPayload(this.playerName).Encode());
|
||||
|
||||
// unsure about this entire packet, but it seems to always follow a name
|
||||
bytes.AddRange(new byte[]
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator,
|
||||
0x01, 0x01, 0x01, 0xFF, 0x01,
|
||||
END_BYTE,
|
||||
});
|
||||
|
||||
return bytes.ToArray();
|
||||
var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get();
|
||||
var res = ssb
|
||||
.PushLinkCharacter(this.playerName, this.serverId)
|
||||
.Append(this.playerName)
|
||||
.PopLink()
|
||||
.ToArray();
|
||||
Lumina.Text.SeStringBuilder.SharedPool.Return(ssb);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// unk
|
||||
reader.ReadByte();
|
||||
var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position));
|
||||
var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan());
|
||||
|
||||
this.serverId = GetInteger(reader);
|
||||
if (!rosps.TryGetExpression(out _, out var worldIdExpression, out _, out var characterNameExpression))
|
||||
return;
|
||||
|
||||
// unk
|
||||
reader.ReadBytes(2);
|
||||
if (!worldIdExpression.TryGetUInt(out var worldId))
|
||||
return;
|
||||
|
||||
var nameLen = (int)GetInteger(reader);
|
||||
this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
||||
if (!characterNameExpression.TryGetString(out var characterName))
|
||||
return;
|
||||
|
||||
this.serverId = worldId;
|
||||
this.playerName = characterName.ExtractText();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,9 +133,7 @@ public class SeString
|
|||
{
|
||||
while (stream.Position < len)
|
||||
{
|
||||
var payload = Payload.Decode(reader);
|
||||
if (payload != null)
|
||||
payloads.Add(payload);
|
||||
payloads.Add(Payload.Decode(reader));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,30 @@ public sealed class XivChatEntry
|
|||
/// <summary>
|
||||
/// Gets or sets the sender name.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; } = string.Empty;
|
||||
public SeString Name
|
||||
{
|
||||
get => SeString.Parse(this.NameBytes);
|
||||
set => this.NameBytes = value.Encode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
public SeString Message { get; set; } = string.Empty;
|
||||
public SeString Message
|
||||
{
|
||||
get => SeString.Parse(this.MessageBytes);
|
||||
set => this.MessageBytes = value.Encode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the name payloads
|
||||
/// </summary>
|
||||
public byte[] NameBytes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the message payloads.
|
||||
/// </summary>
|
||||
public byte[] MessageBytes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether new message sounds should be silenced or not.
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ public static partial class ImGuiComponents
|
|||
/// <param name="icon">Icon to use.</param>
|
||||
/// <param name="text">Text to use.</param>
|
||||
/// <returns>Width.</returns>
|
||||
internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
|
||||
public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
|
||||
{
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public static partial class ImGuiComponents
|
|||
/// <param name="activeColor">The color of the actively-selected button.</param>
|
||||
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
|
||||
/// <returns>True if any button is clicked.</returns>
|
||||
internal static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
public static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
{
|
||||
var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair<FontAwesomeIcon, T>(icon, value));
|
||||
return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor);
|
||||
|
|
@ -43,7 +43,7 @@ public static partial class ImGuiComponents
|
|||
/// <param name="activeColor">The color of the actively-selected button.</param>
|
||||
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
|
||||
/// <returns>True if any button is clicked.</returns>
|
||||
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
public static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
{
|
||||
defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button);
|
||||
activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
|||
using ImGuiNET;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Parse;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
@ -66,8 +67,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
[ServiceManager.ServiceConstructor]
|
||||
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
||||
{
|
||||
this.colorStackSet = new(
|
||||
dm.Excel.GetSheet<UIColor>() ?? throw new InvalidOperationException("Failed to access UIColor sheet."));
|
||||
this.colorStackSet = new(dm.Excel.GetSheet<UIColor>());
|
||||
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Lumina.Text;
|
||||
|
||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass;
|
||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory;
|
||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass;
|
||||
|
|
|
|||
|
|
@ -1,325 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||
|
||||
/// <summary>Enumerates a UTF-N byte sequence by codepoint.</summary>
|
||||
[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")]
|
||||
internal ref struct UtfEnumerator
|
||||
{
|
||||
private readonly ReadOnlySpan<byte> data;
|
||||
private readonly UtfEnumeratorFlags flags;
|
||||
private readonly byte numBytesPerUnit;
|
||||
private bool isBigEndian;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
||||
/// <param name="data">UTF-N byte sequence.</param>
|
||||
/// <param name="flags">Enumeration flags.</param>
|
||||
public UtfEnumerator(ReadOnlySpan<byte> data, UtfEnumeratorFlags flags)
|
||||
{
|
||||
this.data = data;
|
||||
this.flags = flags;
|
||||
this.numBytesPerUnit = (this.flags & UtfEnumeratorFlags.UtfMask) switch
|
||||
{
|
||||
UtfEnumeratorFlags.Utf8 or UtfEnumeratorFlags.Utf8SeString => 1,
|
||||
UtfEnumeratorFlags.Utf16 => 2,
|
||||
UtfEnumeratorFlags.Utf32 => 4,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.flags), this.flags, "Multiple UTF flag specified."),
|
||||
};
|
||||
this.isBigEndian = (flags & UtfEnumeratorFlags.EndiannessMask) switch
|
||||
{
|
||||
UtfEnumeratorFlags.NativeEndian => !BitConverter.IsLittleEndian,
|
||||
UtfEnumeratorFlags.LittleEndian => false,
|
||||
UtfEnumeratorFlags.BigEndian => true,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(flags), flags, "Multiple endianness flag specified."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEnumerator.Current"/>
|
||||
public Subsequence Current { get; private set; } = default;
|
||||
|
||||
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
||||
/// <param name="data">UTF-N byte sequence.</param>
|
||||
/// <param name="flags">Enumeration flags.</param>
|
||||
/// <returns>A new enumerator.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UtfEnumerator From(ReadOnlySpan<byte> data, UtfEnumeratorFlags flags) => new(data, flags);
|
||||
|
||||
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
||||
/// <param name="data">UTF-N byte sequence.</param>
|
||||
/// <param name="flags">Enumeration flags.</param>
|
||||
/// <returns>A new enumerator.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UtfEnumerator From(ReadOnlySpan<char> data, UtfEnumeratorFlags flags) =>
|
||||
new(MemoryMarshal.Cast<char, byte>(data), flags);
|
||||
|
||||
/// <summary>Gets the representative <c>char</c> for a given SeString macro code.</summary>
|
||||
/// <param name="macroCode">The macro code.</param>
|
||||
/// <returns>Representative <c>char</c>, or <see cref="char.MaxValue"/> if none.</returns>
|
||||
public static char RepresentativeCharFor(MacroCode macroCode) => macroCode switch
|
||||
{
|
||||
MacroCode.NewLine => '\u0085',
|
||||
MacroCode.SoftHyphen => '\u00AD',
|
||||
MacroCode.NonBreakingSpace => '\u00A0',
|
||||
MacroCode.Hyphen => '-',
|
||||
MacroCode.Icon or MacroCode.Icon2 => '\uFFFC',
|
||||
_ => char.MaxValue,
|
||||
};
|
||||
|
||||
/// <summary>Attempts to peek the next item.</summary>
|
||||
/// <param name="nextSubsequence">Retrieved next item.</param>
|
||||
/// <param name="isStillBigEndian">Whether it still should be parsed in big endian.</param>
|
||||
/// <returns><c>true</c> if anything is retrieved.</returns>
|
||||
/// <exception cref="EncoderFallbackException">The sequence is not a fully valid Unicode sequence, and
|
||||
/// <see cref="UtfEnumeratorFlags.ThrowOnFirstError"/> is set.</exception>
|
||||
public readonly bool TryPeekNext(out Subsequence nextSubsequence, out bool isStillBigEndian)
|
||||
{
|
||||
var offset = this.Current.ByteOffset + this.Current.ByteLength;
|
||||
isStillBigEndian = this.isBigEndian;
|
||||
while (true)
|
||||
{
|
||||
var subspan = this.data[offset..];
|
||||
|
||||
if (subspan.IsEmpty)
|
||||
{
|
||||
nextSubsequence = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
UtfValue value;
|
||||
int length;
|
||||
var isBroken =
|
||||
this.numBytesPerUnit switch
|
||||
{
|
||||
1 => !UtfValue.TryDecode8(subspan, out value, out length),
|
||||
2 => !UtfValue.TryDecode16(subspan, isStillBigEndian, out value, out length),
|
||||
4 => !UtfValue.TryDecode32(subspan, isStillBigEndian, out value, out length),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
if (!isBroken && value.IntValue == 0xFFFE)
|
||||
{
|
||||
if ((this.flags & UtfEnumeratorFlags.DisrespectByteOrderMask) == 0)
|
||||
{
|
||||
isStillBigEndian = !isStillBigEndian;
|
||||
value = 0xFEFF;
|
||||
}
|
||||
|
||||
if ((this.flags & UtfEnumeratorFlags.YieldByteOrderMask) == 0)
|
||||
{
|
||||
offset += length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBroken || !Rune.IsValid(value))
|
||||
{
|
||||
switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask)
|
||||
{
|
||||
case UtfEnumeratorFlags.ReplaceErrors:
|
||||
break;
|
||||
|
||||
case UtfEnumeratorFlags.IgnoreErrors:
|
||||
offset = Math.Min(offset + this.numBytesPerUnit, this.data.Length);
|
||||
continue;
|
||||
|
||||
case UtfEnumeratorFlags.ThrowOnFirstError:
|
||||
if (isBroken)
|
||||
throw new EncoderFallbackException($"0x{subspan[0]:X02} is not a valid sequence.");
|
||||
throw new EncoderFallbackException(
|
||||
$"U+{value.UIntValue:X08} is not a valid unicode codepoint.");
|
||||
|
||||
case UtfEnumeratorFlags.TerminateOnFirstError:
|
||||
default:
|
||||
nextSubsequence = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBroken)
|
||||
value = subspan[0];
|
||||
|
||||
if (value == SeString.StartByte && (this.flags & UtfEnumeratorFlags.Utf8SeString) != 0)
|
||||
{
|
||||
var e = new ReadOnlySeStringSpan(subspan).GetEnumerator();
|
||||
e.MoveNext();
|
||||
switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask)
|
||||
{
|
||||
case var _ when e.Current.Type is ReadOnlySePayloadType.Macro:
|
||||
nextSubsequence = Subsequence.FromPayload(
|
||||
e.Current.MacroCode,
|
||||
offset,
|
||||
e.Current.EnvelopeByteLength);
|
||||
return true;
|
||||
|
||||
case UtfEnumeratorFlags.ReplaceErrors:
|
||||
value = '\uFFFE';
|
||||
length = e.Current.EnvelopeByteLength;
|
||||
isBroken = true;
|
||||
break;
|
||||
|
||||
case UtfEnumeratorFlags.IgnoreErrors:
|
||||
offset = Math.Min(offset + e.Current.EnvelopeByteLength, this.data.Length);
|
||||
continue;
|
||||
|
||||
case UtfEnumeratorFlags.ThrowOnFirstError:
|
||||
throw new EncoderFallbackException("Invalid SeString payload.");
|
||||
|
||||
case UtfEnumeratorFlags.TerminateOnFirstError:
|
||||
default:
|
||||
nextSubsequence = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nextSubsequence = Subsequence.FromUnicode(value, offset, length, isBroken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (!this.TryPeekNext(out var next, out var isStillBigEndian))
|
||||
return false;
|
||||
|
||||
this.Current = next;
|
||||
this.isBigEndian = isStillBigEndian;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UtfEnumerator GetEnumerator() => new(this.data, this.flags);
|
||||
|
||||
/// <summary>A part of a UTF-N sequence containing one codepoint.</summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
||||
[DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")]
|
||||
public readonly struct Subsequence : IEquatable<Subsequence>
|
||||
{
|
||||
/// <summary>The codepoint. Valid if <see cref="IsSeStringPayload"/> is <c>false</c>.</summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly UtfValue Value;
|
||||
|
||||
/// <summary>The macro code. Valid if <see cref="IsSeStringPayload"/> is <c>true</c>.</summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly MacroCode MacroCode;
|
||||
|
||||
/// <summary>The offset of this part of a UTF-8 sequence.</summary>
|
||||
[FieldOffset(4)]
|
||||
public readonly int ByteOffset;
|
||||
|
||||
/// <summary>The length of this part of a UTF-8 sequence.</summary>
|
||||
/// <remarks>This may not match <see cref="UtfValue.Length8"/>, if <see cref="BrokenSequence"/> is <c>true</c>.
|
||||
/// </remarks>
|
||||
[FieldOffset(8)]
|
||||
public readonly int ByteLength;
|
||||
|
||||
/// <summary>Whether this part of the UTF-8 sequence is broken.</summary>
|
||||
[FieldOffset(12)]
|
||||
public readonly bool BrokenSequence;
|
||||
|
||||
/// <summary>Whether this part of the SeString sequence is a payload.</summary>
|
||||
[FieldOffset(13)]
|
||||
public readonly bool IsSeStringPayload;
|
||||
|
||||
/// <summary>Storage at byte offset 0, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
|
||||
[FieldOffset(0)]
|
||||
private readonly ulong storage0;
|
||||
|
||||
/// <summary>Storage at byte offset 8, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
|
||||
[FieldOffset(8)]
|
||||
private readonly ulong storage1;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="Subsequence"/> struct.</summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
||||
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
|
||||
/// <param name="isSeStringPayload">Whether this part of the SeString sequence is a payload.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Subsequence(uint value, int byteOffset, int byteLength, bool brokenSequence, bool isSeStringPayload)
|
||||
{
|
||||
this.Value = new(value);
|
||||
this.ByteOffset = byteOffset;
|
||||
this.ByteLength = byteLength;
|
||||
this.BrokenSequence = brokenSequence;
|
||||
this.IsSeStringPayload = isSeStringPayload;
|
||||
}
|
||||
|
||||
/// <summary>Gets the effective <c>char</c> value, with invalid or non-representable codepoints replaced.
|
||||
/// </summary>
|
||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
||||
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>
|
||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
||||
public int EffectiveInt =>
|
||||
this.IsSeStringPayload
|
||||
? RepresentativeCharFor(this.MacroCode)
|
||||
: this.BrokenSequence || !this.Value.TryGetRune(out var rune)
|
||||
? 0xFFFD
|
||||
: 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)]
|
||||
public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(Subsequence left, Subsequence right) => !left.Equals(right);
|
||||
|
||||
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a Unicode value.</summary>
|
||||
/// <param name="codepoint">The codepoint.</param>
|
||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
||||
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
|
||||
/// <returns>A new instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) =>
|
||||
new(codepoint, byteOffset, byteLength, brokenSequence, false);
|
||||
|
||||
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a SeString payload.</summary>
|
||||
/// <param name="macroCode">The macro code.</param>
|
||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
||||
/// <returns>A new instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) =>
|
||||
new((uint)macroCode, byteOffset, byteLength, false, true);
|
||||
|
||||
/// <summary>Tests whether this subsequence contains a valid Unicode codepoint.</summary>
|
||||
/// <returns><c>true</c> if this subsequence contains a valid Unicode codepoint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||
|
||||
/// <summary>Flags on enumerating a unicode sequence.</summary>
|
||||
[Flags]
|
||||
internal enum UtfEnumeratorFlags
|
||||
{
|
||||
/// <summary>Use the default configuration of <see cref="Utf8"/> and <see cref="ReplaceErrors"/>.</summary>
|
||||
Default = default,
|
||||
|
||||
/// <summary>Enumerate as UTF-8 (the default.)</summary>
|
||||
Utf8 = Default,
|
||||
|
||||
/// <summary>Enumerate as UTF-8 in a SeString.</summary>
|
||||
Utf8SeString = 1 << 1,
|
||||
|
||||
/// <summary>Enumerate as UTF-16.</summary>
|
||||
Utf16 = 1 << 2,
|
||||
|
||||
/// <summary>Enumerate as UTF-32.</summary>
|
||||
Utf32 = 1 << 3,
|
||||
|
||||
/// <summary>Bitmask for specifying the encoding.</summary>
|
||||
UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32,
|
||||
|
||||
/// <summary>On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.)</summary>
|
||||
ReplaceErrors = Default,
|
||||
|
||||
/// <summary>On error, drop the invalid byte.</summary>
|
||||
IgnoreErrors = 1 << 4,
|
||||
|
||||
/// <summary>On error, stop the handling.</summary>
|
||||
TerminateOnFirstError = 1 << 5,
|
||||
|
||||
/// <summary>On error, throw an exception.</summary>
|
||||
ThrowOnFirstError = 1 << 6,
|
||||
|
||||
/// <summary>Bitmask for specifying the error handling mode.</summary>
|
||||
ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError,
|
||||
|
||||
/// <summary>Use the current system native endianness from <see cref="BitConverter.IsLittleEndian"/>
|
||||
/// (the default.)</summary>
|
||||
NativeEndian = Default,
|
||||
|
||||
/// <summary>Use little endianness.</summary>
|
||||
LittleEndian = 1 << 7,
|
||||
|
||||
/// <summary>Use big endianness.</summary>
|
||||
BigEndian = 1 << 8,
|
||||
|
||||
/// <summary>Bitmask for specifying endianness.</summary>
|
||||
EndiannessMask = NativeEndian | LittleEndian | BigEndian,
|
||||
|
||||
/// <summary>Disrespect byte order mask.</summary>
|
||||
DisrespectByteOrderMask = 1 << 9,
|
||||
|
||||
/// <summary>Yield byte order masks, if it shows up.</summary>
|
||||
YieldByteOrderMask = 1 << 10,
|
||||
}
|
||||
|
|
@ -1,665 +0,0 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||
|
||||
/// <summary>Represents a single value to be used in a UTF-N byte sequence.</summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 4)]
|
||||
[DebuggerDisplay("0x{IntValue,h} ({CharValue})")]
|
||||
internal readonly struct UtfValue : IEquatable<UtfValue>, IComparable<UtfValue>
|
||||
{
|
||||
/// <summary>The unicode codepoint in <c>int</c>, that may not be in a valid range.</summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly int IntValue;
|
||||
|
||||
/// <summary>The unicode codepoint in <c>uint</c>, that may not be in a valid range.</summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly uint UIntValue;
|
||||
|
||||
/// <summary>The high UInt16 value in <c>char</c>, that may have been cut off if outside BMP.</summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly char CharValue;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="UtfValue"/> struct.</summary>
|
||||
/// <param name="value">The raw codepoint value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UtfValue(uint value) => this.UIntValue = value;
|
||||
|
||||
/// <inheritdoc cref="UtfValue(uint)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UtfValue(int value) => this.IntValue = value;
|
||||
|
||||
/// <summary>Gets the length of this codepoint, encoded in UTF-8.</summary>
|
||||
public int Length8
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => GetEncodedLength8(this);
|
||||
}
|
||||
|
||||
/// <summary>Gets the length of this codepoint, encoded in UTF-16.</summary>
|
||||
public int Length16
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => GetEncodedLength16(this);
|
||||
}
|
||||
|
||||
/// <summary>Gets the short name, if supported.</summary>
|
||||
/// <returns>The buffer containing the short name, or empty if unsupported.</returns>
|
||||
public ReadOnlySpan<char> ShortName
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => GetShortName(this);
|
||||
}
|
||||
|
||||
public static implicit operator uint(UtfValue c) => c.UIntValue;
|
||||
|
||||
public static implicit operator int(UtfValue c) => c.IntValue;
|
||||
|
||||
public static implicit operator UtfValue(byte c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(sbyte c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(ushort c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(short c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(uint c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(int c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(char c) => new(c);
|
||||
|
||||
public static implicit operator UtfValue(Rune c) => new(c.Value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(UtfValue left, UtfValue right) => left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(UtfValue left, UtfValue right) => !left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator <(UtfValue left, UtfValue right) => left.CompareTo(right) < 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator >(UtfValue left, UtfValue right) => left.CompareTo(right) > 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator <=(UtfValue left, UtfValue right) => left.CompareTo(right) <= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator >=(UtfValue left, UtfValue right) => left.CompareTo(right) >= 0;
|
||||
|
||||
/// <summary>Gets the short name of the codepoint, for some select codepoints.</summary>
|
||||
/// <param name="codepoint">The codepoint.</param>
|
||||
/// <returns>The value.</returns>
|
||||
public static ReadOnlySpan<char> GetShortName(int codepoint) =>
|
||||
codepoint switch
|
||||
{
|
||||
0x00 => "NUL",
|
||||
0x01 => "SOH",
|
||||
0x02 => "STX",
|
||||
0x03 => "ETX",
|
||||
0x04 => "EOT",
|
||||
0x05 => "ENQ",
|
||||
0x06 => "ACK",
|
||||
0x07 => "BEL",
|
||||
0x08 => "BS",
|
||||
0x09 => "HT",
|
||||
0x0a => "LF",
|
||||
0x0b => "VT",
|
||||
0x0c => "FF",
|
||||
0x0d => "CR",
|
||||
0x0e => "SO",
|
||||
0x0f => "SI",
|
||||
|
||||
0x10 => "DLE",
|
||||
0x11 => "DC1",
|
||||
0x12 => "DC2",
|
||||
0x13 => "DC3",
|
||||
0x14 => "DC4",
|
||||
0x15 => "NAK",
|
||||
0x16 => "SYN",
|
||||
0x17 => "SOH",
|
||||
0x18 => "CAN",
|
||||
0x19 => "EOM",
|
||||
0x1a => "SUB",
|
||||
0x1b => "ESC",
|
||||
0x1c => "FS",
|
||||
0x1d => "GS",
|
||||
0x1e => "RS",
|
||||
0x1f => "US",
|
||||
|
||||
0x80 => "PAD",
|
||||
0x81 => "HOP",
|
||||
0x82 => "BPH",
|
||||
0x83 => "NBH",
|
||||
0x84 => "IND",
|
||||
0x85 => "NEL",
|
||||
0x86 => "SSA",
|
||||
0x87 => "ESA",
|
||||
0x88 => "HTS",
|
||||
0x89 => "HTJ",
|
||||
0x8a => "VTS",
|
||||
0x8b => "PLD",
|
||||
0x8c => "PLU",
|
||||
0x8d => "RI",
|
||||
0x8e => "SS2",
|
||||
0x8f => "SS3",
|
||||
|
||||
0x90 => "DCS",
|
||||
0x91 => "PU1",
|
||||
0x92 => "PU2",
|
||||
0x93 => "STS",
|
||||
0x94 => "CCH",
|
||||
0x95 => "MW",
|
||||
0x96 => "SPA",
|
||||
0x97 => "EPA",
|
||||
0x98 => "SOS",
|
||||
0x99 => "SGC",
|
||||
0x9a => "SCI",
|
||||
0x9b => "CSI",
|
||||
0x9c => "ST",
|
||||
0x9d => "OSC",
|
||||
0x9e => "PM",
|
||||
0x9f => "APC",
|
||||
|
||||
0xa0 => "NBSP",
|
||||
0xad => "SHY",
|
||||
|
||||
_ => default,
|
||||
};
|
||||
|
||||
/// <summary>Gets the length of the codepoint, when encoded in UTF-8.</summary>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <returns>The length.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetEncodedLength8(int codepoint) => (uint)codepoint switch
|
||||
{
|
||||
< 1u << 7 => 1,
|
||||
< 1u << 11 => 2,
|
||||
< 1u << 16 => 3,
|
||||
< 1u << 21 => 4,
|
||||
// Not a valid Unicode codepoint anymore below.
|
||||
< 1u << 26 => 5,
|
||||
< 1u << 31 => 6,
|
||||
_ => 7,
|
||||
};
|
||||
|
||||
/// <summary>Gets the length of the codepoint, when encoded in UTF-16.</summary>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <returns>The length.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetEncodedLength16(int codepoint) => (uint)codepoint switch
|
||||
{
|
||||
< 0x10000 => 2,
|
||||
< 0x10000 + (1 << 20) => 4,
|
||||
// Not a valid Unicode codepoint anymore below.
|
||||
< 0x10000 + (1 << 30) => 6,
|
||||
_ => 8,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="TryDecode8(ReadOnlySpan{byte}, out UtfValue, out int)"/>
|
||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryDecode8(ref ReadOnlySpan<byte> source, out UtfValue value, out int length)
|
||||
{
|
||||
var v = TryDecode8(source, out value, out length);
|
||||
source = source[length..];
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to decode a value from a UTF-8 byte sequence.</summary>
|
||||
/// <param name="source">The span to decode from.</param>
|
||||
/// <param name="value">The decoded value.</param>
|
||||
/// <param name="length">The length of the consumed bytes. <c>1</c> if sequence is broken.</param>
|
||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> can still be returned,
|
||||
/// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only
|
||||
/// indicating whether the sequence could be decoded into a number, without being too short.</remarks>
|
||||
public static unsafe bool TryDecode8(ReadOnlySpan<byte> source, out UtfValue value, out int length)
|
||||
{
|
||||
if (source.IsEmpty)
|
||||
{
|
||||
value = default;
|
||||
length = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed (byte* ptr = source)
|
||||
{
|
||||
if ((ptr[0] & 0x80) == 0)
|
||||
{
|
||||
length = 1;
|
||||
value = ptr[0];
|
||||
}
|
||||
else if ((ptr[0] & 0b11100000) == 0b11000000 && source.Length >= 2
|
||||
&& ((uint)ptr[1] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 2;
|
||||
value = (((uint)ptr[0] & 0x1F) << 6) |
|
||||
(((uint)ptr[1] & 0x3F) << 0);
|
||||
}
|
||||
else if (((uint)ptr[0] & 0b11110000) == 0b11100000 && source.Length >= 3
|
||||
&& ((uint)ptr[1] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[2] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 3;
|
||||
value = (((uint)ptr[0] & 0x0F) << 12) |
|
||||
(((uint)ptr[1] & 0x3F) << 6) |
|
||||
(((uint)ptr[2] & 0x3F) << 0);
|
||||
}
|
||||
else if (((uint)ptr[0] & 0b11111000) == 0b11110000 && source.Length >= 4
|
||||
&& ((uint)ptr[1] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[2] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[3] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 4;
|
||||
value = (((uint)ptr[0] & 0x07) << 18) |
|
||||
(((uint)ptr[1] & 0x3F) << 12) |
|
||||
(((uint)ptr[2] & 0x3F) << 6) |
|
||||
(((uint)ptr[3] & 0x3F) << 0);
|
||||
}
|
||||
else if (((uint)ptr[0] & 0b11111100) == 0b11111000 && source.Length >= 5
|
||||
&& ((uint)ptr[1] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[2] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[3] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[4] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 5;
|
||||
value = (((uint)ptr[0] & 0x03) << 24) |
|
||||
(((uint)ptr[1] & 0x3F) << 18) |
|
||||
(((uint)ptr[2] & 0x3F) << 12) |
|
||||
(((uint)ptr[3] & 0x3F) << 6) |
|
||||
(((uint)ptr[4] & 0x3F) << 0);
|
||||
}
|
||||
else if (((uint)ptr[0] & 0b11111110) == 0b11111100 && source.Length >= 6
|
||||
&& ((uint)ptr[1] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[2] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[3] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[4] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[5] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 6;
|
||||
value = (((uint)ptr[0] & 0x01) << 30) |
|
||||
(((uint)ptr[1] & 0x3F) << 24) |
|
||||
(((uint)ptr[2] & 0x3F) << 18) |
|
||||
(((uint)ptr[3] & 0x3F) << 12) |
|
||||
(((uint)ptr[4] & 0x3F) << 6) |
|
||||
(((uint)ptr[5] & 0x3F) << 0);
|
||||
}
|
||||
else if (((uint)ptr[0] & 0b11111111) == 0b11111110 && source.Length >= 7
|
||||
&& ((uint)ptr[1] & 0b11111100) == 0b10000000
|
||||
&& ((uint)ptr[2] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[3] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[4] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[5] & 0b11000000) == 0b10000000
|
||||
&& ((uint)ptr[6] & 0b11000000) == 0b10000000)
|
||||
{
|
||||
length = 7;
|
||||
value = (((uint)ptr[1] & 0x03) << 30) |
|
||||
(((uint)ptr[2] & 0x3F) << 24) |
|
||||
(((uint)ptr[3] & 0x3F) << 18) |
|
||||
(((uint)ptr[4] & 0x3F) << 12) |
|
||||
(((uint)ptr[5] & 0x3F) << 6) |
|
||||
(((uint)ptr[6] & 0x3F) << 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = 1;
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryDecode16(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
|
||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryDecode16(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
||||
{
|
||||
var v = TryDecode16(source, be, out value, out length);
|
||||
source = source[length..];
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to decode a value from a UTF-16 byte sequence.</summary>
|
||||
/// <param name="source">The span to decode from.</param>
|
||||
/// <param name="be">Whether to use big endian.</param>
|
||||
/// <param name="value">The decoded value.</param>
|
||||
/// <param name="length">The length of the consumed bytes. <c>1</c> if cut short.
|
||||
/// <c>2</c> if sequence is broken.</param>
|
||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> can still be returned,
|
||||
/// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only
|
||||
/// indicating whether the sequence could be decoded into a number, without being too short.</remarks>
|
||||
public static unsafe bool TryDecode16(ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
||||
{
|
||||
if (source.Length < 2)
|
||||
{
|
||||
value = default;
|
||||
length = source.Length;
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed (byte* ptr = source)
|
||||
{
|
||||
var p16 = (ushort*)ptr;
|
||||
var val = be == BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(*p16) : *p16;
|
||||
if (char.IsHighSurrogate((char)val))
|
||||
{
|
||||
var lookahead1 = source.Length >= 4 ? p16[1] : 0;
|
||||
var lookahead2 = source.Length >= 6 ? p16[2] : 0;
|
||||
var lookahead3 = source.Length >= 8 ? p16[3] : 0;
|
||||
if (char.IsLowSurrogate((char)lookahead1))
|
||||
{
|
||||
// Not a valid Unicode codepoint anymore inside the block below.
|
||||
if (char.IsLowSurrogate((char)lookahead2))
|
||||
{
|
||||
if (char.IsLowSurrogate((char)lookahead3))
|
||||
{
|
||||
value = 0x10000
|
||||
+ (((val & 0x3) << 30) |
|
||||
((lookahead1 & 0x3FF) << 20) |
|
||||
((lookahead2 & 0x3FF) << 10) |
|
||||
((lookahead3 & 0x3FF) << 0));
|
||||
length = 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0x10000
|
||||
+ (((val & 0x3FF) << 20) |
|
||||
((lookahead1 & 0x3FF) << 10) |
|
||||
((lookahead2 & 0x3FF) << 0));
|
||||
length = 6;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0x10000 +
|
||||
(((val & 0x3FF) << 10) |
|
||||
((lookahead1 & 0x3FF) << 0));
|
||||
length = 4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calls are supposed to handle unpaired surrogates.
|
||||
value = val;
|
||||
length = 2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryDecode32(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
|
||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryDecode32(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
||||
{
|
||||
var v = TryDecode32(source, be, out value, out length);
|
||||
source = source[length..];
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to decode a value from a UTF-32 byte sequence.</summary>
|
||||
/// <param name="source">The span to decode from.</param>
|
||||
/// <param name="be">Whether to use big endian.</param>
|
||||
/// <param name="value">The decoded value.</param>
|
||||
/// <param name="length">The length of the consumed bytes. <c>1 to 3</c> if cut short.
|
||||
/// <c>4</c> if sequence is broken.</param>
|
||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> can still be returned,
|
||||
/// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only
|
||||
/// indicating whether the sequence could be decoded into a number, without being too short.</remarks>
|
||||
public static bool TryDecode32(ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
||||
{
|
||||
if (source.Length < 4)
|
||||
{
|
||||
value = default;
|
||||
length = source.Length;
|
||||
return false;
|
||||
}
|
||||
|
||||
length = 4;
|
||||
if ((be && BinaryPrimitives.TryReadInt32BigEndian(source, out var i32))
|
||||
|| (!be && BinaryPrimitives.TryReadInt32LittleEndian(source, out i32)))
|
||||
{
|
||||
value = i32;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
||||
/// <param name="target">The target stream.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <returns>The length of the encoded data.</returns>
|
||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Encode8(Stream target, int codepoint)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[7];
|
||||
Encode8(buf, codepoint, out var length);
|
||||
target.Write(buf[..length]);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
||||
/// <param name="target">The target byte span.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <returns>The length of the encoded data.</returns>
|
||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Encode8(ref Span<byte> target, int codepoint)
|
||||
{
|
||||
target = Encode8(target, codepoint, out var length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
||||
/// <param name="target">The optional target byte span.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <param name="length">The length of the encoded data.</param>
|
||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
||||
public static Span<byte> Encode8(Span<byte> target, int codepoint, out int length)
|
||||
{
|
||||
var value = (uint)codepoint;
|
||||
length = GetEncodedLength8(codepoint);
|
||||
if (target.IsEmpty)
|
||||
return target;
|
||||
|
||||
switch (length)
|
||||
{
|
||||
case 1:
|
||||
target[0] = (byte)value;
|
||||
return target[1..];
|
||||
case 2:
|
||||
target[0] = (byte)(0xC0 | ((value >> 6) & 0x1F));
|
||||
target[1] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[2..];
|
||||
case 3:
|
||||
target[0] = (byte)(0xE0 | ((value >> 12) & 0x0F));
|
||||
target[1] = (byte)(0x80 | ((value >> 6) & 0x3F));
|
||||
target[2] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[3..];
|
||||
case 4:
|
||||
target[0] = (byte)(0xF0 | ((value >> 18) & 0x07));
|
||||
target[1] = (byte)(0x80 | ((value >> 12) & 0x3F));
|
||||
target[2] = (byte)(0x80 | ((value >> 6) & 0x3F));
|
||||
target[3] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[4..];
|
||||
case 5:
|
||||
target[0] = (byte)(0xF8 | ((value >> 24) & 0x03));
|
||||
target[1] = (byte)(0x80 | ((value >> 18) & 0x3F));
|
||||
target[2] = (byte)(0x80 | ((value >> 12) & 0x3F));
|
||||
target[3] = (byte)(0x80 | ((value >> 6) & 0x3F));
|
||||
target[4] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[5..];
|
||||
case 6:
|
||||
target[0] = (byte)(0xFC | ((value >> 30) & 0x01));
|
||||
target[1] = (byte)(0x80 | ((value >> 24) & 0x3F));
|
||||
target[2] = (byte)(0x80 | ((value >> 18) & 0x3F));
|
||||
target[3] = (byte)(0x80 | ((value >> 12) & 0x3F));
|
||||
target[4] = (byte)(0x80 | ((value >> 6) & 0x3F));
|
||||
target[5] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[6..];
|
||||
case 7:
|
||||
target[0] = 0xFE;
|
||||
target[1] = (byte)(0x80 | ((value >> 30) & 0x03));
|
||||
target[2] = (byte)(0x80 | ((value >> 24) & 0x3F));
|
||||
target[3] = (byte)(0x80 | ((value >> 18) & 0x3F));
|
||||
target[4] = (byte)(0x80 | ((value >> 12) & 0x3F));
|
||||
target[5] = (byte)(0x80 | ((value >> 6) & 0x3F));
|
||||
target[6] = (byte)(0x80 | ((value >> 0) & 0x3F));
|
||||
return target[7..];
|
||||
default:
|
||||
Debug.Assert(false, $"{nameof(Length8)} property should have produced all possible cases.");
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
||||
/// <param name="target">The target stream.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <param name="be">Whether to use big endian.</param>
|
||||
/// <returns>The length of the encoded data.</returns>
|
||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Encode16(Stream target, int codepoint, bool be)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[8];
|
||||
Encode16(buf, codepoint, be, out var length);
|
||||
target.Write(buf[..length]);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
||||
/// <param name="target">The target byte span.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <param name="be">Whether to use big endian.</param>
|
||||
/// <returns>The length of the encoded data.</returns>
|
||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Encode16(ref Span<byte> target, int codepoint, bool be)
|
||||
{
|
||||
target = Encode16(target, codepoint, be, out var length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
||||
/// <param name="target">The optional target byte span.</param>
|
||||
/// <param name="codepoint">The codepoint to encode.</param>
|
||||
/// <param name="be">Whether to use big endian.</param>
|
||||
/// <param name="length">The length of the encoded data.</param>
|
||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
||||
public static Span<byte> Encode16(Span<byte> target, int codepoint, bool be, out int length)
|
||||
{
|
||||
var value = (uint)codepoint;
|
||||
length = GetEncodedLength16(codepoint);
|
||||
if (target.IsEmpty)
|
||||
return target;
|
||||
|
||||
if (be)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)value);
|
||||
return target[2..];
|
||||
case 4:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[4..];
|
||||
case 6:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[6..];
|
||||
case 8:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16BigEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[8..];
|
||||
default:
|
||||
Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases.");
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)value);
|
||||
return target[2..];
|
||||
case 4:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[4..];
|
||||
case 6:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[6..];
|
||||
case 8:
|
||||
value -= 0x10000;
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF)));
|
||||
return target[8..];
|
||||
default:
|
||||
Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases.");
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(UtfValue other) => this.IntValue == other.IntValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => this.IntValue;
|
||||
|
||||
/// <summary>Attempts to get the corresponding rune.</summary>
|
||||
/// <param name="rune">The retrieved rune.</param>
|
||||
/// <returns><c>true</c> if retrieved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetRune(out Rune rune)
|
||||
{
|
||||
if (Rune.IsValid(this.IntValue))
|
||||
{
|
||||
rune = new(this.IntValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
rune = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Encodes the codepoint to the target.</summary>
|
||||
/// <param name="target">The target byte span.</param>
|
||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> Encode8(Span<byte> target) => Encode8(target, this, out _);
|
||||
}
|
||||
|
|
@ -604,10 +604,17 @@ internal partial class InterfaceManager : IInternalDisposableService
|
|||
if (iniFileInfo.Length > 1200000)
|
||||
{
|
||||
Log.Warning("dalamudUI.ini was over 1mb, deleting");
|
||||
iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName!, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
||||
iniFileInfo.CopyTo(
|
||||
Path.Combine(
|
||||
iniFileInfo.DirectoryName!,
|
||||
$"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
||||
iniFileInfo.Delete();
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
Log.Warning("dalamudUI.ini did not exist, ImGUI will create a new one.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not delete dalamudUI.ini");
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
|||
|
||||
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
|
||||
|
||||
/// <inheritdoc cref="AddonTree"/>
|
||||
public unsafe partial class AddonTree
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -23,12 +24,11 @@ public unsafe partial class AddonTree
|
|||
if (addon->AtkValuesCount > 0 && atkValue != null)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}");
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
using (ImRaii.Table(
|
||||
"atkUnitBase_atkValueTable",
|
||||
3,
|
||||
ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg))
|
||||
using var tbl = ImRaii.Table("atkUnitBase_atkValueTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
|
||||
if (tbl.Success)
|
||||
{
|
||||
ImGui.TableSetupColumn("Index");
|
||||
ImGui.TableSetupColumn("Type");
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ public static class Events
|
|||
|
||||
using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree");
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
using (ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg))
|
||||
using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg);
|
||||
|
||||
if (tbl.Success)
|
||||
{
|
||||
ImGui.TableSetupColumn("#", WidthFixed);
|
||||
ImGui.TableSetupColumn("Type", WidthFixed);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ internal unsafe partial class ResNodeTree
|
|||
/// </summary>
|
||||
private protected void DrawNodeEditorTable()
|
||||
{
|
||||
using (ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX))
|
||||
using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX);
|
||||
if (tbl.Success)
|
||||
{
|
||||
this.DrawEditorRows();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree
|
|||
|
||||
using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth);
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
PrintFieldValuePairs(
|
||||
("Texture Type", $"{this.TexData.TexType}"),
|
||||
|
|
@ -189,7 +189,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree
|
|||
|
||||
private void PrintPartsTable()
|
||||
{
|
||||
using (ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable))
|
||||
using var tbl = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable);
|
||||
if (tbl.Success)
|
||||
{
|
||||
ImGui.TableSetupColumn("Part ID", WidthFixed);
|
||||
ImGui.TableSetupColumn("Part Texture", WidthFixed);
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
||||
|
||||
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
|
||||
|
||||
/// <inheritdoc cref="NineGridNodeTree"/>
|
||||
internal unsafe partial class NineGridNodeTree
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct representing the four offsets of an <see cref="AtkNineGridNode"/>.
|
||||
/// </summary>
|
||||
internal struct NineGridOffsets
|
||||
{
|
||||
/// <summary>Top offset.</summary>
|
||||
internal int Top;
|
||||
|
||||
/// <summary>Left offset.</summary>
|
||||
internal int Left;
|
||||
|
||||
/// <summary>Right offset.</summary>
|
||||
internal int Right;
|
||||
|
||||
/// <summary>Bottom offset.</summary>
|
||||
internal int Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="top">The top offset.</param>
|
||||
/// <param name="right">The right offset.</param>
|
||||
/// <param name="bottom">The bottom offset.</param>
|
||||
/// <param name="left">The left offset.</param>
|
||||
internal NineGridOffsets(int top, int right, int bottom, int left)
|
||||
{
|
||||
this.Top = top;
|
||||
this.Right = right;
|
||||
this.Left = left;
|
||||
this.Bottom = bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="ngNode">The node using these offsets.</param>
|
||||
internal NineGridOffsets(AtkNineGridNode* ngNode)
|
||||
: this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset)
|
||||
{
|
||||
}
|
||||
|
||||
private NineGridOffsets(Vector4 v)
|
||||
: this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W)
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator NineGridOffsets(Vector4 v) => new(v);
|
||||
|
||||
public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left);
|
||||
|
||||
public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a;
|
||||
|
||||
public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a;
|
||||
|
||||
/// <summary>Prints the offsets in ImGui.</summary>
|
||||
internal readonly void Print() => PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -85,4 +87,62 @@ internal unsafe partial class NineGridNodeTree : ImageNodeTree
|
|||
|
||||
this.DrawTextureAndParts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct representing the four offsets of an <see cref="AtkNineGridNode"/>.
|
||||
/// </summary>
|
||||
internal struct NineGridOffsets
|
||||
{
|
||||
/// <summary>Top offset.</summary>
|
||||
internal int Top;
|
||||
|
||||
/// <summary>Left offset.</summary>
|
||||
internal int Left;
|
||||
|
||||
/// <summary>Right offset.</summary>
|
||||
internal int Right;
|
||||
|
||||
/// <summary>Bottom offset.</summary>
|
||||
internal int Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="top">The top offset.</param>
|
||||
/// <param name="right">The right offset.</param>
|
||||
/// <param name="bottom">The bottom offset.</param>
|
||||
/// <param name="left">The left offset.</param>
|
||||
internal NineGridOffsets(int top, int right, int bottom, int left)
|
||||
{
|
||||
this.Top = top;
|
||||
this.Right = right;
|
||||
this.Left = left;
|
||||
this.Bottom = bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="ngNode">The node using these offsets.</param>
|
||||
internal NineGridOffsets(AtkNineGridNode* ngNode)
|
||||
: this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset)
|
||||
{
|
||||
}
|
||||
|
||||
private NineGridOffsets(Vector4 v)
|
||||
: this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W)
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator NineGridOffsets(Vector4 v) => new(v);
|
||||
|
||||
public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left);
|
||||
|
||||
public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a;
|
||||
|
||||
public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a;
|
||||
|
||||
/// <summary>Prints the offsets in ImGui.</summary>
|
||||
internal readonly void Print() => Gui.PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,11 +128,11 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
using var c = ImRaii.PushColor(Text, color);
|
||||
using var col = ImRaii.PushColor(Text, color);
|
||||
using var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth);
|
||||
c.Pop();
|
||||
col.Pop();
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2);
|
||||
|
||||
|
|
@ -319,7 +319,7 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
|
||||
col.Pop();
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2);
|
||||
try
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree
|
|||
{
|
||||
using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}");
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
var utf8String = this.NodeText;
|
||||
var seStringBytes = new byte[utf8String.BufUsed];
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public readonly unsafe partial struct TimelineTree
|
|||
{
|
||||
using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth);
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}");
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ public readonly unsafe partial struct TimelineTree
|
|||
{
|
||||
using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}");
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
PrintFieldValuePair("Animation", $"{address:X}");
|
||||
|
||||
|
|
@ -320,10 +320,9 @@ public readonly unsafe partial struct TimelineTree
|
|||
|
||||
if (columns.Count > 0)
|
||||
{
|
||||
using (ImRaii.Table(
|
||||
$"##{(nint)this.node}animTable{a}",
|
||||
columns.Count,
|
||||
Borders | SizingFixedFit | RowBg | NoHostExtendX))
|
||||
using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX);
|
||||
|
||||
if (tbl.Success)
|
||||
{
|
||||
foreach (var c in columns)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -79,7 +79,9 @@ internal unsafe class ElementSelector : IDisposable
|
|||
/// </summary>
|
||||
internal void DrawInterface()
|
||||
{
|
||||
using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true))
|
||||
using var ch = ImRaii.Child("###sidebar_elementSelector", new(250, -1), true);
|
||||
|
||||
if (ch.Success)
|
||||
{
|
||||
using (ImRaii.PushFont(IconFont))
|
||||
{
|
||||
|
|
@ -153,9 +155,11 @@ internal unsafe class ElementSelector : IDisposable
|
|||
|
||||
using (ImRaii.PushColor(WindowBg, new Vector4(0.5f)))
|
||||
{
|
||||
using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse))
|
||||
using var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse);
|
||||
if (ch.Success)
|
||||
{
|
||||
using (ImRaii.Group())
|
||||
using var gr = ImRaii.Group();
|
||||
if (gr.Success)
|
||||
{
|
||||
Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}");
|
||||
ImGui.Spacing();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ internal class AddonPopoutWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
using (ImRaii.Child($"{this.WindowName}child", new(-1, -1), true))
|
||||
using var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true);
|
||||
if (ch.Success)
|
||||
{
|
||||
this.addonTree.Draw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ internal unsafe class NodePopoutWindow : Window, IDisposable
|
|||
{
|
||||
if (this.Node != null && this.AddonTree.ContainsNode(this.Node))
|
||||
{
|
||||
using (ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true))
|
||||
using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true);
|
||||
if (ch.Success)
|
||||
{
|
||||
ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw);
|
||||
this.firstDraw = false;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ internal unsafe partial class UiDebug2
|
|||
|
||||
private void DrawSidebar()
|
||||
{
|
||||
using (ImRaii.Group())
|
||||
using var gr = ImRaii.Group();
|
||||
if (gr.Success)
|
||||
{
|
||||
this.DrawNameSearch();
|
||||
this.DrawAddonSelectionList();
|
||||
|
|
@ -63,7 +64,9 @@ internal unsafe partial class UiDebug2
|
|||
|
||||
private void DrawNameSearch()
|
||||
{
|
||||
using (ImRaii.Child("###sidebar_nameSearch", new(250, 40), true))
|
||||
using var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true);
|
||||
|
||||
if (ch.Success)
|
||||
{
|
||||
var atkUnitBaseSearch = this.addonNameSearch;
|
||||
|
||||
|
|
@ -90,7 +93,8 @@ internal unsafe partial class UiDebug2
|
|||
|
||||
private void DrawAddonSelectionList()
|
||||
{
|
||||
using (ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar))
|
||||
using var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
||||
if (ch.Success)
|
||||
{
|
||||
var unitListBaseAddr = GetUnitListBaseAddr();
|
||||
|
||||
|
|
@ -146,11 +150,11 @@ internal unsafe partial class UiDebug2
|
|||
|
||||
var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}";
|
||||
|
||||
using var col1 = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||
using var col = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||
using var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}");
|
||||
col1.Pop();
|
||||
col.Pop();
|
||||
|
||||
if (tree)
|
||||
if (tree.Success)
|
||||
{
|
||||
foreach (var option in options)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@ internal partial class UiDebug2 : IDisposable
|
|||
{
|
||||
ImGui.SameLine();
|
||||
|
||||
using (ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar))
|
||||
using var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar);
|
||||
|
||||
if (ch.Success)
|
||||
{
|
||||
if (this.elementSelector.Active)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ internal static class Gui
|
|||
/// <remarks>Colors the text itself either white or black, depending on the luminosity of the background color.</remarks>
|
||||
internal static void PrintColor(Vector4 color, string fmt)
|
||||
{
|
||||
using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1))
|
||||
using (ImRaii.PushColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1))
|
||||
.Push(Button, color)
|
||||
.Push(ButtonActive, color)
|
||||
.Push(ButtonHovered, color))
|
||||
|
|
@ -105,7 +105,9 @@ internal static class Gui
|
|||
|
||||
var index = (int)Math.Floor(prog * tooltips.Length);
|
||||
|
||||
using (ImRaii.Tooltip())
|
||||
using var tt = ImRaii.Tooltip();
|
||||
|
||||
if (tt.Success)
|
||||
{
|
||||
ImGui.TextUnformatted(tooltips[index]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -34,7 +33,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
|
||||
private ImVectorWrapper<byte> testStringBuffer;
|
||||
private string testString = string.Empty;
|
||||
private ExcelSheet<Addon> addons;
|
||||
private ExcelSheet<Addon> addons = null!;
|
||||
private ReadOnlySeString? logkind;
|
||||
private SeStringDrawParams style;
|
||||
private bool interactable;
|
||||
|
|
@ -241,8 +240,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
|
||||
if (ImGui.Button("Print to Chat Log"))
|
||||
{
|
||||
fixed (byte* p = Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span)
|
||||
Service<ChatGui>.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p));
|
||||
Service<ChatGui>.Get().Print(
|
||||
Game.Text.SeStringHandling.SeString.Parse(
|
||||
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(3);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
|
@ -19,16 +18,18 @@ using Dalamud.Interface.Utility;
|
|||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Support;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -60,6 +61,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
|
||||
private State state = State.Hide;
|
||||
|
||||
private int lastLoadedPluginCount = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -441,29 +444,37 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
textNode->TextFlags |= (byte)TextFlags.MultiLine;
|
||||
textNode->AlignmentType = AlignmentType.TopLeft;
|
||||
|
||||
var containsDalamudVersionString = textNode->OriginalTextPointer == textNode->NodeText.StringPtr;
|
||||
if (!this.configuration.ShowTsm || !this.showTsm.Value)
|
||||
{
|
||||
textNode->NodeText.SetString(addon->AtkValues[1].String);
|
||||
if (containsDalamudVersionString)
|
||||
textNode->SetText(addon->AtkValues[1].String);
|
||||
this.lastLoadedPluginCount = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
var pm = Service<PluginManager>.GetNullable();
|
||||
var count = pm?.LoadedPluginCount ?? 0;
|
||||
|
||||
var pluginCount = pm?.InstalledPlugins.Count(c => c.State == PluginState.Loaded) ?? 0;
|
||||
// Avoid rebuilding the string every frame.
|
||||
if (containsDalamudVersionString && count == this.lastLoadedPluginCount)
|
||||
return;
|
||||
this.lastLoadedPluginCount = count;
|
||||
|
||||
var titleVersionText = new SeStringBuilder()
|
||||
.AddText(addon->AtkValues[1].GetValueAsString())
|
||||
.AddText("\n\n")
|
||||
.AddUiGlow(701)
|
||||
.AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539)
|
||||
.AddUiGlowOff()
|
||||
.AddText($" Dalamud: {Util.GetScmVersion()}")
|
||||
.AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded");
|
||||
var lssb = LSeStringBuilder.SharedPool.Get();
|
||||
lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String)).Append("\n\n");
|
||||
lssb.PushEdgeColorType(701).PushColorType(539)
|
||||
.Append(SeIconChar.BoxedLetterD.ToIconChar())
|
||||
.PopColorType().PopEdgeColorType();
|
||||
lssb.Append($" Dalamud: {Util.GetScmVersion()}");
|
||||
|
||||
if (pm?.SafeMode ?? false)
|
||||
titleVersionText.AddUiForeground(" [SAFE MODE]", 17);
|
||||
lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded");
|
||||
|
||||
textNode->NodeText.SetString(titleVersionText.Build().EncodeWithNullTerminator());
|
||||
if (pm?.SafeMode is true)
|
||||
lssb.PushColorType(17).Append(" [SAFE MODE]").PopColorType();
|
||||
|
||||
textNode->SetText(lssb.GetViewAsSpan());
|
||||
LSeStringBuilder.SharedPool.Return(lssb);
|
||||
}
|
||||
|
||||
private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync();
|
||||
|
|
|
|||
|
|
@ -162,6 +162,27 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// </summary>
|
||||
public static int DalamudApiLevel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of loaded plugins.
|
||||
/// </summary>
|
||||
public int LoadedPluginCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var res = 0;
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
foreach (var p in this.installedPluginsList)
|
||||
{
|
||||
if (p.State == PluginState.Loaded)
|
||||
res++;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a copy of the list of all loaded plugins.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -121,4 +121,20 @@ public interface IChatGui
|
|||
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
|
||||
/// <param name="tagColor">Color to display the message tag with.</param>
|
||||
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null);
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
|
||||
/// <param name="tagColor">Color to display the message tag with.</param>
|
||||
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
|
||||
/// <param name="tagColor">Color to display the message tag with.</param>
|
||||
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ using Windows.Win32.Storage.FileSystem;
|
|||
using Windows.Win32.System.Memory;
|
||||
using Windows.Win32.System.Ole;
|
||||
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
using Win32_PInvoke = Windows.Win32.PInvoke;
|
||||
|
|
@ -1028,45 +1030,47 @@ public static class Util
|
|||
dm.Invoke(null, new[] { obj, path, addr });
|
||||
}
|
||||
|
||||
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
|
||||
private static unsafe void ShowSpanPrivate<T>(ulong addr, IList<string> path, int offset, bool isTop, in Span<T> spanobj)
|
||||
{
|
||||
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
if (isTop)
|
||||
{
|
||||
fixed (void* p = spanobj)
|
||||
{
|
||||
if (!ImGui.TreeNode(
|
||||
$"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" +
|
||||
$"##print-obj-{addr:X}-{string.Join("-", path)}-head"))
|
||||
using var tree = ImRaii.TreeNode($"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}-head", ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
if (tree.Success)
|
||||
{
|
||||
return;
|
||||
ShowSpanEntryPrivate(addr, path, offset, spanobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSpanEntryPrivate(addr, path, offset, spanobj);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
private static unsafe void ShowSpanEntryPrivate<T>(ulong addr, IList<string> path, int offset, Span<T> spanobj) {
|
||||
const int batchSize = 20;
|
||||
if (spanobj.Length > batchSize)
|
||||
{
|
||||
var skip = batchSize;
|
||||
while ((spanobj.Length + skip - 1) / skip > batchSize)
|
||||
{
|
||||
skip *= batchSize;
|
||||
}
|
||||
|
||||
for (var i = 0; i < spanobj.Length; i += skip)
|
||||
{
|
||||
var next = Math.Min(i + skip, spanobj.Length);
|
||||
path.Add($"{offset + i:X}_{skip}");
|
||||
if (ImGui.TreeNode(
|
||||
$"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" +
|
||||
$"##print-obj-{addr:X}-{string.Join("-", path)}"))
|
||||
|
||||
using (var tree = ImRaii.TreeNode($"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}", ImGuiTreeNodeFlags.SpanFullWidth))
|
||||
{
|
||||
try
|
||||
if (tree.Success)
|
||||
{
|
||||
ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.TreePop();
|
||||
ShowSpanEntryPrivate(addr, path, offset + i, spanobj[i..next]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1088,13 +1092,8 @@ public static class Util
|
|||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (isTop)
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
}
|
||||
|
||||
private static unsafe void ShowValue(ulong addr, IList<string> path, Type type, object value, bool hideAddress)
|
||||
{
|
||||
|
|
@ -1111,9 +1110,10 @@ public static class Util
|
|||
if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xffcbc0ff))
|
||||
{
|
||||
ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -1165,9 +1165,10 @@ public static class Util
|
|||
/// <param name="hideAddress">Do not print addresses. Use when displaying a copied value.</param>
|
||||
private static void ShowStructInternal(object obj, ulong addr, bool autoExpand = false, IEnumerable<string>? path = null, bool hideAddress = false)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2));
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)))
|
||||
{
|
||||
path ??= new List<string>();
|
||||
var pathList = path is List<string> ? (List<string>)path : path.ToList();
|
||||
var pathList = path as List<string> ?? path.ToList();
|
||||
|
||||
if (moduleEndAddr == 0 && moduleStartAddr == 0)
|
||||
{
|
||||
|
|
@ -1190,28 +1191,36 @@ public static class Util
|
|||
}
|
||||
}
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF);
|
||||
if (autoExpand)
|
||||
{
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
}
|
||||
|
||||
if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}"))
|
||||
using var col = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF);
|
||||
using var tree = ImRaii.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}", ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
col.Pop();
|
||||
|
||||
if (tree.Success)
|
||||
{
|
||||
ImGui.PopStyleColor();
|
||||
foreach (var f in obj.GetType()
|
||||
.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute));
|
||||
var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute));
|
||||
|
||||
if (fixedBuffer != null)
|
||||
{
|
||||
ImGui.Text($"fixed");
|
||||
ImGui.Text("fixed");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1),
|
||||
$"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]");
|
||||
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (offset != null)
|
||||
{
|
||||
ImGui.TextDisabled($"[0x{offset.Value:X}]");
|
||||
ImGui.SameLine();
|
||||
}
|
||||
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}");
|
||||
}
|
||||
|
||||
|
|
@ -1223,15 +1232,24 @@ public static class Util
|
|||
try
|
||||
{
|
||||
if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike))
|
||||
{
|
||||
ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct
|
||||
}
|
||||
else if (f.FieldType == typeof(bool) && offset != null)
|
||||
{
|
||||
ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f));
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)))
|
||||
{
|
||||
ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -1239,7 +1257,7 @@ public static class Util
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0))
|
||||
foreach (var p in obj.GetType().GetProperties().Where(static p => p.GetGetMethod()?.GetParameters().Length == 0))
|
||||
{
|
||||
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}");
|
||||
ImGui.SameLine();
|
||||
|
|
@ -1250,31 +1268,31 @@ public static class Util
|
|||
try
|
||||
{
|
||||
if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType)
|
||||
{
|
||||
ShowSpanProperty(addr, pathList, p, obj);
|
||||
}
|
||||
else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike))
|
||||
{
|
||||
ImGui.Text("Cannot preview ref typed properties.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f));
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)))
|
||||
{
|
||||
ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
pathList.RemoveAt(pathList.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 33a98af530e52d5b54714ec9f7704c07bf9fdd91
|
||||
Subproject commit d4778799d378fe66099fd337f55849acf1f16dcd
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2f37349ffd778561a1103a650683116c43edc86c
|
||||
Subproject commit b0d41471b7ef3d69daaf6d862eb74e7e00a25651
|
||||
Loading…
Add table
Add a link
Reference in a new issue