Merge pull request #5910 from mockydevs/feat/stroke-weight-shortcuts

Stroke Weight Up and Down: Cmd/Ctrl+Alt+. and , step the selected strokes' weight (#5797)
This commit is contained in:
BFlat
2026-11-06 17:31:49 +08:00
committed by GitHub
4 changed files with 150 additions and 2 deletions
+93
View File
@@ -24,6 +24,24 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
stroke_set
),
cmd!(
"stroke.weightUp",
"Stroke Weight Up",
[],
Some("Cmd+Alt+."),
"{ids?|id?} raise each selected stroke's weight by one step of Units › Stroke (a quarter unit below one unit, then whole units; up to 1000 pt), each from its own weight: the selected objects' active or top stroke, type's stroked characters; objects without a stroke are left alone. With nothing selected (and no ids), the weight of the next object drawn → {objects}",
has_doc,
|s, p| stroke_step(s, p, true)
),
cmd!(
"stroke.weightDown",
"Stroke Weight Down",
[],
Some("Cmd+Alt+,"),
"{ids?|id?} lower each selected stroke's weight by one step, as stroke.weightUp raises it, down to a quarter unit (a thinner weight is kept) → {objects}",
has_doc,
|s, p| stroke_step(s, p, false)
),
cmd!(
"stroke.setAdvanced",
"Stroke Options",
@@ -261,6 +279,61 @@ fn stroke_set(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({"objects": ids.iter().collect::<std::collections::BTreeSet<_>>().len()}))
}
/// `w` (pt) one step up or down in `unit` (Units › Stroke): a quarter unit up to one unit,
/// whole units above it, from 0.25 unit to 1000 pt. A weight between steps goes to the next step
/// in that direction (0.6 pt up is 0.75 pt); a weight below a quarter unit doesn't go down.
pub(crate) fn step_weight(w: f64, unit: Unit, up: bool) -> f64 {
const EPS: f64 = 1e-9;
let k = Some(unit.to_pt(1.0)).filter(|k| k.is_finite() && *k > 0.0).unwrap_or(1.0);
let w = if w.is_finite() { w.max(0.0) } else { 0.0 };
let v = w / k;
let next = if up {
if v < 1.0 - EPS { ((v * 4.0 + EPS).floor() + 1.0) / 4.0 } else { (v + EPS).floor() + 1.0 }
} else if v <= 1.0 + EPS {
(((v * 4.0 - EPS).ceil() - 1.0) / 4.0).max(0.25)
} else {
(v - EPS).ceil() - 1.0
};
let r = (next * k).min(1000.0);
// Never a step the wrong way: past 1000 pt up stays, below a quarter unit down stays.
if up { r.max(w) } else { r.min(w) }
}
/// `stroke.weightUp` / `stroke.weightDown`: each target stroke stepped from its own weight.
fn stroke_step(s: &mut Session, p: &Value, up: bool) -> Result<Value> {
let c = if up { "stroke.weightUp" } else { "stroke.weightDown" };
let unit = s.stroke_unit();
let item = item_target(s, p, c)?.of_kind(s, false);
let ids = item.targets(s, p)?;
if no_targets(&ids, p) {
let w = step_weight(s.paint.stroke_width, unit, up);
s.paint.stroke_width = w;
if let Some(st) = s.new_art_stroke_mut() {
st.width = w;
}
return Ok(json!({"objects": 0}));
}
let label = if up { "Stroke Weight Up" } else { "Stroke Weight Down" };
edit_items(s, &ids, item, c, label, false, |n, index| {
if index.is_none()
&& let NodeKind::Text(t) = &mut n.kind
{
// Character strokes are drawn in text space, scaled by the type's transform.
let k = Scaling::factor(t.xf).filter(|k| k.is_finite() && *k > 1e-12).unwrap_or(1.0);
for r in t.runs.iter_mut().filter(|r| !matches!(r.style.stroke, Paint::None)) {
let change = StrokeChange { weight: Some(step_weight(r.style.stroke_width * k, unit, up)), ..StrokeChange::default() };
change.apply_char_scaled(&mut r.style, k);
}
return Ok(());
}
if let Some(st) = n.appearance.stroke_at_mut(index) {
st.width = step_weight(st.width, unit, up);
}
Ok(())
})?;
Ok(json!({"objects": ids.iter().collect::<std::collections::BTreeSet<_>>().len()}))
}
impl Session {
/// Units > Stroke (Preferences): the unit stroke weights and dash lengths show in.
pub fn stroke_unit(&self) -> Unit {
@@ -514,3 +587,23 @@ fn profile_list(s: &mut Session, _: &Value) -> Result<Value> {
let current = s.shown_stroke().map(|st| s.profile_entry(st.profile.as_ref()).map_or("custom", |e| e.id).to_string());
Ok(json!({ "profiles": profiles, "current": current }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weight_steps_follow_units_and_stop_at_their_bounds() {
assert_eq!(step_weight(0.6, Unit::Points, true), 0.75);
assert_eq!(step_weight(0.6, Unit::Points, false), 0.5);
assert_eq!(step_weight(2.5, Unit::Points, true), 3.0);
assert_eq!(step_weight(2.5, Unit::Points, false), 2.0);
assert_eq!(step_weight(0.1, Unit::Points, false), 0.1, "below a quarter point a step down keeps it");
assert_eq!(step_weight(1000.0, Unit::Points, true), 1000.0);
assert_eq!(step_weight(2000.0, Unit::Points, true), 2000.0, "a step up never lowers a weight");
assert_eq!(step_weight(f64::NAN, Unit::Points, true), 0.25);
let mm = Unit::Millimeters.to_pt(1.0);
assert!((step_weight(mm, Unit::Millimeters, true) - 2.0 * mm).abs() < 1e-9);
assert!((step_weight(mm, Unit::Millimeters, false) - 0.75 * mm).abs() < 1e-9);
}
}
+46
View File
@@ -111,3 +111,49 @@ fn inspect_reports_stroke_options() {
let text = doc["layers"][0]["children"].as_array().unwrap().iter().find(|c| c["kind"] == "Text" || c.get("text").is_some()).unwrap().clone();
assert_eq!(text["strokeOptions"]["join"], "round", "{text}");
}
/// Stroke Weight Up and Down (Cmd/Ctrl+Alt+. and ,) step each selected stroke from its own
/// weight, a quarter point at a time below 1 pt and whole points above, in one undo step each.
#[test]
fn stroke_weight_up_and_down_step_each_selected_stroke() {
let mut s = session();
let (a, b, bare) = (rect(&mut s, 10.0), rect(&mut s, 60.0), rect(&mut s, 110.0));
select(&mut s, &[a]);
run(&mut s, "stroke.set", json!({"weight": 0.6}));
select(&mut s, &[b]);
run(&mut s, "stroke.set", json!({"weight": 3}));
select(&mut s, &[bare]);
run(&mut s, "paint.setStroke", json!({"none": true}));
let has_stroke = |s: &Session| {
s.doc().unwrap().doc.node(bare).unwrap().appearance.stroke().is_some_and(|st| !matches!(st.paint, vectorcraft_color::Paint::None))
};
let bare_before = has_stroke(&s);
let w = |s: &Session, id| s.doc().unwrap().doc.node(id).unwrap().appearance.stroke_width();
select(&mut s, &[a, b, bare]);
run(&mut s, "stroke.weightUp", json!({}));
assert_eq!((w(&s, a), w(&s, b)), (0.75, 4.0));
run(&mut s, "stroke.weightUp", json!({}));
assert_eq!((w(&s, a), w(&s, b)), (1.0, 5.0));
assert_eq!(has_stroke(&s), bare_before, "an object without a stroke takes none");
for _ in 0..6 {
run(&mut s, "stroke.weightDown", json!({}));
}
// 1 → 0.75 → 0.5 → 0.25, where it stays; 5 → 4 → 3 → 2 → 1 → 0.75 → 0.5.
assert_eq!((w(&s, a), w(&s, b)), (0.25, 0.5));
run(&mut s, "edit.undo", json!({}));
assert_eq!((w(&s, a), w(&s, b)), (0.25, 0.75), "one undo step per press");
}
/// With nothing selected the next object drawn takes the stepped weight.
#[test]
fn stroke_weight_up_with_nothing_selected_sets_up_new_art() {
let mut s = session();
run(&mut s, "select.none", json!({}));
run(&mut s, "stroke.set", json!({"weight": 2}));
run(&mut s, "stroke.weightUp", json!({}));
assert_eq!(s.paint.stroke_width, 3.0);
let r = rect(&mut s, 10.0);
assert_eq!(s.doc().unwrap().doc.node(r).unwrap().appearance.stroke_width(), 3.0);
assert_eq!(crate::cmd::find_command("stroke.weightUp").unwrap().shortcut, Some("Cmd+Alt+."));
assert_eq!(crate::cmd::find_command("stroke.weightDown").unwrap().shortcut, Some("Cmd+Alt+,"));
}
+6 -1
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -1670,6 +1670,10 @@ A VectorCraft addition, not a feature of the reference app, so no area score mov
A VectorCraft addition (it replaces a script some users ran), so no area score moves. Object › Plug-ins › Color Effects › Create Swatch Legend… (`addon.color.swatchLegend`) opens a dialog (columns, chip size, gap, values, selected swatches only, with a preview) and draws a chip per colour swatch (the selected ones, else all) filled with and linked to its swatch, labelled with its name and values (HEX, RGB, CMYK, Lab or Grayscale; by default its own model; spot swatches marked), black on light chips and white on dark ones, in a grid of chosen columns, chip size and spacing, as one selected group in one undo step (#4919). Tests: `cmd::addons::color::legend::tests` (engine), `addons::color::legend::tests::create_swatch_legend_opens_its_dialog_previews_and_runs_on_ok` (ui-egui).
## Stroke Weight Up and Down shortcuts (2026-11-06)
Cmd/Ctrl+Alt+. (`stroke.weightUp`) and Cmd/Ctrl+Alt+, (`stroke.weightDown`) raise and lower the weight of each selected stroke from its own weight, with the keys a [public feature request reply](https://illustrator.uservoice.com/forums/333657-illustrator-desktop-feature-requests/suggestions/32307397-increment-decrement-stroke-weight-key-commands) names: a quarter unit of Units › Stroke at a time up to one unit, whole units above it, no lower than a quarter unit and no higher than 1000 pt. Type steps its stroked characters; objects without a stroke are left alone; with nothing selected the weight of the next object drawn steps. Each press is one undo step (#5797). Evidence: `tests::strokeux::stroke_weight_up_and_down_step_each_selected_stroke`, `stroke_weight_up_with_nothing_selected_sets_up_new_art`, `cmd::stroke::tests::weight_steps_follow_units_and_stop_at_their_bounds`. Scores unchanged.
## Keyboard shortcuts for Effect menu items (2026-11-01)
Edit › Keyboard Shortcuts lists every item of the Effect menu under its menu path (Effect › Distort & Transform › Transform…) and gives each one a shortcut of its own (stored as `effect:<id>`; they all run `effect.dialog {effect}`, or `effect.apply` for an effect without options, so a shortcut on the command alone couldn't tell them apart). The menu shows the shortcut, and pressing it opens that effect's dialog for the selection (#3296). Test: `shortcuts::tests::an_effect_menu_item_takes_a_shortcut_of_its_own`.
@@ -1698,6 +1702,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
| Date | Change | Summary |
|---|---|---|
| 2026-11-06 | minor | Stroke Weight Up and Down (Cmd/Ctrl+Alt+. and ,) step each selected stroke's weight (#5797); evidence added; scores unchanged. |
| 2026-11-06 | minor | Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged |
| 2026-11-06 | minor | Properties in Edit Artboards: a Transform section (reference point, X, Y, W, H, preset, orientation, Move and Scale Artwork) above the Artboards section (name, Artboard Options, Fill), X and Y of the shared reference point (#5219); evidence updated; scores unchanged. |
| 2026-11-06 | minor | Scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields; a click still opens the panel (#4088); evidence updated; scores unchanged. |
+5 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
## Feature submission evidence
@@ -314,6 +314,9 @@ panel-menu commands such as Break Link to Symbol), so they can be given shortcut
Align panel's Align Objects, Distribute Objects and Distribute Spacing rows under Align, with no default shortcut, and a
shortcut set there runs what a click on the button runs, with the panel's Align To choice and Distribute Spacing value. The [ and ] keys reach the Blob Brush, the Eraser and the Symbolism tools in the app (#1734, part of #1661).
Cmd/Ctrl+Alt+. (`stroke.weightUp`, Stroke Weight Up) and Cmd/Ctrl+Alt+, (`stroke.weightDown`, Stroke Weight Down), the keys a [public feature request reply](https://illustrator.uservoice.com/forums/333657-illustrator-desktop-feature-requests/suggestions/32307397-increment-decrement-stroke-weight-key-commands) gives them, step the weight of each selected stroke from its own weight: by a quarter unit of Units › Stroke up to one
unit, then by whole units, down to a quarter unit; with nothing selected they step the weight of the next object drawn (#5797).
Cmd/Ctrl+Alt+N (`file.newWithoutDialog`, New Without Dialog in the shortcut editor) makes a document with the
settings the New Document dialog opens on (the first Recent entry, or the Print category's Letter while Recent is
empty), without opening the dialog.
@@ -464,6 +467,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-11-06 | minor | Keyboard shortcuts: Cmd/Ctrl+Alt+. and Cmd/Ctrl+Alt+, (Stroke Weight Up and Down) step each selected stroke's weight, or the next object drawn's (#5797); scores and estimates unchanged |
| 2026-11-06 | minor | Properties: Quick Actions has Repeat Options for one selected radial, grid or mirror repeat, opening Object › Repeat › Options with its values (#5181); scores and estimates unchanged |
| 2026-11-06 | minor | Control bar: with type selected, a Paragraph: link (the Paragraph panel in a popover) and the Align left, Align center and Align right buttons follow the Character controls; the bar leaves them out from its right end when short of room, after X/Y/W/H and the Align buttons (#5372); scores and estimates unchanged |
| 2026-11-04 | minor | Contour add-on (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `addon.contour.set` is the command behind it. It is a built-in add-on: Object › Plug-ins › Contour, and its own heading in All Tools, with no default shortcut or toolbar slot. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. |