test(selftest): show what each type role actually resolves to
Block A ends with no call site in the message list -- that arrives in block C -- so without this step there would be nothing to look at and two helpers with no caller at all. It draws rather than asserts, because asserting proves the wrong thing here. SimplePushedFont pushes nothing at all when a handle is not ready, silently, and the text then renders in whatever face was already active. A step that compares two numbers and reports Pass would sail straight past that. So this one puts a timestamp, a sender and a body line next to each other and lets them be looked at, with expected-against-actual printed underneath. The three weight buttons exist because the alternative was three builds and a plugin restart between each, and nobody compares a typeface across a restart. RebuildDelegateFonts is synchronous on this thread, so the sample row picks up the new rasterisation on the next frame.
This commit is contained in:
@@ -497,6 +497,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
new SelfTests.HoverStateFootprintStep(),
|
||||
new SelfTests.HonorificHeaderRenderStep(this),
|
||||
new SelfTests.AboutIntegrationsStatusStep(this),
|
||||
new SelfTests.TypeScaleStep(this),
|
||||
new SelfTests.PerformanceBaselineStep(this),
|
||||
new SelfTests.GlobalStyleScopeAllocStep(this),
|
||||
new SelfTests.MainWindowFocusOpacityStep(this),
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
using HellionChat.Ui.StyleEngine;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// v1.13.0/A7: the type scale has no call site in the message list until block C,
|
||||
// so without this step block A would end with nothing to look at and two helpers
|
||||
// (TypeScale, BaselineMath) with no caller at all.
|
||||
//
|
||||
// It draws rather than asserts. A step that only reports Pass proves nothing about
|
||||
// a font handle -- SimplePushedFont pushes nothing at all when a handle is not
|
||||
// ready, silently, and the text then renders in whatever face was already active.
|
||||
// The only way to see that is to look at it.
|
||||
internal sealed class TypeScaleStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
public TypeScaleStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - type scale";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var fm = this.plugin.FontManager;
|
||||
if (fm is null)
|
||||
{
|
||||
ImGui.Text("FontManager is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (!fm.FontsReady)
|
||||
{
|
||||
ImGui.Text("FontsReady is false - atlas still building, run again in a moment");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.SenderFont is null || fm.MetaFont is null || fm.RegularFont is null)
|
||||
{
|
||||
ImGui.Text("A role handle is missing - see FontManager ctor smoke");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
var basePt = fm.ResolveGlobalFontPt();
|
||||
var scale = ImGuiHelpers.GlobalScale;
|
||||
|
||||
ImGui.TextUnformatted($"base {basePt:0.00}pt, display scale {scale:0.00}");
|
||||
ImGui.Separator();
|
||||
|
||||
// Expected against actual, per role. The resolved point size is what the
|
||||
// layout fingerprint keys on, so a mismatch here is a stale height cache
|
||||
// waiting to happen.
|
||||
ReportRole(fm, TypeRole.Body, basePt, fm.RegularFont);
|
||||
ReportRole(fm, TypeRole.Sender, basePt, fm.SenderFont);
|
||||
ReportRole(fm, TypeRole.Meta, basePt, fm.MetaFont);
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Baseline: timestamp, sender and body on one row.");
|
||||
DrawSampleRow(fm, "14:32", "Julia Moon:", "Convoy approach vector confirmed.");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted(
|
||||
$"Sender weight is {FontManager.SenderWeight:0.00}. "
|
||||
+ "Pick one and watch the row above change."
|
||||
);
|
||||
|
||||
// Three builds would be the alternative, and nobody compares a face
|
||||
// across a restart. RebuildDelegateFonts runs synchronously on this
|
||||
// thread -- self-test steps are on the draw thread, same as every push
|
||||
// site -- so the sample row redraws in the next frame with the new
|
||||
// rasterisation.
|
||||
DrawWeightPicker(fm, 1.2f);
|
||||
ImGui.SameLine();
|
||||
DrawWeightPicker(fm, 1.3f);
|
||||
ImGui.SameLine();
|
||||
DrawWeightPicker(fm, 1.4f);
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp() { }
|
||||
|
||||
private static void DrawWeightPicker(FontManager fm, float weight)
|
||||
{
|
||||
var active = MathF.Abs(FontManager.SenderWeight - weight) < 0.001f;
|
||||
if (ImGui.Button($"{(active ? "> " : "")}{weight:0.0}##sender-weight-{weight}"))
|
||||
{
|
||||
FontManager.SenderWeight = weight;
|
||||
fm.RebuildDelegateFonts();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReportRole(
|
||||
FontManager fm,
|
||||
TypeRole role,
|
||||
float basePt,
|
||||
IFontHandle handle
|
||||
)
|
||||
{
|
||||
var expected = TypeScale.SizePtOf(role, basePt);
|
||||
using (handle.Push())
|
||||
{
|
||||
var actualPx = ImGui.GetFontSize();
|
||||
var expectedPx = FontManager.SizeInPx(expected) * ImGuiHelpers.GlobalScale;
|
||||
var agrees = MathF.Abs(actualPx - expectedPx) < 1.5f;
|
||||
ImGui.TextUnformatted(
|
||||
$"{role,-7} expected {expected:0.00}pt ({expectedPx:0.0}px) "
|
||||
+ $"| actual {actualPx:0.0}px {(agrees ? "OK" : "MISMATCH")}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The one thing arithmetic cannot show: whether the three faces sit on the
|
||||
// same baseline once they are next to each other.
|
||||
private static void DrawSampleRow(FontManager fm, string stamp, string sender, string body)
|
||||
{
|
||||
var scale = ImGuiHelpers.GlobalScale;
|
||||
var origin = ImGui.GetCursorScreenPos();
|
||||
|
||||
float bodyAscent;
|
||||
using (fm.RegularFont!.Push())
|
||||
bodyAscent = ImGui.GetFont().Ascent;
|
||||
|
||||
float metaAscent;
|
||||
using (fm.MetaFont!.Push())
|
||||
metaAscent = ImGui.GetFont().Ascent;
|
||||
|
||||
var metaDrop = BaselineMath.OffsetFor(bodyAscent, metaAscent, scale);
|
||||
|
||||
ImGui.SetCursorScreenPos(origin with { Y = origin.Y + metaDrop });
|
||||
using (fm.MetaFont.Push())
|
||||
ImGui.TextUnformatted(stamp);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() with { Y = origin.Y });
|
||||
using (fm.SenderFont!.Push())
|
||||
ImGui.TextUnformatted(sender);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() with { Y = origin.Y });
|
||||
using (fm.RegularFont.Push())
|
||||
ImGui.TextUnformatted(body);
|
||||
|
||||
ImGui.TextUnformatted($"(meta dropped {metaDrop:0.0}px onto the shared baseline)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user