mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 12:17:14 -06:00
Part of #4326: a drag whose mouse-up was lost ends once macOS reports no button down
egui ends a drag only on a button release or Escape. When the window never receives the mouse-up, a drag such as a dock column's resize keeps following the pointer with no button held. On macOS, each frame's input now gets a release for every button egui still holds when NSEvent.pressedMouseButtons reports no button down, at the pointer's last position. Frames that carry a button event, and control-channel input that holds a button between steps, are left as they are.
This commit is contained in:
@@ -46,6 +46,10 @@ impl eframe::App for App {
|
||||
#[cfg(windows)]
|
||||
windows_stylus::raw_input_hook(raw);
|
||||
file_access::unconfined(|| self.app.raw_input_hook(ctx, raw));
|
||||
// A mouse-up that never reached the window (#4326) is delivered once AppKit reports
|
||||
// that no mouse button is down.
|
||||
#[cfg(target_os = "macos")]
|
||||
self.app.release_lost_buttons(ctx, raw, objc2_app_kit::NSEvent::pressedMouseButtons() != 0);
|
||||
}
|
||||
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
|
||||
@@ -124,6 +124,38 @@ impl VectorcraftApp {
|
||||
self.synthetic_frame = false;
|
||||
}
|
||||
|
||||
/// Release the pointer buttons egui still holds down when the system reports that no mouse
|
||||
/// button is down (`system_held` is false), at the pointer's last position. egui ends a drag
|
||||
/// only on a button release or Escape, so a release the window never received leaves a drag,
|
||||
/// such as a dock column's resize, following the pointer (#4326). Frames that carry a button
|
||||
/// event, and control-channel input that holds a button between steps, are left alone.
|
||||
pub fn release_lost_buttons(&self, ctx: &egui::Context, raw: &mut egui::RawInput, system_held: bool) {
|
||||
if system_held
|
||||
|| self.synthetic_injected
|
||||
|| !self.synthetic.is_empty()
|
||||
|| raw.events.iter().any(|e| matches!(e, egui::Event::PointerButton { .. }))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let (down, pos, modifiers) = ctx.input_for(raw.viewport_id, |i| {
|
||||
let p = &i.pointer;
|
||||
let down: Vec<egui::PointerButton> = [
|
||||
egui::PointerButton::Primary,
|
||||
egui::PointerButton::Secondary,
|
||||
egui::PointerButton::Middle,
|
||||
egui::PointerButton::Extra1,
|
||||
egui::PointerButton::Extra2,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|b| p.button_down(*b))
|
||||
.collect();
|
||||
(down, p.latest_pos().or_else(|| p.interact_pos()).or_else(|| p.press_origin()), i.modifiers)
|
||||
});
|
||||
let Some(pos) = pos else { return };
|
||||
let releases = down.into_iter().map(|button| egui::Event::PointerButton { pos, button, pressed: false, modifiers });
|
||||
raw.events.splice(0..0, releases);
|
||||
}
|
||||
|
||||
/// Inject synthetic events (one press/release step, wheel turn or short key sequence per host
|
||||
/// iteration), carrying control provenance until a settled UI pass has taken the batch. The
|
||||
/// batch belongs to the one root viewport the shipped hosts draw (see
|
||||
|
||||
@@ -28,6 +28,19 @@ impl Harness {
|
||||
fn frame(&mut self, events: Vec<Event>) {
|
||||
self.time += 0.05;
|
||||
let input = egui::RawInput { time: Some(self.time), events, screen_rect: Some(SCREEN), ..Default::default() };
|
||||
self.run(input);
|
||||
}
|
||||
|
||||
/// A frame whose input first goes through [`VectorcraftApp::release_lost_buttons`], with the
|
||||
/// system reporting a mouse button down or not.
|
||||
fn frame_with_system_buttons(&mut self, events: Vec<Event>, system_held: bool) {
|
||||
self.time += 0.05;
|
||||
let mut input = egui::RawInput { time: Some(self.time), events, screen_rect: Some(SCREEN), ..Default::default() };
|
||||
self.app.release_lost_buttons(&self.ctx, &mut input, system_held);
|
||||
self.run(input);
|
||||
}
|
||||
|
||||
fn run(&mut self, input: egui::RawInput) {
|
||||
let app = &mut self.app;
|
||||
let mut out = self.ctx.run_ui(input, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
@@ -398,6 +411,44 @@ fn ui_docking_reports_the_layout_and_hit_rects() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Press on the main column's divider and drag it 60 pt toward the canvas, holding the button.
|
||||
fn press_on_the_main_column_divider(h: &mut Harness) -> f32 {
|
||||
let edge =
|
||||
hit_rects(&h.ctx).into_iter().filter(|(hit, _)| matches!(hit, Hit::Divider(_))).map(|(_, r)| r.center().x).fold(f32::NEG_INFINITY, f32::max);
|
||||
h.press_and_move(pos2(edge, 600.0), pos2(edge - 60.0, 600.0));
|
||||
dock_columns(&h.layout())[0].width
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_column_resize_whose_release_was_lost_ends_when_the_system_reports_no_button_down() {
|
||||
let mut h = Harness::new();
|
||||
let width = press_on_the_main_column_divider(&mut h);
|
||||
for x in [900.0, 1000.0, 850.0, 1100.0] {
|
||||
h.frame_with_system_buttons(vec![Event::PointerMoved(pos2(x, 600.0))], false);
|
||||
assert_eq!(dock_columns(&h.layout())[0].width, width, "the column followed the pointer to {x}");
|
||||
}
|
||||
assert!(!h.ctx.input(|i| i.pointer.any_down()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_column_resize_continues_while_the_system_reports_the_button_down() {
|
||||
let mut h = Harness::new();
|
||||
let width = press_on_the_main_column_divider(&mut h);
|
||||
h.frame_with_system_buttons(vec![Event::PointerMoved(pos2(900.0, 600.0))], true);
|
||||
assert_ne!(dock_columns(&h.layout())[0].width, width);
|
||||
assert!(h.ctx.input(|i| i.pointer.primary_down()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_channel_drags_keep_their_button_between_steps() {
|
||||
let mut h = Harness::new();
|
||||
h.frame(vec![Event::PointerMoved(pos2(800.0, 600.0)), button(pos2(800.0, 600.0), true)]);
|
||||
h.app.synthetic.push(button(pos2(820.0, 600.0), false));
|
||||
let mut input = egui::RawInput { events: vec![Event::PointerMoved(pos2(810.0, 600.0))], ..Default::default() };
|
||||
h.app.release_lost_buttons(&h.ctx, &mut input, false);
|
||||
assert_eq!(input.events, [Event::PointerMoved(pos2(810.0, 600.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_menu_panels_without_a_place_open_in_their_icon_group_or_float() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
Reference in New Issue
Block a user