dialogs come to the front, and the red line names what it needs

- dialogs carry the reference's toplevel bit, so the stack-split window opens above Bagnon instead of behind it
- the failure line fills its blank — "Requires Blacksmith Hammer", not "Requires" — and Opening casts stop printing a label the spell itself marks "no text"
- the pet bar's Attack/Follow/Stay tooltips name their keybinding, and typed text reaches the focused frame through the client's own delivery walk
- AtlasLoot parses again: our Lua keeps 5.0's compatibility semicolon

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-14 19:38:18 +07:00
co-authored by Claude Fable 5
parent 606a72bde5
commit 2c1ffca2ca
57 changed files with 2163 additions and 275 deletions
+13 -4
View File
@@ -39,10 +39,9 @@ impl Loader<'_> {
// is in the set, `OnDragStop`/`OnReceiveDrag` are NOT. This is XML-load-time
// ONLY: the Lua SetScript binding (`0x7748d0`) never auto-enables, so the law
// lives here and not in SetScript itself (a runtime-created frame still needs an
// explicit `EnableMouse(true)`, like the real client). The keyboard kinds
// (`OnChar` = kind 0, `OnKeyDown`/`OnKeyUp` = kind 1) and the wheel kind
// (`OnMouseWheel` = kind 3) have separate indexes in the real client that this
// engine doesn't model yet — un-modeled beside the SetScript keyboard gap above.
// explicit `EnableMouse(true)`, like the real client). The wheel kind
// (`OnMouseWheel` = kind 3) has a separate index this engine doesn't model yet;
// the KEYBOARD kinds are modelled and armed just below.
const MOUSE_KIND: [&str; 5] = [
"OnEnter",
"OnLeave",
@@ -53,6 +52,16 @@ impl Loader<'_> {
if MOUSE_KIND.iter().any(|k| name.eq_ignore_ascii_case(k)) {
self.call(wrapper, "EnableMouse", true, dbg);
}
// The KEYBOARD kinds, the same walker rule one index over (`OnChar` = kind 0,
// `OnKeyDown`/`OnKeyUp` = kind 1). Bucket membership is what the delivery walk
// iterates ([`crate::script::keyboard`]) and it is the FLAG, never the presence of
// a script — so an XML frame carrying a key handler but no `enableKeyboard`
// attribute would never be reached by the dispatcher at all, and its handler could
// not fire. XML-load-time only, like the mouse half.
const KEY_KIND: [&str; 3] = ["OnChar", "OnKeyDown", "OnKeyUp"];
if KEY_KIND.iter().any(|k| name.eq_ignore_ascii_case(k)) {
self.call(wrapper, "EnableKeyboard", true, dbg);
}
if name.eq_ignore_ascii_case("OnLoad") {
onload = Some(func);
}
+10 -4
View File
@@ -461,18 +461,24 @@ mod loader_tests {
assert!(s.eval::<bool>("return FineRan == true").unwrap());
}
/// Unsupported handler names (e.g. the keyboard-focus handlers `OnKeyDown`/`OnChar`, not yet
/// modeled) are warn-once gaps, not hard errors, and don't stop the frame from building.
/// Unsupported handler names are warn-once gaps, not hard errors, and don't stop the frame
/// from building. The example is `OnCursorChanged` — caret geometry is host-side here, so its
/// four float args would all be zero, which is the silent-drop this warn exists to avoid.
/// (It used to be `OnKeyDown`: that one is *fired* since decision 1319 and now belongs to
/// `script::tests::keyboard`.)
#[test]
fn unsupported_script_name_is_a_warning() {
let s = UiScript::new().unwrap();
let doc = parse(
r#"<Ui><Frame name="Keyed"><Scripts><OnKeyDown>x = 1</OnKeyDown></Scripts></Frame></Ui>"#,
r#"<Ui><Frame name="Keyed"><Scripts><OnCursorChanged>x = 1</OnCursorChanged></Scripts></Frame></Ui>"#,
);
let report = load(&s, &doc, &no_files);
assert_eq!(report.frames, 1);
assert!(report.errors.is_empty(), "{:?}", report.errors);
assert!(report.warnings.iter().any(|w| w.contains("OnKeyDown")));
assert!(report
.warnings
.iter()
.any(|w| w.contains("OnCursorChanged")));
}
/// An unknown frame type is an error that drops that subtree but not the rest of the load.