plates take the mouse, Auctioneer loads, and the damage font is yours
- hovering a nameplate lights its unit and clicking one selects it, pfUI's click-through works, a drag that starts on a plate still turns the camera, and turning plates off with V clears them (2159) - Auctioneer's XML documents open (the reference has no XML namespaces), a .toc line reaching Blizzard_AuctionUI's templates resolves for Auctioneer, BeanCounter and SpecialTalentUI, and a missing include logs rather than raising the red error dialog (2155) - a .lua reached through <Script file=> carries the same Interface\AddOns\ chunk name as a .toc-listed one, so Ace2's debugstack split works and AtlasLoot keys its database; 26 more corpus addons load, 176 of 219 (2155) - the floating combat numbers draw in whatever DAMAGE_TEXT_FONT holds at world entry, so MikScrollingBattleText and pfUI pick the font (2156); a dodged swing prints no line and floats no word, and happiness leech lines print (2157) - an unknown frameStrata in XML logs and keeps the default instead of raising, so EQL3's log window loads; FrameXML_Debug is a real switch, so ImprovedErrorFrame loads (2160) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1317,6 +1317,15 @@ pub(super) fn click_button(lua: &Lua, id: u32, button: &str, down: bool, scripte
|
||||
.errors
|
||||
.push(e.to_string());
|
||||
}
|
||||
// **A nameplate's click is the engine's too** (decision 2148): the reference's plate overrides
|
||||
// the button click slot (`0x7cb910`) *and* chains the base, so its unit is selected whether the
|
||||
// click came from the pointer or from Lua's own `Click()` — this funnel is both. Recorded after
|
||||
// the handler, like the loot take below and for the same reason: an addon hook that errors
|
||||
// cannot silently eat the selection. A press (`down`) records nothing — the plate registers for
|
||||
// `LeftButtonUp | RightButtonUp` only (`RegisterForClicks(0x500)` at `0x7cb637`).
|
||||
if !down {
|
||||
super::nameplate::note_click(lua, id, button);
|
||||
}
|
||||
// …and then the take, unconditionally on the handler's outcome. `0x4c1867` is not guarded by
|
||||
// anything the Lua side did: the base call at `0x4c1833` returns void and its result is never
|
||||
// tested. A row whose `OnClick` errored still loots, which is the reference's behaviour and
|
||||
|
||||
@@ -61,6 +61,38 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
)?;
|
||||
g.set("IsMacClient", lua.create_function(|_, ()| Ok(Value::Nil))?)?;
|
||||
|
||||
// **`FrameXML_Debug([v])` — the XML loader's own trace switch, get-or-set** (decision 2160,
|
||||
// wow-re `ui/scratch/framexml-debug-trace-flag.md`). `0x488440` reads the global `[0xceea30]`
|
||||
// through `0x6edb40`, and:
|
||||
//
|
||||
// - a **Lua-truthy** argument takes the SET arm (`0x48845d je` after `lua_toboolean 0x6f34d0`)
|
||||
// — so `FrameXML_Debug(0)` genuinely disables it rather than being a masked no-op, because
|
||||
// the NUMBER zero is truthy in Lua; only `nil`/`false` are not;
|
||||
// - the stored value is `lua_tonumber` truncated **toward zero** (`0x40a2b0`), so `1.9` is 1
|
||||
// and a non-numeric string is 0, which is 5.0's `tonumber` coercion;
|
||||
// - an absent, nil or false argument is a pure GET and leaves the flag alone;
|
||||
// - it always returns ONE number — the flag's value *after* the call
|
||||
// (`re/audit/binding-shapes.tsv`: `argc 1 exact, returns 1, (number), agree`).
|
||||
//
|
||||
// The reference ships a call to it commented out in its own `BasicControls.xml:20`; the
|
||||
// corpus's consumer is `ImprovedErrorFrame`, which drives it off a saved `XMLDebug` CVar at
|
||||
// its OnLoad and died on the missing global. What it gates is
|
||||
// [`crate::loader::LoadReport::traces`].
|
||||
g.set(
|
||||
"FrameXML_Debug",
|
||||
lua.create_function(|lua, v: Value| {
|
||||
let model = lua.app_data_ref::<Model>().expect("model app_data");
|
||||
let truthy = !matches!(v, Value::Nil | Value::Boolean(false));
|
||||
if truthy {
|
||||
// `lua_tonumber`'s coercion, then truncate toward zero. Anything that will not
|
||||
// coerce is 0 — the same answer `0x6f3620` gives for a non-numeric argument.
|
||||
let n = lua.coerce_number(v)?.unwrap_or(0.0);
|
||||
model.framexml_debug.set(n.trunc() as i32);
|
||||
}
|
||||
Ok(model.framexml_debug.get())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// version, build, date — three, and no fourth (decision 1842)
|
||||
g.set(
|
||||
"GetBuildInfo",
|
||||
|
||||
@@ -115,7 +115,7 @@ pub use nameplate::{PlateGeometry, PlateState};
|
||||
mod model;
|
||||
mod modelframe;
|
||||
mod net_stats;
|
||||
mod object;
|
||||
pub(crate) mod object;
|
||||
pub use object::frame_kind_from_tag;
|
||||
mod party;
|
||||
mod pet;
|
||||
|
||||
@@ -125,6 +125,17 @@ pub(crate) struct Model {
|
||||
/// the same kind of thing — VM-global registries the loader fills.
|
||||
pub(crate) framexml_templates:
|
||||
std::cell::RefCell<std::collections::HashMap<String, crate::framexml::Element>>,
|
||||
/// **`FrameXML_Debug`'s flag** — the loader's own trace-severity switch (decision 2160).
|
||||
///
|
||||
/// `0x488440` is a get-or-set over the single global `[0xceea30]`, which boots at 0 and is
|
||||
/// read at six sites image-wide: the binding itself and five inside the XML loader, each
|
||||
/// gating a severity-0 trace line behind `flag > 0` (`0x6ee298 jle` — **greater than**, not
|
||||
/// non-zero, which is why this is signed). The one site walked to the bytes is
|
||||
/// `Instantiate 0x6ee280`, whose gated line is `0x871154 "-- Creating %s named %s"`.
|
||||
///
|
||||
/// Here rather than on `UiScript` for `framexml_templates`' reason: the loader runs from a
|
||||
/// bare `&Lua`, and so does the binding that writes this.
|
||||
pub(crate) framexml_debug: std::cell::Cell<i32>,
|
||||
/// The FrameXML **font-element registry** (a separate namespace — a font inherits a font,
|
||||
/// never a frame template), persisted for the same cross-file reason.
|
||||
pub(crate) framexml_fonts:
|
||||
@@ -1810,6 +1821,7 @@ impl Model {
|
||||
addons_saved_account: None,
|
||||
addons_saved_character: None,
|
||||
framexml_templates: Default::default(),
|
||||
framexml_debug: Default::default(),
|
||||
framexml_fonts: Default::default(),
|
||||
arena: WidgetArena::new(),
|
||||
layout_inputs: HashMap::new(),
|
||||
|
||||
@@ -184,6 +184,13 @@ struct Plate {
|
||||
last: Option<PlateState>,
|
||||
}
|
||||
|
||||
impl Plate {
|
||||
/// The unit this plate is currently bound to, or `None` while it sits in the pool.
|
||||
fn key(&self) -> Option<u64> {
|
||||
self.last.as_ref().map(|s| s.key)
|
||||
}
|
||||
}
|
||||
|
||||
/// The plate pool — grown on demand, **never shrunk**, in creation order.
|
||||
///
|
||||
/// The reference's pool is unbounded and its free list FIFO; what matters to an addon is only that
|
||||
@@ -198,10 +205,28 @@ pub(crate) struct NamePlates {
|
||||
/// `[unit+0xe60]`, from this side. A slot leaves this map only when its unit's plate is
|
||||
/// retired, and the slot itself never moves.
|
||||
assigned: std::collections::HashMap<u64, usize>,
|
||||
/// The reverse of the arena's own structure: which slot a frame handle is. Filled at
|
||||
/// creation and never removed (plates are never destroyed), so the pointer boundary and the
|
||||
/// click funnel can ask "is this frame a plate, and whose?" in one lookup.
|
||||
by_frame: std::collections::HashMap<FrameHandle, usize>,
|
||||
/// Completed clicks on plates, drained by the app each frame
|
||||
/// ([`UiScript::take_nameplate_clicks`]).
|
||||
clicks: Vec<NamePlateClick>,
|
||||
/// The geometry every live plate is currently laid out under.
|
||||
geometry: Option<PlateGeometry>,
|
||||
}
|
||||
|
||||
/// A completed click on a plate — what the reference's own `CGNamePlateFrame` click slot
|
||||
/// (`0x7cb910`) turns into a selection.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NamePlateClick {
|
||||
/// The unit whose plate was clicked ([`PlateState::key`]).
|
||||
pub key: u64,
|
||||
/// `"LeftButton"` / `"RightButton"` — the plate registers for both, on mouse-UP only
|
||||
/// (`RegisterForClicks(0x500)` at `0x7cb637`).
|
||||
pub button: String,
|
||||
}
|
||||
|
||||
/// What a sync produced that has to be fired **outside** the model borrow: the frames whose
|
||||
/// effective visibility flipped (their `OnShow`/`OnHide`), and the bars whose value moved (their
|
||||
/// `OnValueChanged`, which pfUI hooks — `nameplates.lua:393` — and reads `this:GetParent()` in).
|
||||
@@ -234,6 +259,69 @@ impl UiScript {
|
||||
}
|
||||
}
|
||||
|
||||
/// **The unit whose plate the pointer is inside**, or `None` — the reference's plate OnEnter
|
||||
/// (`0x7cb850`) publishing the mouseover global `[0xb4e2c8]`, read from the app's side instead
|
||||
/// of pushed.
|
||||
///
|
||||
/// The app needs this because a plate is real UI here: with the plate mouse-enabled, the UI
|
||||
/// pointer pass owns the cursor over it and `target::hover`'s world pick correctly stands
|
||||
/// down — so the mouseover has to come from the frame that took it. Exactly the hovered frame,
|
||||
/// never an ancestor: an addon frame parented to a plate takes the hover in the reference too,
|
||||
/// and its OnEnter is the one that fires.
|
||||
pub fn hovered_nameplate(&self) -> Option<u64> {
|
||||
let lua = self.lua();
|
||||
let model = lua.app_data_ref::<Model>().expect("model app_data");
|
||||
let frame = model.mouseover?;
|
||||
let slot = *model.nameplates.by_frame.get(&frame)?;
|
||||
model.nameplates.plates.get(slot)?.key()
|
||||
}
|
||||
|
||||
/// Drain the completed plate clicks — the app turns each into a selection, which is what the
|
||||
/// reference's own click slot does. Both a physical click and Lua's `plate:Click("LeftButton")`
|
||||
/// arrive here, because both go through the one click funnel.
|
||||
pub fn take_nameplate_clicks(&mut self) -> Vec<NamePlateClick> {
|
||||
let lua = self.lua();
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
std::mem::take(&mut model.nameplates.clicks)
|
||||
}
|
||||
|
||||
/// **The mouselook toggle** (`0x60f830`): entering camera freelook disables mouse input on
|
||||
/// every plate, leaving re-enables it — the reference walks its own intrusive plate list at
|
||||
/// `ds:0xc4d92c` doing exactly this, called from `0x483e80` (enter) and `0x483e70` (leave).
|
||||
///
|
||||
/// Without it a right-drag that starts over a plate would be a plate click instead of a camera
|
||||
/// turn, and the plates would keep taking a pointer that is no longer on screen.
|
||||
pub fn set_nameplate_mouse(&mut self, enabled: bool) {
|
||||
let lua = self.lua();
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let frames: Vec<FrameHandle> = model.nameplates.plates.iter().map(|p| p.frame).collect();
|
||||
for frame in frames {
|
||||
model.arena.set_mouse_enabled(frame, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Retire every live plate** — hide them all and return them to the pool, without touching
|
||||
/// the geometry they are laid out under.
|
||||
///
|
||||
/// The app's every early return owes this: a painter that stopped drawing left no plates
|
||||
/// behind, but widgets stay until something hides them, so a V press that turns plates off, a
|
||||
/// frame with no camera, or a world exit would otherwise leave the last frame's plates standing
|
||||
/// on screen. It is `0x608a10` applied to the whole active list, which is what the reference
|
||||
/// does when the master toggle clears.
|
||||
pub fn retire_nameplates(&mut self) {
|
||||
let lua = self.lua();
|
||||
let effects = {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let mut effects = SyncEffects::default();
|
||||
model.nameplates.assigned.clear();
|
||||
for i in 0..model.nameplates.plates.len() {
|
||||
Plate::retire(&mut model, i, &mut effects);
|
||||
}
|
||||
effects
|
||||
};
|
||||
super::event::fire_visibility_changes(lua, effects.visibility);
|
||||
}
|
||||
|
||||
/// How many plate widgets the pool holds — the plate driver's census, and the tests'.
|
||||
pub fn nameplate_pool_len(&self) -> usize {
|
||||
let lua = self.lua();
|
||||
@@ -242,6 +330,26 @@ impl UiScript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a completed click on `id` if that frame is a live plate — [`super::button::click_button`]
|
||||
/// calls this for every button click, physical or scripted.
|
||||
pub(super) fn note_click(lua: &mlua::Lua, id: u32, button: &str) {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
let Some(frame) = model.id_to_frame.get(&id).copied() else {
|
||||
return;
|
||||
};
|
||||
let Some(&slot) = model.nameplates.by_frame.get(&frame) else {
|
||||
return;
|
||||
};
|
||||
// A pooled (retired) plate has no unit, and clicking one is not possible anyway — it is hidden.
|
||||
let Some(key) = model.nameplates.plates.get(slot).and_then(Plate::key) else {
|
||||
return;
|
||||
};
|
||||
model.nameplates.clicks.push(NamePlateClick {
|
||||
key,
|
||||
button: button.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
/// The whole per-frame drive: grow, re-lay-out if the window moved, write what changed, retire the
|
||||
/// tail.
|
||||
fn sync(
|
||||
@@ -321,20 +429,13 @@ impl Plate {
|
||||
.unwrap_or_default();
|
||||
model.arena.set_frame_strata(frame, strata);
|
||||
model.arena.set_frame_level(frame, PLATE_LEVEL, true);
|
||||
// **The mouse bit stays OFF, and that is a deliberate gap, not an omission** (2148 §8).
|
||||
// The reference's plate is mouse-enabled — hovering it publishes the mouseover
|
||||
// (`0x7cb850` → `0x492890`) and a click selects through the button's own click slot. On
|
||||
// this engine, a mouse-enabled frame under the cursor is what makes `PointerOverUi` true,
|
||||
// and `target::hover::update_hover` returns early on exactly that: setting the bit without
|
||||
// an engine-side hover/click seam takes the hover and gives nothing back — no model
|
||||
// brighten, no lit bar, and no selection, because the click stops at the UI too. The seam
|
||||
// comes first; then the bit, in the same change.
|
||||
// `a_plate_does_not_yet_claim_the_ui_pointer` is the tripwire.
|
||||
//
|
||||
// It has to be turned OFF explicitly: a `Button` is born mouse-enabled here, faithfully —
|
||||
// `CSimpleButton`'s ctor writes `[+0xcc] = 0x4` (`0x7786a3`), which is why the plate needs
|
||||
// no `EnableMouse` of its own in the reference.
|
||||
model.arena.set_mouse_enabled(frame, false);
|
||||
// The plate takes the mouse, which it is born with: `CSimpleButton`'s ctor writes
|
||||
// `[+0xcc] = 0x4` (`0x7786a3`), so the reference's plate needs no `EnableMouse` of its own
|
||||
// and neither does ours. Hovering it publishes the mouseover (`0x7cb850` → `0x492890`) and
|
||||
// a completed click selects through the button's click slot (`0x7cb910`); both reach the
|
||||
// app through [`UiScript::hovered_nameplate`] and [`UiScript::take_nameplate_clicks`].
|
||||
// `EnableMouse`/`IsMouseEnabled` are in the corpus's own call set, and pfUI's vanilla
|
||||
// click-through block drives them.
|
||||
|
||||
let border = texture(model, frame, DrawLayer::Artwork, BORDER_TEXTURE);
|
||||
let glow = texture(model, frame, DrawLayer::Highlight, GLOW_TEXTURE);
|
||||
@@ -374,6 +475,11 @@ impl Plate {
|
||||
.visibility
|
||||
.extend(model.arena.set_shown(frame, false));
|
||||
|
||||
model
|
||||
.nameplates
|
||||
.by_frame
|
||||
.insert(frame, model.nameplates.plates.len());
|
||||
|
||||
Plate {
|
||||
frame,
|
||||
border,
|
||||
|
||||
@@ -262,7 +262,7 @@ fn enum_token(s: &str) -> String {
|
||||
/// The reference's strata NAME table (`0x8119f8`) has eight rows, `BACKGROUND`..`TOOLTIP`; stratum
|
||||
/// 0 (`WORLD`) has no name and no XML or Lua can put a frame there — the WorldFrame's constructor
|
||||
/// is its only writer (decision 1984, wow-re `worldframe-widget.md` §4).
|
||||
fn strata_from_str(s: &str) -> Option<Strata> {
|
||||
pub(crate) fn strata_from_str(s: &str) -> Option<Strata> {
|
||||
Some(match enum_token(s).as_str() {
|
||||
"BACKGROUND" => Strata::Background,
|
||||
"LOW" => Strata::Low,
|
||||
|
||||
@@ -456,34 +456,97 @@ fn an_addon_frame_under_the_worldframe_is_not_a_plate() {
|
||||
);
|
||||
}
|
||||
|
||||
/// **The plate does NOT claim the UI pointer — not yet, and deliberately.**
|
||||
///
|
||||
/// The reference's plate is a mouse-enabled `Button`: hovering it publishes the mouseover
|
||||
/// (`0x7cb850` OnEnter → `0x492890`) and a click selects the unit through the button's own click
|
||||
/// slot. benilla cannot simply set that bit, because on this engine the UI pointer pass owning a
|
||||
/// frame is what makes `PointerOverUi` true, and `target::hover::update_hover` returns early on
|
||||
/// exactly that — so a mouse-enabled plate with no engine-side seam takes the hover and gives
|
||||
/// nothing back: no model brighten, no lit bar, and no selection on click, because the click never
|
||||
/// reaches the world path either.
|
||||
///
|
||||
/// So the bit stays off until the seam exists (2148 §8), and this test is the tripwire: turning it
|
||||
/// on without wiring the hover and click through the engine fails here, in the ABI suite, rather
|
||||
/// than under the director's cursor.
|
||||
/// **The plate takes the mouse, and hands its unit back.** It is a `Button` and born mouse-enabled
|
||||
/// (`CSimpleButton`'s ctor writes `[+0xcc] = 0x4`), so hovering it makes it the mouse focus — which
|
||||
/// on this engine means the UI pointer pass owns the cursor and the world pick stands down. What
|
||||
/// keeps the mouseover alive is the seam: the widget layer answers WHICH unit's plate the pointer
|
||||
/// is inside, exactly as the reference's OnEnter (`0x7cb850`) publishes `[0xb4e2c8]`.
|
||||
#[test]
|
||||
fn a_plate_does_not_yet_claim_the_ui_pointer() {
|
||||
fn hovering_a_plate_names_its_unit() {
|
||||
let mut s = vm();
|
||||
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
|
||||
let wolf = plate("Wolf", 30.0, 40.0);
|
||||
let key = wolf.key;
|
||||
drive(&mut s, &[wolf]);
|
||||
|
||||
assert_eq!(s.hovered_nameplate(), None, "nothing hovered yet");
|
||||
// The plate's TOP-CENTRE is (500, 400) and it hangs 32 units below, so this is its middle.
|
||||
// `mouse_move` speaks the same y-up FrameXML space the anchors do.
|
||||
//
|
||||
// The WorldFrame itself IS a legitimate hit here — it is mouse-enabled by construction, and
|
||||
// the app filters exactly that one id out (`input::feed_ui_input`, decision 1983) so camera
|
||||
// look, world clicks and hover targeting stay live over it. So the assertion mirrors the
|
||||
// app's own filter: whatever the pointer lands on must be the WorldFrame, never a plate.
|
||||
let focus = s.mouse_move(500.0, 384.0);
|
||||
s.mouse_move(500.0, 384.0);
|
||||
assert_eq!(s.hovered_nameplate(), Some(key));
|
||||
// Off the plate: the WorldFrame takes the focus again, and no plate is named.
|
||||
s.mouse_move(50.0, 50.0);
|
||||
assert_eq!(s.hovered_nameplate(), None);
|
||||
}
|
||||
|
||||
/// A completed click on a plate reaches the app — the reference's click slot (`0x7cb910`), whose
|
||||
/// tail is the same `SetSelection` a click on the body runs.
|
||||
///
|
||||
/// **Mouse-UP only**: the plate registers `LeftButtonUp | RightButtonUp` (`RegisterForClicks(0x500)`
|
||||
/// at `0x7cb637`), so a press alone selects nothing.
|
||||
#[test]
|
||||
fn a_completed_click_on_a_plate_reaches_the_app() {
|
||||
let mut s = vm();
|
||||
let wolf = plate("Wolf", 30.0, 40.0);
|
||||
let key = wolf.key;
|
||||
drive(&mut s, &[wolf]);
|
||||
s.mouse_move(500.0, 384.0);
|
||||
|
||||
s.mouse_button(500.0, 384.0, "LeftButton", true);
|
||||
assert!(
|
||||
focus.is_none_or(|id| s.is_world_frame(id)),
|
||||
"a plate claimed the UI pointer: the hover and click seams (2148 §8) have to land in the \
|
||||
same change that enables the mouse, or plate hover and click-select both die"
|
||||
s.take_nameplate_clicks().is_empty(),
|
||||
"a press is not a click: the plate fires on the UP edge only"
|
||||
);
|
||||
s.mouse_button(500.0, 384.0, "LeftButton", false);
|
||||
let clicks = s.take_nameplate_clicks();
|
||||
assert_eq!(clicks.len(), 1);
|
||||
assert_eq!(clicks[0].key, key);
|
||||
assert_eq!(clicks[0].button, "LeftButton");
|
||||
assert!(
|
||||
s.take_nameplate_clicks().is_empty(),
|
||||
"drained, not repeated"
|
||||
);
|
||||
}
|
||||
|
||||
/// pfUI's click-through calls `plate:Click("LeftButton")` (`nameplates.lua:1274`), and
|
||||
/// CustomNameplates and `_Nameplates` do the same. A scripted click has to select the unit like a
|
||||
/// physical one — in the reference both go through the button's one click slot, and here they go
|
||||
/// through the one funnel that slot's twin lives on.
|
||||
#[test]
|
||||
fn a_scripted_click_selects_too() {
|
||||
let mut s = vm();
|
||||
let wolf = plate("Wolf", 30.0, 40.0);
|
||||
let key = wolf.key;
|
||||
drive(&mut s, &[wolf]);
|
||||
s.run(r#"WorldFrame:GetChildren():Click("RightButton")"#)
|
||||
.unwrap();
|
||||
let clicks = s.take_nameplate_clicks();
|
||||
assert_eq!(clicks.len(), 1);
|
||||
assert_eq!(clicks[0].key, key);
|
||||
assert_eq!(clicks[0].button, "RightButton");
|
||||
}
|
||||
|
||||
/// **The mouselook toggle** (`0x60f830`): entering freelook hands the mouse back on every plate,
|
||||
/// leaving takes it again — the reference walks its own intrusive plate list doing exactly this,
|
||||
/// from `0x483e80` (enter) and `0x483e70` (leave). Without it a right-drag that begins over a plate
|
||||
/// would be a plate click instead of a camera turn.
|
||||
#[test]
|
||||
fn freelook_hands_the_mouse_back() {
|
||||
let mut s = vm();
|
||||
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
|
||||
s.mouse_move(500.0, 384.0);
|
||||
assert!(s.hovered_nameplate().is_some());
|
||||
|
||||
s.set_nameplate_mouse(false);
|
||||
s.mouse_move(500.0, 384.1);
|
||||
assert_eq!(
|
||||
s.hovered_nameplate(),
|
||||
None,
|
||||
"a plate must not hold the pointer while the camera does"
|
||||
);
|
||||
|
||||
s.set_nameplate_mouse(true);
|
||||
s.mouse_move(500.0, 384.0);
|
||||
assert!(
|
||||
s.hovered_nameplate().is_some(),
|
||||
"and it takes it back on leave"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user