mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 13:57:18 -06:00
Ruler: dragging a margin edge leaves the view where it is (#3900)
Each step of a margin drag on a ruler runs layout.margins, and like any command that brought the caret back into view. With the caret off screen (dragging the bottom margin while the text being edited is near the top of the page) the view scrolled mid-drag: the page slid under the pointer, the pointer's distance from the page's top changed with it, and the margin ran far past where it was dropped. A drag of about 1 cm on the vertical ruler set a 2 cm bottom margin to 8.1 cm. The margin handles now run the command with run_keep_view, as table border drags do (#1738). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70b681a9ba
commit
7d3d79e64e
@@ -3139,7 +3139,8 @@ pub(crate) fn margin_drag(edge: MarginEdge, at: f32, s: &wordcraft_doc::section:
|
||||
|
||||
/// A draggable margin edge on a ruler: `to_pt` turns the pointer into points from the page's
|
||||
/// left (or top) edge, and `mirrored` is an even page of a mirrored layout ([`margin_drag`]). A
|
||||
/// whole drag is one undo step, through `layout.margins`.
|
||||
/// whole drag is one undo step, through `layout.margins`. The view stays put: following the caret
|
||||
/// mid-drag would move the page under the pointer and the margin with it.
|
||||
fn margin_handle(
|
||||
app: &mut WordApp,
|
||||
ui: &Ui,
|
||||
@@ -3160,7 +3161,7 @@ fn margin_handle(
|
||||
if !r.drag_started() {
|
||||
app.session.join_next_undo();
|
||||
}
|
||||
let _ = app.run("layout.margins", json!({ stored.param(): v }));
|
||||
let _ = app.run_keep_view("layout.margins", json!({ stored.param(): v }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3979,6 +3980,58 @@ mod tests {
|
||||
assert_eq!(margin_drag(MarginEdge::Top, 50.0, §, true), (MarginEdge::Top, dragged_margin(MarginEdge::Top, 50.0, §)));
|
||||
}
|
||||
|
||||
/// Dragging a margin edge on the vertical ruler leaves the view where it is. Each step of the
|
||||
/// drag runs `layout.margins`, which brought the caret back into view: with the caret off
|
||||
/// screen (the bottom margin is far from the text at the top) the page slid under the pointer
|
||||
/// mid-drag, and the margin ran far past it (a drag of about 1 cm set it about 6 cm larger).
|
||||
#[test]
|
||||
fn dragging_the_bottom_margin_on_the_ruler_keeps_the_view_and_follows_the_pointer() {
|
||||
let text = vec!["A line of text."; 20].join("\n");
|
||||
let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text(&text)), Default::default());
|
||||
a.run("view.ruler", json!({"value": true})).unwrap();
|
||||
a.run("caret.docStart", json!({})).unwrap();
|
||||
let mut h: H = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).with_step_dt(0.25).build_ui_state(
|
||||
|ui, app: &mut WordApp| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
},
|
||||
a,
|
||||
);
|
||||
h.run_steps(6);
|
||||
let body = h.state_mut().session.layout().pages[0].body;
|
||||
let area = h.state().canvas.canvas_rect.expect("the canvas is shown");
|
||||
// Scroll the first page's bottom margin to the middle of the window: the caret, at the
|
||||
// top of the page, is off screen.
|
||||
let bottom = screen(&mut h, 0.0, body.bottom()).y;
|
||||
h.state_mut().canvas.scroll_request = Some(vec2(0.0, bottom - area.center().y));
|
||||
h.run_steps(4);
|
||||
assert!(screen(&mut h, 0.0, body.y).y < area.min.y, "the caret's line is above the view");
|
||||
let offset = h.state().canvas.scroll_offset;
|
||||
let sect = wordcraft_engine::cmd::page::sect(&h.state().session);
|
||||
let depth = h.state().session.undo_depth();
|
||||
|
||||
// The bottom margin's edge on the vertical ruler, dragged up 36 points in a few moves.
|
||||
let x = area.min.x - RULER / 2.0;
|
||||
let start = pos2(x, screen(&mut h, 0.0, body.bottom()).y);
|
||||
let end = pos2(x, screen(&mut h, 0.0, body.bottom() - 36.0).y);
|
||||
h.event(egui::Event::PointerMoved(start));
|
||||
h.run_steps(2);
|
||||
h.event(egui::Event::PointerButton { pos: start, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() });
|
||||
h.step();
|
||||
for k in 1..=5 {
|
||||
h.event(egui::Event::PointerMoved(start + (end - start) * (k as f32 / 5.0)));
|
||||
h.step();
|
||||
assert_eq!(h.state().canvas.scroll_offset, offset, "the view moved during the drag (step {k})");
|
||||
}
|
||||
h.event(egui::Event::PointerButton { pos: end, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().canvas.scroll_offset, offset, "the view moved after the drag");
|
||||
let after = wordcraft_engine::cmd::page::sect(&h.state().session);
|
||||
assert!((after.margin_bottom - (sect.margin_bottom + 36.0)).abs() <= 4.5, "{} → {}", sect.margin_bottom, after.margin_bottom);
|
||||
assert_eq!(h.state().session.undo_depth(), depth + 1, "one undo step for the drag");
|
||||
}
|
||||
|
||||
/// Show revisions in balloons: a document whose only revision is a deletion gets the markup
|
||||
/// area for its balloon; the other modes have nothing to show there, and neither does the view
|
||||
/// while the Comments pane is open.
|
||||
|
||||
Reference in New Issue
Block a user