diff --git a/crates/ui-egui/src/panel_docking/peek.rs b/crates/ui-egui/src/panel_docking/peek.rs index baf147f4fd..ed43b80e3d 100644 --- a/crates/ui-egui/src/panel_docking/peek.rs +++ b/crates/ui-egui/src/panel_docking/peek.rs @@ -41,6 +41,8 @@ struct Shown { edge: Edge, /// When the pointer left them; `None` while it is over them or has not moved since they showed. left_at: Option, + /// Whether a press, a modal dialog, a popup or a flyout held them open last frame. + held: bool, } /// The session's state of the panels shown from the edges. @@ -102,9 +104,11 @@ pub(super) fn update(app: &mut VectorcraftApp, ctx: &egui::Context, area: egui:: return None; } let (now, pointer, down, moved) = ctx.input(|i| { - // A release counts as moving away: a press may have held the panels while it left them. - let moved = - i.pointer.delta() != egui::Vec2::ZERO || i.pointer.any_released() || i.events.iter().any(|e| matches!(e, egui::Event::PointerGone)); + // A release counts as moving away: a press may have held the panels while it left them. A + // pointer entering the window has no delta on its first frame, so the move event counts too. + let moved = i.pointer.delta() != egui::Vec2::ZERO + || i.pointer.any_released() + || i.events.iter().any(|e| matches!(e, egui::Event::PointerMoved(_) | egui::Event::PointerGone)); (i.time, i.pointer.hover_pos(), i.pointer.any_down(), moved) }); // A workspace switched to while panels show may dock none on that side. @@ -123,11 +127,15 @@ pub(super) fn update(app: &mut VectorcraftApp, ctx: &egui::Context, area: egui:: Edge::Right => p.x > uncovered.right(), } }); - if over || down || blocked || flyout { + let held = down || blocked || flyout; + if over || held { shown.left_at = None; - } else if moved || pointer.is_none() { + } else if moved || shown.held { + // A hold that ends with the pointer away from them counts as moving away, as closing a + // popup or flyout with Escape does not move the pointer. shown.left_at.get_or_insert(now); } + shown.held = held; match shown.left_at { Some(left) if now - left >= GRACE => { st.shown = None; @@ -160,7 +168,7 @@ pub(super) fn update(app: &mut VectorcraftApp, ctx: &egui::Context, area: egui:: }; if now - since >= DELAY { st.resting = None; - st.shown = Some(Shown { edge, left_at: None }); + st.shown = Some(Shown { edge, left_at: None, held: false }); return Some(edge); } st.resting = Some((edge, since)); @@ -233,7 +241,7 @@ pub(super) fn command(app: &mut VectorcraftApp, params: &Value) -> Result { diff --git a/crates/ui-egui/src/panel_docking/tests.rs b/crates/ui-egui/src/panel_docking/tests.rs index 2c95ff0e16..c78622458f 100644 --- a/crates/ui-egui/src/panel_docking/tests.rs +++ b/crates/ui-egui/src/panel_docking/tests.rs @@ -642,6 +642,45 @@ fn window_panels_peek_rejects_what_it_cannot_show_and_changes_nothing() { h.app.ui.screen_mode = 0; } +#[test] +fn panels_shown_by_window_panels_peek_stay_until_the_pointer_moves_or_leaves_the_window() { + let mut h = Harness::new(); + let wide = hide_with_tab(&mut h); + assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(), json!("right")); + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_some(), "with no pointer movement, the panels stay"); + assert_eq!(inspect(&h.app, &h.ctx)["peek"], json!("right")); + h.frame(vec![Event::PointerMoved(wide.center())]); + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_none(), "the pointer moved away from them, so they hide"); + // The pointer leaving the window counts as moving away. + h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(); + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_some()); + h.frame(vec![Event::PointerGone]); + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_none(), "the pointer left the window, so they hide"); + assert_eq!(inspect(&h.app, &h.ctx)["peek"], Value::Null); +} + +#[test] +fn panels_held_by_a_flyout_hide_after_it_closes_with_the_pointer_still_away_from_them() { + let mut h = Harness::new(); + let wide = hide_with_tab(&mut h); + // The pointer rests on the canvas, then outside the window, while a flyout holds the panels. + for away in [vec![Event::PointerMoved(wide.center())], vec![Event::PointerGone]] { + h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(); + h.app.ui.open_panel = Some("swatches".into()); + h.frame(away); + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_some(), "an open flyout keeps them"); + h.app.ui.open_panel = None; + wait(&mut h, peek::GRACE * 2.0); + assert!(h.tab("properties").is_none(), "the flyout closed with the pointer away from them, so they hide"); + assert_eq!(inspect(&h.app, &h.ctx)["peek"], Value::Null); + } +} + #[test] fn edge_panels_are_the_docked_panels_on_each_side_of_the_canvas() { let layout = default_from(DockTab::ALL[0], &[], false, None, None);