feat(ui): FA3 -- mount the social panel shell (Friends/Allegiance/Fellowship/Squelch)
Campaign FA slice FA3: retail's four-tab social panel (LayoutDesc
0x2100006E slot 0x1000018F, RetailPanelCatalog id 12), built on the OP3
OptionsPanelController recipe -- Type-8 tab host, ActivateTabBehavior,
per-page scoped controllers.
- SocialPanelController mounts the tab host and wires the close button;
F3 (ToggleAllegiancePanel) and F4 (ToggleFellowshipPanel) open the
panel and switch to their own tab, sharing the same gmPanelUI
one-active-panel exclusivity every sibling main panel already has.
- The live-DAT tab table CORRECTS the coordinator addendum's x-order
guess: button 0x1000028C ("Allegiance") pairs with page 0x10000291 and
is the authored DEFAULT entry, not Friends -- each button's own page
id and its own P0x57 (matching the F3/F4 ActionMap ids on the
Allegiance/Fellowship pages specifically) both corroborate the real
pairing. See SocialPanelController's class doc for the full table.
- SocialFellowshipPageController swaps the two authored empty/full
frames (0x1000026B/0x10000275) on RuntimeFellowshipState's
IsInFellowship -- both frames' full containment (name box, create
button, checkboxes vs. roster list, six buttons) was confirmed by the
live-mount probe, so a single Visible toggle per frame is the whole
swap (closes lane-A unknown U6).
- SocialAllegiancePageController hides the monarch/patron blocks and
blanks their name text to a literal space when
RuntimeAllegianceState.Snapshot.HasProfile is false, using SCOPED
FindDescendant lookups (the panel authors 0x10000492 twice, once per
block).
- SocialFriendsPageController/SocialSquelchPageController bind their
ListBoxes read-only to RuntimeCommunicationState's existing J4.1
Friends/Squelch owners (names only), rebuilding on revision change.
Their action buttons are honest INERT (D1) -- register row AD-79.
- UiTemplateListBox gains Flush() (lane A/D's "Gap found" prerequisite)
so a poll-and-rebuild list can shrink between refreshes.
- RetailPanelCatalog.SocialPanel = 12, byte-verified from the live slot's
own P0x10000029; listed in Mounted only (no toolbar button -- lane A
§6.1: the open path is keyboard-only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b560f415cd
commit
0a9ca2f1f9
12 changed files with 854 additions and 1 deletions
94
src/AcDream.App/UI/Layout/SocialFriendsPageController.cs
Normal file
94
src/AcDream.App/UI/Layout/SocialFriendsPageController.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Social;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FA slice FA3, D1: the Friends page's read-only roster — names
|
||||
/// 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).
|
||||
///
|
||||
/// <para>
|
||||
/// <b>D1 — inert actions.</b> This campaign implements no Friends
|
||||
/// add/remove/appear-offline wire (the trivially-pinnable bar D1 sets was
|
||||
/// not met for this family — see the plan's D1 decision). The three
|
||||
/// buttons (<c>0x10000514</c>/<c>0x10000515</c>/<c>0x10000516</c>) and the
|
||||
/// "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).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Row template <c>0x2100005D</c>/<c>0x10000519</c> (FA3 live-mount probe);
|
||||
/// <see cref="SocialPanelRowText.FindDeepest"/> resolves the row's own
|
||||
/// name-text leaf — see that class's doc for why the deepest match is used
|
||||
/// (gmFriendsUI is outside this campaign's decompiled scope).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class SocialFriendsPageController
|
||||
{
|
||||
private const uint ListBoxId = 0x10000517u;
|
||||
|
||||
private readonly UiTemplateListBox _listBox;
|
||||
private readonly FriendsState _friends;
|
||||
private long _lastRevision = long.MinValue;
|
||||
|
||||
private SocialFriendsPageController(UiTemplateListBox listBox, FriendsState friends)
|
||||
{
|
||||
_listBox = listBox;
|
||||
_friends = friends;
|
||||
}
|
||||
|
||||
public static SocialFriendsPageController? Bind(
|
||||
UiElement pageRoot,
|
||||
FriendsState friends,
|
||||
Func<uint, uint, UiElement?> templateResolver)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pageRoot);
|
||||
ArgumentNullException.ThrowIfNull(friends);
|
||||
ArgumentNullException.ThrowIfNull(templateResolver);
|
||||
|
||||
if (UiElement.FindDescendant(pageRoot, ListBoxId) is not UiTemplateListBox listBox)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[D.2b] SocialFriendsPageController: ListBox 0x{ListBoxId:X8} not "
|
||||
+ "found — Friends page will not populate.");
|
||||
return null;
|
||||
}
|
||||
listBox.TemplateResolver = templateResolver;
|
||||
|
||||
var controller = new SocialFriendsPageController(listBox, friends);
|
||||
controller.Refresh();
|
||||
return controller;
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
long revision = _friends.Revision;
|
||||
if (revision == _lastRevision) return;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
_lastRevision = _friends.Revision;
|
||||
_listBox.Flush();
|
||||
foreach (FriendEntry friend in _friends.Snapshot())
|
||||
{
|
||||
UiElement? row = _listBox.AddItemFromTemplateList(0);
|
||||
if (row is null) continue;
|
||||
if (SocialPanelRowText.FindDeepest(row) is { } text)
|
||||
{
|
||||
string name = friend.Name;
|
||||
text.LinesProvider = () => [new UiText.Line(name, Vector4.One)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue