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
|
|
@ -216,6 +216,22 @@ public sealed record OptionsRuntimeBindings(
|
|||
Func<AudioSettings> LoadAudio,
|
||||
Action<AudioSettings> SaveAudio);
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FA slice FA3: the social panel's (Friends/Allegiance/
|
||||
/// Fellowship/Squelch) own bindings — the FA2 owners' typed snapshot
|
||||
/// readers plus the two J4.1 social-list owners, direct instances (not the
|
||||
/// <see cref="AcDream.Runtime.IRuntimeSocialView"/> view seam bots use — that
|
||||
/// view has no full-collection enumeration, only per-id lookup + counts;
|
||||
/// the panel's read-only rows need the actual entries, same access shape
|
||||
/// <see cref="AcDream.App.Composition.InteractionRetainedUiDependencies.Communication"/>
|
||||
/// already exposes for every other Communication-owned consumer).
|
||||
/// </summary>
|
||||
public sealed record SocialRuntimeBindings(
|
||||
Func<AcDream.Runtime.RuntimeFellowshipSnapshot> FellowshipSnapshot,
|
||||
Func<AcDream.Runtime.RuntimeAllegianceSnapshot> AllegianceSnapshot,
|
||||
AcDream.Core.Social.FriendsState Friends,
|
||||
AcDream.Core.Social.SquelchState Squelch);
|
||||
|
||||
public sealed record InventoryRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
Func<uint> PlayerGuid,
|
||||
|
|
@ -318,6 +334,7 @@ public sealed record RetailUiRuntimeBindings(
|
|||
ConfirmationRuntimeBindings Confirmations,
|
||||
AppraisalRuntimeBindings Appraisal,
|
||||
OptionsRuntimeBindings Options,
|
||||
SocialRuntimeBindings Social,
|
||||
StackSplitQuantityState StackSplitQuantity,
|
||||
BufferedUiRegistry? Plugins,
|
||||
RetailUiPersistenceBindings? Persistence,
|
||||
|
|
@ -402,6 +419,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountIndicators();
|
||||
MountJumpPowerbar();
|
||||
MountDialogFactory();
|
||||
MountSocialPanel();
|
||||
MountCharacter();
|
||||
MountPlugins();
|
||||
MountInventory();
|
||||
|
|
@ -495,6 +513,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
public ExternalContainerController? ExternalContainerController { get; private set; }
|
||||
public VendorUiController? VendorController { get; private set; }
|
||||
public OptionsPanelController? OptionsPanelController { get; private set; }
|
||||
public SocialPanelController? SocialPanelController { get; private set; }
|
||||
|
||||
public static RetailUiRuntime Mount(RetailUiRuntimeBindings bindings)
|
||||
{
|
||||
|
|
@ -537,6 +556,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
JumpPowerbarController?.Tick();
|
||||
SelectedObjectController?.Tick(deltaSeconds);
|
||||
ExternalContainerController?.Tick();
|
||||
SocialPanelController?.Tick();
|
||||
_itemCooldownController?.Tick();
|
||||
DialogFactory?.Tick();
|
||||
Host.Tick(deltaSeconds);
|
||||
|
|
@ -559,9 +579,46 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
OpenSpellbook(SpellbookWindowPage.Components);
|
||||
return true;
|
||||
}
|
||||
// Campaign FA slice FA3: F3/F4 — keyboard-only open paths (lane A
|
||||
// §6.1: neither action authors a toolbar button). Both share the
|
||||
// one social panel (RetailPanelCatalog.SocialPanel) and switch to
|
||||
// their own tab; the panel participates in the SAME gmPanelUI
|
||||
// one-active-panel exclusivity every sibling panel gets from
|
||||
// RetailPanelUiController.RegisterMainPanel.
|
||||
if (action == AcDream.UI.Abstractions.Input.InputAction.ToggleAllegiancePanel)
|
||||
{
|
||||
OpenSocialPanel(showAllegiance: true);
|
||||
return true;
|
||||
}
|
||||
if (action == AcDream.UI.Abstractions.Input.InputAction.ToggleFellowshipPanel)
|
||||
{
|
||||
OpenSocialPanel(showAllegiance: false);
|
||||
return true;
|
||||
}
|
||||
return ToolbarInputController?.Handle(action) == true;
|
||||
}
|
||||
|
||||
/// <summary>Shared F3/F4 handler — same "toggle closes on a repeat press
|
||||
/// of the SAME tab, otherwise show + switch" shape as <see cref="OpenSpellbook"/>.</summary>
|
||||
private void OpenSocialPanel(bool showAllegiance)
|
||||
{
|
||||
bool visible = Host.IsWindowVisible(WindowNames.SocialPanel);
|
||||
bool onTargetTab = showAllegiance
|
||||
? SocialPanelController?.IsShowingAllegiance == true
|
||||
: SocialPanelController?.IsShowingFellowship == true;
|
||||
if (visible && onTargetTab)
|
||||
{
|
||||
CloseWindow(WindowNames.SocialPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (showAllegiance)
|
||||
SocialPanelController?.ShowAllegiance();
|
||||
else
|
||||
SocialPanelController?.ShowFellowship();
|
||||
_panelUi.SetPanelVisibility(RetailPanelCatalog.SocialPanel, visible: true);
|
||||
}
|
||||
|
||||
private void OpenSpellbook(SpellbookWindowPage page)
|
||||
{
|
||||
bool visible = Host.IsWindowVisible(WindowNames.Spellbook);
|
||||
|
|
@ -2526,6 +2583,118 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
Console.WriteLine("[D.6] retail jump bar from gmFloatyPowerBarUI LayoutDesc 0x21000072.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FA slice FA3: retail's four-tab social panel — Friends /
|
||||
/// Allegiance / Fellowship / Squelch, host <c>0x2100006E</c> slot
|
||||
/// <c>0x1000018F</c>, <see cref="RetailPanelCatalog.SocialPanel"/> (12).
|
||||
/// Same five-step shape as <see cref="MountOptionsPanel"/>: Import →
|
||||
/// build callbacks → <see cref="Layout.SocialPanelController.Bind"/> →
|
||||
/// <see cref="Layout.SocialPanelController.ActivateTabs"/> →
|
||||
/// <see cref="RetailWindowFrame.Mount"/> → <see cref="RetailPanelUiController.RegisterMainPanel"/>.
|
||||
/// </summary>
|
||||
private void MountSocialPanel()
|
||||
{
|
||||
ElementInfo? rootInfo;
|
||||
ImportedLayout? layout;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
rootInfo = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats,
|
||||
Layout.SocialPanelController.HostLayoutId,
|
||||
Layout.SocialPanelController.SlotElementId);
|
||||
var resolver = new DatStringResolver(_bindings.Assets.Dats);
|
||||
layout = rootInfo is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
rootInfo,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
resolver.Resolve);
|
||||
}
|
||||
if (rootInfo is null || layout is null)
|
||||
{
|
||||
Console.WriteLine("[UI] social panel: LayoutDesc 0x2100006E slot 0x1000018F not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// The Friends/Squelch row templates (0x2100005D/0x21000060) are
|
||||
// resolved lazily, per row — the SAME "second dat-lock scope per
|
||||
// resolve" shape the Character/Chat/Config tab row templates use
|
||||
// (MountOptionsPanel, above), since each row's own template is a
|
||||
// live DAT read.
|
||||
UiElement? TemplateResolver(uint templateLayoutId, uint templateElementId)
|
||||
{
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
var strings = new DatStringResolver(_bindings.Assets.Dats);
|
||||
ElementInfo? info = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats, templateLayoutId, templateElementId);
|
||||
return info is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
info,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
strings.Resolve).Root;
|
||||
}
|
||||
}
|
||||
|
||||
var callbacks = new Layout.SocialPanelController.Callbacks(
|
||||
Toggle: () => ToggleWindow(WindowNames.SocialPanel),
|
||||
FellowshipSnapshot: _bindings.Social.FellowshipSnapshot,
|
||||
AllegianceSnapshot: _bindings.Social.AllegianceSnapshot,
|
||||
Friends: _bindings.Social.Friends,
|
||||
Squelch: _bindings.Social.Squelch,
|
||||
TemplateResolver: TemplateResolver);
|
||||
|
||||
Layout.SocialPanelController? controller =
|
||||
Layout.SocialPanelController.Bind(layout, callbacks);
|
||||
if (controller is null)
|
||||
{
|
||||
Console.WriteLine("[UI] social panel: required root did not build as UiTabPanel.");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.ActivateTabs();
|
||||
SocialPanelController = controller;
|
||||
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
controller.Root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = WindowNames.SocialPanel,
|
||||
Chrome = RetailWindowChrome.NineSlice,
|
||||
Left = 200f,
|
||||
Top = 140f,
|
||||
Visible = false,
|
||||
// Same shared main-panel geometry policy every gmPanelUI
|
||||
// sibling uses (Options' own MUST-FIX 2 correction, above).
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = ResizeEdges.Bottom,
|
||||
ConstrainDragToParent = true,
|
||||
ConstrainResizeToParent = true,
|
||||
ContentAnchors = AnchorEdges.Left | AnchorEdges.Top
|
||||
| AnchorEdges.Right | AnchorEdges.Bottom,
|
||||
ContentClickThrough = false,
|
||||
DrawChromeCenter = !AuthorsFullPanelCenter(rootInfo),
|
||||
Controller = controller,
|
||||
});
|
||||
_panelUi.RegisterMainPanel(
|
||||
RetailPanelCatalog.SocialPanel,
|
||||
WindowNames.SocialPanel,
|
||||
handle,
|
||||
rootInfo.TryGetEffectiveBool(
|
||||
RetailPanelUiController.RestorePreviousPropertyId,
|
||||
out bool restorePrevious)
|
||||
&& restorePrevious);
|
||||
Console.WriteLine("[UI] retail social panel from LayoutDesc importer (0x2100006E slot 0x1000018F).");
|
||||
}
|
||||
|
||||
private void MountDialogFactory()
|
||||
{
|
||||
uint layoutId;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue