mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
SeString renderer: Implement replacement entity (#1993)
* Refactor * Implement replacement entity * Apply rounding functions more correctly
This commit is contained in:
parent
23a2bd6228
commit
878b96e67d
10 changed files with 1199 additions and 672 deletions
|
|
@ -2,6 +2,7 @@ using System.Buffers.Binary;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Dalamud.Interface;
|
namespace Dalamud.Interface;
|
||||||
|
|
||||||
|
|
@ -247,6 +248,14 @@ public static class ColorHelpers
|
||||||
public static uint Desaturate(uint color, float amount)
|
public static uint Desaturate(uint color, float amount)
|
||||||
=> RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount));
|
=> RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount));
|
||||||
|
|
||||||
|
/// <summary>Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value.</summary>
|
||||||
|
/// <param name="rgba">RGBA value to transform.</param>
|
||||||
|
/// <param name="opacity">Opacity to apply.</param>
|
||||||
|
/// <returns>Transformed value.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static uint ApplyOpacity(uint rgba, float opacity) =>
|
||||||
|
((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fade a color.
|
/// Fade a color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -145,5 +145,16 @@ internal sealed unsafe class GfdFile : FileResource
|
||||||
|
|
||||||
/// <summary>Gets the UV1 of the HQ version of this entry.</summary>
|
/// <summary>Gets the UV1 of the HQ version of this entry.</summary>
|
||||||
public Vector2 HqUv1 => new((this.Left + this.Width) / 256f, (this.Top + this.Height + 170.5f) / 512f);
|
public Vector2 HqUv1 => new((this.Left + this.Width) / 256f, (this.Top + this.Height + 170.5f) / 512f);
|
||||||
|
|
||||||
|
/// <summary>Calculates the size in pixels of a GFD entry when drawn along with a text.</summary>
|
||||||
|
/// <param name="fontSize">Font size in pixels.</param>
|
||||||
|
/// <param name="useHq">Whether to draw the HQ texture.</param>
|
||||||
|
/// <returns>Determined size of the GFD entry when drawn.</returns>
|
||||||
|
public readonly Vector2 CalculateScaledSize(float fontSize, out bool useHq)
|
||||||
|
{
|
||||||
|
useHq = fontSize > 19;
|
||||||
|
var targetHeight = useHq ? fontSize : 20;
|
||||||
|
return new(this.Width * (targetHeight / this.Height), targetHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.Text;
|
||||||
|
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.GeneratedSheets2;
|
||||||
|
using Lumina.Text.Expressions;
|
||||||
|
using Lumina.Text.Payloads;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
|
||||||
|
/// <summary>Color stacks to use while evaluating a SeString.</summary>
|
||||||
|
internal sealed class SeStringColorStackSet
|
||||||
|
{
|
||||||
|
/// <summary>Parsed <see cref="UIColor.UIForeground"/>, containing colors to use with
|
||||||
|
/// <see cref="MacroCode.ColorType"/>.</summary>
|
||||||
|
private readonly uint[] colorTypes;
|
||||||
|
|
||||||
|
/// <summary>Parsed <see cref="UIColor.UIGlow"/>, containing colors to use with
|
||||||
|
/// <see cref="MacroCode.EdgeColorType"/>.</summary>
|
||||||
|
private readonly uint[] edgeColorTypes;
|
||||||
|
|
||||||
|
/// <summary>Foreground color stack while evaluating a SeString for rendering.</summary>
|
||||||
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
|
private readonly List<uint> colorStack = [];
|
||||||
|
|
||||||
|
/// <summary>Edge/border color stack while evaluating a SeString for rendering.</summary>
|
||||||
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
|
private readonly List<uint> edgeColorStack = [];
|
||||||
|
|
||||||
|
/// <summary>Shadow color stack while evaluating a SeString for rendering.</summary>
|
||||||
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
|
private readonly List<uint> shadowColorStack = [];
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="SeStringColorStackSet"/> class.</summary>
|
||||||
|
/// <param name="uiColor">UIColor sheet.</param>
|
||||||
|
public SeStringColorStackSet(ExcelSheet<UIColor> uiColor)
|
||||||
|
{
|
||||||
|
var maxId = 0;
|
||||||
|
foreach (var row in uiColor)
|
||||||
|
maxId = (int)Math.Max(row.RowId, maxId);
|
||||||
|
|
||||||
|
this.colorTypes = new uint[maxId + 1];
|
||||||
|
this.edgeColorTypes = new uint[maxId + 1];
|
||||||
|
foreach (var row in uiColor)
|
||||||
|
{
|
||||||
|
// Contains ABGR.
|
||||||
|
this.colorTypes[row.RowId] = row.UIForeground;
|
||||||
|
this.edgeColorTypes[row.RowId] = row.UIGlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
// ImGui wants RGBA in LE.
|
||||||
|
foreach (ref var r in this.colorTypes.AsSpan())
|
||||||
|
r = BinaryPrimitives.ReverseEndianness(r);
|
||||||
|
foreach (ref var r in this.edgeColorTypes.AsSpan())
|
||||||
|
r = BinaryPrimitives.ReverseEndianness(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether at least one color has been pushed to the edge color stack.</summary>
|
||||||
|
public bool HasAdditionalEdgeColor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Resets the colors in the stack.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
internal void Initialize(scoped ref SeStringDrawState drawState)
|
||||||
|
{
|
||||||
|
this.colorStack.Clear();
|
||||||
|
this.edgeColorStack.Clear();
|
||||||
|
this.shadowColorStack.Clear();
|
||||||
|
this.colorStack.Add(drawState.Color);
|
||||||
|
this.edgeColorStack.Add(drawState.EdgeColor);
|
||||||
|
this.shadowColorStack.Add(drawState.ShadowColor);
|
||||||
|
drawState.Color = ColorHelpers.ApplyOpacity(drawState.Color, drawState.Opacity);
|
||||||
|
drawState.EdgeColor = ColorHelpers.ApplyOpacity(drawState.EdgeColor, drawState.EdgeOpacity);
|
||||||
|
drawState.ShadowColor = ColorHelpers.ApplyOpacity(drawState.ShadowColor, drawState.Opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Handles a <see cref="MacroCode.Color"/> payload.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
internal void HandleColorPayload(scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) =>
|
||||||
|
drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, payload), drawState.Opacity);
|
||||||
|
|
||||||
|
/// <summary>Handles a <see cref="MacroCode.EdgeColor"/> payload.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
internal void HandleEdgeColorPayload(
|
||||||
|
scoped ref SeStringDrawState drawState,
|
||||||
|
ReadOnlySePayloadSpan payload)
|
||||||
|
{
|
||||||
|
var newColor = AdjustStack(this.edgeColorStack, payload);
|
||||||
|
if (!drawState.ForceEdgeColor)
|
||||||
|
drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity);
|
||||||
|
|
||||||
|
this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Handles a <see cref="MacroCode.ShadowColor"/> payload.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
internal void HandleShadowColorPayload(
|
||||||
|
scoped ref SeStringDrawState drawState,
|
||||||
|
ReadOnlySePayloadSpan payload) =>
|
||||||
|
drawState.ShadowColor = ColorHelpers.ApplyOpacity(AdjustStack(this.shadowColorStack, payload), drawState.Opacity);
|
||||||
|
|
||||||
|
/// <summary>Handles a <see cref="MacroCode.ColorType"/> payload.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
internal void HandleColorTypePayload(
|
||||||
|
scoped ref SeStringDrawState drawState,
|
||||||
|
ReadOnlySePayloadSpan payload) =>
|
||||||
|
drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, this.colorTypes, payload), drawState.Opacity);
|
||||||
|
|
||||||
|
/// <summary>Handles a <see cref="MacroCode.EdgeColorType"/> payload.</summary>
|
||||||
|
/// <param name="drawState">Draw state.</param>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
internal void HandleEdgeColorTypePayload(
|
||||||
|
scoped ref SeStringDrawState drawState,
|
||||||
|
ReadOnlySePayloadSpan payload)
|
||||||
|
{
|
||||||
|
var newColor = AdjustStack(this.edgeColorStack, this.edgeColorTypes, payload);
|
||||||
|
if (!drawState.ForceEdgeColor)
|
||||||
|
drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity);
|
||||||
|
|
||||||
|
this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA).</summary>
|
||||||
|
/// <param name="x">Color to process.</param>
|
||||||
|
/// <returns>Swapped color.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16);
|
||||||
|
|
||||||
|
private static unsafe uint AdjustStack(List<uint> rgbaStack, ReadOnlySePayloadSpan payload)
|
||||||
|
{
|
||||||
|
if (!payload.TryGetExpression(out var expr))
|
||||||
|
return rgbaStack[^1];
|
||||||
|
|
||||||
|
// Color payloads have BGRA values as its parameter. ImGui expects RGBA values.
|
||||||
|
// Opacity component is ignored.
|
||||||
|
if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor)
|
||||||
|
{
|
||||||
|
// First item in the stack is the color we started to draw with.
|
||||||
|
if (rgbaStack.Count > 1)
|
||||||
|
rgbaStack.RemoveAt(rgbaStack.Count - 1);
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.TryGetUInt(out var bgra))
|
||||||
|
{
|
||||||
|
rgbaStack.Add(SwapRedBlue(bgra) | 0xFF000000u);
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.TryGetParameterExpression(out var et, out var op) &&
|
||||||
|
et == (int)ExpressionType.GlobalNumber &&
|
||||||
|
op.TryGetInt(out var i) &&
|
||||||
|
RaptureTextModule.Instance() is var rtm &&
|
||||||
|
rtm is not null &&
|
||||||
|
i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count &&
|
||||||
|
rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp)
|
||||||
|
{
|
||||||
|
rgbaStack.Add(SwapRedBlue((uint)gp.IntValue) | 0xFF000000u);
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback value.
|
||||||
|
rgbaStack.Add(0xFF000000u);
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint AdjustStack(List<uint> rgbaStack, uint[] colorTypes, ReadOnlySePayloadSpan payload)
|
||||||
|
{
|
||||||
|
if (!payload.TryGetExpression(out var expr))
|
||||||
|
return rgbaStack[^1];
|
||||||
|
if (!expr.TryGetUInt(out var colorTypeIndex))
|
||||||
|
return rgbaStack[^1];
|
||||||
|
|
||||||
|
if (colorTypeIndex == 0)
|
||||||
|
{
|
||||||
|
// First item in the stack is the color we started to draw with.
|
||||||
|
if (rgbaStack.Count > 1)
|
||||||
|
rgbaStack.RemoveAt(rgbaStack.Count - 1);
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opacity component is ignored.
|
||||||
|
rgbaStack.Add((colorTypeIndex < colorTypes.Length ? colorTypes[colorTypeIndex] : 0u) | 0xFF000000u);
|
||||||
|
|
||||||
|
return rgbaStack[^1];
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -12,9 +12,11 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||||
internal ref struct LineBreakEnumerator
|
internal ref struct LineBreakEnumerator
|
||||||
{
|
{
|
||||||
private readonly UtfEnumeratorFlags enumeratorFlags;
|
private readonly UtfEnumeratorFlags enumeratorFlags;
|
||||||
private readonly int dataLength;
|
|
||||||
|
|
||||||
private UtfEnumerator enumerator;
|
private UtfEnumerator enumerator;
|
||||||
|
private int dataLength;
|
||||||
|
private int currentByteOffsetDelta;
|
||||||
|
|
||||||
private Entry class1;
|
private Entry class1;
|
||||||
private Entry class2;
|
private Entry class2;
|
||||||
|
|
||||||
|
|
@ -24,8 +26,6 @@ internal ref struct LineBreakEnumerator
|
||||||
|
|
||||||
private int consecutiveRegionalIndicators;
|
private int consecutiveRegionalIndicators;
|
||||||
|
|
||||||
private bool finished;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="LineBreakEnumerator"/> struct.</summary>
|
/// <summary>Initializes a new instance of the <see cref="LineBreakEnumerator"/> struct.</summary>
|
||||||
/// <param name="data">UTF-N byte sequence.</param>
|
/// <param name="data">UTF-N byte sequence.</param>
|
||||||
/// <param name="enumeratorFlags">Flags to pass to sub-enumerator.</param>
|
/// <param name="enumeratorFlags">Flags to pass to sub-enumerator.</param>
|
||||||
|
|
@ -58,11 +58,25 @@ internal ref struct LineBreakEnumerator
|
||||||
/// <inheritdoc cref="IEnumerator{T}.Current"/>
|
/// <inheritdoc cref="IEnumerator{T}.Current"/>
|
||||||
public (int ByteOffset, bool Mandatory) Current { get; private set; }
|
public (int ByteOffset, bool Mandatory) Current { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the end of the underlying span has been reached.</summary>
|
||||||
|
public bool Finished { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Resumes enumeration with the given data.</summary>
|
||||||
|
/// <param name="data">The data.</param>
|
||||||
|
/// <param name="offsetDelta">Offset to add to <see cref="Current"/>.<c>ByteOffset</c>.</param>
|
||||||
|
public void ResumeWith(ReadOnlySpan<byte> data, int offsetDelta)
|
||||||
|
{
|
||||||
|
this.enumerator = UtfEnumerator.From(data, this.enumeratorFlags);
|
||||||
|
this.dataLength = data.Length;
|
||||||
|
this.currentByteOffsetDelta = offsetDelta;
|
||||||
|
this.Finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IEnumerator.MoveNext"/>
|
/// <inheritdoc cref="IEnumerator.MoveNext"/>
|
||||||
[SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement", Justification = "No")]
|
[SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement", Justification = "No")]
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (this.finished)
|
if (this.Finished)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
while (this.enumerator.MoveNext())
|
while (this.enumerator.MoveNext())
|
||||||
|
|
@ -77,10 +91,10 @@ internal ref struct LineBreakEnumerator
|
||||||
switch (this.HandleCharacter(effectiveInt))
|
switch (this.HandleCharacter(effectiveInt))
|
||||||
{
|
{
|
||||||
case LineBreakMode.Mandatory:
|
case LineBreakMode.Mandatory:
|
||||||
this.Current = (this.enumerator.Current.ByteOffset, true);
|
this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, true);
|
||||||
return true;
|
return true;
|
||||||
case LineBreakMode.Optional:
|
case LineBreakMode.Optional:
|
||||||
this.Current = (this.enumerator.Current.ByteOffset, false);
|
this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, false);
|
||||||
return true;
|
return true;
|
||||||
case LineBreakMode.Prohibited:
|
case LineBreakMode.Prohibited:
|
||||||
default:
|
default:
|
||||||
|
|
@ -90,8 +104,8 @@ internal ref struct LineBreakEnumerator
|
||||||
|
|
||||||
// Start and end of text:
|
// Start and end of text:
|
||||||
// LB3 Always break at the end of text.
|
// LB3 Always break at the end of text.
|
||||||
this.Current = (this.dataLength, true);
|
this.Current = (this.dataLength + this.currentByteOffsetDelta, true);
|
||||||
this.finished = true;
|
this.Finished = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
|
/// <summary>Predefined channels for drawing onto, for out-of-order drawing.</summary>
|
||||||
|
// Notes: values must be consecutively increasing, starting from 0. Higher values has higher priority.
|
||||||
|
public enum SeStringDrawChannel
|
||||||
|
{
|
||||||
|
/// <summary>Next draw operation on the draw list will be put below <see cref="Background"/>.</summary>
|
||||||
|
BelowBackground,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put onto the background channel.</summary>
|
||||||
|
Background,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put above <see cref="Background"/>.</summary>
|
||||||
|
AboveBackground,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put below <see cref="Shadow"/>.</summary>
|
||||||
|
BelowShadow,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put onto the shadow channel.</summary>
|
||||||
|
Shadow,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put above <see cref="Shadow"/>.</summary>
|
||||||
|
AboveShadow,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put below <see cref="Edge"/>.</summary>
|
||||||
|
BelowEdge,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put onto the edge channel.</summary>
|
||||||
|
Edge,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put above <see cref="Edge"/>.</summary>
|
||||||
|
AboveEdge,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put below <see cref="Foreground"/>.</summary>
|
||||||
|
BelowForeground,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put onto the foreground channel.</summary>
|
||||||
|
Foreground,
|
||||||
|
|
||||||
|
/// <summary>Next draw operation on the draw list will be put above <see cref="Foreground"/>.</summary>
|
||||||
|
AboveForeground,
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -18,14 +16,18 @@ public record struct SeStringDrawParams
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ImDrawListPtr? TargetDrawList { get; set; }
|
public ImDrawListPtr? TargetDrawList { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the font to use.</summary>
|
/// <summary>Gets or sets the function to be called on every codepoint and payload for the purpose of offering
|
||||||
/// <value>Font to use, or <c>null</c> to use <see cref="ImGui.GetFont"/> (the default).</value>
|
/// chances to draw something else instead of glyphs or SeString payload entities.</summary>
|
||||||
public ImFontPtr? Font { get; set; }
|
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
||||||
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos"/>.</value>
|
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos"/>.</value>
|
||||||
public Vector2? ScreenOffset { get; set; }
|
public Vector2? ScreenOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the font to use.</summary>
|
||||||
|
/// <value>Font to use, or <c>null</c> to use <see cref="ImGui.GetFont"/> (the default).</value>
|
||||||
|
public ImFontPtr? Font { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the font size.</summary>
|
/// <summary>Gets or sets the font size.</summary>
|
||||||
/// <value>Font size in pixels, or <c>0</c> to use the current ImGui font size <see cref="ImGui.GetFontSize"/>.
|
/// <value>Font size in pixels, or <c>0</c> to use the current ImGui font size <see cref="ImGui.GetFontSize"/>.
|
||||||
/// </value>
|
/// </value>
|
||||||
|
|
@ -86,83 +88,23 @@ public record struct SeStringDrawParams
|
||||||
public bool Italic { get; set; }
|
public bool Italic { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets a value indicating whether the text is rendered with edge.</summary>
|
/// <summary>Gets or sets a value indicating whether the text is rendered with edge.</summary>
|
||||||
|
/// <remarks>If an edge color is pushed with <see cref="MacroCode.EdgeColor"/> or
|
||||||
|
/// <see cref="MacroCode.EdgeColorType"/>, it will be drawn regardless. Set <see cref="ForceEdgeColor"/> to
|
||||||
|
/// <c>true</c> and set <see cref="EdgeColor"/> to <c>0</c> to fully disable edge.</remarks>
|
||||||
public bool Edge { get; set; }
|
public bool Edge { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets a value indicating whether the text is rendered with shadow.</summary>
|
/// <summary>Gets or sets a value indicating whether the text is rendered with shadow.</summary>
|
||||||
public bool Shadow { get; set; }
|
public bool Shadow { get; set; }
|
||||||
|
|
||||||
private readonly unsafe ImFont* EffectiveFont =>
|
/// <summary>Gets the effective font.</summary>
|
||||||
|
internal readonly unsafe ImFont* EffectiveFont =>
|
||||||
(this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null
|
(this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null
|
||||||
? f.NativePtr
|
? f.NativePtr
|
||||||
: throw new ArgumentException("Specified font is empty.");
|
: throw new ArgumentException("Specified font is empty.");
|
||||||
|
|
||||||
private readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f);
|
/// <summary>Gets the effective line height in pixels.</summary>
|
||||||
|
internal readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f);
|
||||||
|
|
||||||
private readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha;
|
/// <summary>Gets the effective opacity.</summary>
|
||||||
|
internal readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha;
|
||||||
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
|
||||||
[SuppressMessage(
|
|
||||||
"StyleCop.CSharp.OrderingRules",
|
|
||||||
"SA1214:Readonly fields should appear before non-readonly fields",
|
|
||||||
Justification = "Matching the above order.")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal unsafe struct Resolved(in SeStringDrawParams ssdp)
|
|
||||||
{
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
|
||||||
public readonly ImDrawList* DrawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
|
||||||
public readonly ImFont* Font = ssdp.EffectiveFont;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.ScreenOffset"/>
|
|
||||||
public readonly Vector2 ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
|
||||||
public readonly float FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.LineHeight"/>
|
|
||||||
public readonly float LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.WrapWidth"/>
|
|
||||||
public readonly float WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.LinkUnderlineThickness"/>
|
|
||||||
public readonly float LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Opacity"/>
|
|
||||||
public readonly float Opacity = ssdp.EffectiveOpacity;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.EdgeStrength"/>
|
|
||||||
public readonly float EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Color"/>
|
|
||||||
public uint Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.EdgeColor"/>
|
|
||||||
public uint EdgeColor = ssdp.EdgeColor ?? 0xFF000000;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.ShadowColor"/>
|
|
||||||
public uint ShadowColor = ssdp.ShadowColor ?? 0xFF000000;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.LinkHoverBackColor"/>
|
|
||||||
public readonly uint LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.LinkActiveBackColor"/>
|
|
||||||
public readonly uint LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.ForceEdgeColor"/>
|
|
||||||
public readonly bool ForceEdgeColor = ssdp.ForceEdgeColor;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Bold"/>
|
|
||||||
public bool Bold = ssdp.Bold;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Italic"/>
|
|
||||||
public bool Italic = ssdp.Italic;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Edge"/>
|
|
||||||
public bool Edge = ssdp.Edge;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Shadow"/>
|
|
||||||
public bool Shadow = ssdp.Shadow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
400
Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs
Normal file
400
Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs
Normal file
|
|
@ -0,0 +1,400 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Lumina.Text.Payloads;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
|
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public unsafe ref struct SeStringDrawState
|
||||||
|
{
|
||||||
|
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
||||||
|
|
||||||
|
private readonly ImDrawList* drawList;
|
||||||
|
private readonly SeStringColorStackSet colorStackSet;
|
||||||
|
private readonly ImDrawListSplitter* splitter;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="SeStringDrawState"/> struct.</summary>
|
||||||
|
/// <param name="span">Raw SeString byte span.</param>
|
||||||
|
/// <param name="ssdp">Instance of <see cref="SeStringDrawParams"/> to initialize from.</param>
|
||||||
|
/// <param name="colorStackSet">Instance of <see cref="SeStringColorStackSet"/> to use.</param>
|
||||||
|
/// <param name="splitter">Instance of ImGui Splitter to use.</param>
|
||||||
|
internal SeStringDrawState(
|
||||||
|
ReadOnlySpan<byte> span,
|
||||||
|
scoped in SeStringDrawParams ssdp,
|
||||||
|
SeStringColorStackSet colorStackSet,
|
||||||
|
ImDrawListSplitter* splitter)
|
||||||
|
{
|
||||||
|
this.colorStackSet = colorStackSet;
|
||||||
|
this.splitter = splitter;
|
||||||
|
this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
||||||
|
this.Span = span;
|
||||||
|
this.GetEntity = ssdp.GetEntity;
|
||||||
|
this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
||||||
|
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
||||||
|
this.Font = ssdp.EffectiveFont;
|
||||||
|
this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
||||||
|
this.FontSizeScale = this.FontSize / this.Font->FontSize;
|
||||||
|
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||||
|
this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
||||||
|
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||||
|
this.Opacity = ssdp.EffectiveOpacity;
|
||||||
|
this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity;
|
||||||
|
this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
||||||
|
this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000;
|
||||||
|
this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000;
|
||||||
|
this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
||||||
|
this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
||||||
|
this.ForceEdgeColor = ssdp.ForceEdgeColor;
|
||||||
|
this.Bold = ssdp.Bold;
|
||||||
|
this.Italic = ssdp.Italic;
|
||||||
|
this.Edge = ssdp.Edge;
|
||||||
|
this.Shadow = ssdp.Shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
||||||
|
public readonly ImDrawListPtr DrawList => new(this.drawList);
|
||||||
|
|
||||||
|
/// <summary>Gets the raw SeString byte span.</summary>
|
||||||
|
public ReadOnlySpan<byte> Span { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.GetEntity"/>
|
||||||
|
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ScreenOffset"/>
|
||||||
|
public Vector2 ScreenOffset { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
||||||
|
public ImFont* Font { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
||||||
|
public float FontSize { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the multiplier value for glyph metrics, so that it scales to <see cref="FontSize"/>.</summary>
|
||||||
|
/// <remarks>Multiplied to <see cref="ImGuiHelpers.ImFontGlyphReal.XY"/>,
|
||||||
|
/// <see cref="ImGuiHelpers.ImFontGlyphReal.AdvanceX"/>, and distance values from
|
||||||
|
/// <see cref="ImFontPtr.GetDistanceAdjustmentForPair"/>.</remarks>
|
||||||
|
public float FontSizeScale { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LineHeight"/>
|
||||||
|
public float LineHeight { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.WrapWidth"/>
|
||||||
|
public float WrapWidth { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkUnderlineThickness"/>
|
||||||
|
public float LinkUnderlineThickness { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Opacity"/>
|
||||||
|
public float Opacity { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.EdgeStrength"/>
|
||||||
|
public float EdgeOpacity { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Color"/>
|
||||||
|
public uint Color { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.EdgeColor"/>
|
||||||
|
public uint EdgeColor { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ShadowColor"/>
|
||||||
|
public uint ShadowColor { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkHoverBackColor"/>
|
||||||
|
public uint LinkHoverBackColor { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.LinkActiveBackColor"/>
|
||||||
|
public uint LinkActiveBackColor { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.ForceEdgeColor"/>
|
||||||
|
public bool ForceEdgeColor { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Bold"/>
|
||||||
|
public bool Bold { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Italic"/>
|
||||||
|
public bool Italic { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Edge"/>
|
||||||
|
public bool Edge { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SeStringDrawParams.Shadow"/>
|
||||||
|
public bool Shadow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
|
public readonly bool ShouldDrawEdge =>
|
||||||
|
(this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000;
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
|
public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 };
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
|
public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 };
|
||||||
|
|
||||||
|
/// <summary>Sets the current channel in the ImGui draw list splitter.</summary>
|
||||||
|
/// <param name="channelIndex">Channel to switch to.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly void SetCurrentChannel(SeStringDrawChannel channelIndex) =>
|
||||||
|
ImGuiNative.ImDrawListSplitter_SetCurrentChannel(this.splitter, this.drawList, (int)channelIndex);
|
||||||
|
|
||||||
|
/// <summary>Draws a single texture.</summary>
|
||||||
|
/// <param name="igTextureId">ImGui texture ID to draw from.</param>
|
||||||
|
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
||||||
|
/// <param name="size">Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list.</param>
|
||||||
|
/// <param name="uv0">Left top corner of the glyph w.r.t. its glyph origin in the source texture.</param>
|
||||||
|
/// <param name="uv1">Right bottom corner of the glyph w.r.t. its glyph origin in the source texture.</param>
|
||||||
|
/// <param name="color">Color of the glyph in RGBA.</param>
|
||||||
|
public readonly void Draw(
|
||||||
|
nint igTextureId,
|
||||||
|
Vector2 offset,
|
||||||
|
Vector2 size,
|
||||||
|
Vector2 uv0,
|
||||||
|
Vector2 uv1,
|
||||||
|
uint color = uint.MaxValue)
|
||||||
|
{
|
||||||
|
offset += this.ScreenOffset;
|
||||||
|
this.DrawList.AddImageQuad(
|
||||||
|
igTextureId,
|
||||||
|
offset,
|
||||||
|
offset + size with { X = 0 },
|
||||||
|
offset + size,
|
||||||
|
offset + size with { Y = 0 },
|
||||||
|
new(uv0.X, uv0.Y),
|
||||||
|
new(uv0.X, uv1.Y),
|
||||||
|
new(uv1.X, uv1.Y),
|
||||||
|
new(uv1.X, uv0.Y),
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Draws a single texture.</summary>
|
||||||
|
/// <param name="igTextureId">ImGui texture ID to draw from.</param>
|
||||||
|
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
||||||
|
/// <param name="xy0">Left top corner of the glyph w.r.t. its glyph origin in the target draw list.</param>
|
||||||
|
/// <param name="xy1">Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list.</param>
|
||||||
|
/// <param name="uv0">Left top corner of the glyph w.r.t. its glyph origin in the source texture.</param>
|
||||||
|
/// <param name="uv1">Right bottom corner of the glyph w.r.t. its glyph origin in the source texture.</param>
|
||||||
|
/// <param name="color">Color of the glyph in RGBA.</param>
|
||||||
|
/// <param name="dyItalic">Transformation for <paramref name="xy0"/> and <paramref name="xy1"/> that will push
|
||||||
|
/// top and bottom pixels to apply faux italicization by <see cref="Vector2.X"/> and <see cref="Vector2.Y"/>
|
||||||
|
/// respectively.</param>
|
||||||
|
public readonly void Draw(
|
||||||
|
nint igTextureId,
|
||||||
|
Vector2 offset,
|
||||||
|
Vector2 xy0,
|
||||||
|
Vector2 xy1,
|
||||||
|
Vector2 uv0,
|
||||||
|
Vector2 uv1,
|
||||||
|
uint color = uint.MaxValue,
|
||||||
|
Vector2 dyItalic = default)
|
||||||
|
{
|
||||||
|
offset += this.ScreenOffset;
|
||||||
|
this.DrawList.AddImageQuad(
|
||||||
|
igTextureId,
|
||||||
|
offset + new Vector2(xy0.X + dyItalic.X, xy0.Y),
|
||||||
|
offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y),
|
||||||
|
offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y),
|
||||||
|
offset + new Vector2(xy1.X + dyItalic.X, xy0.Y),
|
||||||
|
new(uv0.X, uv0.Y),
|
||||||
|
new(uv0.X, uv1.Y),
|
||||||
|
new(uv1.X, uv1.Y),
|
||||||
|
new(uv1.X, uv0.Y),
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Draws a single glyph using current styling configurations.</summary>
|
||||||
|
/// <param name="g">Glyph to draw.</param>
|
||||||
|
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
||||||
|
internal readonly void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
||||||
|
{
|
||||||
|
var texId = this.Font->ContainerAtlas->Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
||||||
|
var xy0 = new Vector2(
|
||||||
|
MathF.Round(g.X0 * this.FontSizeScale),
|
||||||
|
MathF.Round(g.Y0 * this.FontSizeScale));
|
||||||
|
var xy1 = new Vector2(
|
||||||
|
MathF.Round(g.X1 * this.FontSizeScale),
|
||||||
|
MathF.Round(g.Y1 * this.FontSizeScale));
|
||||||
|
var dxBold = this.Bold ? 2 : 1;
|
||||||
|
var dyItalic = this.Italic
|
||||||
|
? new Vector2(this.FontSize - xy0.Y, this.FontSize - xy1.Y) / 6
|
||||||
|
: Vector2.Zero;
|
||||||
|
// Note: dyItalic values can be non-rounded; the glyph will be rendered sheared anyway.
|
||||||
|
|
||||||
|
offset.Y += MathF.Round((this.LineHeight - this.FontSize) / 2f);
|
||||||
|
|
||||||
|
if (this.ShouldDrawShadow)
|
||||||
|
{
|
||||||
|
this.SetCurrentChannel(SeStringDrawChannel.Shadow);
|
||||||
|
for (var i = 0; i < dxBold; i++)
|
||||||
|
this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.ShadowColor, dyItalic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ShouldDrawEdge)
|
||||||
|
{
|
||||||
|
this.SetCurrentChannel(SeStringDrawChannel.Edge);
|
||||||
|
|
||||||
|
// Top & Bottom
|
||||||
|
for (var i = -1; i <= dxBold; i++)
|
||||||
|
{
|
||||||
|
this.Draw(texId, offset + new Vector2(i, -1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic);
|
||||||
|
this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left & Right
|
||||||
|
this.Draw(texId, offset + new Vector2(-1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic);
|
||||||
|
this.Draw(texId, offset + new Vector2(1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ShouldDrawForeground)
|
||||||
|
{
|
||||||
|
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
||||||
|
for (var i = 0; i < dxBold; i++)
|
||||||
|
this.Draw(texId, offset + new Vector2(i, 0), xy0, xy1, g.UV0, g.UV1, this.Color, dyItalic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Draws an underline, for links.</summary>
|
||||||
|
/// <param name="offset">Offset of the glyph in pixels w.r.t.
|
||||||
|
/// <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
||||||
|
/// <param name="advanceWidth">Advance width of the glyph.</param>
|
||||||
|
internal readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth)
|
||||||
|
{
|
||||||
|
if (this.LinkUnderlineThickness < 1f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
offset += this.ScreenOffset;
|
||||||
|
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
||||||
|
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale));
|
||||||
|
|
||||||
|
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
||||||
|
this.DrawList.AddLine(
|
||||||
|
offset,
|
||||||
|
offset + new Vector2(advanceWidth, 0),
|
||||||
|
this.Color,
|
||||||
|
this.LinkUnderlineThickness);
|
||||||
|
|
||||||
|
if (this is { Shadow: true, ShadowColor: >= 0x1000000 })
|
||||||
|
{
|
||||||
|
this.SetCurrentChannel(SeStringDrawChannel.Shadow);
|
||||||
|
this.DrawList.AddLine(
|
||||||
|
offset + new Vector2(0, 1),
|
||||||
|
offset + new Vector2(advanceWidth, 1),
|
||||||
|
this.ShadowColor,
|
||||||
|
this.LinkUnderlineThickness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
||||||
|
/// <param name="rune">An instance of <see cref="Rune"/> that represents a character to display.</param>
|
||||||
|
/// <returns>Corresponding glyph, or glyph of a fallback character specified from
|
||||||
|
/// <see cref="ImFont.FallbackChar"/>.</returns>
|
||||||
|
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
||||||
|
{
|
||||||
|
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
||||||
|
? ImGuiNative.ImFont_FindGlyph(this.Font, (ushort)rune.Value)
|
||||||
|
: this.Font->FallbackGlyph;
|
||||||
|
return ref *(ImGuiHelpers.ImFontGlyphReal*)p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
||||||
|
/// <param name="rune">An instance of <see cref="Rune"/> that represents a character to display, that will be
|
||||||
|
/// changed on return to the rune corresponding to the fallback glyph if a glyph not corresponding to the
|
||||||
|
/// requested glyph is being returned.</param>
|
||||||
|
/// <returns>Corresponding glyph, or glyph of a fallback character specified from
|
||||||
|
/// <see cref="ImFont.FallbackChar"/>.</returns>
|
||||||
|
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(ref Rune rune)
|
||||||
|
{
|
||||||
|
ref var glyph = ref this.FindGlyph(rune);
|
||||||
|
if (rune.Value != glyph.Codepoint && !Rune.TryCreate(glyph.Codepoint, out rune))
|
||||||
|
rune = Rune.ReplacementChar;
|
||||||
|
return ref glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the kerning adjustment between two glyphs in a succession corresponding to the given runes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Rune representing the glyph on the left side of a pair.</param>
|
||||||
|
/// <param name="right">Rune representing the glyph on the right side of a pair.</param>
|
||||||
|
/// <returns>Distance adjustment in pixels, scaled to the size specified from
|
||||||
|
/// <see cref="SeStringDrawParams.FontSize"/>, and rounded.</returns>
|
||||||
|
internal readonly float CalculateScaledDistance(Rune left, Rune right)
|
||||||
|
{
|
||||||
|
// Kerning distance entries are ignored if NUL, U+FFFF(invalid Unicode character), or characters outside
|
||||||
|
// the basic multilingual plane(BMP) is involved.
|
||||||
|
if (left.Value is <= 0 or >= char.MaxValue)
|
||||||
|
return 0;
|
||||||
|
if (right.Value is <= 0 or >= char.MaxValue)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return MathF.Round(
|
||||||
|
ImGuiNative.ImFont_GetDistanceAdjustmentForPair(
|
||||||
|
this.Font,
|
||||||
|
(ushort)left.Value,
|
||||||
|
(ushort)right.Value) * this.FontSizeScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Handles style adjusting payloads.</summary>
|
||||||
|
/// <param name="payload">Payload to handle.</param>
|
||||||
|
/// <returns><c>true</c> if the payload was handled.</returns>
|
||||||
|
internal bool HandleStyleAdjustingPayloads(ReadOnlySePayloadSpan payload)
|
||||||
|
{
|
||||||
|
switch (payload.MacroCode)
|
||||||
|
{
|
||||||
|
case MacroCode.Color:
|
||||||
|
this.colorStackSet.HandleColorPayload(ref this, payload);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.EdgeColor:
|
||||||
|
this.colorStackSet.HandleEdgeColorPayload(ref this, payload);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.ShadowColor:
|
||||||
|
this.colorStackSet.HandleShadowColorPayload(ref this, payload);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||||
|
// doesn't actually work in chat log
|
||||||
|
this.Bold = u != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||||
|
this.Italic = u != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.Edge when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||||
|
this.Edge = u != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.Shadow when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||||
|
this.Shadow = u != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.ColorType:
|
||||||
|
this.colorStackSet.HandleColorTypePayload(ref this, payload);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MacroCode.EdgeColorType:
|
||||||
|
this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Splits the draw list.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal readonly void SplitDrawList() =>
|
||||||
|
ImGuiNative.ImDrawListSplitter_Split(this.splitter, this.drawList, ChannelCount);
|
||||||
|
|
||||||
|
/// <summary>Merges the draw list.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal readonly void MergeDrawList() => ImGuiNative.ImDrawListSplitter_Merge(this.splitter, this.drawList);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
|
/// <summary>Replacement entity to draw instead while rendering a SeString.</summary>
|
||||||
|
public readonly record struct SeStringReplacementEntity
|
||||||
|
{
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="SeStringReplacementEntity"/> struct.</summary>
|
||||||
|
/// <param name="byteLength">Number of bytes taken by this entity. Must be at least 0. If <c>0</c>, then the entity
|
||||||
|
/// is considered as empty.</param>
|
||||||
|
/// <param name="size">Size of this entity in pixels. Components must be non-negative.</param>
|
||||||
|
/// <param name="draw">Draw callback.</param>
|
||||||
|
public SeStringReplacementEntity(int byteLength, Vector2 size, DrawDelegate draw)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(byteLength);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(size.X, nameof(size));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(size.Y, nameof(size));
|
||||||
|
ArgumentNullException.ThrowIfNull(draw);
|
||||||
|
this.ByteLength = byteLength;
|
||||||
|
this.Size = size;
|
||||||
|
this.Draw = draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the replacement entity.</summary>
|
||||||
|
/// <param name="state">Draw state.</param>
|
||||||
|
/// <param name="byteOffset">Byte offset in <see cref="SeStringDrawState.Span"/>.</param>
|
||||||
|
/// <returns>Replacement entity definition, or <c>default</c> if none.</returns>
|
||||||
|
public delegate SeStringReplacementEntity GetEntityDelegate(scoped in SeStringDrawState state, int byteOffset);
|
||||||
|
|
||||||
|
/// <summary>Draws the replacement entity.</summary>
|
||||||
|
/// <param name="state">Draw state.</param>
|
||||||
|
/// <param name="byteOffset">Byte offset in <see cref="SeStringDrawState.Span"/>.</param>
|
||||||
|
/// <param name="offset">Relative offset in pixels w.r.t. <see cref="SeStringDrawState.ScreenOffset"/>.</param>
|
||||||
|
public delegate void DrawDelegate(scoped in SeStringDrawState state, int byteOffset, Vector2 offset);
|
||||||
|
|
||||||
|
/// <summary>Gets the number of bytes taken by this entity.</summary>
|
||||||
|
public int ByteLength { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the size of this entity in pixels.</summary>
|
||||||
|
public Vector2 Size { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the Draw callback.</summary>
|
||||||
|
public DrawDelegate Draw { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether this entity is empty.</summary>
|
||||||
|
/// <param name="e">Instance of <see cref="SeStringReplacementEntity"/> to test.</param>
|
||||||
|
public static implicit operator bool(in SeStringReplacementEntity e) => e.ByteLength != 0;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -6,7 +7,9 @@ using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -32,6 +35,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
private ReadOnlySeString? logkind;
|
private ReadOnlySeString? logkind;
|
||||||
private SeStringDrawParams style;
|
private SeStringDrawParams style;
|
||||||
private bool interactable;
|
private bool interactable;
|
||||||
|
private bool useEntity;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string DisplayName { get; init; } = "SeStringRenderer Test";
|
public string DisplayName { get; init; } = "SeStringRenderer Test";
|
||||||
|
|
@ -45,12 +49,12 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
this.style = default;
|
this.style = new() { GetEntity = this.GetEntity };
|
||||||
this.addons = null;
|
this.addons = null;
|
||||||
this.uicolor = null;
|
this.uicolor = null;
|
||||||
this.logkind = null;
|
this.logkind = null;
|
||||||
this.testString = string.Empty;
|
this.testString = string.Empty;
|
||||||
this.interactable = true;
|
this.interactable = this.useEntity = true;
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,11 +89,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
var t3 = this.style.LineHeight ?? 1f;
|
var t3 = this.style.LineHeight ?? 1f;
|
||||||
if (ImGui.DragFloat("Line Height", ref t3, 0.01f, 0.4f, 3f, "%.02f"))
|
if (ImGui.DragFloat("Line Height", ref t3, 0.01f, 0.4f, 3f, "%.02f"))
|
||||||
this.style.LineHeight = t3;
|
this.style.LineHeight = t3;
|
||||||
|
|
||||||
t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha;
|
t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha;
|
||||||
if (ImGui.DragFloat("Opacity", ref t3, 0.005f, 0f, 1f, "%.02f"))
|
if (ImGui.DragFloat("Opacity", ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||||
this.style.Opacity = t3;
|
this.style.Opacity = t3;
|
||||||
|
|
||||||
t3 = this.style.EdgeStrength ?? 0.25f;
|
t3 = this.style.EdgeStrength ?? 0.25f;
|
||||||
if (ImGui.DragFloat("Edge Strength", ref t3, 0.005f, 0f, 1f, "%.02f"))
|
if (ImGui.DragFloat("Edge Strength", ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||||
this.style.EdgeStrength = t3;
|
this.style.EdgeStrength = t3;
|
||||||
|
|
@ -123,11 +127,15 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
if (ImGui.Checkbox("Word Wrap", ref t))
|
if (ImGui.Checkbox("Word Wrap", ref t))
|
||||||
this.style.WrapWidth = t ? null : float.PositiveInfinity;
|
this.style.WrapWidth = t ? null : float.PositiveInfinity;
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
t = this.interactable;
|
t = this.interactable;
|
||||||
if (ImGui.Checkbox("Interactable", ref t))
|
if (ImGui.Checkbox("Interactable", ref t))
|
||||||
this.interactable = t;
|
this.interactable = t;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
t = this.useEntity;
|
||||||
|
if (ImGui.Checkbox("Use Entity Replacements", ref t))
|
||||||
|
this.useEntity = t;
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("UIColor Preview"))
|
if (ImGui.CollapsingHeader("UIColor Preview"))
|
||||||
{
|
{
|
||||||
if (this.uicolor is null)
|
if (this.uicolor is null)
|
||||||
|
|
@ -267,7 +275,22 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(3);
|
ImGuiHelpers.ScaledDummy(3);
|
||||||
ImGuiHelpers.CompileSeStringWrapped(
|
ImGuiHelpers.CompileSeStringWrapped(
|
||||||
"· For ease of testing, <colortype(506)><edgecolortype(507)>line breaks<colortype(0)><edgecolortype(0)> are automatically replaced to <colortype(502)><edgecolortype(503)>\\<br><colortype(0)><edgecolortype(0)>.",
|
"Optional features implemented for the following test input:<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>line breaks<colortype(0)><edgecolortype(0)> are automatically replaced to <colortype(502)><edgecolortype(503)>\\<br><colortype(0)><edgecolortype(0)>.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>D<link(0xCE)>alamud<colortype(0)><edgecolortype(0)> will display Dalamud.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>W<link(0xCE)>hite<colortype(0)><edgecolortype(0)> will display White.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>D<link(0xCE)>efaultIcon<colortype(0)><edgecolortype(0)> will display DefaultIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>D<link(0xCE)>isabledIcon<colortype(0)><edgecolortype(0)> will display DisabledIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>O<link(0xCE)>utdatedInstallableIcon<colortype(0)><edgecolortype(0)> will display OutdatedInstallableIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>T<link(0xCE)>roubleIcon<colortype(0)><edgecolortype(0)> will display TroubleIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>D<link(0xCE)>evPluginIcon<colortype(0)><edgecolortype(0)> will display DevPluginIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>U<link(0xCE)>pdateIcon<colortype(0)><edgecolortype(0)> will display UpdateIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>I<link(0xCE)>nstalledIcon<colortype(0)><edgecolortype(0)> will display InstalledIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>T<link(0xCE)>hirdIcon<colortype(0)><edgecolortype(0)> will display ThirdIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>T<link(0xCE)>hirdInst<link(0xCE)>alledIcon<colortype(0)><edgecolortype(0)> will display ThirdInstalledIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>C<link(0xCE)>hangelogApiBumpIcon<colortype(0)><edgecolortype(0)> will display ChangelogApiBumpIcon.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>icon<link(0xCE)>(5)<colortype(0)><edgecolortype(0)> will display icon(5). This is different from \\<icon<link(0xCE)>(5)>.<br>" +
|
||||||
|
"· <colortype(506)><edgecolortype(507)>tex<link(0xCE)>(ui/loadingimage/-nowloading_base25_hr1.tex)<colortype(0)><edgecolortype(0)> will display tex(ui/loadingimage/-nowloading_base25_hr1.tex).",
|
||||||
this.style);
|
this.style);
|
||||||
ImGuiHelpers.ScaledDummy(3);
|
ImGuiHelpers.ScaledDummy(3);
|
||||||
|
|
||||||
|
|
@ -302,10 +325,14 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
if (this.interactable)
|
if (this.interactable)
|
||||||
{
|
{
|
||||||
if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is
|
if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is
|
||||||
{ InteractedPayload: { } payload, InteractedPayloadOffset: var offset, InteractedPayloadEnvelope: var envelope } rr)
|
{
|
||||||
|
InteractedPayload: { } payload, InteractedPayloadOffset: var offset,
|
||||||
|
InteractedPayloadEnvelope: var envelope,
|
||||||
|
Clicked: var clicked
|
||||||
|
})
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}");
|
ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}");
|
||||||
if (rr.Clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp)
|
if (clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp)
|
||||||
Util.OpenLink(dlp.ExtraString);
|
Util.OpenLink(dlp.ExtraString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,4 +341,138 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style);
|
ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SeStringReplacementEntity GetEntity(scoped in SeStringDrawState state, int byteOffset)
|
||||||
|
{
|
||||||
|
if (!this.useEntity)
|
||||||
|
return default;
|
||||||
|
if (state.Span[byteOffset..].StartsWith("Dalamud"u8))
|
||||||
|
return new(7, new(state.FontSize, state.FontSize), DrawDalamud);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("White"u8))
|
||||||
|
return new(5, new(state.FontSize, state.FontSize), DrawWhite);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("DefaultIcon"u8))
|
||||||
|
return new(11, new(state.FontSize, state.FontSize), DrawDefaultIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("DisabledIcon"u8))
|
||||||
|
return new(12, new(state.FontSize, state.FontSize), DrawDisabledIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("OutdatedInstallableIcon"u8))
|
||||||
|
return new(23, new(state.FontSize, state.FontSize), DrawOutdatedInstallableIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("TroubleIcon"u8))
|
||||||
|
return new(11, new(state.FontSize, state.FontSize), DrawTroubleIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("DevPluginIcon"u8))
|
||||||
|
return new(13, new(state.FontSize, state.FontSize), DrawDevPluginIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("UpdateIcon"u8))
|
||||||
|
return new(10, new(state.FontSize, state.FontSize), DrawUpdateIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("ThirdIcon"u8))
|
||||||
|
return new(9, new(state.FontSize, state.FontSize), DrawThirdIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("ThirdInstalledIcon"u8))
|
||||||
|
return new(18, new(state.FontSize, state.FontSize), DrawThirdInstalledIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("ChangelogApiBumpIcon"u8))
|
||||||
|
return new(20, new(state.FontSize, state.FontSize), DrawChangelogApiBumpIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("InstalledIcon"u8))
|
||||||
|
return new(13, new(state.FontSize, state.FontSize), DrawInstalledIcon);
|
||||||
|
if (state.Span[byteOffset..].StartsWith("tex("u8))
|
||||||
|
{
|
||||||
|
var off = state.Span[byteOffset..].IndexOf((byte)')');
|
||||||
|
var tex = Service<TextureManager>
|
||||||
|
.Get()
|
||||||
|
.Shared
|
||||||
|
.GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)]))
|
||||||
|
.GetWrapOrEmpty();
|
||||||
|
return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Span[byteOffset..].StartsWith("icon("u8))
|
||||||
|
{
|
||||||
|
var off = state.Span[byteOffset..].IndexOf((byte)')');
|
||||||
|
if (int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed))
|
||||||
|
{
|
||||||
|
var tex = Service<TextureManager>
|
||||||
|
.Get()
|
||||||
|
.Shared
|
||||||
|
.GetFromGameIcon(parsed)
|
||||||
|
.GetWrapOrEmpty();
|
||||||
|
return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
|
||||||
|
static void DrawTexture(scoped in SeStringDrawState state, int byteOffset, Vector2 offset)
|
||||||
|
{
|
||||||
|
var off = state.Span[byteOffset..].IndexOf((byte)')');
|
||||||
|
var tex = Service<TextureManager>
|
||||||
|
.Get()
|
||||||
|
.Shared
|
||||||
|
.GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)]))
|
||||||
|
.GetWrapOrEmpty();
|
||||||
|
state.Draw(
|
||||||
|
tex.ImGuiHandle,
|
||||||
|
offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2),
|
||||||
|
tex.Size * (state.FontSize / tex.Size.Y),
|
||||||
|
Vector2.Zero,
|
||||||
|
Vector2.One);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset)
|
||||||
|
{
|
||||||
|
var off = state.Span[byteOffset..].IndexOf((byte)')');
|
||||||
|
if (!int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed))
|
||||||
|
return;
|
||||||
|
var tex = Service<TextureManager>
|
||||||
|
.Get()
|
||||||
|
.Shared
|
||||||
|
.GetFromGameIcon(parsed)
|
||||||
|
.GetWrapOrEmpty();
|
||||||
|
state.Draw(
|
||||||
|
tex.ImGuiHandle,
|
||||||
|
offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2),
|
||||||
|
tex.Size * (state.FontSize / tex.Size.Y),
|
||||||
|
Vector2.Zero,
|
||||||
|
Vector2.One);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawAsset(scoped in SeStringDrawState state, Vector2 offset, DalamudAsset asset) =>
|
||||||
|
state.Draw(
|
||||||
|
Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(asset).ImGuiHandle,
|
||||||
|
offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2),
|
||||||
|
new(state.FontSize, state.FontSize),
|
||||||
|
Vector2.Zero,
|
||||||
|
Vector2.One);
|
||||||
|
|
||||||
|
static void DrawDalamud(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.LogoSmall);
|
||||||
|
|
||||||
|
static void DrawWhite(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.White4X4);
|
||||||
|
|
||||||
|
static void DrawDefaultIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.DefaultIcon);
|
||||||
|
|
||||||
|
static void DrawDisabledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.DisabledIcon);
|
||||||
|
|
||||||
|
static void DrawOutdatedInstallableIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.OutdatedInstallableIcon);
|
||||||
|
|
||||||
|
static void DrawTroubleIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.TroubleIcon);
|
||||||
|
|
||||||
|
static void DrawDevPluginIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.DevPluginIcon);
|
||||||
|
|
||||||
|
static void DrawUpdateIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.UpdateIcon);
|
||||||
|
|
||||||
|
static void DrawInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.InstalledIcon);
|
||||||
|
|
||||||
|
static void DrawThirdIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.ThirdIcon);
|
||||||
|
|
||||||
|
static void DrawThirdInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.ThirdInstalledIcon);
|
||||||
|
|
||||||
|
static void DrawChangelogApiBumpIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) =>
|
||||||
|
DrawAsset(state, offset, DalamudAsset.ChangelogApiBumpIcon);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue