feat(ui): #409 — client-wide retail tooltip system
Full re-derivation from named-retail decomp: UIElement::StartTooltipAtMouse @0x00460D70 -> UIElementManager::StartTooltip @0x0045DE90/@0x00459700, UIElement::MouseHover @0x00462520 (P0x4B TooltipOn gate + global m_tooltipEnable), UIElementManager::CheckTooltip @0x0045B6E0 (dwell/ auto-hide timer, default 0.25s/10s), SwitchMouseOver/DeletingElement (dismissal). Corrects the earlier GF-16 investigation: P0x47 is the element-desc id WITHIN the popup LayoutDesc (P0x48), not a "behavior enum"; P0x4A is read off the popup's own instantiated root, not the trigger element. - ElementInfo/UiElement gain six tooltip data fields (P0x47/48/49/4A/4B/50), read generically by ElementReader and copied through LayoutImporter, mirroring the existing AuthoredInvisible passthrough pattern. - UiRoot's existing CheckTooltip-derived hover timer gains TooltipShow/ TooltipHide events, a per-element P0x50 delay override, and dismissal wiring at every retail-confirmed teardown site. - RetailTooltipPresenter (owned by RetailUiRuntime, mounted alongside RetailDialogFactory) builds the popup via the existing LayoutImporter dat-lock seam, auto-resizes by the measured-vs-authored text delta (word-wrapped via the existing UiText.WrapWords primitive), positions at the mouse clamped to the display, and stays topmost over dialogs via its own later per-tick BringToFront (register AD-106). - Misc.TooltipEnable/Misc.TooltipDelay are client-local UserPreferences (retail's own 2013 Config tab authors no visible row for either) — SettingsStore gains a MiscSettings section, no new options-panel row. - Live-DAT sweep: 434 elements author >=1 trigger property (243 with literal text this port shows; 191 rely on retail's dynamic InqProperty(0x49) override, deferred as register TS-85 alongside the unmodeled P0x3D wrap-width override). Gates: Release build 0 errors; App suite (live-DAT env) 5410/5407 passed/ 3 skipped (was 5379/3); Runtime 1735/0 unchanged; UI.Abstractions 926/0; full solution 14,617/14,548 passed/69 skipped/0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d3755eb231
commit
a377b9bff7
16 changed files with 1386 additions and 9 deletions
|
|
@ -266,6 +266,95 @@ public sealed class ElementInfo
|
|||
/// </summary>
|
||||
public int MarginLeft, MarginRight, MarginTop, MarginBottom;
|
||||
|
||||
/// <summary>
|
||||
/// #409 (client-wide retail tooltip system): whether THIS element is
|
||||
/// eligible to trigger retail's hover tooltip at all — dat property
|
||||
/// <c>0x4B</c> (<c>BoolBaseProperty</c>). Retail
|
||||
/// <c>UIElement::OnSetAttribute @0x00462d80</c> case <c>0x18</c>
|
||||
/// (<c>GetPropertyName()-0x33==0x18</c>, i.e. property id
|
||||
/// <c>0x33+0x18=0x4B</c>) calls <c>UIRegion::SetTooltipOn</c>, which
|
||||
/// toggles bit 5 of <c>__bitfield164</c>; <c>UIElement::MouseHover
|
||||
/// @0x00462520</c> checks exactly that bit (plus the GLOBAL
|
||||
/// <c>UIElementManager::m_tooltipEnable</c> preference) before calling
|
||||
/// <c>StartTooltipAtMouse</c>. Default false — the ctor-cleared bit,
|
||||
/// matching "an element must explicitly author <c>P0x4B=true</c> to
|
||||
/// ever show a tooltip."
|
||||
/// </summary>
|
||||
public bool TooltipEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// #409: the tooltip's TEXT, dat property <c>0x49</c>
|
||||
/// (<c>StringInfo</c>). Retail <c>UIElement::StartTooltipAtMouse
|
||||
/// @0x00460D70</c> prefers the element's own runtime-settable
|
||||
/// <c>m_TTText</c> field (set only by the separate, non-dat-authored
|
||||
/// <c>UIElement::SetTooltip</c> API — game code showing dynamic
|
||||
/// status text, out of this property's scope) and falls back to
|
||||
/// <c>vtable->InqProperty(0x49, ...)</c> — the generic dat-authored
|
||||
/// property bag read — whenever <c>m_TTText</c> is empty, which for a
|
||||
/// pure DAT-imported element is ALWAYS (nothing in
|
||||
/// <c>OnSetAttribute</c>'s switch populates <c>m_TTText</c> from a dat
|
||||
/// property). Kept RAW (unresolved <see cref="UiStringInfoValue"/>) at
|
||||
/// this layer — <see cref="ElementReader"/> is dat-free/string-free by
|
||||
/// design; resolution through <c>DatStringResolver</c> happens in
|
||||
/// <c>DatWidgetFactory</c>, mirroring the existing <c>0x17</c> caption
|
||||
/// pattern exactly.
|
||||
/// </summary>
|
||||
public UiStringInfoValue? TooltipText;
|
||||
|
||||
/// <summary>
|
||||
/// #409: the ROOT ELEMENT ID to instantiate WITHIN the tooltip popup
|
||||
/// LayoutDesc (<see cref="TooltipLayoutDid"/>) — dat property
|
||||
/// <c>0x47</c> (<c>EnumBaseProperty</c>, read via
|
||||
/// <c>UIElement::GetAttribute_Enum(this, 0x47, ...)</c>). Retail
|
||||
/// <c>UIElementManager::StartTooltip @0x0045DE90</c> passes this value
|
||||
/// straight to <c>LayoutDesc::AccessElementDesc</c> — it is an
|
||||
/// element-desc token WITHIN the popup layout, NOT a "tooltip behavior
|
||||
/// enum" (an earlier, less complete investigation guessed the latter;
|
||||
/// full decomp re-derivation for this port corrects it). Zero when the
|
||||
/// element authors no tooltip.
|
||||
/// </summary>
|
||||
public uint TooltipRootElementId;
|
||||
|
||||
/// <summary>
|
||||
/// #409: the tooltip POPUP LayoutDesc DID — dat property <c>0x48</c>
|
||||
/// (<c>DataIdBaseProperty</c>, read via
|
||||
/// <c>UIElement::GetAttribute_DataID(this, 0x48, ...)</c>). Retail
|
||||
/// authors <c>0x21000041</c> here on every tooltip-bearing element
|
||||
/// (live-DAT-probe-confirmed by the #409 investigation). Retail falls
|
||||
/// back to the HOVERING element's OWN containing <c>m_layout</c> DID
|
||||
/// when <c>P0x48</c> is absent but <c>P0x47</c> is present — an edge
|
||||
/// case this port does not reproduce (every live-DAT element the #409
|
||||
/// sweep found authors <c>P0x48</c> directly alongside <c>P0x47</c>;
|
||||
/// see the divergence register for the honest row). Zero when absent.
|
||||
/// </summary>
|
||||
public uint TooltipLayoutDid;
|
||||
|
||||
/// <summary>
|
||||
/// #409: the tooltip TEXT CHILD's element id — dat property
|
||||
/// <c>0x4A</c> (<c>EnumBaseProperty</c>). Unlike the other five
|
||||
/// tooltip properties, retail reads THIS one off the freshly
|
||||
/// INSTANTIATED popup's own ROOT element (<c>UIElement::
|
||||
/// GetAttribute_Enum(tooltipRootElement, 0x4a, &textChildId)</c>
|
||||
/// inside <c>UIElementManager::StartTooltip @0x0045DE90</c>'s
|
||||
/// fallback branch), not off the hovering trigger element — so this
|
||||
/// field is meaningful only when read from the popup layout's OWN
|
||||
/// resolved root <see cref="ElementInfo"/>/<c>UiElement</c>, which the
|
||||
/// same generic property-read machinery populates uniformly for every
|
||||
/// imported element regardless of role.
|
||||
/// </summary>
|
||||
public uint TooltipTextChildElementId;
|
||||
|
||||
/// <summary>
|
||||
/// #409: a per-element hover-dwell delay OVERRIDE in seconds — dat
|
||||
/// property <c>0x50</c> (<c>FloatBaseProperty</c>). Retail
|
||||
/// <c>UIElementManager::CheckTooltip @0x0045B6E0</c> reads
|
||||
/// <c>UIElement::GetAttribute_Float(hoveredElement, 0x50, &delay)</c>
|
||||
/// and uses it in place of the global <c>m_tooltipDelay</c>
|
||||
/// (<c>Misc.TooltipDelay</c> preference, default 0.25 s) when present.
|
||||
/// Null when the element authors no override.
|
||||
/// </summary>
|
||||
public float? TooltipDelaySeconds;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a property for a state using retail's DirectState-as-base rule. A
|
||||
/// named state's key overrides DirectState by presence, including false/zero.
|
||||
|
|
@ -603,6 +692,32 @@ public static class ElementReader
|
|||
{
|
||||
info.Invisible = invisible;
|
||||
}
|
||||
|
||||
// #409 (client-wide retail tooltip system): the six per-element
|
||||
// tooltip properties. See each ElementInfo field's own doc comment
|
||||
// for the exact decomp anchor. TooltipTextChildElementId (0x4A) is
|
||||
// read the SAME generic way here even though retail only consults
|
||||
// it off a tooltip POPUP's own root element — the read is uniform
|
||||
// and harmless for every other element (it stays 0).
|
||||
if (info.TryGetEffectiveBool(0x4Bu, out bool tooltipOn))
|
||||
{
|
||||
info.TooltipEnabled = tooltipOn;
|
||||
}
|
||||
|
||||
if (info.TryGetEffectiveProperty(0x49u, out var tooltipText)
|
||||
&& tooltipText.Kind == UiPropertyKind.StringInfo)
|
||||
{
|
||||
info.TooltipText = tooltipText.StringInfoValue;
|
||||
}
|
||||
|
||||
info.TooltipRootElementId = ReadReferencedElementId(info, 0x47u);
|
||||
info.TooltipLayoutDid = ReadReferencedElementId(info, 0x48u);
|
||||
info.TooltipTextChildElementId = ReadReferencedElementId(info, 0x4Au);
|
||||
|
||||
if (info.TryGetEffectiveFloat(0x50u, out float tooltipDelay))
|
||||
{
|
||||
info.TooltipDelaySeconds = tooltipDelay;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<UiTabTableEntry> ReadTabTable(ElementInfo info)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue