the numerals kit goes stock, and a chat link is its own mouse target
- the status-bar numerals kit (`TextStatusBar.xml` + `.lua`) is the reference's own, and the attempt found a class of silent failure: `TextStatusBar_Initialize()` takes no argument in 1.12 and acts on `this`; ours took an optional bar, our unit frames passed one, and the reference's version ignores it — so the numerals would simply never appear, no error, nothing missing; seventeen of our signatures differ from the reference's that way (Lua drops a surplus argument in silence), written down before their files migrate, and the three "ours narrower" ones — `StaticPopup_Hide(which, data)`, `StaticPopup_FindVisible`, `WorldMapButton_OnClick(button, frame)` — closed now, because that direction bites when a STOCK file arrives and calls our version
- a chat link needs no mouse on its frame: wow-re read the client's ctors as mouse-enabling Button, CheckButton, Slider, ColorSelect and EditBox and NOT the scroll frames; the objection ("something makes a 1.12 chat link clickable") resolved as the engine synthesising one mouse-enabled `CSimpleHyperlinkButton` per link span (`0x7a3240`) — so a link span is a hit-test candidate on its own, the chat window is inert everywhere else, and the wheel is its own predicate rather than a borrowed mouse bit; seven `*ScrollFrame` known divergences retire
- the relic fit rule: `IsValidForSlot`'s slot-17 leg is an EQUALITY — `(InventoryType == RELIC) == hasRelicSlot` — so a relic lights the ranged slot for a druid and a bow does not, on the bag bar and the paper doll alike; closing it half-right would have lit the highlight for a druid dragging a bow
- a chain `.xml` whose `.lua` never loaded: `MoneyInputFrame.xml` had been on the chain without its `.lua`, so the template loaded and all ten `MoneyInputFrame_*` verbs read nil, silently — a gate now fails any chain `.xml` whose `.lua` sibling is neither sourced nor listed; the gap report's LOAD column carries the probe's collision caveat, its fx column stops crying wolf (it follows `<Script file=>`, stops reading `.Field(` as a global, subtracts a file's own locals), and a window is its xml PLUS the lua it sources
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1115,7 +1115,7 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// a property of the SLOT, not of the player. It pairs with `UnitHasRelicSlot("player")`,
|
||||
// which is the class half, and stock reads the two as a conjunction
|
||||
// (`PaperDollFrame.lua:680`/`:744`). This comment used to say `UnitHasRelicSlot` is a
|
||||
// later-era concept, always false in vanilla; it is not — decision 1785.
|
||||
// later-era concept, always false in vanilla; it is not — decision 1796.
|
||||
g.set(
|
||||
"GetInventorySlotInfo",
|
||||
lua.create_function(|lua, name: String| {
|
||||
|
||||
@@ -101,6 +101,7 @@ mod model;
|
||||
mod modelframe;
|
||||
mod net_stats;
|
||||
mod object;
|
||||
pub use object::frame_kind_from_tag;
|
||||
mod party;
|
||||
mod pet;
|
||||
mod petition;
|
||||
|
||||
@@ -192,12 +192,20 @@ pub(super) fn draw_layer_name(l: DrawLayer) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget tag or `CreateFrame` type string → its [`FrameKind`], case- and separator-insensitive
|
||||
/// ([`enum_token`]). Public because it is the ONE mapping: `benilla-app`'s `frame_flag_gate` sweep
|
||||
/// needs it to ask the engine whether a tag is mouse-enabled by construction, and a second copy
|
||||
/// there is exactly what drifted.
|
||||
pub fn frame_kind_from_tag(s: &str) -> Option<FrameKind> {
|
||||
frame_kind_from_str(s)
|
||||
}
|
||||
|
||||
fn frame_kind_from_str(s: &str) -> Option<FrameKind> {
|
||||
Some(match enum_token(s).as_str() {
|
||||
"FRAME" => FrameKind::Frame,
|
||||
"BUTTON" => FrameKind::Button,
|
||||
"CHECKBUTTON" => FrameKind::CheckButton,
|
||||
// A real registered type (`0x4959a6`), not an alias for Button — decision 1788. Registering
|
||||
// A real registered type (`0x4959a6`), not an alias for Button — decision 1799. Registering
|
||||
// it AS a Button would load `LootFrame.xml` and leave every row dead, because 1.12 has no
|
||||
// Lua verb for "take slot N": `LootSlot` is the bind-confirm continuation, hard-wired to
|
||||
// `flag = 1`.
|
||||
|
||||
@@ -50,7 +50,7 @@ use crate::order;
|
||||
use crate::widget::FrameHandle;
|
||||
|
||||
use super::clip::{effective_clip, scroll_clip_sources};
|
||||
use super::{button, cursor, editbox, event, UiScript};
|
||||
use super::{button, cursor, editbox, event, Model, UiScript};
|
||||
|
||||
/// The double-click interval — **300 ms, VERIFIED off the bytes**, a hardcoded instruction
|
||||
/// immediate: `0x77937b 81 f9 2c 01 00 00 cmp ecx, 0x12c`, comparing `now − [this+0x334]` inside
|
||||
@@ -127,6 +127,15 @@ pub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether `(x, y)` falls inside one of this frame's hyperlink spans — the engine half of the
|
||||
/// reference's per-span `CSimpleHyperlinkButton` children.
|
||||
fn link_span_hit(model: &Model, fh: FrameHandle, x: f32, y: f32) -> bool {
|
||||
model
|
||||
.link_spans
|
||||
.get(&fh)
|
||||
.is_some_and(|spans| spans.iter().any(|(r, _, _)| point_in_rect(*r, x, y)))
|
||||
}
|
||||
|
||||
impl UiScript {
|
||||
/// Hit-test the cursor at `(x, y)` and return the **handle** of the captured frame (the
|
||||
/// topmost-drawn mouse-enabled, effective-visible frame whose rect contains the point **and**
|
||||
@@ -146,7 +155,22 @@ impl UiScript {
|
||||
let sorted = order::traversal(&model.arena);
|
||||
let scroll_sources = scroll_clip_sources(&model);
|
||||
order::hit_test(&sorted, |fh| {
|
||||
model.arena.is_mouse_enabled(fh)
|
||||
// Mouse-enabled, **or inside one of this frame's hyperlink spans**.
|
||||
//
|
||||
// That second half is how the reference makes a chat link clickable on a frame that
|
||||
// takes no mouse at all. `CSimpleMessageScrollFrame`'s ctor leaves `[+0xcc] = 0`, so
|
||||
// `ChatFrame1` is never in the hit-test index — instead the engine synthesises one
|
||||
// mouse-enabled `CSimpleHyperlinkButton` child PER SPAN (`0x7a3240`, born through the
|
||||
// `CSimpleButton` ctor) and routes the three hyperlink scripts back to the parent
|
||||
// (wow-re `mouse-enable-law.md` + the follow-up round `68987021`).
|
||||
//
|
||||
// We hold the spans as rects on the parent rather than as child frames, so the same
|
||||
// law expresses as a disjunct here. What it buys is everything the reference gets from
|
||||
// the split: the chat window is a hit candidate only over a LINK, so `GetMouseFocus()`
|
||||
// answers what is behind it elsewhere, its `movable="true"` +
|
||||
// `<TitleRegion setAllPoints="true"/>` stay inert so it drags by its tab and not its
|
||||
// body, and a hover over plain text reassigns to whatever is underneath.
|
||||
(model.arena.is_mouse_enabled(fh) || link_span_hit(&model, fh, x, y))
|
||||
&& model.resolved.get(&fh).is_some_and(|r| {
|
||||
point_in_rect(inset_rect(*r, model.arena.hit_rect_insets(fh)), x, y)
|
||||
})
|
||||
@@ -155,6 +179,26 @@ impl UiScript {
|
||||
})
|
||||
}
|
||||
|
||||
/// [`Self::hit_test`] over the frames that take the WHEEL — either bit, since a mouse-enabled
|
||||
/// frame with an `<OnMouseWheel>` is a candidate for both. Same rect and clip rules; only the
|
||||
/// enable predicate differs.
|
||||
fn hit_test_wheel(&self, x: f32, y: f32) -> Option<u32> {
|
||||
let model = self.model_ref();
|
||||
let sorted = order::traversal(&model.arena);
|
||||
let scroll_sources = scroll_clip_sources(&model);
|
||||
let fh = order::hit_test(&sorted, |fh| {
|
||||
(model.arena.is_mouse_wheel_enabled(fh)
|
||||
|| model.arena.is_mouse_enabled(fh)
|
||||
|| link_span_hit(&model, fh, x, y))
|
||||
&& model.resolved.get(&fh).is_some_and(|r| {
|
||||
point_in_rect(inset_rect(*r, model.arena.hit_rect_insets(fh)), x, y)
|
||||
})
|
||||
&& effective_clip(&model, &scroll_sources, fh)
|
||||
.is_none_or(|c| point_in_rect(c, x, y))
|
||||
})?;
|
||||
model.frame_to_id.get(&fh).copied()
|
||||
}
|
||||
|
||||
/// Hit-test the cursor at `(x, y)` and return the **id** of the captured frame, or `None` —
|
||||
/// [`Self::hit_test_frame`] with the id lookup every caller in the input path wants. The id is
|
||||
/// the same one used across this host (e.g. the layout [`crate::layout::Handle`]); the app can
|
||||
@@ -749,7 +793,13 @@ impl UiScript {
|
||||
/// [`UiScript::errors`].
|
||||
pub fn mouse_wheel(&mut self, x: f32, y: f32, delta: f32) {
|
||||
let target: Option<u32> = {
|
||||
let hit = self.hit_test(x, y);
|
||||
// **Wheel-enabled OR mouse-enabled**, not mouse-enabled alone. The reference's wheel
|
||||
// dispatcher `0x7664f0` walks its OWN index and calls the hit probe directly, ignoring
|
||||
// hover, capture and the mouse bit — `EnableMouse` is not a prerequisite
|
||||
// (wow-re `mouse-enable-law.md` §6). Ours went through the mouse hit test, which
|
||||
// coupled the two: correcting the ctor list to the reference's (ScrollFrame is inert
|
||||
// on the mouse bit) silently stopped every scroll pane from taking the wheel.
|
||||
let hit = self.hit_test_wheel(x, y);
|
||||
let model = self.model_ref();
|
||||
hit.and_then(|id| {
|
||||
let mut h = *model.id_to_frame.get(&id)?;
|
||||
|
||||
@@ -599,7 +599,7 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// **This shipped absent for a long time, on a claim that was simply false** — that the relic
|
||||
// slot post-dates 1.12 and `UnitHasRelicSlot` is a later-era verb. The base `dbc.MPQ` copy of
|
||||
// `ChrClasses.dbc` really does have no field 16; the `patch.MPQ` copy the client actually
|
||||
// reads has it, set for Paladin, Shaman and Druid. Decision 1785. Stock `PaperDollFrame.lua`
|
||||
// reads has it, set for Paladin, Shaman and Druid. Decision 1796. Stock `PaperDollFrame.lua`
|
||||
// calls this **unconditionally** at l.429 and l.580 — so while it was missing, the character
|
||||
// sheet raised `attempt to call global 'UnitHasRelicSlot' (a nil value)` for *every* class,
|
||||
// not just the three.
|
||||
|
||||
@@ -173,7 +173,7 @@ pub struct UnitState {
|
||||
/// which is the nil leg.
|
||||
///
|
||||
/// True for exactly Paladin, Shaman and Druid in 1.12 (Libram, Totem, Idol). It was believed
|
||||
/// here for a long time that no 1.12 class had one; see decision 1785 for how that survived.
|
||||
/// here for a long time that no 1.12 class had one; see decision 1796 for how that survived.
|
||||
pub has_relic_slot: bool,
|
||||
/// The unit's sex on the `UnitSex` scale: `2` male, `3` female (`1` = neuter/unknown). `0`
|
||||
/// (the unfilled default) reports as nil — the API's "can't tell", like [`Self::reaction`].
|
||||
|
||||
@@ -1295,7 +1295,7 @@ fn unit_is_party_leader_ors_two_legs_and_answers_one_when_solo() {
|
||||
/// `UnitHasRelicSlot` — the number 1 or nil, per token, never a boolean.
|
||||
///
|
||||
/// This shipped **absent** for months on the belief that the relic slot post-dates 1.12, which is
|
||||
/// false (decision 1785). Stock `PaperDollFrame.lua` calls it unconditionally at l.429 and l.580,
|
||||
/// false (decision 1796). Stock `PaperDollFrame.lua` calls it unconditionally at l.429 and l.580,
|
||||
/// so while it was missing the character sheet raised `attempt to call global` for every class —
|
||||
/// which is why the nil-global case is asserted here too, not just the answer.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user