OnClick fires on release while the button is still PUSHED, as in 1.12.1 (#923)
A Button release un-pushed the button before `OnMouseUp` and `OnClick` ran, so both read NORMAL. A plain click on a floating chat tab then took the pick-up branch and left the window following the cursor. In the reference, the mouse-up handler fires `OnMouseUp` first, then reads the state: a PUSHED button with the cursor inside it and the Up registration fires `OnDoubleClick` or `OnClick`, and only then is it set NORMAL, unless locked. A failed gate still un-pushes; an `OnMouseUp` that un-pushes cancels the click. The release now runs that order after `OnMouseUp`, with the started-drag skip kept. Tests read the state inside each handler and drive the stock chat tab. Fixes #699 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ff3d15b132
commit
d65fd19095
@@ -356,7 +356,7 @@ impl UiScript {
|
||||
drag_release,
|
||||
world_dropped,
|
||||
slider_jump,
|
||||
double_id,
|
||||
released,
|
||||
abandoned,
|
||||
color_jump,
|
||||
): (
|
||||
@@ -364,7 +364,7 @@ impl UiScript {
|
||||
Option<cursor::DragRelease>,
|
||||
bool,
|
||||
Option<(u32, f32)>,
|
||||
Option<u32>,
|
||||
Option<crate::widget::FrameHandle>,
|
||||
Option<crate::widget::FrameHandle>,
|
||||
Option<(u32, f64, f64, f64)>,
|
||||
) = {
|
||||
@@ -428,19 +428,6 @@ impl UiScript {
|
||||
}
|
||||
let release = cursor::take_drag(&mut model, button);
|
||||
let started = release.as_ref().is_some_and(|r| r.started);
|
||||
// The Button release slot `0x7792d0` clicks only a PUSHED button (`0x7792fc`) with
|
||||
// the cursor inside its own rect (`0x779335`), not "the topmost frame under the
|
||||
// cursor", read before the release edge below un-pushes it.
|
||||
let sources = scroll_clip_sources(&model);
|
||||
let may_click = pressed.filter(|&h| {
|
||||
button::release_may_click(&model, h) && frame_contains(&model, &sources, h, x, y)
|
||||
});
|
||||
// The release edge, `SetButtonState(NORMAL)` (`0x7793de`), on the pressed frame
|
||||
// wherever the cursor is, skipped after a started drag (`0x7792df` tests
|
||||
// `0x76c040`), which already un-pressed it.
|
||||
if let (Some(h), false) = (pressed, started) {
|
||||
button::edge(&mut model, h, ButtonState::on_mouse_up);
|
||||
}
|
||||
// The world drop: a completed left click on the world, never a drag release, which
|
||||
// keeps carrying (`0x495300` runs on the WorldFrame click release). The world is
|
||||
// `over_world`, not "no frame": the stock `WorldFrame` covers the screen.
|
||||
@@ -450,42 +437,9 @@ impl UiScript {
|
||||
&& over_world(&model, pressed)
|
||||
&& !capture_lost
|
||||
&& cursor::world_drop_click(&mut model);
|
||||
let wants = format!("{button}Up");
|
||||
let click = if started {
|
||||
None
|
||||
} else {
|
||||
may_click
|
||||
.filter(|&h| button::wants_click(&model, h, &wants))
|
||||
.and_then(|h| model.frame_to_id.get(&h).copied())
|
||||
};
|
||||
// The double click, on the release that qualified as a click: `0x77938d` in the
|
||||
// mouse-up dispatcher `0x7792d0` fires `OnDoubleClick` instead of the second
|
||||
// `OnClick` (`0x77939d jmp 0x7793b5` skips the single leg), only for a frame with
|
||||
// an `OnDoubleClick` script (`[+0x4d4] != 0`). What suppresses the click
|
||||
// suppresses it too.
|
||||
let target = click.and(may_click);
|
||||
let double = target.filter(|&h| {
|
||||
model
|
||||
.scripts
|
||||
.get(&h)
|
||||
.is_some_and(|s| s.contains("OnDoubleClick"))
|
||||
&& model
|
||||
.last_click
|
||||
.get(&h)
|
||||
.is_some_and(|&t| now - t <= DOUBLE_CLICK_SECS)
|
||||
});
|
||||
if let Some(h) = double {
|
||||
// A double clears the stamp (`0x779393`), so clicks pair: four fast clicks are
|
||||
// click, double, click, double.
|
||||
model.last_click.remove(&h);
|
||||
} else if let Some(h) = target {
|
||||
// A single stamps the detector (`0x7793af`) whether or not an `OnClick` script
|
||||
// is bound.
|
||||
model.last_click.insert(h, now);
|
||||
}
|
||||
let double_id = double.and_then(|h| model.frame_to_id.get(&h).copied());
|
||||
let click = if double_id.is_some() { None } else { click };
|
||||
(click, release, dropped, None, double_id, None, None)
|
||||
// The Button release slot `0x7792d0` (below, after `OnMouseUp`) does nothing on a
|
||||
// started drag (`0x7792df` tests `0x76c040`), which already un-pressed the button.
|
||||
(None, release, dropped, None, pressed.filter(|_| !started), None, None)
|
||||
}
|
||||
};
|
||||
// A track press's value jump fires `OnValueChanged` first, outside the borrow.
|
||||
@@ -590,13 +544,8 @@ impl UiScript {
|
||||
if let Some(h) = press_art {
|
||||
button::edge(&mut self.model_mut(), h, ButtonState::on_mouse_down);
|
||||
}
|
||||
// `OnDoubleClick(self, button)` in place of `OnClick` (`0x77938d call [edx+0x98]`), with
|
||||
// the same click sound: its firer `0x779650` is the twin of `OnClick`'s `0x779540` and
|
||||
// passes only the button name of the completing release.
|
||||
if let Some(id) = double_id {
|
||||
if let Err(e) = event::fire_widget_handler(&self.lua, id, "OnDoubleClick", vec![btn]) {
|
||||
self.push_error(e);
|
||||
}
|
||||
if let Some(h) = released {
|
||||
self.release_slot(h, button, (x, y), now, &btn);
|
||||
}
|
||||
// A world-frame hit is not the UI consuming the event: the reference runs its
|
||||
// `OnMouseDown` without consuming (`0x483c40`), then the `BUTTON1`/`BUTTON2` binding, the
|
||||
@@ -604,6 +553,74 @@ impl UiScript {
|
||||
hit_id.is_some_and(|id| !self.is_world_frame(id)) || world_dropped
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// not disabled (`0x7793c2`..`0x7793d4`), on every path that got past the pushed gate and
|
||||
/// wherever the cursor is. So a handler reads PUSHED, and what it sets (a lock, a disable)
|
||||
/// decides the tail. The state is read here, after `OnMouseUp`, so a script that un-pushes the
|
||||
/// button there cancels the click.
|
||||
fn release_slot(
|
||||
&mut self,
|
||||
h: crate::widget::FrameHandle,
|
||||
button: &str,
|
||||
(x, y): (f32, f32),
|
||||
now: f64,
|
||||
btn: &Value,
|
||||
) {
|
||||
let (click, double) = {
|
||||
let model = self.model_ref();
|
||||
if model.arena.frame(h).is_none() {
|
||||
return;
|
||||
}
|
||||
let sources = scroll_clip_sources(&model);
|
||||
let clicks = button::release_may_click(&model, h)
|
||||
&& frame_contains(&model, &sources, h, x, y)
|
||||
&& button::wants_click(&model, h, &format!("{button}Up"));
|
||||
// The double click: `0x77938d` fires `OnDoubleClick` instead of the second `OnClick`
|
||||
// (`0x77939d jmp 0x7793b5` skips the single leg), only for a frame with an
|
||||
// `OnDoubleClick` script (`[+0x4d4] != 0`) whose last click was within the interval.
|
||||
// What suppresses the click suppresses it too.
|
||||
let double = clicks
|
||||
&& model
|
||||
.scripts
|
||||
.get(&h)
|
||||
.is_some_and(|s| s.contains("OnDoubleClick"))
|
||||
&& model
|
||||
.last_click
|
||||
.get(&h)
|
||||
.is_some_and(|&t| now - t <= DOUBLE_CLICK_SECS);
|
||||
(clicks, double)
|
||||
};
|
||||
let id = self.model_ref().frame_to_id.get(&h).copied();
|
||||
if let (true, Some(id)) = (click, id) {
|
||||
if double {
|
||||
if let Err(e) =
|
||||
event::fire_widget_handler(&self.lua, id, "OnDoubleClick", vec![btn.clone()])
|
||||
{
|
||||
self.push_error(e);
|
||||
}
|
||||
} else {
|
||||
button::click_button(&self.lua, id, button, false, false);
|
||||
}
|
||||
// A double clears the stamp (`0x779393`), so clicks pair: four fast clicks are click,
|
||||
// double, click, double. A single stamps the detector (`0x7793af`) whether or not an
|
||||
// `OnClick` script is bound.
|
||||
let mut model = self.model_mut();
|
||||
if model.arena.frame(h).is_some() {
|
||||
if double {
|
||||
model.last_click.remove(&h);
|
||||
} else {
|
||||
model.last_click.insert(h, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The release edge, `SetButtonState(NORMAL)` (`0x7793de`).
|
||||
button::edge(&mut self.model_mut(), h, ButtonState::on_mouse_up);
|
||||
}
|
||||
|
||||
/// Fire `OnDragStop` on a gesture's source, the one place a started drag ends; a source that
|
||||
/// died since the press fires nothing.
|
||||
pub(super) fn fire_drag_stop(&mut self, source: crate::widget::FrameHandle) {
|
||||
|
||||
Reference in New Issue
Block a user