diff --git a/crates/benilla-ui/src/script/cursor/drag.rs b/crates/benilla-ui/src/script/cursor/drag.rs index f3a2635c..29a84fbd 100644 --- a/crates/benilla-ui/src/script/cursor/drag.rs +++ b/crates/benilla-ui/src/script/cursor/drag.rs @@ -183,6 +183,101 @@ mod tests { assert!(s.errors().is_empty(), "{:?}", s.errors()); } + /// A frame whose every mouse script appends to `log`, registered for a LeftButton drag. + fn logged_drag_frame() -> UiScript { + let mut s = drag_script(); + s.run( + r#" + log = {} + local function note(name) return function() table.insert(log, name) end end + local a = CreateFrame("Frame", "A") + a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(400); a:SetHeight(600); a:EnableMouse(true) + a:RegisterForDrag("LeftButton") + a:SetScript("OnMouseDown", note("down")) + a:SetScript("OnMouseUp", note("up")) + a:SetScript("OnDragStart", note("dragstart")) + a:SetScript("OnDragStop", note("dragstop")) + a:SetScript("OnReceiveDrag", note("receive")) + "#, + ) + .unwrap(); + s.resolve(); + s + } + + fn log(s: &UiScript) -> String { + s.eval::("return table.concat(log, ' ')").unwrap() + } + + /// `0x76c040` on a release for a drag-registered button whose drag has started clears the arm, + /// fires `OnDragStop` (`0x76c06e`) and the hover frame's drop (`0x76c081`) and returns 1 + /// (`0x76c08f`), never reaching its `OnMouseUp` (`0x76c099`). + #[test] + fn a_drag_release_fires_dragstop_and_no_mouseup() { + let mut s = logged_drag_frame(); + s.mouse_button(100.0, 300.0, "LeftButton", true); + s.mouse_move(130.0, 300.0); // 30 units: past the threshold + s.mouse_button(130.0, 300.0, "LeftButton", false); + assert_eq!(log(&s), "down dragstart dragstop receive"); + assert!(s.errors().is_empty(), "{:?}", s.errors()); + } + + /// A press that never crossed the threshold falls through to `OnMouseUp` (`0x76c067` je). + #[test] + fn a_plain_click_on_a_drag_frame_fires_mouseup() { + let mut s = logged_drag_frame(); + s.mouse_button(100.0, 300.0, "LeftButton", true); + s.mouse_button(100.0, 300.0, "LeftButton", false); + assert_eq!(log(&s), "down up"); + } + + /// The gate `0x76c050` tests the released button against the frame's registered buttons: a + /// frame registered for LeftButton drag still gets `OnMouseUp` for a RightButton release. + #[test] + fn a_button_not_registered_for_drag_still_fires_mouseup() { + let mut s = logged_drag_frame(); + s.mouse_button(100.0, 300.0, "RightButton", true); + s.mouse_move(130.0, 300.0); + s.mouse_button(130.0, 300.0, "RightButton", false); + assert_eq!(log(&s), "down up"); + } + + /// A stock resize grip starts sizing in `OnMouseDown` and is not drag-registered, so its + /// `OnMouseUp` (which stops the sizing) fires even after the cursor left it. + #[test] + fn a_resize_grip_still_gets_mouseup() { + let mut s = drag_script(); + s.run( + r#" + ups = 0 + local f = CreateFrame("Frame", "Sized", UIParent) + f:SetResizable(true) + f:SetPoint("BOTTOMLEFT", 100, 100); f:SetWidth(200); f:SetHeight(100) + local g = CreateFrame("Button", "Grip", f) + g:SetPoint("BOTTOMRIGHT", 0, 0); g:SetWidth(16); g:SetHeight(16) + g:EnableMouse(true) + g:SetScript("OnMouseDown", function() Sized:StartSizing("BOTTOMRIGHT") end) + g:SetScript("OnMouseUp", function() ups = ups + 1; Sized:StopMovingOrSizing() end) + "#, + ) + .unwrap(); + s.resolve(); + s.mouse_button(295.0, 105.0, "LeftButton", true); + s.mouse_move(395.0, 55.0); // dragged 100 right, 50 down, off the grip + s.mouse_button(395.0, 55.0, "LeftButton", false); + s.resolve(); + assert_eq!(s.eval::("return ups").unwrap(), 1); + assert_eq!(s.eval::("return Sized:GetWidth()").unwrap(), 300.0); + s.mouse_move(500.0, 300.0); + s.resolve(); + assert_eq!( + s.eval::("return Sized:GetWidth()").unwrap(), + 300.0, + "the sizing ended on the release" + ); + assert!(s.errors().is_empty(), "{:?}", s.errors()); + } + #[test] fn drag_not_started_leaves_the_ordinary_click_path_untouched() { let mut s = drag_script(); diff --git a/crates/benilla-ui/src/script/pointer.rs b/crates/benilla-ui/src/script/pointer.rs index ee687bc5..6cce9292 100644 --- a/crates/benilla-ui/src/script/pointer.rs +++ b/crates/benilla-ui/src/script/pointer.rs @@ -464,9 +464,34 @@ impl UiScript { // the hover frame, so a press on A released over B fires `OnMouseUp` on A, and a press // that captured nothing fires nothing (`0x766498`). A resize grip dragged out from under // the cursor still gets its `OnMouseUp`, so its resize ends. - let script = if down { "OnMouseDown" } else { "OnMouseUp" }; - if let Some(id) = if down { press_id } else { captured_id } { - if let Err(e) = event::fire_widget_handler(&self.lua, id, script, vec![btn.clone()]) { + // + // The frame's mouse-up `0x76c040` runs the drag leg first: a release of a button the frame + // is registered to drag (`0x76c050`), with the drag started (`0x76c055`), fires + // `OnDragStop` (`0x76c06e`) and the hover frame's `OnReceiveDrag` (`0x76c081`) and returns + // 1 (`0x76c08f`) before the `OnMouseUp` leg (`0x76c099`). So a drag release fires no + // `OnMouseUp`; a release of a button the frame does not drag, or of a drag that never + // started, still does. + if down { + if let Some(id) = press_id { + if let Err(e) = + event::fire_widget_handler(&self.lua, id, "OnMouseDown", vec![btn.clone()]) + { + self.push_error(e); + } + } + } else if let Some(release) = drag_release.as_ref().filter(|r| r.started) { + self.fire_drag_stop(release.source); + if let Some(id) = hit_id { + if let Err(e) = + event::fire_widget_handler(&self.lua, id, "OnReceiveDrag", Vec::new()) + { + self.push_error(e); + } + } + } else if let Some(id) = captured_id { + if let Err(e) = + event::fire_widget_handler(&self.lua, id, "OnMouseUp", vec![btn.clone()]) + { self.push_error(e); } } @@ -523,16 +548,6 @@ impl UiScript { if let Some(source) = abandoned { self.fire_drag_stop(source); } - if let Some(release) = drag_release.filter(|r| r.started) { - self.fire_drag_stop(release.source); - if let Some(id) = hit_id { - if let Err(e) = - event::fire_widget_handler(&self.lua, id, "OnReceiveDrag", Vec::new()) - { - self.push_error(e); - } - } - } if let Some(id) = click_id { // Shared with `Click()`: gated on the Button being enabled; a CheckButton toggles // before `OnClick` fires. @@ -554,7 +569,7 @@ impl UiScript { } /// The Button release slot `0x7792d0` past its base (`0x76c040`: `OnMouseUp`, which the caller - /// has fired), on the frame that took the press: a PUSHED button (`0x7792fc`) with the cursor + /// has fired; a started drag returns 1 there and never reaches this), on the frame that took the press: a PUSHED button (`0x7792fc`) with the cursor /// inside its own rect (`0x779335`), not "the topmost frame under the cursor", and registered /// for this button's Up (`0x77934e`) fires `OnDoubleClick` (`0x77938d`) or `OnClick` /// (`0x7793a9`); only then does the tail set NORMAL (`0x7793de`) if the button is unlocked and