fix(studio): Character window — show only the active tab page (clears the dark overlay)

LayoutDesc 0x2100002E is a tabbed window; all three pages (Attributes/Skills/Titles) mount
their content as overlapping root children. acdream drew all three, so the inactive pages'
backdrops painted over the active content — the dim "almost opaque" cover the user reported,
and it displaced the XP meter. The shared gmStatManagement ids are duplicated across pages and
_byId keeps the LAST-mounted copy, so every bound widget lives in exactly one page; the
controller now keeps that page visible (ContainsWidget reference-walk from the bound name
element) and hides the rest. Header/attrs/XP-bar now render clean + correctly placed.
Tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-25 20:00:09 +02:00
parent 0d87e4dc31
commit 27819514af

View file

@ -76,6 +76,32 @@ public static class CharacterStatController
rows.LinesProvider = () => AttributeRows(data());
list.AddChild(rows);
}
// ── Active-page selection (fixes the "dark overlay") ──────────────────
// 0x2100002E is a tabbed window: all three tab pages (Attributes / Skills / Titles) mount
// their content as separate root children that OVERLAP in the same content rect. Retail
// shows only the active page; acdream draws them all, so the inactive pages' backdrops paint
// over the active content (the dim "almost opaque" cover). The shared gmStatManagement ids
// are duplicated across pages and acdream's _byId keeps the LAST-mounted copy, so every widget
// we just bound lives in exactly ONE page. Keep that page visible; hide the rest.
if (layout.FindElement(NameId) is { } anchor && layout.Root is { } root)
{
foreach (var page in root.Children)
{
if (page.Children.Count == 0) continue; // tab-bar buttons are leaves — leave them
page.Visible = ContainsWidget(page, anchor); // only the bound page stays visible
}
}
}
/// <summary>Depth-first reference search: is <paramref name="target"/> somewhere under
/// <paramref name="node"/>? Used to find which overlapping tab page owns the bound widgets.</summary>
private static bool ContainsWidget(UiElement node, UiElement target)
{
if (ReferenceEquals(node, target)) return true;
foreach (var c in node.Children)
if (ContainsWidget(c, target)) return true;
return false;
}
private const float LineHeight = 18f;