fix: social gate round 2, part 1 - border-only move cursor, literal-\n
empty state, retail amber row selection - Move cursor (user-directed, ALL windows): HoverWindowMove now advertises only where the window frame element itself wins the hit-test - its border pixels; interior points resolve to content children. Matches retail's Dragbar-chrome-only move cursor. Whole-surface dragging still works, it just does not advertise. - Empty-state text (round 2): the DAT stores the LITERAL two-character escape backslash-n (probe-verified - the dump printed the escape, not line breaks), so the round-1 newline split never matched. Escapes are normalized before splitting in DatWidgetFactory authored text. - Selected fellow amber (user: "check retail"): probe-verified - the row name band 0x10000282 AUTHORS the retail selected-row art (DirectState 0x06001450 + Highlight 0x06001451, the amber). Selection flips the band's ActiveState to Highlight; no invented tint. App suite 4,976/3 skips. Confirmation-text + refused-drop-notification research (the round's items 4-5) lands as part 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
72ceddce2e
commit
fc62cb6397
4 changed files with 56 additions and 5 deletions
|
|
@ -707,11 +707,17 @@ public static class DatWidgetFactory
|
|||
// newlines (the fellowship empty-state is three sentences over
|
||||
// '\n's). A single Line renders them as one clipped run — split
|
||||
// into one Line per authored line, exactly as retail's multiline
|
||||
// UIElement_Text draws them. The provider re-reads DefaultColor
|
||||
// UIElement_Text draws them. Gate round 2: the DAT stores the
|
||||
// LITERAL two-character escape "\n" (0x5C 0x6E — probe-verified:
|
||||
// the dump printed backslash-n, not a line break), so normalize
|
||||
// the escape before splitting. The provider re-reads DefaultColor
|
||||
// per call (NOT captured eagerly) so state-driven font-color
|
||||
// changes keep tracking, the same live-color contract the
|
||||
// single-line provider always had.
|
||||
string[] parts = [.. authored.Split('\n').Select(static p => p.TrimEnd('\r'))];
|
||||
string[] parts = [.. authored
|
||||
.Replace("\\n", "\n")
|
||||
.Split('\n')
|
||||
.Select(static p => p.TrimEnd('\r'))];
|
||||
t.LinesProvider = () =>
|
||||
[.. parts.Select(p => new UiText.Line(p, t.DefaultColor))];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,13 @@ public sealed class SocialFellowshipPageController
|
|||
private const uint ShareLootCheckboxId = 0x10000273u;
|
||||
|
||||
// ── Row-template element ids (same doc, the 8-part fellow row) ──
|
||||
/// <summary>Gate round 2 (2026-08-13): the row's name BAND — probe-verified
|
||||
/// to author the retail selected-row art: DirectState media
|
||||
/// <c>0x06001450</c> + a <c>'Highlight'</c> state with the amber
|
||||
/// <c>0x06001451</c> (ProbeSocialClickRouting's ROWSTATE dump). Selection
|
||||
/// flips its <see cref="UiDatElement.ActiveState"/> — the authored
|
||||
/// mechanism, replacing the retired invented tints.</summary>
|
||||
private const uint RowNameBandId = 0x10000282u;
|
||||
private const uint RowNameTextId = 0x10000283u;
|
||||
private const uint RowStatsTextId = 0x10000284u;
|
||||
private const uint RowHealthMeterId = 0x10000285u;
|
||||
|
|
@ -168,6 +175,7 @@ public sealed class SocialFellowshipPageController
|
|||
Func<uint, uint, string?> ResolveString);
|
||||
|
||||
private readonly record struct FellowRowWidgets(
|
||||
UiDatElement? NameBand,
|
||||
UiText? Name,
|
||||
UiText? Stats,
|
||||
UiMeter? Health,
|
||||
|
|
@ -716,6 +724,7 @@ public sealed class SocialFellowshipPageController
|
|||
}
|
||||
|
||||
var widgets = new FellowRowWidgets(
|
||||
UiElement.FindDescendant(row, RowNameBandId) as UiDatElement,
|
||||
UiElement.FindDescendant(row, RowNameTextId) as UiText,
|
||||
UiElement.FindDescendant(row, RowStatsTextId) as UiText,
|
||||
UiElement.FindDescendant(row, RowHealthMeterId) as UiMeter,
|
||||
|
|
@ -757,6 +766,11 @@ public sealed class SocialFellowshipPageController
|
|||
nameText.LinesProvider = () => [new UiText.Line(name, MemberNameColor)];
|
||||
}
|
||||
|
||||
// Gate round 2: the AUTHORED selected-row cue — the name band's
|
||||
// 'Highlight' state is retail's amber (see RowNameBandId's doc).
|
||||
if (widgets.NameBand is { } nameBand)
|
||||
nameBand.ActiveState = _selectedFellowGuid == member.Guid ? "Highlight" : "";
|
||||
|
||||
if (widgets.Stats is { } statsText)
|
||||
{
|
||||
string text = FormatStatsText(member, snapshot);
|
||||
|
|
|
|||
|
|
@ -148,9 +148,13 @@ public sealed class UiRoot : UiElement
|
|||
var window = FindWindow(target);
|
||||
if (window is not { Draggable: true })
|
||||
return false;
|
||||
return !target.IsDragSource
|
||||
&& !target.CapturesPointerDrag
|
||||
&& !target.HandlesClick;
|
||||
// 2026-08-13 gate (user-directed, all windows): the move cursor
|
||||
// advertises ONLY on the window's own chrome — the frame's border
|
||||
// pixels are the only place the frame element itself wins the
|
||||
// hit-test (interior points resolve to content children), which
|
||||
// matches retail's Dragbar-chrome-only move cursor. Whole-surface
|
||||
// dragging still WORKS; it just no longer advertises over content.
|
||||
return ReferenceEquals(target, window);
|
||||
}
|
||||
}
|
||||
private (uint tex, int w, int h)? _dragGhost;
|
||||
|
|
|
|||
|
|
@ -637,6 +637,20 @@ public sealed class SocialPanelLiveMountProbeTests
|
|||
$"[clickprobe] NOT-in-fellowship synthetic click -> optionWrites=[{string.Join(",", optionWrites)}]");
|
||||
}
|
||||
|
||||
// The fellow row template's authored STATES (gate round 2: retail's
|
||||
// selected-row amber — which element carries a Highlight/selected
|
||||
// state, and what media?).
|
||||
if (UiElement.FindDescendant(uiRoot, 0x10000279u) is UiTemplateListBox rosterBox
|
||||
&& rosterBox.Templates.Count > 0)
|
||||
{
|
||||
ElementInfo? rowInfo = LayoutImporter.ImportInfos(
|
||||
dats,
|
||||
rosterBox.Templates[0].TemplateLayoutId,
|
||||
rosterBox.Templates[0].TemplateElementId);
|
||||
if (rowInfo is not null)
|
||||
DumpStates(rowInfo, 0);
|
||||
}
|
||||
|
||||
// Friends + Squelch action widgets: authored labels → button roles
|
||||
// (never guess an id's role).
|
||||
foreach (uint pageId in new[] { 0x10000513u, 0x1000054Au })
|
||||
|
|
@ -672,6 +686,19 @@ public sealed class SocialPanelLiveMountProbeTests
|
|||
return (x, y);
|
||||
}
|
||||
|
||||
private static void DumpStates(ElementInfo info, int depth)
|
||||
{
|
||||
string states = string.Join(
|
||||
",",
|
||||
info.StateMedia.Select(kv => $"'{kv.Key}'=0x{kv.Value.File:X8}"));
|
||||
Console.WriteLine(
|
||||
$"[clickprobe] {new string(' ', depth * 2)}ROWSTATE 0x{info.Id:X8} type=0x{info.Type:X8} "
|
||||
+ $"({info.X},{info.Y} {info.Width}x{info.Height}) default='{info.DefaultStateName}' "
|
||||
+ $"media=[{states}]");
|
||||
foreach (ElementInfo c in info.Children)
|
||||
DumpStates(c, depth + 1);
|
||||
}
|
||||
|
||||
private static void DumpActionWidgets(UiElement el, int depth)
|
||||
{
|
||||
string extra = el switch
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue