diff --git a/crates/ui-egui/src/chrome/control_bar.rs b/crates/ui-egui/src/chrome/control_bar.rs index ecd43ce55d..cd833c4a36 100644 --- a/crates/ui-egui/src/chrome/control_bar.rs +++ b/crates/ui-egui/src/chrome/control_bar.rs @@ -392,15 +392,17 @@ const FLOAT_WIDTH: f32 = 800.0; const DOCK_ZONE: f32 = 24.0; /// The key of a gripper drag in progress: where the pointer holds the bar from its top-left -/// corner. +/// corner, and the gripper's widget id. fn move_id() -> egui::Id { egui::Id::unique("control_bar_gripper_move") } -/// Is the Control bar's gripper being dragged? An Escape then cancels the drag, and nothing else. -/// A drag left over from a bar hidden while the button was held ends with the button. +/// Is the Control bar's gripper being dragged, the frame that releases it included? An Escape then +/// cancels the drag, and nothing else. A drag left over from a bar hidden while the button was held +/// ends with the button. pub fn control_bar_moving(ctx: &egui::Context) -> bool { - ctx.input(|i| i.pointer.primary_down()) && ctx.data(|d| d.get_temp::(move_id()).is_some()) + let Some((_, gripper)) = ctx.data(|d| d.get_temp::<(egui::Vec2, egui::Id)>(move_id())) else { return false }; + ctx.input(|i| i.pointer.primary_down()) || ctx.drag_stopped_id() == Some(gripper) } /// Where a gripper release puts the Control bar. @@ -545,18 +547,18 @@ fn gripper(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens, bar: egui::Rect, d if resp.drag_started() && let Some(press) = ctx.input(|i| i.pointer.press_origin()) { - ctx.data_mut(|d| d.insert_temp(move_id(), press - bar.min)); + ctx.data_mut(|d| d.insert_temp(move_id(), (press - bar.min, resp.id))); } - let Some(grab) = ctx.data(|d| d.get_temp::(move_id())) else { return }; + let Some((grab, _)) = ctx.data(|d| d.get_temp::<(egui::Vec2, egui::Id)>(move_id())) else { return }; let canceled = ctx.input(|i| i.key_pressed(egui::Key::Escape) || !i.focused); if canceled || !(resp.dragged() || resp.drag_stopped()) { - ctx.data_mut(|d| d.remove::(move_id())); + ctx.data_mut(|d| d.remove::<(egui::Vec2, egui::Id)>(move_id())); return; } let window = ctx.content_rect(); let target = ctx.input(|i| i.pointer.latest_pos()).and_then(|at| release_at(at, grab, dock_room, window)); if resp.drag_stopped() { - ctx.data_mut(|d| d.remove::(move_id())); + ctx.data_mut(|d| d.remove::<(egui::Vec2, egui::Id)>(move_id())); match target { Some(Release::Edge(bottom)) if app.ui.control_bar_floating || bottom != app.ui.control_bar_bottom => { dock(app, CONTROL_BAR_EDGES[usize::from(bottom)]); diff --git a/crates/ui-egui/src/chrome/tests.rs b/crates/ui-egui/src/chrome/tests.rs index e26dcdc593..50c1cdcd0c 100644 --- a/crates/ui-egui/src/chrome/tests.rs +++ b/crates/ui-egui/src/chrome/tests.rs @@ -2515,11 +2515,40 @@ fn control_bar_drag_escape_restores_placement() { control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(from.lerp(to, k as f32 / 4.0))]); } assert!(super::control_bar_moving(&ctx), "the drag goes on"); - let mut out = ctx.run_ui(egui::RawInput { events: vec![press_at(to, false)], screen_rect: Some(DOCK_SCREEN), ..Default::default() }, |_| {}); - out.textures_delta.clear(); + for events in [vec![press_at(to, false)], vec![]] { + let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(DOCK_SCREEN), ..Default::default() }, |_| {}); + out.textures_delta.clear(); + } assert!(!super::control_bar_moving(&ctx), "released with the bar hidden, the drag is over"); } +/// An Escape in the same frame as the gripper's release is the drag's: the frame's shortcuts, which +/// run before the bar is drawn, still see the drag, and the bar stays where it was. +#[test] +fn control_bar_drag_escape_on_release_frame_is_the_drags() { + let mut app = crate::VectorcraftApp::new(Session::new(), Default::default()); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let (from, to) = (docked_grip(false), egui::pos2(400.0, 150.0)); + control_bar_frame(&mut app, &ctx, vec![]); + control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(from), press_at(from, true)]); + for k in 1..=4 { + control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(from.lerp(to, k as f32 / 4.0))]); + } + let escape = egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }; + let mut moving = None; + let input = egui::RawInput { events: vec![escape, press_at(to, false)], screen_rect: Some(DOCK_SCREEN), ..Default::default() }; + let mut out = ctx.run_ui(input, |ui| { + moving = Some(super::control_bar_moving(ui.ctx())); + super::control_bar(&mut app, ui); + }); + out.textures_delta.clear(); + assert_eq!(moving, Some(true), "the release frame's shortcuts see the drag"); + assert!(!app.ui.control_bar_floating && !app.ui.control_bar_bottom, "the canceled drag leaves the bar at the top"); + control_bar_frame(&mut app, &ctx, vec![]); + assert!(!super::control_bar_moving(&ctx), "the drag is over"); +} + /// A gripper drag released outside the window leaves the bar where it was. #[test] fn control_bar_drag_release_outside_window_keeps_previous_placement() {