mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 18:56:55 -06:00
Toolbar: double-clicking the Type on a Path tool opens Type on a Path Options
A double-click on the Type on a Path or Vertical Type on a Path tool reported that the tool has no options. It now opens Type on a Path Options for the selected type on a path, the same dialog as Type › Type on a Path › Type on a Path Options…. With nothing selected, or with no type on a path selected, nothing opens and `tool.options` returns the menu command's error, as the Rotate tool's double-click does without a selection. Docs: docs/control-protocol.md, the `tool.options` params doc.
This commit is contained in:
@@ -616,7 +616,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"tool.options",
|
||||
"Tool Options…",
|
||||
"",
|
||||
"{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), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, 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}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), livePreview (pencil, on: off draws a thin line while the stroke is drawn, and the path's fill and stroke on release), size (pt), angle (°, −180–180; 0) and roundness (%, 1–100; 100) of the eraser's tip; OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", 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), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), typeOnPath|verticalTypeOnPath → Type on a Path Options for the selected type on a path (dialog `command` running type.pathOptions; error `nothing selected` without a selection, and the command's error when no type on a path is selected),symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, 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}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), livePreview (pencil, on: off draws a thin line while the stroke is drawn, and the path's fill and stroke on release), size (pt), angle (°, −180–180; 0) and roundness (%, 1–100; 100) of the eraser's tip; OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values})",
|
||||
),
|
||||
(
|
||||
"ui.colorGuideOptions",
|
||||
|
||||
@@ -328,7 +328,8 @@ fn flyout_press(ui: &Ui, resp: &egui::Response, rect: egui::Rect) -> Option<Flyo
|
||||
/// Reflect and Shear tools' are their Object › Transform dialogs, the selection tools' are the
|
||||
/// Move dialog, and the Pencil, Paintbrush, Smooth, Blob Brush, Eraser and Shape Builder tools' are
|
||||
/// their Tool Options, as are the Line Segment, Arc, Rectangular Grid and Polar Grid tools'
|
||||
/// ([`crate::dialogs::shapes::open_options`]).
|
||||
/// ([`crate::dialogs::shapes::open_options`]). The Type on a Path and Vertical Type on a Path
|
||||
/// tools' are Type on a Path Options for the selected type on a path.
|
||||
pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::Value, String> {
|
||||
match tool {
|
||||
"dimension" => crate::dialogs::dimension::open(app),
|
||||
@@ -377,6 +378,17 @@ pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::
|
||||
_ if vectorcraft_tools::settings::SYMBOLISM.contains(&tool) => Ok(crate::dialogs::symbolism_options::open(app, tool)),
|
||||
// The graph tools: Graph Type for the selected graph.
|
||||
_ if vectorcraft_tools::extra::is_graph_tool(tool) => crate::menus::graph_dialog(app, "graph.setType"),
|
||||
// The Type on a Path tools: Type on a Path Options for the selected type on a path, as
|
||||
// Type › Type on a Path › Type on a Path Options… opens it.
|
||||
"typeOnPath" | "verticalTypeOnPath" => {
|
||||
let id = "type.pathOptions";
|
||||
if let Some(c) = vectorcraft_engine::find_command(id) {
|
||||
(c.enabled)(&app.session)?;
|
||||
}
|
||||
app.session.execute(id, &json!({})).map_err(|e| e.to_string())?;
|
||||
crate::menus::invoke(app, id, json!({}));
|
||||
Ok(json!({ "dialog": "command" }))
|
||||
}
|
||||
_ if vectorcraft_tools::tool_info(tool).is_none() => Err(format!("unknown tool `{tool}`")),
|
||||
_ => Err(format!("the {tool} tool has no options")),
|
||||
}
|
||||
@@ -1029,6 +1041,53 @@ pub(crate) mod tests {
|
||||
assert!(app.run("tool.options", json!({"tool": "lasso"})).is_err());
|
||||
}
|
||||
|
||||
/// A double-click on the Type on a Path or Vertical Type on a Path tool opens Type on a Path
|
||||
/// Options for the selected type on a path, as the menu item does; with nothing selected, or
|
||||
/// with no type on a path selected, nothing opens.
|
||||
#[test]
|
||||
fn double_clicking_a_type_on_a_path_tool_opens_type_on_a_path_options() {
|
||||
for tool in ["typeOnPath", "verticalTypeOnPath"] {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.ui.toolbar_advanced = true;
|
||||
app.run("file.new", json!({"width": 600, "height": 400})).unwrap();
|
||||
app.select_tool(tool);
|
||||
let index = slots(&app).iter().position(|(_, tools)| tools.contains(&tool)).expect("a toolbar slot");
|
||||
let ctx = egui::Context::default();
|
||||
let at = frame_in(&mut app, &ctx, 0.0, vec![], 2000.0)[index].center();
|
||||
let double_click = |app: &mut VectorcraftApp, time: f64| {
|
||||
let b = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame_in(app, &ctx, time, vec![Event::PointerMoved(at), b(true), b(false), b(true), b(false)], 2000.0);
|
||||
frame_in(app, &ctx, time + 0.1, vec![], 2000.0);
|
||||
};
|
||||
// Nothing selected: nothing opens.
|
||||
double_click(&mut app, 1.0);
|
||||
assert!(app.ui.dialog.is_none(), "{tool}");
|
||||
assert_eq!(app.run("tool.options", json!({ "tool": tool })), Err("nothing selected".into()));
|
||||
// A path that holds no type: nothing opens either.
|
||||
let path = app.run("path.create", json!({"d": "M100 200 L500 200"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("select.set", json!({"ids": [path]})).unwrap();
|
||||
double_click(&mut app, 2.0);
|
||||
assert!(app.ui.dialog.is_none(), "{tool}");
|
||||
let e = app.run("tool.options", json!({ "tool": tool })).unwrap_err();
|
||||
assert!(e.contains("`type.pathOptions`: no text objects selected"), "{tool}: {e}");
|
||||
assert!(app.ui.dialog.is_none(), "{tool}");
|
||||
// Type on a path selected: the dialog opens on its options.
|
||||
let r = app.run("text.createInPath", json!({"path": path, "mode": "onPath", "text": "Along", "at": [300, 210]})).unwrap();
|
||||
app.run("select.set", json!({"ids": [r["id"]]})).unwrap();
|
||||
app.run("type.pathOptions", json!({"effect": "skew"})).unwrap();
|
||||
assert_eq!(app.session.tool_id(), tool);
|
||||
double_click(&mut app, 3.0);
|
||||
let d = app.ui.dialog.clone().unwrap_or_else(|| panic!("{tool}: Type on a Path Options"));
|
||||
assert_eq!((d.str("__command"), d.str("__label")), ("type.pathOptions".to_string(), "Type on a Path Options".to_string()));
|
||||
assert_eq!(d.fields.get("effect"), Some(&json!("skew")), "{tool}");
|
||||
assert!(!d.fields.contains_key("start") && !d.fields.contains_key("end"), "{tool}: the menu item's fields");
|
||||
// OK applies what the dialog holds.
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("effect".into(), json!("gravity"));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.execute("type.pathOptions", &json!({})).unwrap()["effect"], "gravity", "{tool}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polygon_and_star_canvas_counts_persist_without_new_toolbar_dialogs() {
|
||||
for (tool, key, count) in [("polygon", "sides", 3), ("star", "points", 7)] {
|
||||
|
||||
@@ -187,7 +187,9 @@ tools opens its dialog too (nothing happens without a selection), with the trans
|
||||
point. For `lineSegment`, `arc`, `rectangularGrid` and `polarGrid` it opens the tool's Tool Options: the dialog its
|
||||
click opens (kind = the tool id), without `x` and `y` and with `__toolOptions: true`, whose `ui.dialog.confirm` runs
|
||||
`tool.setOption {tool, values}` and draws nothing; the next drag with the tool and its click dialog use the values
|
||||
(the line's `fillLine`, the grids' Default Size `width`, `height` and `reference` too). Gradient tool handles snap to
|
||||
(the line's `fillLine`, the grids' Default Size `width`, `height` and `reference` too). For `typeOnPath` and
|
||||
`verticalTypeOnPath` it opens Type on a Path Options for the selected type on a path (a `command` dialog running
|
||||
`type.pathOptions`), or fails when no type on a path is selected. Gradient tool handles snap to
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user