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:
Sam
2026-09-11 21:03:49 +07:00
co-authored by Claude Fable 5.1
parent 5af3a15d0e
commit eebbcc050f
53 changed files with 1978 additions and 541 deletions
+9 -15
View File
@@ -284,21 +284,15 @@ impl Loader<'_> {
x.unwrap_or(0.0),
y.unwrap_or(0.0),
);
// A target that is not built yet waits for the frame's subtree (`Loader::deferred_anchors`).
if args
.1
.as_deref()
.is_some_and(|n| !self.anchor_target_exists(n))
{
self.deferred_anchors.push(super::DeferredAnchor {
wrapper: wrapper.clone(),
region: false,
args,
dbg: dbg.to_string(),
});
continue;
}
self.call(wrapper, "SetPoint", args, dbg);
let d = super::DeferredAnchor {
wrapper: wrapper.clone(),
region: false,
args,
dbg: dbg.to_string(),
};
// The XML path's own law, which resolves the name itself and defers a target
// the enclosing frame's subtree has not built yet (`Loader::apply_anchor`).
self.apply_anchor(d, true);
}
}
}
+47 -9
View File
@@ -975,10 +975,52 @@ impl Loader<'_> {
/// Returns the frame's own name (`None` if it is anonymous) and the name of its nearest
/// **named** ancestor — rf27 rule 3's walk, with [`framexml::DEFAULT_PARENT_NAME`] when there
/// is none.
/// Can a `relativeTo` name resolve right now — a frame in the arena or a named region?
pub(super) fn anchor_target_exists(&self, name: &str) -> bool {
let model = self.model();
model.arena.lookup(name).is_some() || model.region_names.contains_key(name)
pub(super) fn apply_anchor(&mut self, d: DeferredAnchor, may_defer: bool) {
let rel: Value = match d.args.1.as_deref() {
Some(name) => {
match crate::script::object::anchor_args::resolve_xml_relative_to(self.lua(), name)
{
Some(t) => Value::Table(t),
// Nothing by that name YET — a target declared later in the enclosing frame's
// subtree. Hold it until the subtree is complete (`deferred_anchors`) and try
// once more; a miss THEN is a real miss and takes the reference's leg.
None if may_defer => {
self.deferred_anchors.push(d);
return;
}
None => {
self.report
.warnings
.push(format!("{}: Couldn't find relative frame: {name}", d.dbg));
return;
}
}
}
// No `relativeTo=`: `ebx` still holds `[ebp-0x14]`, the layout parent default that
// `0x76785b call [edx+0xc]` (= `0x76c6e0`) seeded before the `<Anchor>` loop. A frame
// with no parent gets the screen root there, which is what a Lua `nil` means here.
None => match d
.wrapper
.call_method::<Option<Table>>("GetParent", ())
.ok()
.flatten()
{
Some(t) => Value::Table(t),
None => Value::Nil,
},
};
let DeferredAnchor {
wrapper,
region,
args: (point, _, rel_point, x, y),
dbg,
} = d;
let args = (point, rel, rel_point, x, y);
if region {
self.call_region(&wrapper, "SetPoint", args, &dbg);
} else {
self.call(&wrapper, "SetPoint", args, &dbg);
}
}
/// Apply the anchors deferred since `mark` (the enclosing `decorate`'s entry), in order. A
@@ -986,11 +1028,7 @@ impl Loader<'_> {
pub(super) fn drain_deferred_anchors(&mut self, mark: usize) {
let pending: Vec<DeferredAnchor> = self.deferred_anchors.drain(mark..).collect();
for d in pending {
if d.region {
self.call_region(&d.wrapper, "SetPoint", d.args, &d.dbg);
} else {
self.call(&d.wrapper, "SetPoint", d.args, &d.dbg);
}
self.apply_anchor(d, false);
}
}
+9 -15
View File
@@ -283,21 +283,15 @@ impl Loader<'_> {
x.unwrap_or(0.0),
y.unwrap_or(0.0),
);
// A target that is not built yet waits for the frame's subtree (`Loader::deferred_anchors`).
if args
.1
.as_deref()
.is_some_and(|n| !self.anchor_target_exists(n))
{
self.deferred_anchors.push(super::DeferredAnchor {
wrapper: wrapper.clone(),
region: true,
args,
dbg: dbg.to_string(),
});
continue;
}
self.call_region(wrapper, "SetPoint", args, dbg);
let d = super::DeferredAnchor {
wrapper: wrapper.clone(),
region: true,
args,
dbg: dbg.to_string(),
};
// The XML path's own law, which resolves the name itself and defers a target
// the enclosing frame's subtree has not built yet (`Loader::apply_anchor`).
self.apply_anchor(d, true);
}
}
}