the mailbox takes typing again, and events carry their real arguments

- the mail window's send tab takes typing again: the subject, body and money boxes were taking no input while the recipient box swallowed every key, and a sent letter clears its subject and item icon instead of putting them straight back (2145)
- seven events fire the way the reference does: UNIT_DYNAMIC_FLAGS fires at all, swapping two identical stacks between bank slots fires PLAYERBANKSLOTS_CHANGED twice, the pet sheet's training points repaint, and ITEM_LOCK_CHANGED loses its invented arguments (2140)
- nine widget methods 1.12 addons probe for are in: ShaguTweaks stops recolouring textures the reference leaves alone, pfUI and _Nameplates stop raising on a tooltip's GetAnchorType, and 63 AceEvent addons get RegisterAllEvents (2142)
- six post-1.12 methods leave the widget surface (SetSize, SetShown, HasFocus, SetFormattedText and two tooltip verbs), so an addon reaching for a later client's API finds what a real 1.12 client gives it (2142, 2146)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-11 20:52:50 +07:00
co-authored by Claude Fable 5.1
parent 5432a8bfbd
commit 31371641eb
100 changed files with 3837 additions and 869 deletions
@@ -7,7 +7,6 @@
use mlua::{Lua, Table, Value};
use crate::script::object::frame_handle_of;
use crate::script::Model;
use crate::widget::EditBoxState;
use super::{
@@ -127,14 +126,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
Ok(())
})?,
)?;
m.set(
"HasFocus",
lua.create_function(|lua, this: Table| {
let h = frame_handle_of(lua, &this)?;
let model = lua.app_data_ref::<Model>().expect("model app_data");
Ok(model.focused_editbox == Some(h))
})?,
)?;
// **No `HasFocus`.** 1.12's EditBox table registers the two setters above and no getter at
// all, and an addon that wants the answer keeps its own flag around `SetFocus`/`ClearFocus` —
// `pfQuest/browser.lua:760` documents exactly that and ships the workaround. Ours was an
// unexplained superset a feature-detecting addon would take the wrong branch on (1188, and
// the census that removed it, 2142). Host-side, the focus cell reads back through
// [`crate::script::UiScript::focused_editbox_name`].
// GetInputLanguage() / ToggleInputLanguage() — `0x799550` / `0x799610`, the edit box's
// **IME** language, not the chat language: `ChatEdit_OnInputLanguageChanged` shows
+19 -16
View File
@@ -32,7 +32,7 @@ fn an_autofocus_box_self_acquires_on_the_first_event() {
.unwrap();
// No show transition has run for this box, so nothing owns the keyboard yet.
assert!(!s.has_keyboard_focus());
assert!(!s.eval::<bool>("return E:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name(), None);
// The first char self-acquires focus AND processes that same event.
assert!(
@@ -40,7 +40,7 @@ fn an_autofocus_box_self_acquires_on_the_first_event() {
"an autoFocus box consumes the acquiring event"
);
assert!(s.has_keyboard_focus());
assert!(s.eval::<bool>("return E:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name().as_deref(), Some("E"));
assert_eq!(s.eval::<String>("return E:GetText()").unwrap(), "a");
}
@@ -62,8 +62,9 @@ fn an_autofocus_box_takes_the_keyboard_when_it_is_shown() {
assert!(!s.has_keyboard_focus(), "hidden and unfocused to start");
s.run("E:Show()").unwrap();
assert!(
s.eval::<bool>("return E:HasFocus()").unwrap(),
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("E"),
"showing an autoFocus box focuses it",
);
@@ -108,18 +109,19 @@ fn the_show_focus_is_refused_without_autofocus_or_with_the_keyboard_taken() {
)
.unwrap();
s.run("LATE:Show()").unwrap();
assert!(
s.eval::<bool>("return HOLDER:HasFocus()").unwrap(),
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("Holder"),
"a shown autoFocus box does not steal a focus that is already held",
);
assert!(!s.eval::<bool>("return LATE:HasFocus()").unwrap());
// And hiding a box that does NOT hold the keyboard leaves it where it is — `0x77e410`'s own
// per-box guard (`cmp ecx,eax; jne ret`), which is what makes the override's unconditional
// tail-jmp harmless.
s.run("LATE:Hide()").unwrap();
assert!(
s.eval::<bool>("return HOLDER:HasFocus()").unwrap(),
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("Holder"),
"hiding an unfocused box does not clear somebody else's focus",
);
assert!(s.errors().is_empty(), "{:?}", s.errors());
@@ -179,7 +181,7 @@ fn set_focus_on_hidden_box_is_a_noop() {
"#,
)
.unwrap();
assert!(!s.eval::<bool>("return E:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name(), None);
assert!(!s.has_keyboard_focus());
assert_eq!(
s.eval::<i64>("return gained").unwrap(),
@@ -198,7 +200,7 @@ fn click_focuses_regardless_of_autofocus_and_transition_order_is_lost_then_gaine
local function wire(name, y)
local f = CreateFrame("EditBox", name)
f:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, y)
f:SetSize(100, 20)
f:SetWidth(100); f:SetHeight(20)
f:SetScript("OnEditFocusGained", function() table.insert(log, "gained"..name) end)
f:SetScript("OnEditFocusLost", function() table.insert(log, "lost"..name) end)
end
@@ -212,11 +214,11 @@ fn click_focuses_regardless_of_autofocus_and_transition_order_is_lost_then_gaine
// Neither box has autoFocus, yet a click focuses each.
s.mouse_button(50.0, 10.0, "LeftButton", true);
s.mouse_button(50.0, 10.0, "LeftButton", false);
assert!(s.eval::<bool>("return A:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name().as_deref(), Some("A"));
s.mouse_button(50.0, 110.0, "LeftButton", true);
s.mouse_button(50.0, 110.0, "LeftButton", false);
assert!(s.eval::<bool>("return B:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name().as_deref(), Some("B"));
let log: Vec<String> = s.eval("return log").unwrap();
assert_eq!(log, vec!["gainedA", "lostA", "gainedB"]);
@@ -473,8 +475,9 @@ fn enter_escape_tab_space_fire_their_slots() {
.unwrap();
assert!(s.key_input("ENTER")); // single-line → OnEnterPressed
assert!(s.key_input("ESCAPE")); // fires, does NOT release focus
assert!(
s.eval::<bool>("return E:HasFocus()").unwrap(),
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("E"),
"ESCAPE keeps focus"
);
assert!(s.key_input("TAB"));
@@ -1353,7 +1356,7 @@ fn creating_a_box_does_not_focus_it_the_way_showing_one_does() {
!s.has_keyboard_focus(),
"a box born visible has not been SHOWN, so it takes no keyboard",
);
assert!(!s.eval::<bool>("return E:HasFocus()").unwrap());
assert_eq!(s.focused_editbox_name(), None);
assert!(s.errors().is_empty(), "{:?}", s.errors());
}