mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 01:50:22 -06:00
Selection: a drag with Real-time Drawing off shows the bounds of a selection too big to outline
With Real-time Drawing and Editing off, a drag drew one outline per selected object with no limit, so a selection with hundreds of thousands of points built the overlay mesh that the canvas's 100,000-point limit keeps it from drawing at rest (#525, #2460). Past the same number of points the drag now draws the moved selection's bounds, as the canvas does for the selection at rest.
This commit is contained in:
@@ -1703,6 +1703,39 @@ fn deferred_preview_overlays_are_cached_across_redraws() {
|
||||
assert_eq!(s.doc().unwrap().revision, rev, "the preview command is not run again");
|
||||
}
|
||||
|
||||
/// With Real-time Drawing off, a moved selection whose outlines have more points than the canvas
|
||||
/// outlines (100,000) shows its moved bounds instead (#525, #2460).
|
||||
#[test]
|
||||
fn real_time_drawing_off_draws_the_bounds_of_a_selection_too_big_to_outline() {
|
||||
use vectorcraft_tools::Overlay;
|
||||
use vectorcraft_tools::draw2::FEEDBACK;
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 600, "height": 600})).unwrap();
|
||||
let anchors: Vec<Value> = (0..120_000).map(|i| json!({"x": 100.0 + f64::from(i) * 0.003, "y": if i % 2 == 0 { 100 } else { 150 }})).collect();
|
||||
let id = s.execute("path.create", &json!({"anchors": anchors, "closed": false})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
let at_rest = s.doc().unwrap().doc.bounds_of(&s.doc().unwrap().selection.objects, true).unwrap();
|
||||
let v = ViewInfo::default();
|
||||
s.select_tool("selection", v).unwrap();
|
||||
set_pref(&mut s, "realTimeDrawing", json!(false));
|
||||
s.pointer(&PointerEvent::new(PointerKind::Down, 200.0, 100.0), v).unwrap();
|
||||
s.pointer(&PointerEvent::new(PointerKind::Drag, 260.0, 160.0), v).unwrap();
|
||||
let feedback: Vec<_> = s
|
||||
.overlays(v)
|
||||
.into_iter()
|
||||
.filter_map(|o| match o {
|
||||
Overlay::Path { path, color, .. } if color == FEEDBACK => Some(path),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let points: usize = feedback.iter().map(|p| p.elements().len()).sum();
|
||||
assert!(points <= 100_000, "the drag feedback has {points} points");
|
||||
let [bounds] = feedback.as_slice() else { panic!("one bounds rectangle, got {} paths", feedback.len()) };
|
||||
let b = kurbo::Shape::bounding_box(bounds);
|
||||
let moved = (b.x0 - at_rest.x0, b.y0 - at_rest.y0, b.width() - at_rest.width(), b.height() - at_rest.height());
|
||||
assert!(moved.0 > 1.0 && moved.1 > 1.0 && moved.2.abs() < 1e-6 && moved.3.abs() < 1e-6, "the bounds moved with the drag: {moved:?}");
|
||||
}
|
||||
|
||||
/// A deferred commit that fails its preview cancels the interaction instead of leaving it open.
|
||||
#[test]
|
||||
fn deferred_commit_preview_failure_cancels_interaction() {
|
||||
|
||||
@@ -887,13 +887,28 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// The most points the outlines of [`outlines_for_selection`] are drawn for, the same number as the
|
||||
/// canvas's limit on the anchor points it outlines (`OVERLAY_MAX_ANCHORS` in
|
||||
/// `crates/ui-egui/src/canvas/outline.rs`). Past it the selection's bounds are drawn instead.
|
||||
const FEEDBACK_MAX_POINTS: usize = 100_000;
|
||||
|
||||
/// The outline of every selected object, or the selection's bounds when the outlines have more than
|
||||
/// [`FEEDBACK_MAX_POINTS`] points.
|
||||
fn outlines_for_selection(doc: &vectorcraft_doc::Document, selection: &Selection) -> Vec<Overlay> {
|
||||
selection
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|&id| {
|
||||
let n = doc.node(id)?;
|
||||
vectorcraft_render::drawn_outline(doc, n).map(|(path, _)| Overlay::Path { path, color: FEEDBACK, width: 1.0, dashed: false })
|
||||
})
|
||||
.collect()
|
||||
let mut left = FEEDBACK_MAX_POINTS;
|
||||
let mut outlines = vec![];
|
||||
for &id in &selection.objects {
|
||||
let Some((path, _)) = doc.node(id).and_then(|n| vectorcraft_render::drawn_outline(doc, n)) else { continue };
|
||||
let points = path.elements().iter().filter(|el| !matches!(el, kurbo::PathEl::ClosePath)).count();
|
||||
let Some(l) = left.checked_sub(points) else {
|
||||
return doc
|
||||
.bounds_of(&selection.objects, true)
|
||||
.map(|b| Overlay::Path { path: vectorcraft_geom::shapes::rectangle(b).to_bezpath(), color: FEEDBACK, width: 1.0, dashed: false })
|
||||
.into_iter()
|
||||
.collect();
|
||||
};
|
||||
left = l;
|
||||
outlines.push(Overlay::Path { path, color: FEEDBACK, width: 1.0, dashed: false });
|
||||
}
|
||||
outlines
|
||||
}
|
||||
|
||||
@@ -633,6 +633,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Preferences audit (2026-10-30, #394): every Preferences dialog option is read by shipped code, by its field or its key, except User Interface › Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Plug-ins & Scratch Disks › Primary and Secondary, and Clipboard Handling › Legacy vector clipboard and its mode, which are stored only; [gaps.md G12](gaps.md#g12-preferences-and-panels-that-dont-act-yet) says what each waits on.
|
||||
- With Performance › Real-time Drawing and Editing off, the bounding box moves with a Selection-tool move drag and the object moves when the button is released ([help](https://web.archive.org/web/20220324053041id_/https://helpx.adobe.com/illustrator/using/real-time-editing.html); `real_time_drawing_off_moves_the_bounding_box_with_a_move_drag`).
|
||||
- Performance › Real-time Drawing and Editing off defers a drag's edits to its release, but a ruler guide dragged out of a ruler or moved still shows as it is dragged, where only its position label showed (#4646; `a_guide_dragged_out_of_a_ruler_shows_while_dragged`, `real_time_drawing_off_still_shows_a_dragged_guide`).
|
||||
- With Real-time Drawing and Editing off, a dragged selection whose outlines have more than 100,000 points shows its moved bounds instead, as the selection at rest does (#525, #2460; `real_time_drawing_off_draws_the_bounds_of_a_selection_too_big_to_outline`).
|
||||
- Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423).
|
||||
- Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once.
|
||||
- While Alt (Option on macOS) is held, the Flare Tool Options' Cancel button reads Reset, which puts every option back to the Flare tool's default and leaves the dialog open (`ui.dialogReset`).
|
||||
|
||||
Reference in New Issue
Block a user