fix(chargen): Campaign CC gate round 1 re-test 2 — R3-1/R3-2 caption wrap
Batch E's UiButton.DrawBlockLabel/WrapBlockLines auto-wrapped any caption that didn't fit its box width — decomp-wrong. UIElement_Text:: CalcJustification @0x00467260 (shared by GlyphList::Recalculate's horizontal/vertical branches) shows retail's real per-glyph break decision (both the width-triggered wrap AND the explicit-newline break) sits behind ONE gate keyed on the OneLine flag; nothing in the decomp confines a caption's wrap width to a sibling element's rect (Batch E's own ValueBox confinement for the coexisting-value-label shape). Live-DAT evidence: the Coordination attribute-slider label (0x100002ed) authors OneLine=true (should never wrap); the Skills credits button's "Available Skill Credits" caption measures 193px against its own full 231px button width (fits comfortably) — the 113px confined width Batch E fed the wrap decision was never a real retail quantity. Fixed: WrapBlockLines now splits ONLY on the explicit (already-normalized) '\n' — never width-based. Strict superset of the pre-Batch-E single-line draw for every already-correct caption; "Attribute\n Credits" still works. The ValueBox confinement computation stays in OnDraw (still feeds the Center-alignment tx formula) but no longer gates the wrap decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
956b8d5b6b
commit
7d6a7898f6
2 changed files with 189 additions and 72 deletions
|
|
@ -166,6 +166,37 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector4 Tint { get; set; } = Vector4.One;
|
public Vector4 Tint { get; set; } = Vector4.One;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// R3-5 (Campaign CC gate round 1 re-test 2): optional resolver
|
||||||
|
/// returning a PRE-BAKED, already color-key-recolored texture handle
|
||||||
|
/// (from <see cref="AcDream.App.Rendering.TextureCache.UploadRgba8"/>
|
||||||
|
/// or equivalent), drawn UNTINTED (1:1, no UV repeat) INSTEAD of the
|
||||||
|
/// ordinary <see cref="FaceFileOverride"/>/<c>ActiveFile</c> sprite.
|
||||||
|
/// Retail's own <c>gmCGAppearancePage::DoColorSpots @0x0047d850</c>
|
||||||
|
/// does NOT multiply-tint the swatch's authored ring+spot sprite (a
|
||||||
|
/// multiply of a target color against BLACK — the spot template's own
|
||||||
|
/// placeholder fill, live-DAT-pixel-confirmed — stays black regardless
|
||||||
|
/// of the tint, and multiplying the ring's own non-black border pixels
|
||||||
|
/// shifts their hue/brightness, corrupting them). Retail instead calls
|
||||||
|
/// <c>SurfaceWindow::ReplaceColor</c>: build a fresh composited surface
|
||||||
|
/// once, blit the spot template onto it, then swap every EXACT-black
|
||||||
|
/// pixel for the swatch's real color — the ring border (never black)
|
||||||
|
/// is untouched. This property is that same mechanism's C# seam.
|
||||||
|
/// <see cref="Tint"/> itself is left completely unchanged in meaning
|
||||||
|
/// and is STILL the value callers set to communicate "this button's
|
||||||
|
/// color is X" (existing callers/tests that only read
|
||||||
|
/// <see cref="Tint"/> are unaffected) — this resolver is a SEPARATE
|
||||||
|
/// decision (deliberately not fed by <see cref="Tint"/>: a caller may
|
||||||
|
/// need to distinguish more states — e.g. "beyond count, show the
|
||||||
|
/// blocked art" versus "no color data yet, show nothing" — than one
|
||||||
|
/// Vector4 can encode) that only changes what OnDraw does when
|
||||||
|
/// non-null: consult it for a texture instead of directly multiplying
|
||||||
|
/// the authored sprite. Null (default, every pre-existing button)
|
||||||
|
/// preserves the exact prior FaceFileOverride/ActiveFile +
|
||||||
|
/// multiply-Tint draw.
|
||||||
|
/// </summary>
|
||||||
|
public Func<uint>? ColorKeyFaceResolver { get; set; }
|
||||||
|
|
||||||
/// <summary>Additional left inset for left-aligned labels.</summary>
|
/// <summary>Additional left inset for left-aligned labels.</summary>
|
||||||
public float LabelOffsetX { get; set; } = 3f;
|
public float LabelOffsetX { get; set; } = 3f;
|
||||||
|
|
||||||
|
|
@ -469,6 +500,22 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
foreach (FaceSegment segment in _faceSegments)
|
foreach (FaceSegment segment in _faceSegments)
|
||||||
DrawFace(ctx, ActiveFile(segment.Info), segment.Rect(Width, Height));
|
DrawFace(ctx, ActiveFile(segment.Info), segment.Rect(Width, Height));
|
||||||
}
|
}
|
||||||
|
else if (ColorKeyFaceResolver is { } colorKeyResolver)
|
||||||
|
{
|
||||||
|
// R3-5: a pre-baked, already-recolored texture (see this
|
||||||
|
// property's own doc) — drawn UNTINTED and 1:1 (no UV repeat;
|
||||||
|
// the baked bitmap is uploaded at its own native size, which
|
||||||
|
// for the chargen swatches equals the button's own authored
|
||||||
|
// rect, live-DAT-measured).
|
||||||
|
uint bakedTexture = colorKeyResolver();
|
||||||
|
if (bakedTexture != 0)
|
||||||
|
{
|
||||||
|
float faceWidth = FaceWidth > 0f ? FaceWidth : Width;
|
||||||
|
float faceHeight = FaceHeight > 0f ? FaceHeight : Height;
|
||||||
|
ctx.DrawSprite(bakedTexture, FaceLeft, FaceTop, faceWidth, faceHeight,
|
||||||
|
0f, 0f, 1f, 1f, Vector4.One);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uint file = FaceFileOverride ?? ActiveFile(_mediaInfo);
|
uint file = FaceFileOverride ?? ActiveFile(_mediaInfo);
|
||||||
|
|
@ -497,18 +544,18 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
float boxWidth = LabelBox?.Width ?? Width;
|
float boxWidth = LabelBox?.Width ?? Width;
|
||||||
float boxHeight = LabelBox?.Height ?? Height;
|
float boxHeight = LabelBox?.Height ?? Height;
|
||||||
|
|
||||||
// R2-2/R2-3 (Campaign CC gate round 1 Batch E): when this button
|
// R2-2/R2-3 (Campaign CC gate round 1 Batch E) + R3-2 correction
|
||||||
// ALSO carries a coexisting ValueLabel (GF-4a's own-caption +
|
// (re-test 2): when this button ALSO carries a coexisting
|
||||||
// separate value slot — the Profession attribute/health/stamina/
|
// ValueLabel (GF-4a's own-caption + separate value slot — the
|
||||||
// mana credits buttons, the Skills credits button), the caption's
|
// Profession attribute/health/stamina/mana credits buttons, the
|
||||||
// own drawable region stops before the value's authored rect
|
// Skills credits button), boxWidth still narrows to stop before
|
||||||
// starts. LabelBox and ValueBox are mutually exclusive by
|
// the value's authored rect for the (currently unused, since
|
||||||
// construction (DatWidgetFactory.BuildButton only ever sets one
|
// every known ValueBox button is Left-aligned) Center-tx
|
||||||
// or the other), so this never fights GF-11c's own LabelBox
|
// formula and the explicit-newline clip rect below — see
|
||||||
// confinement above. Live-DAT-measured: "Available Skill Credits"
|
// DrawBlockLabel's own doc for why this no longer gates
|
||||||
// is 193px wide in the Skills credits button's 231px-wide box
|
// WHETHER a single-line caption wraps or clips (R3-2: it never
|
||||||
// whose value box starts at local x=116 — without this, the live
|
// did in retail — live-DAT-measured, "Available Skill Credits"
|
||||||
// credits number draws on top of the caption's own tail.
|
// fits the button's own full 231px width with room to spare).
|
||||||
if (ValueBox is { X: var valueBoxX } && valueBoxX > boxX)
|
if (ValueBox is { X: var valueBoxX } && valueBoxX > boxX)
|
||||||
boxWidth = MathF.Min(boxWidth, valueBoxX - boxX);
|
boxWidth = MathF.Min(boxWidth, valueBoxX - boxX);
|
||||||
|
|
||||||
|
|
@ -543,20 +590,58 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// R2-2 (Campaign CC gate round 1 Batch E): retail's <c>UIElement_Button</c>
|
/// R2-2 (Campaign CC gate round 1 Batch E) + R3-1/R3-2 (re-test 2
|
||||||
/// IS a <c>UIElement_Text</c> (<c>struct UIElement_Button : UIElement_Text</c>,
|
/// correction): retail's <c>UIElement_Button</c> IS a
|
||||||
/// <c>acclient.h</c>) — these captions author <c>OneLine=false</c>
|
/// <c>UIElement_Text</c> (<c>struct UIElement_Button : UIElement_Text</c>,
|
||||||
/// (live-DAT-probe-confirmed on 0x100003e2-e5/0x100003f9), so a caption
|
/// <c>acclient.h</c>) — a caption that carries an authored newline
|
||||||
/// that carries an authored newline (already normalized to a real
|
/// (already normalized to a real <c>'\n'</c> by
|
||||||
/// <c>'\n'</c> by <see cref="Layout.DatWidgetFactory"/>'s shared
|
/// <see cref="Layout.DatWidgetFactory"/>'s shared
|
||||||
/// <c>ResolveAuthoredString</c>) OR simply doesn't fit
|
/// <c>ResolveAuthoredString</c>) lays out as multiple stacked lines. A
|
||||||
/// <paramref name="boxWidth"/> lays out as multiple stacked lines, using
|
/// single line that already fits draws with byte-identical geometry to
|
||||||
/// the SAME word-wrap <see cref="UiText.WrapWords"/> any other Type-12
|
/// the pre-Batch-E unconditional one-line math (same centered-block Y,
|
||||||
/// text box uses. A single line that already fits draws with byte-
|
/// same tx formula).
|
||||||
/// identical geometry to the pre-fix unconditional one-line math (same
|
/// <para>
|
||||||
/// centered-block Y, same tx formula) — this is a strict superset, not a
|
/// Batch E ALSO auto-wrapped a paragraph that doesn't fit
|
||||||
/// behavior change, for every button whose caption was already short
|
/// <paramref name="boxWidth"/> via <see cref="UiText.WrapWords"/> — re-
|
||||||
/// enough to fit on one line.
|
/// derived at re-test 2 (R3-1 "Coordination"/R3-2 "Available Skill
|
||||||
|
/// Credits") as the wrong shape and REMOVED: live-DAT-probed, the
|
||||||
|
/// Coordination slider label (<c>0x100002ed</c>) authors <c>OneLine=
|
||||||
|
/// true</c> (dat property <c>0x20</c>) and the Skills credits button
|
||||||
|
/// (<c>0x100003f9</c>) authors <c>OneLine=false</c> yet BOTH render one
|
||||||
|
/// line in retail. Tracing <c>GlyphList::Recalculate
|
||||||
|
/// @0x00473800</c>'s per-glyph loop: the ENTIRE width-triggered break
|
||||||
|
/// decision (and, separately, the explicit-newline break) sits behind
|
||||||
|
/// one gate, <c>if (arg3 == 0)</c> where <c>arg3</c> is the SAME
|
||||||
|
/// <c>OneLine</c> boolean passed in from
|
||||||
|
/// <c>UIElement_Text::ResizeToPaper</c>/<c>InqSize</c> — i.e. a
|
||||||
|
/// caption's width is measured against its own FULL element rect (minus
|
||||||
|
/// margins), never against a sibling/child element's geometry; nothing
|
||||||
|
/// in the decomp confines a caption's wrap width to stop before another
|
||||||
|
/// element's rect. The 193px "Available Skill Credits" caption fits the
|
||||||
|
/// button's own full 231px width (live-DAT-measured) with room to
|
||||||
|
/// spare — it never needed to wrap at all. So: split ONLY on the
|
||||||
|
/// explicit <c>\n</c> (never invoke <see cref="UiText.WrapWords"/>) — a
|
||||||
|
/// strict superset of the pre-Batch-E single-line draw for every
|
||||||
|
/// caption that was already correct, and the exact shape "Attribute\n
|
||||||
|
/// Credits" (an authored break) still needs.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// R3-2 deliberately does NOT clip a single (unwrapped) line to
|
||||||
|
/// <paramref name="boxWidth"/> either, even when the caller narrowed it
|
||||||
|
/// via a coexisting <see cref="ValueBox"/> — clipping would cut the
|
||||||
|
/// caption's own tail off mid-word, which contradicts "retail is ONE
|
||||||
|
/// line" just as much as wrapping does (a viewer would call that
|
||||||
|
/// truncated, not "one line"). The 193px-in-231px Skills-credits
|
||||||
|
/// geometry means the caption's rendered span (x≈3 to x≈196) does
|
||||||
|
/// overlap the value's own rect (x=116 to x=150, live-DAT-measured) in
|
||||||
|
/// principle — Batch E's own diagnosis of the ORIGINAL R2-2/R2-3
|
||||||
|
/// "24dits"/"Credit0Credits" reports. That overlap is NOT re-solved
|
||||||
|
/// here: this fix only removes the false wrap this specific finding
|
||||||
|
/// (R3-2) reported, and inventing an unevidenced clip boundary to
|
||||||
|
/// pre-empt a DIFFERENT, not-currently-reported symptom would be
|
||||||
|
/// exactly the guessing this project's workflow forbids. Flagged in
|
||||||
|
/// the findings doc for the user's own re-check once the wrap is gone.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void DrawBlockLabel(
|
private void DrawBlockLabel(
|
||||||
UiRenderContext ctx,
|
UiRenderContext ctx,
|
||||||
|
|
@ -574,13 +659,16 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
text, font.MeasureWidth, font.LineHeight,
|
text, font.MeasureWidth, font.LineHeight,
|
||||||
boxX, boxY, boxWidth, boxHeight, align, leftOffset);
|
boxX, boxY, boxWidth, boxHeight, align, leftOffset);
|
||||||
|
|
||||||
// A multi-line result clips to its own box — the button's normal
|
// A multi-line result (an authored '\n') clips to its own box — the
|
||||||
// draw has no ambient clip, and an oversized wrapped caption (e.g.
|
// button's normal draw has no ambient clip, and an oversized
|
||||||
// the Skills credits button's own tight 28px height) should be cut
|
// wrapped caption (e.g. the Skills credits button's own tight 28px
|
||||||
// off at the box edge rather than spill into whatever sits below the
|
// height) should be cut off at the box edge rather than spill into
|
||||||
// button, matching every other clipped Type-12 text box in this
|
// whatever sits below the button, matching every other clipped
|
||||||
// codebase (UiText.DrawText's own PushClip). Single-line captions —
|
// Type-12 text box in this codebase (UiText.DrawText's own
|
||||||
// the overwhelming majority — never pay this cost.
|
// PushClip). Single-line captions — the overwhelming majority,
|
||||||
|
// and (post-R3-2) EVERY caption with no authored newline — never
|
||||||
|
// pay this cost; see this method's own doc for why a single line
|
||||||
|
// is deliberately left unclipped even when boxWidth was narrowed.
|
||||||
bool clip = lines.Count > 1;
|
bool clip = lines.Count > 1;
|
||||||
if (clip)
|
if (clip)
|
||||||
ctx.PushClip(boxX, boxY, boxWidth, boxHeight);
|
ctx.PushClip(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|
@ -597,14 +685,24 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pure geometry half of <see cref="DrawBlockLabel"/> — normalized
|
/// Pure geometry half of <see cref="DrawBlockLabel"/> — split ONLY on an
|
||||||
/// newline split + word-wrap (<see cref="UiText.WrapWords"/>) to
|
/// authored explicit <c>'\n'</c>, then block-centered vertically within
|
||||||
/// <paramref name="boxWidth"/>, then block-centered vertically within
|
|
||||||
/// <paramref name="boxHeight"/>. Pulled out as a static/pure method
|
/// <paramref name="boxHeight"/>. Pulled out as a static/pure method
|
||||||
/// (same shape as <see cref="UiText.ContentOffsetX"/>) so the wrap/
|
/// (same shape as <see cref="UiText.ContentOffsetX"/>) so the geometry
|
||||||
/// confinement math is unit-testable without a font atlas or draw
|
/// is unit-testable without a font atlas or draw context —
|
||||||
/// context — <paramref name="measureWidth"/> takes the place of
|
/// <paramref name="measureWidth"/> takes the place of
|
||||||
/// <see cref="UiDatFont.MeasureWidth(string)"/>.
|
/// <see cref="UiDatFont.MeasureWidth(string)"/>.
|
||||||
|
/// <para>
|
||||||
|
/// R3-1/R3-2 (re-test 2): deliberately does NOT width-wrap a paragraph
|
||||||
|
/// that overflows <paramref name="boxWidth"/> — see
|
||||||
|
/// <see cref="DrawBlockLabel"/>'s own doc for the decomp citation
|
||||||
|
/// (<c>GlyphList::Recalculate</c>'s width-triggered break sits behind
|
||||||
|
/// the SAME <c>OneLine</c> gate as the explicit-newline break, and
|
||||||
|
/// retail never confines a caption's wrap width to a sibling element's
|
||||||
|
/// rect). A paragraph that overflows still draws as one line, unclipped
|
||||||
|
/// by width — matching every plain (no authored <c>\n</c>) button
|
||||||
|
/// caption in retail, which is never observed to wrap.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IReadOnlyList<(string Text, float X, float Y)> WrapBlockLines(
|
internal static IReadOnlyList<(string Text, float X, float Y)> WrapBlockLines(
|
||||||
string text,
|
string text,
|
||||||
|
|
@ -617,26 +715,13 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
LabelAlignment align,
|
LabelAlignment align,
|
||||||
float leftOffset)
|
float leftOffset)
|
||||||
{
|
{
|
||||||
float availableWidth = MathF.Max(
|
string[] lines = text.Split('\n');
|
||||||
1f,
|
|
||||||
boxWidth - (align == LabelAlignment.Left ? leftOffset : 0f));
|
|
||||||
|
|
||||||
var lines = new List<string>();
|
float totalHeight = lines.Length * lineHeight;
|
||||||
foreach (string paragraph in text.Split('\n'))
|
|
||||||
{
|
|
||||||
if (measureWidth(paragraph) <= availableWidth)
|
|
||||||
{
|
|
||||||
lines.Add(paragraph);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lines.AddRange(UiText.WrapWords(paragraph, measureWidth, availableWidth));
|
|
||||||
}
|
|
||||||
|
|
||||||
float totalHeight = lines.Count * lineHeight;
|
|
||||||
float startY = boxY + (boxHeight - totalHeight) * 0.5f;
|
float startY = boxY + (boxHeight - totalHeight) * 0.5f;
|
||||||
|
|
||||||
var result = new List<(string, float, float)>(lines.Count);
|
var result = new List<(string, float, float)>(lines.Length);
|
||||||
for (int i = 0; i < lines.Count; i++)
|
for (int i = 0; i < lines.Length; i++)
|
||||||
{
|
{
|
||||||
string line = lines[i];
|
string line = lines[i];
|
||||||
float tx = align == LabelAlignment.Left
|
float tx = align == LabelAlignment.Left
|
||||||
|
|
|
||||||
|
|
@ -461,33 +461,65 @@ public class UiButtonTests
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// R2-3: a single-paragraph caption with NO authored newline still
|
/// R3-2 (re-test 2 correction, supersedes the retired Batch E
|
||||||
/// word-wraps when it doesn't fit the available width — the exact
|
/// "WordWrapsToFitAvailableWidth" expectation): a single-paragraph
|
||||||
/// live-DAT shape of the Skills credits button's own "Available Skill
|
/// caption with NO authored newline stays ONE line even when it
|
||||||
/// Credits" caption (measured 193px in a 231px-wide button whose value
|
/// overflows the available width — the exact live-DAT shape of the
|
||||||
/// box starts at local x=116, i.e. only ~113px of caption width is
|
/// Skills credits button's own "Available Skill Credits" caption
|
||||||
/// actually available once R2-2/R2-3's confinement applies).
|
/// (measured 193px, live-DAT-probed against the button's own FULL
|
||||||
|
/// 231px width, which it fits comfortably — the 113px figure in the
|
||||||
|
/// old test was the WRONG width in the first place, since retail never
|
||||||
|
/// confines a caption's wrap width to a sibling value element's rect;
|
||||||
|
/// see <see cref="UiButton.DrawBlockLabel"/>'s own doc for the
|
||||||
|
/// GlyphList::Recalculate citation). Even forced into an artificially
|
||||||
|
/// narrow box (as here), the caption must NOT wrap — retail's
|
||||||
|
/// UIElement_Button captions only ever split on an authored <c>\n</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WrapBlockLines_LongSingleParagraph_WordWrapsToFitAvailableWidth()
|
public void WrapBlockLines_LongSingleParagraph_NeverWordWraps()
|
||||||
{
|
{
|
||||||
var lines = UiButton.WrapBlockLines(
|
var lines = UiButton.WrapBlockLines(
|
||||||
"Available Skill Credits", BitmapMeasure, lineHeight: 24f,
|
"Available Skill Credits", BitmapMeasure, lineHeight: 24f,
|
||||||
boxX: 0f, boxY: 0f, boxWidth: 113f, boxHeight: 28f,
|
boxX: 0f, boxY: 0f, boxWidth: 113f, boxHeight: 28f,
|
||||||
UiButton.LabelAlignment.Left, leftOffset: 3f);
|
UiButton.LabelAlignment.Left, leftOffset: 3f);
|
||||||
|
|
||||||
Assert.True(lines.Count > 1, "a 193px caption must wrap within a 110px available width");
|
Assert.Single(lines);
|
||||||
foreach (var line in lines)
|
Assert.Equal("Available Skill Credits", lines[0].Text);
|
||||||
Assert.True(BitmapMeasure(line.Text) <= 110f, $"line '{line.Text}' overflowed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// R2-2/R2-3 confinement itself, exercised through OnDraw's own gate:
|
/// R3-1 (re-test 2): the Profession/chargen attribute slider name label
|
||||||
/// a button with BOTH Label and a coexisting ValueBox shrinks the
|
/// (element <c>0x100002ed</c>, e.g. "Coordination") authors <c>OneLine=
|
||||||
/// caption's OWN drawable width to stop before the value box starts —
|
/// true</c> and a 115px-wide box — live-DAT-measured against the real
|
||||||
/// this is what the two live-DAT overlap reports (R2-2 "24dits", R2-3
|
/// dat font, the caption itself is 113px wide, just 1px narrower than
|
||||||
/// "Credit0Credits") trace to: the caption used to draw across the
|
/// the raw box but 1px WIDER than the box minus the class's own default
|
||||||
/// WHOLE button width regardless of where the value sat.
|
/// 3px <c>LabelOffsetX</c> (112px) — exactly the boundary the retired
|
||||||
|
/// Batch E width-check would have tripped on, wrapping a single WORD
|
||||||
|
/// (no space to break at) into a garbled two-line split. Pins that this
|
||||||
|
/// no longer happens for any box/text combination, narrow or not.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void WrapBlockLines_SingleWordNarrowerThanBoxButWiderThanOffsetAdjustedWidth_StaysOneLine()
|
||||||
|
{
|
||||||
|
var lines = UiButton.WrapBlockLines(
|
||||||
|
"Coordination", BitmapMeasure, lineHeight: 24f,
|
||||||
|
boxX: 0f, boxY: 0f, boxWidth: 115f, boxHeight: 24f,
|
||||||
|
UiButton.LabelAlignment.Left, leftOffset: 3f);
|
||||||
|
|
||||||
|
Assert.Single(lines);
|
||||||
|
Assert.Equal("Coordination", lines[0].Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ValueBox-vs-Label confinement math itself, exercised through
|
||||||
|
/// OnDraw's own computation: a button with BOTH Label and a coexisting
|
||||||
|
/// ValueBox still shrinks the caption's OWN boxWidth to stop before the
|
||||||
|
/// value box starts. As of R3-2 (re-test 2) this confined width no
|
||||||
|
/// longer changes whether or how the caption draws — a single
|
||||||
|
/// (unwrapped) line is never clipped to it (see
|
||||||
|
/// <see cref="UiButton.DrawBlockLabel"/>'s own doc) — so this test only
|
||||||
|
/// pins that the computation itself is unchanged, not that it gates
|
||||||
|
/// any rendering decision.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BuildButton_OwnCaptionWithCoexistingValueBox_ConfinesLabelWidthBeforeValueBox()
|
public void BuildButton_OwnCaptionWithCoexistingValueBox_ConfinesLabelWidthBeforeValueBox()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue