NTFS load order, a title-sorted AddOns list, and tooltips at the cursor
- addons load in the order NTFS lists a folder, case-insensitive with an underscore before the letters, the order the vanilla corpus was written against and the one that decides which addon provides a shared library (2178) - GetNumAddOns and the index form of every AddOn verb walk a title-sorted list that hides the Blizzard addons the server says to hide, and the character-select AddOns list is in title order (2175) - SetPoint and SetAllPoints raise the reference's own errors for a frame name that does not exist or an omitted relative frame instead of quietly anchoring to the parent, and an explicit nil means the screen root (2176) - a tooltip owned with ANCHOR_CURSOR follows the mouse, which pfUI's tooltip, xpbar and chat modules, pfQuest's browser and TipBuddy ask for; an omitted or unknown anchor is ANCHOR_LEFT (2176) - a $parent name handed to CreateFrame, CreateTexture or CreateFontString expands as it does in XML, so FonzAppraiser's thirty dropdowns and pfQuest's search icon get the names they asked for (2176) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,8 @@ use super::{FontObject, Model, RegionData};
|
||||
use crate::layout::{Anchor, Point};
|
||||
use crate::order::DrawLayer;
|
||||
use crate::widget::{
|
||||
FrameHandle, KindState, RegionKind, TooltipState, TOOLTIP_DOUBLE_GAP, TOOLTIP_FADE_SECS,
|
||||
TOOLTIP_LINE_GAP, TOOLTIP_PAD,
|
||||
FrameHandle, KindState, RegionKind, TooltipAnchor, TooltipState, TOOLTIP_DOUBLE_GAP,
|
||||
TOOLTIP_FADE_SECS, TOOLTIP_LINE_GAP, TOOLTIP_PAD,
|
||||
};
|
||||
|
||||
/// Registry key of the GameTooltip method table (the MAXCSTACK discipline: named registry root).
|
||||
@@ -673,6 +673,67 @@ fn cell(model: &Model, rh: crate::widget::RegionHandle) -> Cell {
|
||||
/// (`resolve` solves, fills the pending measures inline, and solves again, so this pre-pass runs
|
||||
/// a second time with real extents); without one it is the host's batch round-trip, one frame
|
||||
/// later, which is the engine-less path every measurer-free VM still takes.
|
||||
/// **ANCHOR_CURSOR — mode 6, re-anchored to the live cursor every frame** (`0x530b20`, the
|
||||
/// `CGameTooltip` override of `vtable+0x38` on `0x808f60`; wow-re
|
||||
/// `system/ui/scratch/tooltip-cursor-anchor-law.md` §1, three independent derivations agreeing
|
||||
/// instruction-for-instruction). Nine corpus files ask for it — `pfUI`'s tooltip, xpbar and chat
|
||||
/// modules, `pfQuest/browser.lua`, `TipBuddy` — and until decision 2176 benilla warned and placed
|
||||
/// them by some other mode.
|
||||
///
|
||||
/// The whole of the re-anchor is one `SetPoint`:
|
||||
///
|
||||
/// ```text
|
||||
/// 530b32 cmp [this+0x318],6 ; jne -- every other mode skips
|
||||
/// 530b3b eax = [this+0xa0] -- the CSimpleTop root == [0xcf0bd8]
|
||||
/// 530b4f ecx = [eax+0x111c] ; edx = [eax+0x1118] -- the SAVED cursor, normalised [0,1]
|
||||
/// 530b63 call 0x41ad80 -- x *= G44 (screen w), y *= G48 (screen h)
|
||||
/// 530b68 fld [this+0x7c] ; fdivr 1.0f -- 1 / the tooltip's own effective scale
|
||||
/// 530b97 call 0x767c70(BOTTOM=7, [0xcf0bd8], BOTTOMLEFT=6, x/scale, y/scale, doResolve=1)
|
||||
/// ```
|
||||
///
|
||||
/// So **the plate's BOTTOM-centre is pinned to the screen's BOTTOMLEFT plus the cursor's absolute
|
||||
/// position** — centred horizontally on the cursor and sitting directly above it. Four details are
|
||||
/// worth keeping because none is guessable from the name:
|
||||
///
|
||||
/// * The `relativeTo` is the screen root passed **raw**, not `+0x24` like every other anchor — the
|
||||
/// `CSimpleTop`'s `CLayoutFrame` base sits at offset 0. Ours is [`SCREEN`](super::SCREEN), which
|
||||
/// is the same object.
|
||||
/// * The **divide by the tooltip's own effective scale** is the "cursor box recip": an anchor
|
||||
/// offset is stored raw and multiplied by the child's scale at resolve time, so dividing here is
|
||||
/// what makes the result land at the cursor's *absolute* position. Ours does the same divide,
|
||||
/// against the same scale [`GetWidth`](super::object::eff_scale) reads.
|
||||
/// * **The `SetOwner`-time offsets `+0x3c4/+0x3c8` do not participate** — 12 reads of them, every
|
||||
/// one on a modes-0..5 arm of `0x52fe90`.
|
||||
/// * There is **no `ClearAllPoints` here** (earned zero over the function's complete call set): one
|
||||
/// anchor slot is overwritten, and `0x767c70`'s own no-op gate — same target, same
|
||||
/// `relativePoint`, both offsets within 2⁻²² — is what keeps a still cursor free.
|
||||
///
|
||||
/// It runs from the per-frame update pump, which a hidden tooltip is not in (`0x76ad9d`
|
||||
/// deregisters on hide), and in the **same frame** as the placement it produces: the layout drain
|
||||
/// `0x768ed0` runs at `0x765799`, right after the `+0x38` loop and before the draw. Hence its home
|
||||
/// here, at the top of the resolve, beside the auto-size pass.
|
||||
fn cursor_anchor(model: &mut Model, h: FrameHandle) {
|
||||
let scale = crate::script::object::eff_scale(model, h);
|
||||
let (cx, cy) = model.cursor_pos;
|
||||
let new = Anchor::new(
|
||||
Point::Bottom,
|
||||
super::SCREEN,
|
||||
Point::BottomLeft,
|
||||
cx / scale,
|
||||
cy / scale,
|
||||
);
|
||||
let input = model.layout_inputs.entry(h).or_default();
|
||||
// `0x767c70`'s change-detect, in our own terms: one slot, replaced only on a real change.
|
||||
let same =
|
||||
input.anchors.len() == 1 && crate::script::object::anchor_bits_eq(&input.anchors[0], &new);
|
||||
if same {
|
||||
return;
|
||||
}
|
||||
let old_targets: Vec<u32> = input.anchors.iter().map(|a| a.relative_to).collect();
|
||||
input.anchors = vec![new];
|
||||
model.touch_layout_retarget_frame(h, &old_targets, &[super::SCREEN]);
|
||||
}
|
||||
|
||||
pub(super) fn layout_tooltips(model: &mut Model) {
|
||||
// The arena's tooltip registry, not the resolve's whole frame roster: this pre-pass runs at
|
||||
// the top of EVERY resolve, and finding two or three tooltips by scanning ~4000 ids was most
|
||||
@@ -686,6 +747,16 @@ pub(super) fn layout_tooltips(model: &mut Model) {
|
||||
.filter(|h| model.frame_to_id.contains_key(h))
|
||||
.collect();
|
||||
for h in tips {
|
||||
// Mode 6 first, and **before the line gate below**: the reference's per-frame update is
|
||||
// not gated on content at all, only on the plate being in the pump's list — which is the
|
||||
// shown list. See [`cursor_anchor`].
|
||||
let cursor_mode = match model.arena.frame(h).map(|f| (&f.kind_state, f.shown)) {
|
||||
Some((KindState::Tooltip(t), shown)) => shown && t.anchor == TooltipAnchor::Cursor,
|
||||
_ => false,
|
||||
};
|
||||
if cursor_mode {
|
||||
cursor_anchor(model, h);
|
||||
}
|
||||
let (num, lefts, rights, min_w, pad_w) = match model.arena.frame(h).map(|f| &f.kind_state) {
|
||||
Some(KindState::Tooltip(t)) => (
|
||||
t.num_lines,
|
||||
|
||||
Reference in New Issue
Block a user