fix(chargen): Campaign CC gate round 1 Batch E — text origin, caption escapes, value rects, scrollbars, name prefill
R2-1/R2-6 (description-box text clipped left of the frame, regressed from
Batch C's frame un-consume): root cause was never the un-consume change
itself — the Heritage/Profession/Town/Summary description boxes
(0x100003C4/0x100003E0/0x10000409/0x10000404) all author retail's four
independent text-inset margins (dat properties 0x23-0x26,
UIElement_Text::OnSetAttribute cases 0xf-0x12: margL=9/margR=26/margU=15/
margD=15), which this codebase never read at all, before or after Batch C.
Un-consuming the gold-frame children just made the pre-existing missing-
margin bug visible for the first time (the frame's own left border now
draws around the same x=0 origin text always used). Fixed end to end:
ElementInfo.MarginLeft/Right/Top/Bottom (read in
ApplyCanonicalLegacyProjection, propagated in Merge), UiText.MarginLeft/
Right/Top/Bottom (additive with the pre-existing Padding), a new pure
UiText.ContentOffsetX static consumed by the multi-line draw path's
per-line placement, and matching wrap-width shrinkage in
DatRichText.Compose and BuildText's own authored-multiline path. Scoped to
the multi-line (non-OneLine) path only.
R2-2/R2-3 (Attribute\n Credits renders the literal backslash-n; the live
credit value overlaps mid-caption): two stacked gaps. (1) UiButton
captions never escape-normalized the DAT's literal "\n" — centralized the
normalize into DatWidgetFactory's ResolveAuthoredString (the one choke
point every P0x17 resolution already shares) plus a NormalizeEscapes
helper for the per-state caption loop, so every caller normalizes
identically. (2) UiButton.Label only ever drew one line — retail's
UIElement_Button IS a UIElement_Text with OneLine=false on these buttons,
so a caption should word-wrap/stack like any other Type-12 box. Added
UiButton.DrawBlockLabel + the pure, unit-tested WrapBlockLines. The
value-overlap itself: ValueBox was never wrong (live-DAT-measured correct
child rects) — the caption was drawing unconfined across the button's
full width ("Available Skill Credits" measures 193px in a 231px button
whose value box starts at x=116). Fixed by confining the caption's own
drawable width to stop before ValueBox.X whenever a ValueLabel coexists.
R2-7a (Summary overview listbox missing its scrollbar): pure wiring gap —
the listbox authors a linked scrollbar via dat property 0x72
(ScrollbarElementId=0x10000401) that CharacterCreationSummaryPage's
constructor never resolved, unlike every other UiTemplateListBox owner in
the codebase. Fixed with the same resolve-and-wire pattern.
R2-7b (how-to box scrollbar overlaps text, no thumb): traced to a
downstream symptom of R2-1, not an independent bug — UiScrollbar only
paints its thumb when the linked model has overflow, and the pre-fix wrap
width (un-inset) produced fewer/shorter lines than fit the view. Pinned
directly against the real installed strings/font (Aluvian's how-to text)
that the margin-correct width overflows. No UiScrollbar code changed.
R2-8 (name field should show "[ Name ]"): re-checked the one hypothesis
Batch A's GF-15 closure left open — an authored initial-text string on
the field's own P0x17. Confirmed absent on every state in the installed
DAT. No code change; Batch A's closure stands, now pinned as a live-DAT
regression test.
App suite 5334/3 (was 5321/3, +13, zero regressions). Runtime 1735/0
unchanged. Full solution Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2ad805469d
commit
e24ec20882
11 changed files with 895 additions and 23 deletions
|
|
@ -730,6 +730,14 @@ public static class DatWidgetFactory
|
|||
// ElementInfo.Outline's own default, so this is a no-op for the ~99% of text
|
||||
// elements that don't author it.
|
||||
Outline = info.Outline,
|
||||
// R2-1 (Campaign CC gate round 1 Batch E): the four text-inset
|
||||
// margins (dat properties 0x23-0x26 — MarginLeft's own doc
|
||||
// comment on UiText). Default 0 — a no-op for every element that
|
||||
// doesn't author them (only consumed by the multi-line path).
|
||||
MarginLeft = info.MarginLeft,
|
||||
MarginRight = info.MarginRight,
|
||||
MarginTop = info.MarginTop,
|
||||
MarginBottom = info.MarginBottom,
|
||||
};
|
||||
t.ConfigureDatState(info);
|
||||
|
||||
|
|
@ -781,7 +789,12 @@ public static class DatWidgetFactory
|
|||
cachedWidth = t.Width;
|
||||
cachedFont = t.DatFont;
|
||||
cachedColor = t.DefaultColor;
|
||||
float maximumWidth = Math.Max(1f, t.Width - 2f * t.Padding);
|
||||
// R2-1: shrink by BOTH Padding and the four retail
|
||||
// margins — see DatRichText.Compose's own comment on
|
||||
// the same formula.
|
||||
float maximumWidth = Math.Max(
|
||||
1f,
|
||||
t.Width - (t.Padding + t.MarginLeft) - (t.Padding + t.MarginRight));
|
||||
Func<string, float> measure = t.DatFont is { } font
|
||||
? font.MeasureWidth
|
||||
: static value => value.Length * 8f;
|
||||
|
|
@ -810,7 +823,8 @@ public static class DatWidgetFactory
|
|||
|| !state.Properties.Values.TryGetValue(0x17u, out var stateCaption)
|
||||
|| stateCaption.Kind != UiPropertyKind.StringInfo)
|
||||
continue;
|
||||
if (stringResolve?.Invoke(stateCaption.StringInfoValue) is { Length: > 0 } text)
|
||||
if (NormalizeEscapes(stringResolve?.Invoke(stateCaption.StringInfoValue))
|
||||
is { Length: > 0 } text)
|
||||
(stateStrings ??= new Dictionary<uint, string>())[stateId] = text;
|
||||
}
|
||||
if (stateStrings is not null)
|
||||
|
|
@ -1028,6 +1042,32 @@ public static class DatWidgetFactory
|
|||
|| !info.TryGetEffectiveProperty(0x17u, out var property)
|
||||
|| property.Kind != UiPropertyKind.StringInfo)
|
||||
return null;
|
||||
return stringResolve(property.StringInfoValue);
|
||||
string? resolved = stringResolve(property.StringInfoValue);
|
||||
// R2-2 (Campaign CC gate round 1 Batch E): the DAT stores the LITERAL
|
||||
// two-character escape "\n" (0x5C 0x6E), not a real line break — same
|
||||
// fact BuildText's own authored-string path already normalized for
|
||||
// (see that call site's own comment). Centralizing the normalize
|
||||
// HERE, at the single choke point every P0x17 caption resolution in
|
||||
// this file goes through (BuildText, BuildButton's own caption AND
|
||||
// its lifted-child caption, BuildButton's coexisting ValueLabel,
|
||||
// BuildCheckbox), closes the exact class of bug R2-2 found: a caption
|
||||
// like the Profession credits button's own "Attribute\n Credits"
|
||||
// rendered the literal backslash-n because BuildButton never
|
||||
// normalized while BuildText did. BuildText's own subsequent
|
||||
// Replace("\\n","\n") is now a harmless no-op (idempotent) — left in
|
||||
// place rather than removed, since it costs nothing and documents the
|
||||
// same fact locally.
|
||||
return NormalizeEscapes(resolved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R2-2 (Campaign CC gate round 1 Batch E): the shared escape-normalize
|
||||
/// <see cref="ResolveAuthoredString"/> applies, pulled out so the
|
||||
/// per-STATE authored-caption loop below (which resolves a state's own
|
||||
/// <c>0x17</c> directly, bypassing the effective-property resolution
|
||||
/// <see cref="ResolveAuthoredString"/> wraps) gets the SAME normalize
|
||||
/// instead of a second, easily-forgotten copy.
|
||||
/// </summary>
|
||||
private static string? NormalizeEscapes(string? raw) =>
|
||||
raw?.Replace("\\n", "\n").Replace("\r", string.Empty);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue