fix(ui): FA3 fix-round — wire Friends/Squelch scrollbars, gate rebuild on visibility, fix revision-latch ordering
Campaign FA slice FA3 dual-lens review fix round. Blast MUST-FIX 1: SocialFriendsPageController/SocialSquelchPageController never wired their ListBox's own sibling scrollbar (Friends 0x10000517->0x10000518, Squelch 0x1000053E->0x10000543) — the two lists had NO scroll driver at all (no wheel fallback exists on UiScrollablePanel), making any roster past the visible extent completely unreachable, not just awkward to scroll. Both controllers now wire `scrollbar.Model = listBox.Scroll` scoped to their own page root, exactly like the four existing UiTemplateListBox consumers (Character/Chat/Config/KeyboardConfig). New tests prove the wiring AND that a >panel-height roster is actually reachable through it. Blast SHOULD-FIX 2/3: the Friends/Squelch resolver re-ran LayoutImporter.ImportInfos (a full DAT tree walk) under the shared DAT lock on EVERY row, every revision, even while the panel was closed — RetailUiRuntime.MountSocialPanel's TemplateResolver now caches each row template's ElementInfo the first time it is resolved and never re-Imports for that template id again. SocialPanelController additionally gates the Friends/Squelch Tick-driven rebuild on the panel's own visibility via the (previously unused) IRetainedPanelController OnShown/OnHidden hooks, so no DAT-locked rebuild work runs at all while the panel is closed. SocialFriendsPageController/SocialSquelchPageController also now only advance _lastRevision after every row resolves — a transient resolver miss no longer latches an empty roster until the next server-side change; it retries on the next Tick instead. Mechanism SHOULD-FIX 8: SocialPanelController.Tick() now returns early once disposed, matching every other J-slice teardown discipline (it was being ticked unconditionally forever since RetailUiRuntime never nulls the field). Mechanism SHOULD-FIX 1: IsShowingAllegiance's doc claimed the F3/F4 close-on-second-press semantics were "retail's Toggle-action semantics" — re-derived and confirmed NO retail OnAction consumer exists for either action anywhere in the binary. Relabeled as acdream's own OpenSpellbook-precedent convention; no register row added, following the same no-row precedent OpenSpellbook and every other non-toolbar Toggle panel already sets. Unknown filed as U11 in the panel-structure research doc's §8 table. Tests: 5 new (2 scrollbar-wiring pins, 2 long-roster-reachable-via- scrollbar, 1 hidden-panel-does-not-rebuild/shown-panel-catches-up); 2 existing tests updated for the new visibility gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e622f565e
commit
9afa05b563
5 changed files with 355 additions and 40 deletions
|
|
@ -9,8 +9,10 @@ namespace AcDream.App.UI.Layout;
|
|||
/// only, bound directly to the J4.1 <see cref="FriendsState"/> owner
|
||||
/// (<c>RuntimeCommunicationState.Friends</c>). Rebuilds on
|
||||
/// <see cref="FriendsState.Revision"/> change; polled every frame from
|
||||
/// <see cref="SocialPanelController.Tick"/> (cheap — a single
|
||||
/// <see langword="long"/> comparison when nothing changed).
|
||||
/// <see cref="SocialPanelController.Tick"/> WHILE THE PANEL IS VISIBLE
|
||||
/// (cheap — a single <see langword="long"/> comparison when nothing
|
||||
/// changed; see the scrollbar/rebuild-discipline paragraph below for the
|
||||
/// fix-round visibility gate).
|
||||
///
|
||||
/// <para>
|
||||
/// <b>D1 — inert actions.</b> This campaign implements no Friends
|
||||
|
|
@ -20,9 +22,10 @@ namespace AcDream.App.UI.Layout;
|
|||
/// "Appear Offline" checkbox (<c>0x1000052C</c>) are left AUTHORED and
|
||||
/// CLICKABLE with no handler — <see cref="DatWidgetFactory"/> already
|
||||
/// builds every button/checkbox with a null <c>OnClick</c> by default, so
|
||||
/// no explicit "leave unbound" code is needed here. See this commit's
|
||||
/// single register row covering the whole D1 Friends/Squelch inert-actions
|
||||
/// scope (one row, not one per button).
|
||||
/// no explicit "leave unbound" code is needed here. See register row
|
||||
/// AD-79 (<c>docs/architecture/retail-divergence-register.md</c>), which
|
||||
/// covers the whole D1 Friends/Squelch inert-actions scope (one row, not
|
||||
/// one per button).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
|
|
@ -31,11 +34,39 @@ namespace AcDream.App.UI.Layout;
|
|||
/// name-text leaf — see that class's doc for why the deepest match is used
|
||||
/// (gmFriendsUI is outside this campaign's decompiled scope).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Scrollbar (fix-round blast MF-1).</b> The ListBox <c>0x10000517</c>
|
||||
/// authors a direct sibling scrollbar, <c>0x10000518</c> — wired to
|
||||
/// <see cref="UiTemplateListBox.Scroll"/> the SAME way every other
|
||||
/// <c>UiTemplateListBox</c> consumer wires its own scrollbar
|
||||
/// (<c>CharacterOptionsPageController</c>/<c>ChatOptionsPageController</c>/
|
||||
/// <c>ConfigOptionsPageController</c>/<c>KeyboardConfigController</c>).
|
||||
/// Without it the list has NO wheel fallback (<see cref="UiScrollablePanel"/>
|
||||
/// has no wheel handler) and is completely unreachable past the box's
|
||||
/// visible extent.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Rebuild discipline (fix-round blast SF-2/SF-3).</b> <see cref="Refresh"/>
|
||||
/// only advances <see cref="_lastRevision"/> after a successful rebuild — a
|
||||
/// transient resolver miss (<see cref="UiTemplateListBox.AddItemFromTemplateList"/>
|
||||
/// returning null) leaves the revision UNCONSUMED so the next
|
||||
/// <see cref="Tick"/> retries instead of latching an empty list until the
|
||||
/// NEXT server-side change. <see cref="SocialPanelController"/> also gates
|
||||
/// this controller's <see cref="Tick"/> on the panel's own visibility (its
|
||||
/// <c>IRetainedPanelController.OnShown</c>/<c>OnHidden</c> hooks) — the
|
||||
/// per-row template resolve takes the shared DAT lock, so no rebuild work
|
||||
/// runs while the panel is closed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class SocialFriendsPageController
|
||||
{
|
||||
private const uint ListBoxId = 0x10000517u;
|
||||
|
||||
/// <summary>The ListBox's own linked scrollbar (see class doc).</summary>
|
||||
private const uint ScrollbarElementId = 0x10000518u;
|
||||
|
||||
private readonly UiTemplateListBox _listBox;
|
||||
private readonly FriendsState _friends;
|
||||
private long _lastRevision = long.MinValue;
|
||||
|
|
@ -64,6 +95,13 @@ public sealed class SocialFriendsPageController
|
|||
}
|
||||
listBox.TemplateResolver = templateResolver;
|
||||
|
||||
if (UiElement.FindDescendant(pageRoot, ScrollbarElementId) is UiScrollbar scrollbar)
|
||||
scrollbar.Model = listBox.Scroll;
|
||||
else
|
||||
Console.WriteLine(
|
||||
$"[D.2b] SocialFriendsPageController: scrollbar 0x{ScrollbarElementId:X8} "
|
||||
+ "not found — the Friends list will not scroll.");
|
||||
|
||||
var controller = new SocialFriendsPageController(listBox, friends);
|
||||
controller.Refresh();
|
||||
return controller;
|
||||
|
|
@ -78,17 +116,21 @@ public sealed class SocialFriendsPageController
|
|||
|
||||
private void Refresh()
|
||||
{
|
||||
_lastRevision = _friends.Revision;
|
||||
long revision = _friends.Revision;
|
||||
_listBox.Flush();
|
||||
bool allRowsResolved = true;
|
||||
foreach (FriendEntry friend in _friends.Snapshot())
|
||||
{
|
||||
UiElement? row = _listBox.AddItemFromTemplateList(0);
|
||||
if (row is null) continue;
|
||||
if (row is null) { allRowsResolved = false; continue; }
|
||||
if (SocialPanelRowText.FindDeepest(row) is { } text)
|
||||
{
|
||||
string name = friend.Name;
|
||||
text.LinesProvider = () => [new UiText.Line(name, Vector4.One)];
|
||||
}
|
||||
}
|
||||
// SF-3: only latch the revision once the rebuild actually reflects it —
|
||||
// a resolver miss must not silently swallow a future revision bump.
|
||||
if (allRowsResolved) _lastRevision = revision;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue