ui: GetCursorPosition answered 0,0, so nothing could drag or size a frame
`Model::cursor_pos` is read by the verb in two places and was written by nothing in the workspace: declared, initialised to the origin, never assigned. So `GetCursorPosition()` answered `0, 0` for every call of every session. That kills the universal drag-and-size idiom — stamp the cursor on `OnMouseDown`, subtract it each `OnUpdate`, size by the difference. Every delta came out zero, the frame was re-anchored to its own current size sixty times a second while the player dragged, and the addon's own "that was a click, not a drag" guard then declined to save. Nothing raises and nothing logs. grom reported it twice as OpenMapClassic's map window refusing to resize; the round before this one painted its invisible grips and left this underneath. Fed in `ui_script/input.rs` beside `set_mouse_buttons`, for the reason that one states: the pointer feed below is skipped for a cursor off the window and for a UI hidden by TOGGLEUI, and the reference answers through both. UI units, y-up from the bottom-left, undivided — UIParent is scale 1 here, so an addon's `x / frame:GetEffectiveScale()` lands in the frame's own units as on the reference. The test pins the whole idiom rather than the getter, because the getter answering *something* was never the question. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -153,6 +153,16 @@ pub(super) fn feed_ui_input(
|
||||
buttons.pressed(MouseButton::Other(4)),
|
||||
buttons.pressed(MouseButton::Other(5)),
|
||||
]);
|
||||
// `GetCursorPosition()`: the device's own position, in UI units as `GetLeft()` gives them.
|
||||
// Fed here rather than from the pointer feed below for the same reason the buttons are — that
|
||||
// one is skipped for a cursor off the window or a hidden UI, and the reference answers through
|
||||
// both. Nothing wrote it before, so the verb answered `0, 0` all session, and every addon that
|
||||
// sizes or drags a frame by stamping the cursor and subtracting it measured a delta of zero:
|
||||
// OpenMapClassic's map window could not be resized by its grips at all.
|
||||
if let Some(cursor) = window.cursor_position() {
|
||||
let s = super::seam_scale(window.height(), ui_scale.0);
|
||||
script.set_cursor_position(cursor.x / s, (window.height() - cursor.y) / s);
|
||||
}
|
||||
// ── Mouse ── A cursor off the window, or a UI hidden by TOGGLEUI, skips only the mouse feed.
|
||||
// The headless hover probe's aim stands in for a missing cursor, so `PointerOverUi` rises and
|
||||
// falls over a panel in an automated run as it does for a person; a real pointer always wins.
|
||||
|
||||
@@ -920,6 +920,25 @@ impl UiScript {
|
||||
model.mouse_buttons = down;
|
||||
}
|
||||
|
||||
/// Push the cursor's position behind `GetCursorPosition()`, in UI units y-up from the
|
||||
/// bottom-left — the space `GetLeft()`/`GetBottom()` answer in, since `UIParent` is scale 1
|
||||
/// here, so the universal `x / frame:GetEffectiveScale()` idiom lands in the frame's own
|
||||
/// units as it does on the reference.
|
||||
///
|
||||
/// Call it beside [`Self::set_mouse_buttons`], from the host's own cursor rather than the
|
||||
/// pointer feed: that one is skipped for a cursor off the window and for a UI hidden by
|
||||
/// TOGGLEUI, while `GetCursorPosition` answers through both.
|
||||
///
|
||||
/// Nothing wrote this before, so the verb answered `0, 0` for the whole session. It reads as
|
||||
/// a frame that cannot be dragged or sized: the idiom is to stamp the cursor on mouse-down
|
||||
/// and subtract it each `OnUpdate`, and a constant gives every frame a delta of zero.
|
||||
/// OpenMapClassic's map window is exactly that shape (`Map/Window.lua`'s `Grip`) and could
|
||||
/// not be resized at all.
|
||||
pub fn set_cursor_position(&mut self, x: f32, y: f32) {
|
||||
let mut model = self.model_mut();
|
||||
model.cursor_pos = (x, y);
|
||||
}
|
||||
|
||||
/// Push the player's WMO-containment state (the client's `0xceaa60`) onto every Minimap: it
|
||||
/// picks which persisted zoom index `GetZoom`/`SetZoom` act on. Call it before the script
|
||||
/// tick, on each inside/outside edge and whenever [`Self::minimap_widgets_created`] moves.
|
||||
|
||||
@@ -923,3 +923,61 @@ fn pfuis_nameplate_right_click_emulator_arms_and_then_synthesises_the_click() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// `GetCursorPosition()` answers the host's own cursor, in the units `GetLeft()` gives — the
|
||||
/// whole drag-and-size idiom, end to end.
|
||||
///
|
||||
/// The idiom is universal: stamp the cursor on `OnMouseDown`, subtract it each `OnUpdate`, size
|
||||
/// the frame by the difference. `Model::cursor_pos` had no writer at all, so the verb answered
|
||||
/// `0, 0` every call of every session, every delta came out zero, and a frame built this way
|
||||
/// could not be dragged or sized by one pixel. grom reported it as OpenMapClassic's map window
|
||||
/// refusing to resize (`Map/Window.lua`'s `Grip` is exactly this shape); nothing in the Lua was
|
||||
/// wrong, and no error was raised anywhere, because a constant cursor is a perfectly well-formed
|
||||
/// answer.
|
||||
#[test]
|
||||
fn get_cursor_position_drives_the_drag_to_size_idiom() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Sizer")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) f:SetWidth(200) f:SetHeight(100)
|
||||
start = nil
|
||||
function Grab()
|
||||
local x, y = GetCursorPosition()
|
||||
start = { x = x, y = y, w = f:GetWidth(), h = f:GetHeight() }
|
||||
end
|
||||
function Drag()
|
||||
local x, y = GetCursorPosition()
|
||||
local s = f:GetEffectiveScale()
|
||||
f:SetWidth(start.w + (x - start.x) / s)
|
||||
f:SetHeight(start.h + (y - start.y) / s)
|
||||
end
|
||||
function Size() return f:GetWidth(), f:GetHeight() end
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>("return GetCursorPosition()").unwrap(),
|
||||
(0.0, 0.0),
|
||||
"nothing fed yet, so the verb answers the origin"
|
||||
);
|
||||
|
||||
s.set_cursor_position(300.0, 250.0);
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>("return GetCursorPosition()").unwrap(),
|
||||
(300.0, 250.0),
|
||||
"UI units, y-up, undivided: UIParent is scale 1 here"
|
||||
);
|
||||
|
||||
s.run("Grab()").unwrap();
|
||||
s.set_cursor_position(360.0, 275.0);
|
||||
s.run("Drag()").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>("return Size()").unwrap(),
|
||||
(260.0, 125.0),
|
||||
"the frame grew by exactly the cursor's own travel"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user