mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Merge pull request #2308 from Soreepeong/feature/sestring-to-texture
Add ITextureProvider.CreateTextureFromSeString
This commit is contained in:
commit
bfd592abbe
19 changed files with 629 additions and 215 deletions
|
|
@ -15,10 +15,6 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
|||
/// <summary>Color stacks to use while evaluating a SeString.</summary>
|
||||
internal sealed class SeStringColorStackSet
|
||||
{
|
||||
/// <summary>Parsed <see cref="UIColor"/>, containing colors to use with <see cref="MacroCode.ColorType"/> and
|
||||
/// <see cref="MacroCode.EdgeColorType"/>.</summary>
|
||||
private readonly uint[,] colorTypes;
|
||||
|
||||
/// <summary>Foreground color stack while evaluating a SeString for rendering.</summary>
|
||||
/// <remarks>Touched only from the main thread.</remarks>
|
||||
private readonly List<uint> colorStack = [];
|
||||
|
|
@ -39,30 +35,38 @@ internal sealed class SeStringColorStackSet
|
|||
foreach (var row in uiColor)
|
||||
maxId = (int)Math.Max(row.RowId, maxId);
|
||||
|
||||
this.colorTypes = new uint[maxId + 1, 4];
|
||||
this.ColorTypes = new uint[maxId + 1, 4];
|
||||
foreach (var row in uiColor)
|
||||
{
|
||||
// Contains ABGR.
|
||||
this.colorTypes[row.RowId, 0] = row.Dark;
|
||||
this.colorTypes[row.RowId, 1] = row.Light;
|
||||
this.colorTypes[row.RowId, 2] = row.ClassicFF;
|
||||
this.colorTypes[row.RowId, 3] = row.ClearBlue;
|
||||
this.ColorTypes[row.RowId, 0] = row.Dark;
|
||||
this.ColorTypes[row.RowId, 1] = row.Light;
|
||||
this.ColorTypes[row.RowId, 2] = row.ClassicFF;
|
||||
this.ColorTypes[row.RowId, 3] = row.ClearBlue;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
// ImGui wants RGBA in LE.
|
||||
fixed (uint* p = this.colorTypes)
|
||||
fixed (uint* p = this.ColorTypes)
|
||||
{
|
||||
foreach (ref var r in new Span<uint>(p, this.colorTypes.GetLength(0) * this.colorTypes.GetLength(1)))
|
||||
foreach (ref var r in new Span<uint>(p, this.ColorTypes.GetLength(0) * this.ColorTypes.GetLength(1)))
|
||||
r = BinaryPrimitives.ReverseEndianness(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SeStringColorStackSet"/> class.</summary>
|
||||
/// <param name="colorTypes">Color types.</param>
|
||||
public SeStringColorStackSet(uint[,] colorTypes) => this.ColorTypes = colorTypes;
|
||||
|
||||
/// <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>Gets the parsed <see cref="UIColor"/> containing colors to use with <see cref="MacroCode.ColorType"/>
|
||||
/// and <see cref="MacroCode.EdgeColorType"/>.</summary>
|
||||
public uint[,] ColorTypes { get; }
|
||||
|
||||
/// <summary>Resets the colors in the stack.</summary>
|
||||
/// <param name="drawState">Draw state.</param>
|
||||
internal void Initialize(scoped ref SeStringDrawState drawState)
|
||||
|
|
@ -191,9 +195,9 @@ internal sealed class SeStringColorStackSet
|
|||
}
|
||||
|
||||
// Opacity component is ignored.
|
||||
var color = themeIndex >= 0 && themeIndex < this.colorTypes.GetLength(1) &&
|
||||
colorTypeIndex < this.colorTypes.GetLength(0)
|
||||
? this.colorTypes[colorTypeIndex, themeIndex]
|
||||
var color = themeIndex >= 0 && themeIndex < this.ColorTypes.GetLength(1) &&
|
||||
colorTypeIndex < this.ColorTypes.GetLength(0)
|
||||
? this.ColorTypes[colorTypeIndex, themeIndex]
|
||||
: 0u;
|
||||
|
||||
rgbaStack.Add(color | 0xFF000000u);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
|||
|
||||
/// <summary>Draws SeString.</summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||
internal class SeStringRenderer : IServiceType
|
||||
{
|
||||
private const int ImGuiContextCurrentWindowOffset = 0x3FF0;
|
||||
private const int ImGuiWindowDcOffset = 0x118;
|
||||
|
|
@ -47,28 +48,19 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
|
||||
/// <summary>Parsed text fragments from a SeString.</summary>
|
||||
/// <remarks>Touched only from the main thread.</remarks>
|
||||
private readonly List<TextFragment> fragments = [];
|
||||
private readonly List<TextFragment> fragmentsMainThread = [];
|
||||
|
||||
/// <summary>Color stacks to use while evaluating a SeString for rendering.</summary>
|
||||
/// <remarks>Touched only from the main thread.</remarks>
|
||||
private readonly SeStringColorStackSet colorStackSet;
|
||||
|
||||
/// <summary>Splits a draw list so that different layers of a single glyph can be drawn out of order.</summary>
|
||||
private ImDrawListSplitter* splitter = ImGui.ImDrawListSplitter();
|
||||
private readonly SeStringColorStackSet colorStackSetMainThread;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
||||
{
|
||||
this.colorStackSet = new(dm.Excel.GetSheet<UIColor>());
|
||||
this.colorStackSetMainThread = new(dm.Excel.GetSheet<UIColor>());
|
||||
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="SeStringRenderer"/> class.</summary>
|
||||
~SeStringRenderer() => this.ReleaseUnmanagedResources();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources();
|
||||
|
||||
/// <summary>Compiles and caches a SeString from a text macro representation.</summary>
|
||||
/// <param name="text">SeString text macro representation.
|
||||
/// Newline characters will be normalized to newline payloads.</param>
|
||||
|
|
@ -80,6 +72,44 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
text.ReplaceLineEndings("<br>"),
|
||||
new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }));
|
||||
|
||||
/// <summary>Creates a draw data that will draw the given SeString onto it.</summary>
|
||||
/// <param name="sss">SeString to render.</param>
|
||||
/// <param name="drawParams">Parameters for drawing.</param>
|
||||
/// <returns>A new self-contained draw data.</returns>
|
||||
public unsafe BufferBackedImDrawData CreateDrawData(
|
||||
ReadOnlySeStringSpan sss,
|
||||
scoped in SeStringDrawParams drawParams = default)
|
||||
{
|
||||
if (drawParams.TargetDrawList is not null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"{nameof(SeStringDrawParams.TargetDrawList)} may not be specified.",
|
||||
nameof(drawParams));
|
||||
}
|
||||
|
||||
var dd = BufferBackedImDrawData.Create();
|
||||
|
||||
try
|
||||
{
|
||||
var size = this.Draw(sss, drawParams with { TargetDrawList = dd.ListPtr }).Size;
|
||||
|
||||
var offset = drawParams.ScreenOffset ?? Vector2.Zero;
|
||||
foreach (var vtx in new Span<ImDrawVert>(dd.ListPtr.VtxBuffer.Data, dd.ListPtr.VtxBuffer.Size))
|
||||
offset = Vector2.Min(offset, vtx.Pos);
|
||||
|
||||
dd.Data.DisplayPos = offset;
|
||||
dd.Data.DisplaySize = size - offset;
|
||||
dd.Data.Valid = 1;
|
||||
dd.UpdateDrawDataStatistics();
|
||||
return dd;
|
||||
}
|
||||
catch
|
||||
{
|
||||
dd.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Compiles and caches a SeString from a text macro representation, and then draws it.</summary>
|
||||
/// <param name="text">SeString text macro representation.
|
||||
/// Newline characters will be normalized to newline payloads.</param>
|
||||
|
|
@ -113,28 +143,42 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
|
||||
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
|
||||
/// <returns>Interaction result of the rendered text.</returns>
|
||||
public SeStringDrawResult Draw(
|
||||
public unsafe SeStringDrawResult Draw(
|
||||
ReadOnlySeStringSpan sss,
|
||||
scoped in SeStringDrawParams drawParams = default,
|
||||
ImGuiId imGuiId = default,
|
||||
ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault)
|
||||
{
|
||||
// Drawing is only valid if done from the main thread anyway, especially with interactivity.
|
||||
ThreadSafety.AssertMainThread();
|
||||
// Interactivity is supported only from the main thread.
|
||||
if (!imGuiId.IsEmpty())
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
if (drawParams.TargetDrawList is not null && imGuiId)
|
||||
throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId));
|
||||
|
||||
// This also does argument validation for drawParams. Do it here.
|
||||
var state = new SeStringDrawState(sss, drawParams, this.colorStackSet, this.splitter);
|
||||
using var cleanup = new DisposeSafety.ScopedFinalizer();
|
||||
|
||||
// Reset and initialize the state.
|
||||
this.fragments.Clear();
|
||||
this.colorStackSet.Initialize(ref state);
|
||||
ImFont* font = null;
|
||||
if (drawParams.Font.HasValue)
|
||||
font = drawParams.Font.Value;
|
||||
if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null)
|
||||
font = ImGui.GetFont();
|
||||
if (font is null)
|
||||
throw new ArgumentException("Specified font is empty.");
|
||||
|
||||
// This also does argument validation for drawParams. Do it here.
|
||||
// `using var` makes a struct read-only, but we do want to modify it.
|
||||
using var stateStorage = new SeStringDrawState(
|
||||
sss,
|
||||
drawParams,
|
||||
ThreadSafety.IsMainThread ? this.colorStackSetMainThread : new(this.colorStackSetMainThread.ColorTypes),
|
||||
ThreadSafety.IsMainThread ? this.fragmentsMainThread : [],
|
||||
font);
|
||||
ref var state = ref Unsafe.AsRef(in stateStorage);
|
||||
|
||||
// Analyze the provided SeString and break it up to text fragments.
|
||||
this.CreateTextFragments(ref state);
|
||||
var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments);
|
||||
var fragmentSpan = CollectionsMarshal.AsSpan(state.Fragments);
|
||||
|
||||
// Calculate size.
|
||||
var size = Vector2.Zero;
|
||||
|
|
@ -147,24 +191,17 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
|
||||
state.SplitDrawList();
|
||||
|
||||
// Handle cases where ImGui.AlignTextToFramePadding has been called.
|
||||
var context = ImGui.GetCurrentContext();
|
||||
var currLineTextBaseOffset = 0f;
|
||||
if (!context.IsNull)
|
||||
{
|
||||
var currentWindow = context.CurrentWindow;
|
||||
if (!currentWindow.IsNull)
|
||||
{
|
||||
currLineTextBaseOffset = currentWindow.DC.CurrLineTextBaseOffset;
|
||||
}
|
||||
}
|
||||
|
||||
var itemSize = size;
|
||||
if (currLineTextBaseOffset != 0f)
|
||||
if (drawParams.TargetDrawList is null)
|
||||
{
|
||||
itemSize.Y += 2 * currLineTextBaseOffset;
|
||||
foreach (ref var f in fragmentSpan)
|
||||
f.Offset += new Vector2(0, currLineTextBaseOffset);
|
||||
// Handle cases where ImGui.AlignTextToFramePadding has been called.
|
||||
var currLineTextBaseOffset = ImGui.GetCurrentContext().CurrentWindow.DC.CurrLineTextBaseOffset;
|
||||
if (currLineTextBaseOffset != 0f)
|
||||
{
|
||||
itemSize.Y += 2 * currLineTextBaseOffset;
|
||||
foreach (ref var f in fragmentSpan)
|
||||
f.Offset += new Vector2(0, currLineTextBaseOffset);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all text fragments.
|
||||
|
|
@ -280,15 +317,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
return displayRune.Value != 0;
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
if (this.splitter is not null)
|
||||
{
|
||||
this.splitter->Destroy();
|
||||
this.splitter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates text fragment, taking line and word breaking into account.</summary>
|
||||
/// <param name="state">Draw state.</param>
|
||||
private void CreateTextFragments(ref SeStringDrawState state)
|
||||
|
|
@ -391,7 +419,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth;
|
||||
|
||||
// Test if the fragment does not fit into the current line and the current line is not empty.
|
||||
if (xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows)
|
||||
if (xy.X != 0 && state.Fragments.Count > 0 && !state.Fragments[^1].BreakAfter && overflows)
|
||||
{
|
||||
// Introduce break if this is the first time testing the current break unit or the current fragment
|
||||
// is an entity.
|
||||
|
|
@ -401,7 +429,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
xy.X = 0;
|
||||
xy.Y += state.LineHeight;
|
||||
w = 0;
|
||||
CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true;
|
||||
CollectionsMarshal.AsSpan(state.Fragments)[^1].BreakAfter = true;
|
||||
fragment.Offset = xy;
|
||||
|
||||
// Now that the fragment is given its own line, test if it overflows again.
|
||||
|
|
@ -419,16 +447,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth);
|
||||
}
|
||||
}
|
||||
else if (this.fragments.Count > 0 && xy.X != 0)
|
||||
else if (state.Fragments.Count > 0 && xy.X != 0)
|
||||
{
|
||||
// New fragment fits into the current line, and it has a previous fragment in the same line.
|
||||
// If the previous fragment ends with a soft hyphen, adjust its width so that the width of its
|
||||
// trailing soft hyphens are not considered.
|
||||
if (this.fragments[^1].EndsWithSoftHyphen)
|
||||
xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth;
|
||||
if (state.Fragments[^1].EndsWithSoftHyphen)
|
||||
xy.X += state.Fragments[^1].AdvanceWidthWithoutSoftHyphen - state.Fragments[^1].AdvanceWidth;
|
||||
|
||||
// Adjust this fragment's offset from kerning distance.
|
||||
xy.X += state.CalculateScaledDistance(this.fragments[^1].LastRune, fragment.FirstRune);
|
||||
xy.X += state.CalculateScaledDistance(state.Fragments[^1].LastRune, fragment.FirstRune);
|
||||
fragment.Offset = xy;
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +467,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
w = Math.Max(w, xy.X + fragment.VisibleWidth);
|
||||
xy.X += fragment.AdvanceWidth;
|
||||
prev = fragment.To;
|
||||
this.fragments.Add(fragment);
|
||||
state.Fragments.Add(fragment);
|
||||
|
||||
if (fragment.BreakAfter)
|
||||
{
|
||||
|
|
@ -491,7 +519,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
if (gfdTextureSrv != 0)
|
||||
{
|
||||
state.Draw(
|
||||
new ImTextureID(gfdTextureSrv),
|
||||
new(gfdTextureSrv),
|
||||
offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)),
|
||||
size,
|
||||
useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0,
|
||||
|
|
@ -528,7 +556,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
|
||||
return;
|
||||
|
||||
static nint GetGfdTextureSrv()
|
||||
static unsafe nint GetGfdTextureSrv()
|
||||
{
|
||||
var uim = UIModule.Instance();
|
||||
if (uim is null)
|
||||
|
|
@ -553,7 +581,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
/// <summary>Determines a bitmap icon to display for the given SeString payload.</summary>
|
||||
/// <param name="sss">Byte span that should include a SeString payload.</param>
|
||||
/// <returns>Icon to display, or <see cref="None"/> if it should not be displayed as an icon.</returns>
|
||||
private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan<byte> sss)
|
||||
private unsafe BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan<byte> sss)
|
||||
{
|
||||
var e = new ReadOnlySeStringSpan(sss).GetEnumerator();
|
||||
if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2)
|
||||
|
|
@ -710,38 +738,4 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
firstDisplayRune ?? default,
|
||||
lastNonSoftHyphenRune);
|
||||
}
|
||||
|
||||
/// <summary>Represents a text fragment in a SeString span.</summary>
|
||||
/// <param name="From">Starting byte offset (inclusive) in a SeString.</param>
|
||||
/// <param name="To">Ending byte offset (exclusive) in a SeString.</param>
|
||||
/// <param name="Link">Byte offset of the link that decorates this text fragment, or <c>-1</c> if none.</param>
|
||||
/// <param name="Offset">Offset in pixels w.r.t. <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
||||
/// <param name="Entity">Replacement entity, if any.</param>
|
||||
/// <param name="VisibleWidth">Visible width of this text fragment. This is the width required to draw everything
|
||||
/// without clipping.</param>
|
||||
/// <param name="AdvanceWidth">Advance width of this text fragment. This is the width required to add to the cursor
|
||||
/// to position the next fragment correctly.</param>
|
||||
/// <param name="AdvanceWidthWithoutSoftHyphen">Same with <paramref name="AdvanceWidth"/>, but trimming all the
|
||||
/// trailing soft hyphens.</param>
|
||||
/// <param name="BreakAfter">Whether to insert a line break after this text fragment.</param>
|
||||
/// <param name="EndsWithSoftHyphen">Whether this text fragment ends with one or more soft hyphens.</param>
|
||||
/// <param name="FirstRune">First rune in this text fragment.</param>
|
||||
/// <param name="LastRune">Last rune in this text fragment, for the purpose of calculating kerning distance with
|
||||
/// the following text fragment in the same line, if any.</param>
|
||||
private record struct TextFragment(
|
||||
int From,
|
||||
int To,
|
||||
int Link,
|
||||
Vector2 Offset,
|
||||
SeStringReplacementEntity Entity,
|
||||
float VisibleWidth,
|
||||
float AdvanceWidth,
|
||||
float AdvanceWidthWithoutSoftHyphen,
|
||||
bool BreakAfter,
|
||||
bool EndsWithSoftHyphen,
|
||||
Rune FirstRune,
|
||||
Rune LastRune)
|
||||
{
|
||||
public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||
|
||||
/// <summary>Represents a text fragment in a SeString span.</summary>
|
||||
/// <param name="From">Starting byte offset (inclusive) in a SeString.</param>
|
||||
/// <param name="To">Ending byte offset (exclusive) in a SeString.</param>
|
||||
/// <param name="Link">Byte offset of the link that decorates this text fragment, or <c>-1</c> if none.</param>
|
||||
/// <param name="Offset">Offset in pixels w.r.t. <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
||||
/// <param name="Entity">Replacement entity, if any.</param>
|
||||
/// <param name="VisibleWidth">Visible width of this text fragment. This is the width required to draw everything
|
||||
/// without clipping.</param>
|
||||
/// <param name="AdvanceWidth">Advance width of this text fragment. This is the width required to add to the cursor
|
||||
/// to position the next fragment correctly.</param>
|
||||
/// <param name="AdvanceWidthWithoutSoftHyphen">Same with <paramref name="AdvanceWidth"/>, but trimming all the
|
||||
/// trailing soft hyphens.</param>
|
||||
/// <param name="BreakAfter">Whether to insert a line break after this text fragment.</param>
|
||||
/// <param name="EndsWithSoftHyphen">Whether this text fragment ends with one or more soft hyphens.</param>
|
||||
/// <param name="FirstRune">First rune in this text fragment.</param>
|
||||
/// <param name="LastRune">Last rune in this text fragment, for the purpose of calculating kerning distance with
|
||||
/// the following text fragment in the same line, if any.</param>
|
||||
internal record struct TextFragment(
|
||||
int From,
|
||||
int To,
|
||||
int Link,
|
||||
Vector2 Offset,
|
||||
SeStringReplacementEntity Entity,
|
||||
float VisibleWidth,
|
||||
float AdvanceWidth,
|
||||
float AdvanceWidthWithoutSoftHyphen,
|
||||
bool BreakAfter,
|
||||
bool EndsWithSoftHyphen,
|
||||
Rune FirstRune,
|
||||
Rune LastRune)
|
||||
{
|
||||
/// <summary>Gets a value indicating whether the fragment ends with a visible soft hyphen.</summary>
|
||||
public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -6,6 +7,8 @@ using System.Text;
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
@ -14,51 +17,80 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
|||
|
||||
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe ref struct SeStringDrawState
|
||||
public unsafe ref struct SeStringDrawState : IDisposable
|
||||
{
|
||||
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
||||
|
||||
private readonly ImDrawList* drawList;
|
||||
private readonly SeStringColorStackSet colorStackSet;
|
||||
private readonly ImDrawListSplitter* splitter;
|
||||
|
||||
private 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>
|
||||
/// <param name="fragments">Fragments.</param>
|
||||
/// <param name="font">Font to use.</param>
|
||||
internal SeStringDrawState(
|
||||
ReadOnlySpan<byte> span,
|
||||
scoped in SeStringDrawParams ssdp,
|
||||
SeStringColorStackSet colorStackSet,
|
||||
ImDrawListSplitter* splitter)
|
||||
List<TextFragment> fragments,
|
||||
ImFont* font)
|
||||
{
|
||||
this.colorStackSet = colorStackSet;
|
||||
this.splitter = splitter;
|
||||
this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
||||
this.Span = span;
|
||||
this.ColorStackSet = colorStackSet;
|
||||
this.Fragments = fragments;
|
||||
this.Font = font;
|
||||
|
||||
if (ssdp.TargetDrawList is null)
|
||||
{
|
||||
if (!ThreadSafety.IsMainThread)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"{nameof(ssdp.TargetDrawList)} must be set to render outside the main thread.");
|
||||
}
|
||||
|
||||
this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
||||
this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
||||
this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
||||
this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
||||
this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
||||
this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
||||
this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
||||
this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.drawList = ssdp.TargetDrawList.Value;
|
||||
this.ScreenOffset = Vector2.Zero;
|
||||
this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
|
||||
$"{nameof(ssdp.FontSize)} must be set to render outside the main thread.");
|
||||
this.WrapWidth = ssdp.WrapWidth ?? float.MaxValue;
|
||||
this.Color = ssdp.Color ?? uint.MaxValue;
|
||||
this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread.
|
||||
this.LinkActiveBackColor = 0; // Interactivity is unused outside the main thread.
|
||||
this.ThemeIndex = ssdp.ThemeIndex ?? 0;
|
||||
}
|
||||
|
||||
this.splitter = default;
|
||||
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.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
||||
this.Bold = ssdp.Bold;
|
||||
this.Italic = ssdp.Italic;
|
||||
this.Edge = ssdp.Edge;
|
||||
this.Shadow = ssdp.Shadow;
|
||||
|
||||
this.ColorStackSet.Initialize(ref this);
|
||||
fragments.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
||||
|
|
@ -135,7 +167,7 @@ public unsafe ref struct SeStringDrawState
|
|||
|
||||
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||
public readonly bool ShouldDrawEdge =>
|
||||
(this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000;
|
||||
(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 };
|
||||
|
|
@ -143,11 +175,21 @@ public unsafe ref struct SeStringDrawState
|
|||
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||
public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 };
|
||||
|
||||
/// <summary>Gets the color stacks.</summary>
|
||||
internal SeStringColorStackSet ColorStackSet { get; }
|
||||
|
||||
/// <summary>Gets the text fragments.</summary>
|
||||
internal List<TextFragment> Fragments { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() =>
|
||||
ImGuiNative.Destroy((ImDrawListSplitter*)Unsafe.AsPointer(ref this.splitter));
|
||||
|
||||
/// <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) =>
|
||||
this.splitter->SetCurrentChannel(this.drawList, (int)channelIndex);
|
||||
public void SetCurrentChannel(SeStringDrawChannel channelIndex) =>
|
||||
this.splitter.SetCurrentChannel(this.drawList, (int)channelIndex);
|
||||
|
||||
/// <summary>Draws a single texture.</summary>
|
||||
/// <param name="igTextureId">ImGui texture ID to draw from.</param>
|
||||
|
|
@ -216,7 +258,7 @@ public unsafe ref struct SeStringDrawState
|
|||
/// <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)
|
||||
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
||||
{
|
||||
var texId = this.Font->ContainerAtlas->Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
||||
var xy0 = new Vector2(
|
||||
|
|
@ -268,7 +310,7 @@ public unsafe ref struct SeStringDrawState
|
|||
/// <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)
|
||||
internal void DrawLinkUnderline(Vector2 offset, float advanceWidth)
|
||||
{
|
||||
if (this.LinkUnderlineThickness < 1f)
|
||||
return;
|
||||
|
|
@ -350,15 +392,15 @@ public unsafe ref struct SeStringDrawState
|
|||
switch (payload.MacroCode)
|
||||
{
|
||||
case MacroCode.Color:
|
||||
this.colorStackSet.HandleColorPayload(ref this, payload);
|
||||
this.ColorStackSet.HandleColorPayload(ref this, payload);
|
||||
return true;
|
||||
|
||||
case MacroCode.EdgeColor:
|
||||
this.colorStackSet.HandleEdgeColorPayload(ref this, payload);
|
||||
this.ColorStackSet.HandleEdgeColorPayload(ref this, payload);
|
||||
return true;
|
||||
|
||||
case MacroCode.ShadowColor:
|
||||
this.colorStackSet.HandleShadowColorPayload(ref this, payload);
|
||||
this.ColorStackSet.HandleShadowColorPayload(ref this, payload);
|
||||
return true;
|
||||
|
||||
case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||
|
|
@ -379,11 +421,11 @@ public unsafe ref struct SeStringDrawState
|
|||
return true;
|
||||
|
||||
case MacroCode.ColorType:
|
||||
this.colorStackSet.HandleColorTypePayload(ref this, payload);
|
||||
this.ColorStackSet.HandleColorTypePayload(ref this, payload);
|
||||
return true;
|
||||
|
||||
case MacroCode.EdgeColorType:
|
||||
this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload);
|
||||
this.ColorStackSet.HandleEdgeColorTypePayload(ref this, payload);
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
@ -393,10 +435,9 @@ public unsafe ref struct SeStringDrawState
|
|||
|
||||
/// <summary>Splits the draw list.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly void SplitDrawList() =>
|
||||
this.splitter->Split(this.drawList, ChannelCount);
|
||||
internal void SplitDrawList() => this.splitter.Split(this.drawList, ChannelCount);
|
||||
|
||||
/// <summary>Merges the draw list.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly void MergeDrawList() => this.splitter->Merge(this.drawList);
|
||||
internal void MergeDrawList() => this.splitter.Merge(this.drawList);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,6 +533,13 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
this.creditsDarkeningAnimation.Restart();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DataWindow.GetWidget{T}"/>
|
||||
public T GetDataWindowWidget<T>() where T : IDataWindowWidget => this.dataWindow.GetWidget<T>();
|
||||
|
||||
/// <summary>Sets the data window current widget.</summary>
|
||||
/// <param name="widget">Widget to set current.</param>
|
||||
public void SetDataWindowWidget(IDataWindowWidget widget) => this.dataWindow.CurrentWidget = widget;
|
||||
|
||||
private void OnDraw()
|
||||
{
|
||||
this.FrameCount++;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
private bool isExcept;
|
||||
private bool selectionCollapsed;
|
||||
private IDataWindowWidget currentWidget;
|
||||
|
||||
private bool isLoaded;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -82,9 +82,12 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
this.RespectCloseHotkey = false;
|
||||
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
||||
this.currentWidget = this.orderedModules.First();
|
||||
this.CurrentWidget = this.orderedModules.First();
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the current widget.</summary>
|
||||
public IDataWindowWidget CurrentWidget { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.modules.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
||||
|
||||
|
|
@ -99,6 +102,20 @@ internal class DataWindow : Window, IDisposable
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets the data window widget of the specified type.</summary>
|
||||
/// <typeparam name="T">Type of the data window widget to find.</typeparam>
|
||||
/// <returns>Found widget.</returns>
|
||||
public T GetWidget<T>() where T : IDataWindowWidget
|
||||
{
|
||||
foreach (var m in this.modules)
|
||||
{
|
||||
if (m is T w)
|
||||
return w;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"No widget of type {typeof(T).FullName} found.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the DataKind dropdown menu.
|
||||
/// </summary>
|
||||
|
|
@ -110,7 +127,7 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
if (this.modules.FirstOrDefault(module => module.IsWidgetCommand(dataKind)) is { } targetModule)
|
||||
{
|
||||
this.currentWidget = targetModule;
|
||||
this.CurrentWidget = targetModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -153,9 +170,9 @@ internal class DataWindow : Window, IDisposable
|
|||
{
|
||||
foreach (var widget in this.orderedModules)
|
||||
{
|
||||
if (ImGui.Selectable(widget.DisplayName, this.currentWidget == widget))
|
||||
if (ImGui.Selectable(widget.DisplayName, this.CurrentWidget == widget))
|
||||
{
|
||||
this.currentWidget = widget;
|
||||
this.CurrentWidget = widget;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,9 +223,9 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
try
|
||||
{
|
||||
if (this.currentWidget is { Ready: true })
|
||||
if (this.CurrentWidget is { Ready: true })
|
||||
{
|
||||
this.currentWidget.Draw();
|
||||
this.CurrentWidget.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Textures.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Internal;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
||||
|
|
@ -87,12 +93,30 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
|||
ImGuiHelpers.ScaledDummy(10f);
|
||||
for (var i = 0; i < this.icons?.Count; i++)
|
||||
{
|
||||
if (this.icons[i] == FontAwesomeIcon.None)
|
||||
continue;
|
||||
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text($"0x{(int)this.icons[i].ToIconChar():X}");
|
||||
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
||||
ImGui.Text($"{this.iconNames?[i]}");
|
||||
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
||||
ImGui.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont);
|
||||
ImGui.Text(this.icons[i].ToIconString());
|
||||
ImGuiHelpers.ScaledRelativeSameLine(320f);
|
||||
if (this.useFixedWidth
|
||||
? ImGui.Button($"{(char)this.icons[i]}##FontAwesomeIconButton{i}")
|
||||
: ImGuiComponents.IconButton($"##FontAwesomeIconButton{i}", this.icons[i]))
|
||||
{
|
||||
_ = Service<DevTextureSaveMenu>.Get().ShowTextureSaveMenuAsync(
|
||||
this.DisplayName,
|
||||
this.icons[i].ToString(),
|
||||
Task.FromResult(
|
||||
Service<TextureManager>.Get().CreateTextureFromSeString(
|
||||
ReadOnlySeString.FromText(this.icons[i].ToIconString()),
|
||||
new() { Font = ImGui.GetFont(), FontSize = ImGui.GetFontSize() })));
|
||||
}
|
||||
|
||||
ImGui.PopFont();
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Data;
|
||||
|
|
@ -9,11 +10,13 @@ using Dalamud.Interface.ImGuiSeStringRenderer;
|
|||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||
using Dalamud.Interface.Textures.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Internal;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Parse;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
|
|
@ -56,11 +59,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ?? ImGui.GetColorU32(ImGuiCol.Text));
|
||||
var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ??= ImGui.GetColorU32(ImGuiCol.Text));
|
||||
if (ImGui.ColorEdit4("Color", ref t2))
|
||||
this.style.Color = ImGui.ColorConvertFloat4ToU32(t2);
|
||||
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ?? 0xFF000000u);
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ??= 0xFF000000u);
|
||||
if (ImGui.ColorEdit4("Edge Color", ref t2))
|
||||
this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||
|
||||
|
|
@ -69,27 +72,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
if (ImGui.Checkbox("Forced"u8, ref t))
|
||||
this.style.ForceEdgeColor = t;
|
||||
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ?? 0xFF000000u);
|
||||
if (ImGui.ColorEdit4("Shadow Color", ref t2))
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ??= 0xFF000000u);
|
||||
if (ImGui.ColorEdit4("Shadow Color"u8, ref t2))
|
||||
this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered));
|
||||
if (ImGui.ColorEdit4("Link Hover Color", ref t2))
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonHovered));
|
||||
if (ImGui.ColorEdit4("Link Hover Color"u8, ref t2))
|
||||
this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive));
|
||||
if (ImGui.ColorEdit4("Link Active Color", ref t2))
|
||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonActive));
|
||||
if (ImGui.ColorEdit4("Link Active Color"u8, ref t2))
|
||||
this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||
|
||||
var t3 = this.style.LineHeight ?? 1f;
|
||||
var t3 = this.style.LineHeight ??= 1f;
|
||||
if (ImGui.DragFloat("Line Height"u8, ref t3, 0.01f, 0.4f, 3f, "%.02f"))
|
||||
this.style.LineHeight = t3;
|
||||
|
||||
t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha;
|
||||
t3 = this.style.Opacity ??= 1f;
|
||||
if (ImGui.DragFloat("Opacity"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||
this.style.Opacity = t3;
|
||||
|
||||
t3 = this.style.EdgeStrength ?? 0.25f;
|
||||
t3 = this.style.EdgeStrength ??= 0.25f;
|
||||
if (ImGui.DragFloat("Edge Strength"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||
this.style.EdgeStrength = t3;
|
||||
|
||||
|
|
@ -240,6 +243,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Copy as Image"))
|
||||
{
|
||||
_ = Service<DevTextureSaveMenu>.Get().ShowTextureSaveMenuAsync(
|
||||
this.DisplayName,
|
||||
$"From {nameof(SeStringRendererTestWidget)}",
|
||||
Task.FromResult(
|
||||
Service<TextureManager>.Get().CreateTextureFromSeString(
|
||||
ReadOnlySeString.FromMacroString(
|
||||
this.testString,
|
||||
new(ExceptionMode: MacroStringParseExceptionMode.EmbedError)),
|
||||
this.style with
|
||||
{
|
||||
Font = ImGui.GetFont(),
|
||||
FontSize = ImGui.GetFontSize(),
|
||||
WrapWidth = ImGui.GetContentRegionAvail().X,
|
||||
ThemeIndex = AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType,
|
||||
})));
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(3);
|
||||
ImGuiHelpers.CompileSeStringWrapped(
|
||||
"Optional features implemented for the following test input:<br>" +
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -306,12 +306,12 @@ internal class TexWidget : IDataWindowWidget
|
|||
pres->Release();
|
||||
|
||||
ImGui.Text($"RC: Resource({rcres})/View({rcsrv})");
|
||||
ImGui.Text(source.ToString());
|
||||
ImGui.Text($"{source.Width} x {source.Height} | {source}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("RC: -"u8);
|
||||
ImGui.Text(" "u8);
|
||||
ImGui.Text("RC: -");
|
||||
ImGui.Text(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,6 +342,10 @@ internal class TexWidget : IDataWindowWidget
|
|||
runLater?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>Adds a texture wrap for debug display purposes.</summary>
|
||||
/// <param name="textureTask">Task returning a texture.</param>
|
||||
public void AddTexture(Task<IDalamudTextureWrap> textureTask) => this.addedTextures.Add(new(Api10: textureTask));
|
||||
|
||||
private unsafe void DrawBlame(List<TextureManager.IBlameableDalamudTextureWrap> allBlames)
|
||||
{
|
||||
var im = Service<InterfaceManager>.Get();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
|
|
@ -33,10 +34,22 @@ public interface IFontHandle : IDisposable
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.<br />
|
||||
/// Alternatively, use <see cref="WaitAsync"/> to wait for this property to become <c>true</c>.
|
||||
/// Alternatively, use <see cref="WaitAsync()"/> to wait for this property to become <c>true</c>.
|
||||
/// </remarks>
|
||||
bool Available { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to lock the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
||||
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
||||
/// Modification of the font will exhibit undefined behavior if some other thread also uses the font.
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The error message, if any.</param>
|
||||
/// <returns>
|
||||
/// An instance of <see cref="ILockedImFont"/> that <b>must</b> be disposed after use on success;
|
||||
/// <c>null</c> with <paramref name="errorMessage"/> populated on failure.
|
||||
/// </returns>
|
||||
ILockedImFont? TryLock(out string? errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Locks the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
||||
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
||||
|
|
@ -92,4 +105,11 @@ public interface IFontHandle : IDisposable
|
|||
/// </summary>
|
||||
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
||||
Task<IFontHandle> WaitAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for <see cref="Available"/> to become <c>true</c>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
||||
Task<IFontHandle> WaitAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,12 +238,17 @@ internal abstract class FontHandle : IFontHandle
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IFontHandle> WaitAsync()
|
||||
public Task<IFontHandle> WaitAsync() => this.WaitAsync(CancellationToken.None);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IFontHandle> WaitAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.Available)
|
||||
return Task.FromResult<IFontHandle>(this);
|
||||
|
||||
var tcs = new TaskCompletionSource<IFontHandle>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
cancellationToken.Register(() => tcs.TrySetCanceled());
|
||||
|
||||
this.ImFontChanged += OnImFontChanged;
|
||||
this.Disposed += OnDisposed;
|
||||
if (this.Available)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly SeStringRenderer seStringRenderer = Service<SeStringRenderer>.Get();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||
ReadOnlySpan<byte> text,
|
||||
scoped in SeStringDrawParams drawParams = default,
|
||||
string? debugName = null)
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
using var dd = this.seStringRenderer.CreateDrawData(text, drawParams);
|
||||
var texture = this.CreateDrawListTexture(debugName ?? nameof(this.CreateTextureFromSeString));
|
||||
try
|
||||
{
|
||||
texture.Size = dd.Data.DisplaySize;
|
||||
texture.Draw(dd.DataPtr);
|
||||
return texture;
|
||||
}
|
||||
catch
|
||||
{
|
||||
texture.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.IoC;
|
||||
|
|
@ -283,6 +284,18 @@ internal sealed class TextureManagerPluginScoped
|
|||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||
ReadOnlySpan<byte> text,
|
||||
scoped in SeStringDrawParams drawParams = default,
|
||||
string? debugName = null)
|
||||
{
|
||||
var manager = this.ManagerOrThrow;
|
||||
var textureWrap = manager.CreateTextureFromSeString(text, drawParams, debugName);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
||||
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
|
@ -691,13 +692,14 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
|||
FontAtlasAutoRebuildMode autoRebuildMode,
|
||||
bool isGlobalScaled = true,
|
||||
string? debugName = null) =>
|
||||
this.scopedFinalizer.Add(Service<FontAtlasFactory>
|
||||
.Get()
|
||||
.CreateFontAtlas(
|
||||
this.namespaceName + ":" + (debugName ?? "custom"),
|
||||
autoRebuildMode,
|
||||
isGlobalScaled,
|
||||
this.plugin));
|
||||
this.scopedFinalizer.Add(
|
||||
Service<FontAtlasFactory>
|
||||
.Get()
|
||||
.CreateFontAtlas(
|
||||
this.namespaceName + ":" + (debugName ?? "custom"),
|
||||
autoRebuildMode,
|
||||
isGlobalScaled,
|
||||
this.plugin));
|
||||
|
||||
/// <summary>
|
||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||
|
|
@ -868,6 +870,15 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
|||
// Note: do not dispose w; we do not own it
|
||||
}
|
||||
|
||||
public ILockedImFont? TryLock(out string? errorMessage)
|
||||
{
|
||||
if (this.wrapped is { } w)
|
||||
return w.TryLock(out errorMessage);
|
||||
|
||||
errorMessage = nameof(ObjectDisposedException);
|
||||
return null;
|
||||
}
|
||||
|
||||
public ILockedImFont Lock() =>
|
||||
this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
||||
|
|
@ -876,7 +887,13 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
|||
public void Pop() => this.WrappedNotDisposed.Pop();
|
||||
|
||||
public Task<IFontHandle> WaitAsync() =>
|
||||
this.WrappedNotDisposed.WaitAsync().ContinueWith(_ => (IFontHandle)this);
|
||||
this.wrapped?.WaitAsync().ContinueWith(_ => (IFontHandle)this)
|
||||
?? Task.FromException<IFontHandle>(new ObjectDisposedException(nameof(FontHandleWrapper)));
|
||||
|
||||
public Task<IFontHandle> WaitAsync(CancellationToken cancellationToken) =>
|
||||
this.wrapped?.WaitAsync(cancellationToken)
|
||||
.ContinueWith(_ => (IFontHandle)this, cancellationToken)
|
||||
?? Task.FromException<IFontHandle>(new ObjectDisposedException(nameof(FontHandleWrapper)));
|
||||
|
||||
public override string ToString() =>
|
||||
$"{nameof(FontHandleWrapper)}({this.wrapped?.ToString() ?? "disposed"})";
|
||||
|
|
|
|||
112
Dalamud/Interface/Utility/BufferBackedImDrawData.cs
Normal file
112
Dalamud/Interface/Utility/BufferBackedImDrawData.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace Dalamud.Interface.Utility;
|
||||
|
||||
/// <summary>Wrapper aroundx <see cref="ImDrawData"/> containing one <see cref="ImDrawList"/>.</summary>
|
||||
public unsafe struct BufferBackedImDrawData : IDisposable
|
||||
{
|
||||
private nint buffer;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="BufferBackedImDrawData"/> struct.</summary>
|
||||
/// <param name="buffer">Address of buffer to use.</param>
|
||||
private BufferBackedImDrawData(nint buffer) => this.buffer = buffer;
|
||||
|
||||
/// <summary>Gets the <see cref="ImDrawData"/> stored in this buffer.</summary>
|
||||
public readonly ref ImDrawData Data => ref ((DataStruct*)this.buffer)->Data;
|
||||
|
||||
/// <summary>Gets the <see cref="ImDrawDataPtr"/> stored in this buffer.</summary>
|
||||
public readonly ImDrawDataPtr DataPtr => new((ImDrawData*)Unsafe.AsPointer(ref this.Data));
|
||||
|
||||
/// <summary>Gets the <see cref="ImDrawList"/> stored in this buffer.</summary>
|
||||
public readonly ref ImDrawList List => ref ((DataStruct*)this.buffer)->List;
|
||||
|
||||
/// <summary>Gets the <see cref="ImDrawListPtr"/> stored in this buffer.</summary>
|
||||
public readonly ImDrawListPtr ListPtr => new((ImDrawList*)Unsafe.AsPointer(ref this.List));
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="BufferBackedImDrawData"/>.</summary>
|
||||
/// <returns>A new instance of <see cref="BufferBackedImDrawData"/>.</returns>
|
||||
public static BufferBackedImDrawData Create()
|
||||
{
|
||||
if (ImGui.GetCurrentContext().IsNull || ImGui.GetIO().FontDefault.Handle is null)
|
||||
throw new("ImGui is not ready");
|
||||
|
||||
var res = new BufferBackedImDrawData(Marshal.AllocHGlobal(sizeof(DataStruct)));
|
||||
var ds = (DataStruct*)res.buffer;
|
||||
*ds = default;
|
||||
|
||||
var atlas = ImGui.GetIO().Fonts;
|
||||
ref var atlasTail = ref ImFontAtlasTailReal.From(atlas);
|
||||
ds->SharedData = *ImGui.GetDrawListSharedData().Handle;
|
||||
ds->SharedData.TexIdCommon = atlas.Textures[atlasTail.TextureIndexCommon].TexID;
|
||||
ds->SharedData.TexUvWhitePixel = atlas.TexUvWhitePixel;
|
||||
ds->SharedData.TexUvLines = (Vector4*)Unsafe.AsPointer(ref atlas.TexUvLines[0]);
|
||||
ds->SharedData.Font = ImGui.GetIO().FontDefault;
|
||||
ds->SharedData.FontSize = ds->SharedData.Font->FontSize;
|
||||
ds->SharedData.ClipRectFullscreen = new(
|
||||
float.NegativeInfinity,
|
||||
float.NegativeInfinity,
|
||||
float.PositiveInfinity,
|
||||
float.PositiveInfinity);
|
||||
|
||||
ds->List.Data = &ds->SharedData;
|
||||
ds->ListPtr = &ds->List;
|
||||
ds->Data.CmdLists = &ds->ListPtr;
|
||||
ds->Data.CmdListsCount = 1;
|
||||
ds->Data.FramebufferScale = Vector2.One;
|
||||
|
||||
res.ListPtr._ResetForNewFrame();
|
||||
res.ListPtr.PushClipRectFullScreen();
|
||||
res.ListPtr.PushTextureID(new(atlasTail.TextureIndexCommon));
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>Updates the statistics information stored in <see cref="DataPtr"/> from <see cref="ListPtr"/>.</summary>
|
||||
public readonly void UpdateDrawDataStatistics()
|
||||
{
|
||||
this.Data.TotalIdxCount = this.List.IdxBuffer.Size;
|
||||
this.Data.TotalVtxCount = this.List.VtxBuffer.Size;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.buffer != 0)
|
||||
{
|
||||
this.ListPtr._ClearFreeMemory();
|
||||
Marshal.FreeHGlobal(this.buffer);
|
||||
this.buffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DataStruct
|
||||
{
|
||||
public ImDrawData Data;
|
||||
public ImDrawList* ListPtr;
|
||||
public ImDrawList List;
|
||||
public ImDrawListSharedData SharedData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ImFontAtlasTailReal
|
||||
{
|
||||
/// <summary>Index of texture containing the below.</summary>
|
||||
public int TextureIndexCommon;
|
||||
|
||||
/// <summary>Custom texture rectangle ID for both of the below.</summary>
|
||||
public int PackIdCommon;
|
||||
|
||||
/// <summary>Custom texture rectangle for white pixel and mouse cursors.</summary>
|
||||
public ImFontAtlasCustomRect RectMouseCursors;
|
||||
|
||||
/// <summary>Custom texture rectangle for baked anti-aliased lines.</summary>
|
||||
public ImFontAtlasCustomRect RectLines;
|
||||
|
||||
public static ref ImFontAtlasTailReal From(ImFontAtlasPtr fontAtlasPtr) =>
|
||||
ref *(ImFontAtlasTailReal*)(&fontAtlasPtr.Handle->FontBuilderFlags + sizeof(uint));
|
||||
}
|
||||
}
|
||||
|
|
@ -575,6 +575,15 @@ public static partial class ImGuiHelpers
|
|||
public static unsafe ImFontPtr OrElse(this ImFontPtr self, ImFontPtr other) =>
|
||||
self.IsNull ? other : self;
|
||||
|
||||
/// <summary>Creates a draw data that will draw the given SeString onto it.</summary>
|
||||
/// <param name="sss">SeString to render.</param>
|
||||
/// <param name="style">Initial rendering style.</param>
|
||||
/// <returns>A new self-contained draw data.</returns>
|
||||
internal static BufferBackedImDrawData CreateDrawData(
|
||||
ReadOnlySpan<byte> sss,
|
||||
scoped in SeStringDrawParams style = default) =>
|
||||
Service<SeStringRenderer>.Get().CreateDrawData(sss, style);
|
||||
|
||||
/// <summary>
|
||||
/// Mark 4K page as used, after adding a codepoint to a font.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
using Dalamud.Interface.Textures.Internal;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Serilog;
|
||||
|
|
@ -33,6 +35,14 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
|||
this.interfaceManager.Draw += this.InterfaceManagerOnDraw;
|
||||
}
|
||||
|
||||
private enum ContextMenuActionType
|
||||
{
|
||||
None,
|
||||
SaveAsFile,
|
||||
CopyToClipboard,
|
||||
SendToTexWidget,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.interfaceManager.Draw -= this.InterfaceManagerOnDraw;
|
||||
|
||||
|
|
@ -66,15 +76,16 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
|||
var textureManager = await Service<TextureManager>.GetAsync();
|
||||
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.Handle.Handle:X}";
|
||||
|
||||
ContextMenuActionType action;
|
||||
BitmapCodecInfo? encoder;
|
||||
{
|
||||
var first = true;
|
||||
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
|
||||
var tcs = new TaskCompletionSource<BitmapCodecInfo?>(
|
||||
var tcs = new TaskCompletionSource<(ContextMenuActionType Action, BitmapCodecInfo? Codec)>(
|
||||
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Service<InterfaceManager>.Get().Draw += DrawChoices;
|
||||
|
||||
encoder = await tcs.Task;
|
||||
(action, encoder) = await tcs.Task;
|
||||
|
||||
[SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "This shall not escape")]
|
||||
void DrawChoices()
|
||||
|
|
@ -98,13 +109,20 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
|||
}
|
||||
|
||||
if (ImGui.Selectable("Copy"u8))
|
||||
tcs.TrySetResult(null);
|
||||
tcs.TrySetResult((ContextMenuActionType.CopyToClipboard, null));
|
||||
if (ImGui.Selectable("Send to TexWidget"u8))
|
||||
tcs.TrySetResult((ContextMenuActionType.SendToTexWidget, null));
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
foreach (var encoder2 in encoders)
|
||||
{
|
||||
if (ImGui.Selectable(encoder2.Name))
|
||||
tcs.TrySetResult(encoder2);
|
||||
tcs.TrySetResult((ContextMenuActionType.SaveAsFile, encoder2));
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
const float previewImageWidth = 320;
|
||||
var size = textureWrap.Size;
|
||||
if (size.X > previewImageWidth)
|
||||
|
|
@ -120,50 +138,68 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
if (encoder is null)
|
||||
switch (action)
|
||||
{
|
||||
isCopy = true;
|
||||
await textureManager.CopyToClipboardAsync(textureWrap, name, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var props = new Dictionary<string, object>();
|
||||
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
||||
props["CompressionQuality"] = 1.0f;
|
||||
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
||||
props["ImageQuality"] = 1.0f;
|
||||
case ContextMenuActionType.CopyToClipboard:
|
||||
isCopy = true;
|
||||
await textureManager.CopyToClipboardAsync(textureWrap, name, true);
|
||||
break;
|
||||
|
||||
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
this.fileDialogManager.SaveFileDialog(
|
||||
"Save texture...",
|
||||
$"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
|
||||
name + encoder.Extensions.First(),
|
||||
encoder.Extensions.First(),
|
||||
(ok, path2) =>
|
||||
{
|
||||
if (!ok)
|
||||
tcs.SetCanceled();
|
||||
else
|
||||
tcs.SetResult(path2);
|
||||
});
|
||||
var path = await tcs.Task.ConfigureAwait(false);
|
||||
|
||||
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
||||
|
||||
var notif = Service<NotificationManager>.Get().AddNotification(
|
||||
new()
|
||||
{
|
||||
Content = $"File saved to: {path}",
|
||||
Title = initiatorName,
|
||||
Type = NotificationType.Success,
|
||||
});
|
||||
notif.Click += n =>
|
||||
case ContextMenuActionType.SendToTexWidget:
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
n.Notification.DismissNow();
|
||||
};
|
||||
var framework = await Service<Framework>.GetAsync();
|
||||
var dalamudInterface = await Service<DalamudInterface>.GetAsync();
|
||||
await framework.RunOnFrameworkThread(
|
||||
() =>
|
||||
{
|
||||
var texWidget = dalamudInterface.GetDataWindowWidget<TexWidget>();
|
||||
dalamudInterface.SetDataWindowWidget(texWidget);
|
||||
texWidget.AddTexture(Task.FromResult(textureWrap.CreateWrapSharingLowLevelResource()));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case ContextMenuActionType.SaveAsFile when encoder is not null:
|
||||
{
|
||||
var props = new Dictionary<string, object>();
|
||||
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
||||
props["CompressionQuality"] = 1.0f;
|
||||
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
||||
props["ImageQuality"] = 1.0f;
|
||||
|
||||
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
this.fileDialogManager.SaveFileDialog(
|
||||
"Save texture...",
|
||||
$"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
|
||||
name + encoder.Extensions.First(),
|
||||
encoder.Extensions.First(),
|
||||
(ok, path2) =>
|
||||
{
|
||||
if (!ok)
|
||||
tcs.SetCanceled();
|
||||
else
|
||||
tcs.SetResult(path2);
|
||||
});
|
||||
var path = await tcs.Task.ConfigureAwait(false);
|
||||
|
||||
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
||||
|
||||
var notif = Service<NotificationManager>.Get().AddNotification(
|
||||
new()
|
||||
{
|
||||
Content = $"File saved to: {path}",
|
||||
Title = initiatorName,
|
||||
Type = NotificationType.Success,
|
||||
});
|
||||
notif.Click += n =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
n.Notification.DismissNow();
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
|
|
@ -186,6 +187,17 @@ public interface ITextureProvider : IDalamudService
|
|||
string? debugName = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Creates a texture by drawing a SeString onto it.</summary>
|
||||
/// <param name="text">SeString to render.</param>
|
||||
/// <param name="drawParams">Parameters for drawing.</param>
|
||||
/// <param name="debugName">Name for debug display purposes.</param>
|
||||
/// <returns>The new texture.</returns>
|
||||
/// <remarks>Can be only be used from the main thread.</remarks>
|
||||
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||
ReadOnlySpan<byte> text,
|
||||
scoped in SeStringDrawParams drawParams = default,
|
||||
string? debugName = null);
|
||||
|
||||
/// <summary>Gets the supported bitmap decoders.</summary>
|
||||
/// <returns>The supported bitmap decoders.</returns>
|
||||
/// <remarks>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue