Merge pull request #265 from theironchef/fix/tool-double-clicks

Toolbar: double-clicking Hand, Zoom and the transform tools works
This commit is contained in:
BFlat 2026-10-07 12:31:25 +07:00 committed by GitHub
commit 40bdfd3aca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 4 deletions

View File

@ -110,6 +110,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
- **Live Corners:** drag a live rectangle's corner widgets (Selection or Direct Selection) to round all corners, with a radius readout. - **Live Corners:** drag a live rectangle's corner widgets (Selection or Direct Selection) to round all corners, with a radius readout.
- **Graphs:** Column, Stacked Column, Bar, Stacked Bar, Line, Area, Scatter, Pie and Radar graph tools with Graph Data and Graph Type. - **Graphs:** Column, Stacked Column, Bar, Stacked Bar, Line, Area, Scatter, Pie and Radar graph tools with Graph Data and Graph Type.
- **Other:** Eyedropper, Gradient annotator, Artboard, Measure, Type, Hand, Zoom, Rotate View. - **Other:** Eyedropper, Gradient annotator, Artboard, Measure, Type, Hand, Zoom, Rotate View.
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, Gradient, Eyedropper, Blend and the Liquify tools their panels or options.
- **Drawing aids:** Smart Guides and snapping, guides dragged out of the rulers, and Draw Normal / Behind / Inside modes. - **Drawing aids:** Smart Guides and snapping, guides dragged out of the rulers, and Draw Normal / Behind / Inside modes.
- **Geometry and effects:** - **Geometry and effects:**
- Pathfinder (10 exact curve booleans), Offset, Outline Stroke, Simplify, Clean Up, Split Into Grid, Divide Objects Below. - Pathfinder (10 exact curve booleans), Offset, Outline Stroke, Simplify, Clean Up, Split Into Grid, Divide Objects Below.

View File

@ -239,7 +239,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"tool.options", "tool.options",
"Tool Options…", "Tool Options…",
"", "",
"{tool: id} what double-clicking a tool button opens: gradient → the Gradient panel (window.panel), eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values})", "{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), gradient → the Gradient panel (window.panel), eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values})",
), ),
( (
"ui.colorGuideOptions", "ui.colorGuideOptions",

View File

@ -208,9 +208,21 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
/// Open a tool's options (`tool.options`, a double-click on its button): the Gradient tool's are /// Open a tool's options (`tool.options`, a double-click on its button): the Gradient tool's are
/// the Gradient panel, the Eyedropper's the Eyedropper Options dialog, a Liquify tool's its Tool /// the Gradient panel, the Eyedropper's the Eyedropper Options dialog, a Liquify tool's its Tool
/// Options dialog, the Blend tool's Blend /// Options dialog, the Blend tool's Blend
/// Options; the Print Tiling tool's resets the print tiling. /// Options; the Print Tiling tool's resets the print tiling. As in the reference app, the Hand
/// tool's fits the artboard in the window, the Zoom tool's shows it at 100%, and the Rotate, Scale,
/// Reflect and Shear tools' are their Object › Transform dialogs.
pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::Value, String> { pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::Value, String> {
match tool { match tool {
"hand" => app.run("view.fitArtboard", json!({})),
"zoom" => app.run("view.actualSize", json!({})),
"rotate" | "scale" | "reflect" | "shear" => {
let id = format!("object.{tool}");
if let Some(c) = vectorcraft_engine::find_command(&id) {
(c.enabled)(&app.session)?;
}
crate::menus::invoke(app, &id, json!({}));
Ok(json!({ "dialog": tool }))
}
"gradient" if app.ui.open_panel.as_deref() == Some("gradient") => Ok(json!({ "open": "gradient" })), "gradient" if app.ui.open_panel.as_deref() == Some("gradient") => Ok(json!({ "open": "gradient" })),
"gradient" => app.run("window.panel", json!({ "panel": "gradient" })), "gradient" => app.run("window.panel", json!({ "panel": "gradient" })),
"eyedropper" => { "eyedropper" => {
@ -486,7 +498,35 @@ pub(crate) mod tests {
crate::dialogs::confirm(&mut app).unwrap(); crate::dialogs::confirm(&mut app).unwrap();
let o = app.session.prefs.eyedropper; let o = app.session.prefs.eyedropper;
assert!(o.pick_up.appearance.transparency && !o.apply.appearance.transparency && o.apply.appearance.fill.color); assert!(o.pick_up.appearance.transparency && !o.apply.appearance.transparency && o.apply.appearance.fill.color);
assert!(app.run("tool.options", json!({"tool": "zoom"})).is_err()); assert!(app.run("tool.options", json!({"tool": "lasso"})).is_err());
}
#[test]
fn double_clicking_the_hand_zoom_and_transform_tools() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
app.canvas_rect = Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(660.0, 460.0)));
app.run("view.setZoom", json!({"zoom": 333, "center": [10, 10]})).unwrap();
// Hand: the artboard fits the window. Zoom: 100%.
app.run("tool.options", json!({"tool": "hand"})).unwrap();
let v = *app.view().unwrap();
assert_eq!((v.center.x, v.center.y, v.zoom), (150.0, 100.0, 2.0));
app.run("tool.options", json!({"tool": "zoom"})).unwrap();
assert_eq!(app.view().unwrap().zoom, 1.0);
// The transform tools open their dialogs, like Object › Transform: not with nothing selected.
assert_eq!(app.run("tool.options", json!({"tool": "rotate"})), Err("nothing selected".into()));
assert!(app.ui.dialog.is_none());
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 40, "height": 20})).unwrap()["id"].as_u64().unwrap();
for tool in ["rotate", "scale", "reflect", "shear"] {
app.run("tool.options", json!({"tool": tool})).unwrap();
assert_eq!(app.ui.dialog.take().map(|d| d.kind), Some(tool.to_string()));
}
// OK in Rotate turns the selection about its centre.
app.run("tool.options", json!({"tool": "rotate"})).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("angle".into(), json!(90));
crate::dialogs::confirm(&mut app).unwrap();
let b = app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().geometric_bounds().unwrap();
assert!((b.x0 - 20.0).abs() < 1e-9 && (b.y0 - 0.0).abs() < 1e-9 && (b.width() - 20.0).abs() < 1e-9, "{b:?}");
} }
#[test] #[test]

View File

@ -87,7 +87,9 @@ point (`paint.freeform.selectPoint`); its fields are `color` (hex), `opacity` an
Tool options: double-clicking a tool button runs `tool.options {tool}`. For `gradient` it opens the Gradient panel; Tool options: double-clicking a tool button runs `tool.options {tool}`. For `gradient` it opens the Gradient panel;
for `eyedropper` it opens Eyedropper Options, an `eyedropperOptions` dialog (fields `sampleSize` 1/3/5, `pickUp` and for `eyedropper` it opens Eyedropper Options, an `eyedropperOptions` dialog (fields `sampleSize` 1/3/5, `pickUp` and
`apply`, the attribute trees of `eyedropper.setOptions`) whose `ui.dialog.confirm` runs `eyedropper.setOptions` (what `apply`, the attribute trees of `eyedropper.setOptions`) whose `ui.dialog.confirm` runs `eyedropper.setOptions` (what
`appearance.copyFrom` copies). Gradient tool handles snap to `appearance.copyFrom` copies). For `hand` it fits the artboard in the window (`view.fitArtboard`) and for `zoom` it
shows 100% (`view.actualSize`). For `rotate`, `scale`, `reflect` and `shear` it opens the same dialog as Object ›
Transform (dialog kind = the tool id), or fails with `nothing selected`. Gradient tool handles snap to
anchors, edges and smart guides; Shift constrains them to 45° steps from the `constrainAngle` preference. anchors, edges and smart guides; Shift constrains them to 45° steps from the `constrainAngle` preference.
Effect dialogs: `engine.execute {command: "effect.dialog", params: {effect, index?, item?}}` opens the `effect` dialog Effect dialogs: `engine.execute {command: "effect.dialog", params: {effect, index?, item?}}` opens the `effect` dialog