mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 10:39:01 -06:00
fix(ui): the page ignores the pointer while a panel is being dragged (#4581)
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
//! The pointer as the pages see it, read once per frame from the page view's content widget.
|
||||
//!
|
||||
//! egui knows what is drawn over the pages (a dialog, a menu, the palette, the quick bar, the
|
||||
//! chrome over a page scrolled under it) and keeps a press with the widget it began on until
|
||||
//! release. egui's raw pointer state knows neither, and its hover position is gone on the frame
|
||||
//! chrome over a page scrolled under it) and which widget holds a drag (a panel's tab carried
|
||||
//! over them), and keeps a press with the widget it began on until release. egui's raw pointer
|
||||
//! state knows none of that, and its hover position is gone on the frame
|
||||
//! a touch or a Windows pen lifts: egui-winit sends `PointerGone` with every touch release, and
|
||||
//! winit reports Windows pen input as touch. So page modules read [`PagePointer`], never egui's
|
||||
//! pointer positions or button states; a test below keeps it that way.
|
||||
@@ -26,10 +27,11 @@ const MAX_EDGES: usize = 8;
|
||||
/// One frame's pointer, as the pages see it.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub(crate) struct PagePointer {
|
||||
/// Where the pointer is, while the pages are what egui has under it, or while they hold a
|
||||
/// press (until the frame of its release, past a page's edge included). With a dialog open
|
||||
/// only a drag the pages already have keeps it. It is egui's `interact_pos`, which still has
|
||||
/// the position on the frame a touch lifts.
|
||||
/// Where the pointer is, while the pages are what egui has under it and no other widget holds
|
||||
/// a drag (a panel's tab carried over them), or while they hold a press (until the frame of
|
||||
/// its release, past a page's edge included). With a dialog open only a drag the pages
|
||||
/// already have keeps it. It is egui's `interact_pos`, which still has the position on the
|
||||
/// frame a touch lifts.
|
||||
pub pos: Option<Pos2>,
|
||||
/// Where the press the pages hold began, also on the frame of its release when the press
|
||||
/// came in that same frame (egui has forgotten it by then). `None` for a press that began on
|
||||
@@ -59,7 +61,8 @@ impl PagePointer {
|
||||
// pages (egui's interact radius), so the top layer there must be the pages' own. A
|
||||
// dialog covers them from the frame it opens, before egui knows its layer.
|
||||
let layer = ui.layer_id();
|
||||
let on_top = |p: &Pos2| !modal && resp.contains_pointer() && ui.ctx().layer_id_at(*p).is_none_or(|l| l == layer);
|
||||
let on_top =
|
||||
|p: &Pos2| !modal && !dragged_elsewhere(ui, resp) && resp.contains_pointer() && ui.ctx().layer_id_at(*p).is_none_or(|l| l == layer);
|
||||
let pos = at.filter(|p| held || on_top(p));
|
||||
if !held {
|
||||
return Self { pos, origin: None, edges: Vec::new(), primary_down, any_down };
|
||||
@@ -78,6 +81,16 @@ impl PagePointer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether egui gives the drag to a widget that is not the pages' (a panel's tab, a floating panel's
|
||||
/// grip): egui hovers nothing else until it stops, though it still reports the pages under the
|
||||
/// pointer. A widget on the pages (a form field's editor) keeps them.
|
||||
fn dragged_elsewhere(ui: &egui::Ui, resp: &egui::Response) -> bool {
|
||||
let ctx = ui.ctx();
|
||||
ctx.dragged_id().is_some_and(|id| {
|
||||
id != resp.id && !ctx.read_response(id).is_some_and(|r| r.layer_id == resp.layer_id && resp.interact_rect.contains_rect(r.interact_rect))
|
||||
})
|
||||
}
|
||||
|
||||
fn first_press(edges: &[Edge]) -> Option<Pos2> {
|
||||
edges.iter().find_map(|e| match e {
|
||||
Edge::Press(p) => Some(*p),
|
||||
|
||||
@@ -339,6 +339,52 @@ fn link_tip_stays_off_while_the_palette_is_open() {
|
||||
h.get_by_label("https://example.org/");
|
||||
}
|
||||
|
||||
/// A panel's tab carried over a link shows neither its tip nor its cursor, and the link answers
|
||||
/// again once the tab is let go.
|
||||
#[test]
|
||||
fn a_dragged_panel_tab_passes_over_a_link_without_its_tip() {
|
||||
use egui::accesskit::Role;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut h = open(link_pdf(), "150", false);
|
||||
h.state_mut().set_option("panel", "comments").unwrap();
|
||||
for _ in 0..4 {
|
||||
h.run_steps(2);
|
||||
}
|
||||
let on_link = at(&h, 100.0, 340.0);
|
||||
assert_eq!(cursor_at(&mut h, on_link), egui::CursorIcon::PointingHand);
|
||||
h.run_steps(2);
|
||||
assert!(tip_shown(&h), "the link's tip shows over an uncovered page");
|
||||
// Away from the link, then the Inspector's tab is picked up and carried over it.
|
||||
h.hover_at(at(&h, 150.0, 100.0));
|
||||
h.run_steps(4);
|
||||
let tab = h.get_by(|n| n.role() == Role::Tab && n.label().as_deref() == Some("Inspector")).rect().center();
|
||||
h.hover_at(tab);
|
||||
h.run_steps(1);
|
||||
h.drag_at(tab);
|
||||
h.run_steps(1);
|
||||
for i in 1..=8 {
|
||||
h.hover_at(tab + (on_link - tab) * (i as f32 / 8.0));
|
||||
h.run_steps(1);
|
||||
}
|
||||
for _ in 0..4 {
|
||||
h.run_steps(2);
|
||||
assert!(h.ctx.dragged_id().is_some(), "the tab is still being dragged");
|
||||
assert!(!tip_shown(&h), "no link tip under a dragged panel tab");
|
||||
assert_ne!(h.output().platform_output.cursor_icon, egui::CursorIcon::PointingHand);
|
||||
}
|
||||
// Back to the tab and let go there, which leaves the layout as it was.
|
||||
for i in 1..=8 {
|
||||
h.hover_at(on_link + (tab - on_link) * (i as f32 / 8.0));
|
||||
h.run_steps(1);
|
||||
}
|
||||
h.drop_at(tab);
|
||||
h.run_steps(2);
|
||||
assert!(h.ctx.dragged_id().is_none());
|
||||
assert_eq!(cursor_at(&mut h, on_link), egui::CursorIcon::PointingHand, "the link answers again");
|
||||
h.run_steps(2);
|
||||
assert!(tip_shown(&h), "and shows its tip");
|
||||
}
|
||||
|
||||
fn wheel(h: &mut Harness<'_, PdfCraftApp>, delta: egui::Vec2) {
|
||||
h.hover_at(egui::pos2(800.0, 500.0));
|
||||
h.event(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Point, delta, modifiers: egui::Modifiers::NONE, phase: egui::TouchPhase::Move });
|
||||
|
||||
Reference in New Issue
Block a user