mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Pan the grid on a one-finger touch drag instead of selecting
Fixes #1907 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
66ce6be474
commit
0e8e7e34e4
@@ -149,6 +149,8 @@ pub struct GridState {
|
||||
pub hscroll: Option<Rect>,
|
||||
/// Ctrl+wheel zoom in progress (see [`WheelZoom`]).
|
||||
pub wheel_zoom: Option<WheelZoom>,
|
||||
/// A one-finger touch drag in progress: it pans the sheet instead of selecting.
|
||||
pub touch_pan: bool,
|
||||
}
|
||||
|
||||
/// Ctrl+wheel zoom for one (doc uid, sheet). egui eases a notch in over several frames and
|
||||
@@ -641,6 +643,19 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) {
|
||||
view.scroll -= d / z;
|
||||
}
|
||||
}
|
||||
// A one-finger touch drag pans the sheet, the content following the finger; it doesn't select.
|
||||
// (Two fingers pinch-zoom through `zoom_delta` above.)
|
||||
if resp.drag_started() && ui.input(|i| i.any_touches()) {
|
||||
app.grid.touch_pan = true;
|
||||
}
|
||||
if app.grid.touch_pan && resp.dragged() && ui.input(|i| i.multi_touch().is_none()) {
|
||||
let z = if paper { make_geo(app, view.scroll).z } else { sh.zoom as f32 / 100.0 * DISPLAY_SCALE };
|
||||
let mut d = resp.drag_delta();
|
||||
if sh.right_to_left {
|
||||
d.x = -d.x;
|
||||
}
|
||||
view.scroll -= d / z;
|
||||
}
|
||||
// The zoom glides to its step like egui eases a wheel notch (90% of the way in 0.1 s), or goes
|
||||
// there at once without animations (as for the selection outline below: View › Animations
|
||||
// off, egui's off in the headless harness, or a screenshot on its way).
|
||||
@@ -754,8 +769,15 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
// Interaction.
|
||||
let previous_popups = (app.grid.filter_menu, app.grid.list_picker, app.grid.header_menu);
|
||||
crate::paper_view::interact(app, ui, &resp, &geo, sh, &wb);
|
||||
interact(app, ui, &resp, &geo, sh, &wb);
|
||||
if app.grid.touch_pan {
|
||||
// Skip the frames of a touch pan, its release included.
|
||||
if !resp.dragged() {
|
||||
app.grid.touch_pan = false;
|
||||
}
|
||||
} else {
|
||||
crate::paper_view::interact(app, ui, &resp, &geo, sh, &wb);
|
||||
interact(app, ui, &resp, &geo, sh, &wb);
|
||||
}
|
||||
crate::text_box::show(app, ui, &geo, sh);
|
||||
if app.editor.as_ref().is_some_and(|e| !e.from_formula_bar) {
|
||||
in_cell_editor(app, ui, &geo, sh, &wb);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Touch input on the grid: one finger pans, a tap selects (issue #1907).
|
||||
|
||||
use egui::{Event, Modifiers, PointerButton, TouchDeviceId, TouchId, TouchPhase};
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_engine::core::CellRef;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
|
||||
fn harness(session: Session) -> egui_kittest::Harness<'static, SheetApp> {
|
||||
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SheetApp| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
},
|
||||
SheetApp::new(session, Default::default()),
|
||||
);
|
||||
h.run_steps(4);
|
||||
h
|
||||
}
|
||||
|
||||
fn blank() -> Session {
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s
|
||||
}
|
||||
|
||||
/// One finger, sent the way egui-winit does: a touch event plus the emulated pointer events.
|
||||
fn touch(h: &mut egui_kittest::Harness<'static, SheetApp>, phase: TouchPhase, pos: egui::Pos2) {
|
||||
let ev = &mut h.input_mut().events;
|
||||
ev.push(Event::Touch { device_id: TouchDeviceId(1), id: TouchId(0), phase, pos, force: None });
|
||||
match phase {
|
||||
TouchPhase::Start => {
|
||||
ev.push(Event::PointerMoved(pos));
|
||||
ev.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
|
||||
}
|
||||
TouchPhase::Move => ev.push(Event::PointerMoved(pos)),
|
||||
TouchPhase::End | TouchPhase::Cancel => {
|
||||
ev.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
|
||||
ev.push(Event::PointerGone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn swipe(h: &mut egui_kittest::Harness<'static, SheetApp>, from: egui::Pos2, by: egui::Vec2) {
|
||||
touch(h, TouchPhase::Start, from);
|
||||
h.run_steps(2);
|
||||
for i in 1..=10 {
|
||||
touch(h, TouchPhase::Move, from + by * (i as f32 / 10.0));
|
||||
h.run_steps(1);
|
||||
}
|
||||
touch(h, TouchPhase::End, from + by);
|
||||
h.run_steps(3);
|
||||
}
|
||||
|
||||
fn assert_selection_untouched(h: &egui_kittest::Harness<'static, SheetApp>) {
|
||||
let sel = &h.state().session.active().unwrap().selection;
|
||||
assert_eq!(sel.active, CellRef::new(0, 0), "a touch pan must not move the active cell: {sel:?}");
|
||||
assert_eq!(sel.ranges.len(), 1, "{sel:?}");
|
||||
assert_eq!(sel.ranges[0].start, CellRef::new(0, 0), "{sel:?}");
|
||||
assert_eq!(sel.ranges[0].end, CellRef::new(0, 0), "a touch pan must not select cells: {sel:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_finger_drag_pans_the_grid_instead_of_selecting() {
|
||||
let mut h = harness(blank());
|
||||
let cells = h.state().grid.cells_rect.unwrap();
|
||||
swipe(&mut h, cells.center() + egui::vec2(0.0, 100.0), egui::vec2(0.0, -200.0));
|
||||
let scroll = h.state().view().scroll;
|
||||
assert!(scroll.y > 0.0, "dragging a finger up scrolls down: scroll = {scroll:?}");
|
||||
assert_selection_untouched(&h);
|
||||
|
||||
swipe(&mut h, cells.center() + egui::vec2(100.0, 0.0), egui::vec2(-200.0, 0.0));
|
||||
let scroll = h.state().view().scroll;
|
||||
assert!(scroll.x > 0.0, "dragging a finger left scrolls right: scroll = {scroll:?}");
|
||||
assert_selection_untouched(&h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_finger_tap_still_selects_the_cell() {
|
||||
let mut h = harness(blank());
|
||||
let cells = h.state().grid.cells_rect.unwrap();
|
||||
let p = cells.min + egui::vec2(5.0, 5.0) + egui::vec2(0.0, cells.height() / 2.0);
|
||||
touch(&mut h, TouchPhase::Start, p);
|
||||
h.run_steps(1);
|
||||
touch(&mut h, TouchPhase::End, p);
|
||||
h.run_steps(3);
|
||||
let sel = &h.state().session.active().unwrap().selection;
|
||||
assert_eq!(sel.active.col, 0, "{sel:?}");
|
||||
assert!(sel.active.row > 0, "a tap selects the tapped cell: {sel:?}");
|
||||
assert_eq!(h.state().view().scroll, egui::Vec2::ZERO);
|
||||
}
|
||||
Reference in New Issue
Block a user