mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Hide Edges: the hidden bounding box, type widget and corner widgets do not answer the pointer
With View › Hide Edges on, the canvas does not draw the bounding box, the type widget beside it or the Live Corners widgets, but the selection tools still answered the pointer on them. A press on a hidden corner widget rounded the corners, a drag from a hidden handle scaled the selection, a double-click on the hidden type widget converted the type, and the cursor changed over each of them. While edges are hidden, the app now tells the tools that the bounding box and the corner widgets are off. A press at their places reaches the art or the canvas under them. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -4402,6 +4402,108 @@ mod tests {
|
||||
assert_eq!(widgets(&mut app), 0);
|
||||
}
|
||||
|
||||
/// View › Hide Edges hides a live rectangle's corner widgets, and the Selection tool then
|
||||
/// neither shows the corner cursor over one's place nor rounds the corners from it. With
|
||||
/// edges shown, a drag from the same widget rounds them.
|
||||
#[test]
|
||||
fn hidden_corner_widgets_do_not_answer_the_pointer() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
let id = app.session.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap()["id"].as_u64().unwrap();
|
||||
let id = vectorcraft_doc::NodeId(id);
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
app.select_tool("selection");
|
||||
let widget = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
vectorcraft_tools::corners::CornerWidgets::of(&st.doc, &st.selection, app.view_info().zoom, false).unwrap().visible().next().unwrap()
|
||||
};
|
||||
let radius = |app: &VectorcraftApp| {
|
||||
let c = vectorcraft_doc::LiveCorners::of(app.session.active().unwrap().doc.node(id).unwrap()).unwrap();
|
||||
c.style(&c.all()).0
|
||||
};
|
||||
let drag = |app: &mut VectorcraftApp, from: Point| {
|
||||
let (view, to) = (app.view_info(), from + vectorcraft_geom::Vec2::new(10.0, 10.0));
|
||||
for (kind, at) in [(PointerKind::Down, from), (PointerKind::Drag, to), (PointerKind::Up, to)] {
|
||||
app.session.pointer(&PointerEvent::new(kind, at.x, at.y), view).unwrap();
|
||||
}
|
||||
};
|
||||
app.ui.view.edges = false;
|
||||
let at = widget(&app);
|
||||
let view = app.view_info();
|
||||
assert_ne!(app.session.cursor(at, Mods::default(), view), Cursor::CornerRadius);
|
||||
drag(&mut app, at);
|
||||
assert_eq!(radius(&app), Some(0.0), "a press on a hidden widget does not round the corners");
|
||||
app.ui.view.edges = true;
|
||||
let at = widget(&app);
|
||||
let view = app.view_info();
|
||||
assert_eq!(app.session.cursor(at, Mods::default(), view), Cursor::CornerRadius);
|
||||
drag(&mut app, at);
|
||||
assert!(radius(&app).is_some_and(|r| r > 0.0), "{:?}", radius(&app));
|
||||
}
|
||||
|
||||
/// View › Hide Edges hides the bounding box and the type widget beside it, and the Selection
|
||||
/// tool then answers neither: a double-click on the widget's place leaves point type as it is,
|
||||
/// and a drag from a corner handle's place does not scale. With edges shown, both answer.
|
||||
#[test]
|
||||
fn a_hidden_bounding_box_and_type_widget_do_not_answer_the_pointer() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
let id = app.session.execute("text.create", &json!({"x": 100, "y": 100, "text": "Type"})).unwrap()["id"].as_u64().unwrap();
|
||||
let id = vectorcraft_doc::NodeId(id);
|
||||
app.session.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
app.select_tool("selection");
|
||||
let bx = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
st.doc.oriented_bounds(&st.selection.objects, app.session.prefs.use_preview_bounds).unwrap()
|
||||
};
|
||||
let widget = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
vectorcraft_tools::typewidget::TypeWidget::of(&st.doc, &st.selection, &bx(app), app.view_info().zoom).unwrap()
|
||||
};
|
||||
let area = |app: &VectorcraftApp| match &app.session.active().unwrap().doc.node(id).unwrap().kind {
|
||||
NodeKind::Text(t) => matches!(t.kind, vectorcraft_doc::TextKind::Area { .. }),
|
||||
k => panic!("not type: {k:?}"),
|
||||
};
|
||||
let double_click = |app: &mut VectorcraftApp, at: Point| {
|
||||
let view = app.view_info();
|
||||
for kind in [PointerKind::Down, PointerKind::Up, PointerKind::Down, PointerKind::Up, PointerKind::DoubleClick] {
|
||||
app.session.pointer(&PointerEvent::new(kind, at.x, at.y), view).unwrap();
|
||||
}
|
||||
};
|
||||
let width = |app: &VectorcraftApp| bx(app).rect.width();
|
||||
let scale = |app: &mut VectorcraftApp| {
|
||||
let b = bx(app);
|
||||
let corner = b.to_doc() * Point::new(b.rect.x1, b.rect.y1);
|
||||
let (view, to) = (app.view_info(), corner + vectorcraft_geom::Vec2::new(30.0, 0.0));
|
||||
for (kind, at) in [(PointerKind::Down, corner), (PointerKind::Drag, to), (PointerKind::Up, to)] {
|
||||
app.session.pointer(&PointerEvent::new(kind, at.x, at.y), view).unwrap();
|
||||
}
|
||||
app.session.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
};
|
||||
let w = widget(&app);
|
||||
app.ui.view.edges = false;
|
||||
let view = app.view_info();
|
||||
assert_ne!(app.session.cursor(w.at, Mods::default(), view), Cursor::TypeWidget);
|
||||
double_click(&mut app, w.at);
|
||||
assert!(!area(&app), "a double-click on the hidden widget leaves point type");
|
||||
app.session.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
let before = width(&app);
|
||||
scale(&mut app);
|
||||
assert!((width(&app) - before).abs() < 1e-6, "a drag from a hidden handle does not scale: {before} -> {}", width(&app));
|
||||
app.ui.view.edges = true;
|
||||
let w = widget(&app);
|
||||
let view = app.view_info();
|
||||
assert_eq!(app.session.cursor(w.at, Mods::default(), view), Cursor::TypeWidget);
|
||||
scale(&mut app);
|
||||
assert!(width(&app) > before + 1.0, "a drag from the handle scales: {before} -> {}", width(&app));
|
||||
let w = widget(&app);
|
||||
double_click(&mut app, w.at);
|
||||
assert!(area(&app), "a double-click on the widget converts to area type");
|
||||
}
|
||||
|
||||
/// #511: a star shows a widget in each of its ten corners with Direct Selection (none with
|
||||
/// the Selection tool: it isn't a live shape); View → Hide Corner Widget hides them.
|
||||
#[test]
|
||||
|
||||
@@ -637,9 +637,11 @@ impl VectorcraftApp {
|
||||
guides: self.ui.view.guides,
|
||||
snap_to_grid: self.ui.view.snap_to_grid,
|
||||
snap_to_pixel: self.ui.view.snap_to_pixel,
|
||||
show_bbox: self.ui.view.bounding_box,
|
||||
// View › Hide Edges: the canvas draws neither the bounding box (with the type widget)
|
||||
// nor the Live Corners widgets, and the tools do not answer the pointer on them.
|
||||
show_bbox: self.ui.view.bounding_box && self.ui.view.edges,
|
||||
snap_to_point: self.ui.view.snap_to_point,
|
||||
corner_widgets: self.ui.view.corner_widgets,
|
||||
corner_widgets: self.ui.view.corner_widgets && self.ui.view.edges,
|
||||
screen: self.screen_frame(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +490,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Pen:** Alt pressed in the middle of dragging out an anchor splits its handles and keeps the curve already shaped into it (only the outgoing handle follows the pointer); Space held while dragging out an anchor moves it, handles and all. A press on the first anchor of the path being drawn closes it whatever the drag then does: the drag shapes the closing curve (symmetric handles, Alt breaks them) and the release closes the path wherever it ends (#501). Clicking the last anchor of the path being drawn retracts its outgoing handle (convert cursor), and dragging from it pulls out a new one on its own. Auto Add/Delete: over a selected path, a click on a segment adds an anchor and a click on an anchor deletes it (add and delete cursors); Shift held, or Preferences › General › Disable Auto Add/Delete, starts a new path instead. Alt held over a handle end or an anchor of a selected path (the one being drawn too) works as the Anchor Point tool, with its convert cursor: dragging a handle moves it alone, clicking a smooth anchor makes it a corner, dragging an anchor pulls out new symmetric handles, dragging a segment reshapes it; drawing then goes on. Cmd (Ctrl) held drags with the selection tool chosen last, Direct Selection until one is (handles, anchors and segments of the path being drawn or another), and the Pen then goes on drawing its path while that stays selected; the pointer shows that tool's while Cmd is held. The same holds for agents (`pointer_gesture` with `mods.cmd`), as the borrowing now lives in the engine (#494). With the Pen, a press that moves under 3 screen pixels is a click (a corner without handles), and a Precise Cursors command toggles the existing preference and can take a shortcut (#1663).
|
||||
- **Direct Selection (#1278):** clicking a segment picks its connection, with hollow endpoint anchors and only that curve's direction handles. Delete leaves the neighboring curves and appearance intact, opens/splits contours in one undo step, and another Delete removes the remaining selected path. A whole-path drag and deliberate anchor selection keep their existing behavior; egui frames and native control/art checked, OS mouse/visual QA pending. Where anchors of overlapping objects coincide, a click or drag takes the anchor of the selected object, also when another object lies over it (#1509, fixes #1508). Delete removes every selected anchor of a closed path (it used to remove the wrong ones), removes objects selected whole along with the selected anchors of others, and keeps the remainder selected so a second Delete removes it (#1674). In isolation mode Direct Selection and Group Selection pick only inside the isolated object (#1666). Escape cancels a Direct Selection drag (#1803, #1743). Double-clicking type (including type nested inside a group) switches to the Type tool and puts the caret at the clicked character.
|
||||
- **Live Corners:** drag a live rectangle's corner widgets (Selection or Direct Selection) to round all corners, with a radius readout; with Direct Selection, select corner anchors to show and round just those corners (#381). Each corner keeps its own radius and kind (Round, Inverted Round, Chamfer): Alt-click a widget to cycle the kind, double-click it for the Corners dialog, and the Properties and Transform radius fields set the selected corners (`object.setLiveShape {corners, radius, kind}`). A link toggle behind Corner Radius (the `singleCornerRadius` preference) switches the panels to a field for each corner of a live rectangle: Top Left, Top Right, Bottom Right and Bottom Left. Corners stay circular when the rectangle is scaled unevenly or along its own sides when rotated (the radius keeps its size, or scales by the mean scale with Scale Corners on); only a shear distorts them. On a rectangle that isn't square every corner is the same circle: a radius past half the shorter side stops there for all of them, the widgets sit on the corners as drawn and turn the edited corners red at that limit, CSS export writes the drawn radii, and rectangles saved stretched by older versions get circular corners when their corners are next set (#442). Live Corners work on any path's corners too, as on a rectangle's (#511): a star's, a live polygon's (which also shows them to the Selection tool, has a Corner Radius field in the Properties and Transform panels and keeps its radius as its sides change) and a pen path's, wherever two sides meet at an angle at an anchor that isn't smooth: curved sides too, trimmed to what the cut leaves of them (#1144). Direct Selection shows the widgets of every corner, or of the corners holding selected anchors; each corner is a circle tangent to both sides, drawn no larger than takes its cut halfway along its shorter side; a path keeps its uncut outline so its corners stay editable, and the same command, dialog, angle preference and View › Hide Corner Widget apply. With Direct Selection a click on a widget (no drag) selects its corner, as a click on the corner's anchor does, so the next drag on it rounds that corner alone; Shift-click toggles it (#1239). While a corner widget is dragged with the Selection tool the bounding box hides, so the rounding shows against the path alone (#1229). A round corner next to a curved side curves smoothly instead of sticking out of the shape (#1547, fixes #1512).
|
||||
View › Hide Edges hides the Live Corners widgets, the bounding box and the type widget, and the selection tools then do not answer a press on them or change the cursor over them.
|
||||
- **Path editing (#494):** the handles shown are the handles that drag: Direct Selection drags those of a path selected as a whole (as the Pen leaves it), the Anchor Point tool and Pen+Alt no longer grab handles that Show handles when multiple anchors are selected hides, a handle shorter than the tolerance leaves its anchor to be picked, and the anchor tools pick within Selection & Anchor Display › Tolerance. A dragged segment keeps a smooth anchor at either end smooth. With Direct Selection, the Pen, the Curvature and the anchor tools, a path selected as a whole shows all its anchors selected, and the Control bar and the Properties panel offer Convert (corner, smooth) for them too; Remove and Cut wait for anchors direct-selected (#504). A handle drag gathers its snap targets once per drag (it took 21 ms per pointer move on a 20,000-path document, now 1 ms; `vectorcraft-cli perf` measures handle and segment drags on a 5,000-anchor path). Join merges the selected anchor points that lie on one another, as after Average, into one anchor (#1582, fixes #1533); Average and Join in one step, with a corner or smooth choice for the joined anchor, is missing ([gap 1](gaps.md)).
|
||||
- **Graphs:** Column, Stacked Column, Bar, Stacked Bar, Line, Area, Scatter, Pie and Radar graph tools with Graph Data and Graph Type. A series starts in greyscale; Group Selection's fill and stroke are kept separately on the graph and reapplied when its data, type or size changes, and through save, open and undo. Graph Type's Override Calculated Values (both `axisMin` and `axisMax`) uses exactly that value-axis range, split into the tick count (5 when automatic). Line graphs take Edge-to-Edge Lines (off by default: the points sit at the centres of their categories; graphs saved before the option keep their edge-to-edge lines), and radar graphs follow Mark Data Points and Connect Data Points. Blank cells (an empty CSV cell or `null`) are values left out, not zeros: columns, bars and stacked segments skip them, lines and scatter lines break around them and the value axis ignores them, while area, pie and radar draw them as 0; a number in straight quotes is a label, and the data window writes it back quoted. Graph types combine: Graph Type on series picked with Group Selection (or `series` indexes) draws them as their own type, with column, stacked column, line and area mixing on the vertical value axis and bar and stacked bar on the horizontal one; stacked columns take one slot of the cluster, lines draw in front of columns and areas behind. The value axis goes on the left, the right or both sides (column, stacked column, line, area and scatter graphs; bar graphs keep theirs along the bottom), and Separate Scales gives the series assigned to the right axis a scale of their own with its own tick values. Each value axis takes tick marks of no, short or full (across the plot) length and a count per division, and a prefix and suffix for its numbers; the category axis takes tick marks too (none by default), at or between its labels. Graph designs: Object › Graph › Design lists the designs with a preview of the selected one, and New Design (the selected art), Rename, Delete Design, Paste Design (a copy back to edit) and Select Unused, and Marker draws the data points and legend swatches of line, scatter and radar series with a design, scaled so its backmost object fills the default marker's square. Graph Data transposes rows and columns and switches a scatter graph's x and y, and a `|` in a category or series label starts a new line. Pie graphs take Legends in Wedges, Position (Even, Ratio sized by area, Stacked on one centre) and Sort (None, All, First). Graph Type takes column and cluster widths up to 1000% (overlapping past 100%) with First Row and First Column in Front (First Row orders each series' categories; across series the series order decides, so series stay one group each for Group Selection), draws filled lines, adds drop shadows behind columns, bars, lines and wedges, and puts the legend in rows across the top. Text formatted with the Character or Appearance controls, on the whole graph or on labels picked with Group Selection, is kept per kind of label (value axis numbers, category labels, legend labels) through the graph being drawn again. Object › Graph › Data opens a worksheet: cells picked with a click and edited above the sheet (Tab moves right, Enter down, the arrows between cells), Import Data from a tab-separated text file, Transpose, Switch x/y, Cell Style (decimals shown, column width), Revert and Apply. Column designs remain open. `graph.setData` returns and takes the data as CSV with each value written exactly, not rounded to two decimals, so applying the returned CSV changes nothing (#1485).
|
||||
- **Other:** Eyedropper, Gradient annotator, Artboard (Alt-drag duplicates an artboard, and its art with Move/Copy Artwork with Artboard on; Copy, Cut and Paste take the artboard with its art, Paste right of the last artboard, Paste in Place where it was, into this or another document, #446; Object › Artboards › Rearrange All Artboards lays them out in a grid by row or by column, in one row or one column, left to right or right to left, from the menu, the Artboards panel or Properties' Rearrange All, #681; Scale Artwork with Artboard, in the Control bar and Properties beside Move Artwork with Artboard, resizes the artboard proportionally with the art fully inside it and its guides, strokes and corners per the Scale preferences, #602), Measure, Type, Hand, Zoom, Rotate View.
|
||||
|
||||
Reference in New Issue
Block a user