From c969b7857df0d7d9539b8e6039e457781277e6f1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 6 Nov 2026 11:05:27 -0800 Subject: [PATCH] Type: an Eyedropper pick on a selected story styles every frame appearance.copyFrom and a fill or stroke change on a story selected by Select All no longer skip locked frames, hidden frames and frames on a locked artboard. They apply to every frame of the story, as the Character panel's story edits do. appearance.copyFrom still leaves out a frame that is its source. --- crates/engine/src/cmd/appearance.rs | 9 ++++----- crates/engine/src/cmd/paint.rs | 11 +++-------- crates/engine/src/tests/textedit.rs | 6 +++--- docs/mcp.md | 5 ++--- docs/target-app-parity.md | 2 +- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/engine/src/cmd/appearance.rs b/crates/engine/src/cmd/appearance.rs index adf5ecca8..9cfedcb7a 100644 --- a/crates/engine/src/cmd/appearance.rs +++ b/crates/engine/src/cmd/appearance.rs @@ -126,7 +126,7 @@ pub fn specs() -> Vec { "Eyedropper", [], None, - "{source: id, ids?, pickUp?, apply? (trees as eyedropper.setOptions, merged over the Eyedropper Options for this call), reverse?: bool, append?: bool, defaultsOnly?: bool} copy the attributes both picked up and applied (by default the whole appearance stack, opacity and blend mode, and from type to type the character and paragraph attributes) from `source` to ids (default: highlighted characters while editing type, after Select All in a frame of threaded text the text of the story's frames that are not locked, hidden or on a locked artboard, leaving out `source`, otherwise the object selection; highlighted characters reject enabled non-neutral object transparency), the fill, stroke and weight also to the paint defaults; `defaultsOnly` updates only paint defaults and cannot be combined with ids, reverse or append; `reverse` (Alt-click) copies from the first selected object (or ids) onto `source` instead, or with nothing selected what the Eyedropper picked up last (a click without Alt); `append` (Shift+Alt-click) adds the source's fills, strokes and effects on top of each target's stack; placed gradients land at the same place relative to each target's bounds (defaults: fitted to new art) → {ids}", + "{source: id, ids?, pickUp?, apply? (trees as eyedropper.setOptions, merged over the Eyedropper Options for this call), reverse?: bool, append?: bool, defaultsOnly?: bool} copy the attributes both picked up and applied (by default the whole appearance stack, opacity and blend mode, and from type to type the character and paragraph attributes) from `source` to ids (default: highlighted characters while editing type, after Select All in a frame of threaded text the text of the story's frames, leaving out `source`, otherwise the object selection; highlighted characters reject enabled non-neutral object transparency), the fill, stroke and weight also to the paint defaults; `defaultsOnly` updates only paint defaults and cannot be combined with ids, reverse or append; `reverse` (Alt-click) copies from the first selected object (or ids) onto `source` instead, or with nothing selected what the Eyedropper picked up last (a click without Alt); `append` (Shift+Alt-click) adds the source's fills, strokes and effects on top of each target's stack; placed gradients land at the same place relative to each target's bounds (defaults: fitted to new art) → {ids}", has_doc, copy_from ), @@ -1104,14 +1104,13 @@ fn copy_from(s: &mut Session, p: &Value) -> Result { s.eyedropper_sample = Some(node); } // The highlighted characters' type objects and ranges: after Select All in threaded text, all - // the text of every frame of the story that is not locked, hidden or on a locked artboard, - // except a frame the attributes come from, in place of the object selection. + // the text of every frame of the story, except a frame the attributes come from, in place of + // the object selection. let story = super::textedit::editing_story(s); let ranges: Vec<(NodeId, Option<(usize, usize)>)> = match range { Some((id, _, _)) if story.contains(&id) => { targets.clear(); - let editable = s.doc()?.doc.editable(); - story.iter().copied().filter(|f| Some(*f) != source && editable(*f)).map(|f| (f, None)).collect() + story.iter().copied().filter(|f| Some(*f) != source).map(|f| (f, None)).collect() } Some((id, start, end)) if targets.contains(&id) => vec![(id, Some((start, end)))], _ => vec![], diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index 5031ea902..390f6ef49 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -433,15 +433,10 @@ pub(crate) fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) && let Some((id, a, b)) = super::textedit::editing_range(s)? && a != b { - // After Select All in threaded text, the text of every frame that is not locked, hidden or - // on a locked artboard takes it. + // After Select All in threaded text, every frame's text takes it. let story = super::textedit::editing_story(s); - let ranges: Vec<(NodeId, usize, usize)> = if story.is_empty() { - vec![(id, a, b)] - } else { - let editable = s.doc()?.doc.editable(); - story.iter().copied().filter(|f| editable(*f)).map(|f| (f, 0, usize::MAX)).collect() - }; + let ranges: Vec<(NodeId, usize, usize)> = + if story.is_empty() { vec![(id, a, b)] } else { story.iter().map(|f| (*f, 0, usize::MAX)).collect() }; let ids: Vec = ranges.iter().map(|(f, _, _)| *f).collect(); // When the type comes out as it was, the document and its undo history stay as they are. let before = s.doc()?.doc.clone(); diff --git a/crates/engine/src/tests/textedit.rs b/crates/engine/src/tests/textedit.rs index 4e1faf611..b58f62798 100644 --- a/crates/engine/src/tests/textedit.rs +++ b/crates/engine/src/tests/textedit.rs @@ -1687,7 +1687,7 @@ fn a_selection_inside_one_threaded_frame_stays_in_that_frame_under_the_eyedroppe } #[test] -fn an_eyedropper_pick_on_the_whole_story_leaves_locked_and_hidden_frames() { +fn an_eyedropper_pick_on_the_whole_story_styles_locked_and_hidden_frames() { for command in ["object.lock", "object.hide"] { for held_first in [false, true] { for mods in eyedropper_picks() { @@ -1699,8 +1699,8 @@ fn an_eyedropper_pick_on_the_whole_story_leaves_locked_and_hidden_frames() { s.select_tool("eyedropper", ViewInfo::default()).unwrap(); let undo = s.doc().unwrap().history.undo.len(); pick_red_source(&mut s, mods); - assert!(all_red(&s, other), "{command}: the editable frame takes the pick"); - assert!(!any_red(&s, held), "{command}: the held frame stays as it is"); + assert!(all_red(&s, other), "{command}: the other frame takes the pick"); + assert!(all_red(&s, held), "{command}: the held frame takes the pick"); assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "{command}: one undo step"); } } diff --git a/docs/mcp.md b/docs/mcp.md index 96430c5ae..fe8eff6f4 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5017,9 +5017,8 @@ every frame (the Character panel sends `text.setRangeStyle {id, story: true}`, w undo step). Remove Line Breaks, Remove Paragraph Breaks and Convert to Text also act on the text of every frame, as one undo step. A caret move or a click ends the story selection. Switching to the Eyedropper keeps the story selected, and switching back to a Type tool restores it. `appearance.copyFrom` and `paint.sampleColor` then apply to the text of -every frame that is not locked, hidden or on a locked artboard as one undo step, and `appearance.copyFrom` leaves out a -frame that is its `source`. A fill or stroke change also leaves locked and hidden frames and frames on a locked artboard -as they are. Once a thread is removed, typing replaces the text of the edited frame only. +every frame as one undo step, and `appearance.copyFrom` leaves out a frame that is its `source`. Once a thread is +removed, typing replaces the text of the edited frame only. ```json {"name":"press_key","arguments":{"key":"A","mods":{"cmd":true}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 81f446dca..f128c1e9d 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -735,7 +735,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - The function key shortcuts for Brushes, Color, Layers, Info, Gradient, Stroke, Attributes, Graphic Styles, Appearance, Align, Transform, Pathfinder, Transparency, and Symbols activate a background tab on the first press, hide its expanded dock or floating group when that tab is in front, and restore the group with all its tabs together on the next press; in an icon column they open and close the flyout while keeping the icons. A reassigned chord keeps that behavior, and Window > a hidden sibling restores the group. - Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too, and elsewhere the in-window menus' do: the field that had the keyboard when the menu opened gets them, with its selection, and the keyboard back (#516). In a frame of threaded text, Select All selects the whole story, highlighted in every frame: Copy and Cut take all of it, typing or Delete replaces it as one undo step, and a fill, a size step or a Character or Paragraph panel change applies to every frame's text (VectorCraft's own behavior; `textedit::select_all_in_a_threaded_frame_selects_the_whole_story`, `textedit::typing_over_the_whole_story_replaces_every_frames_text_in_one_step`). - With the whole story of threaded text selected by Select All, Remove Line Breaks, Remove Paragraph Breaks and Type › Bullets and Numbering › Convert to Text act on the text of every frame as one undo step, and the story stays selected after Convert to Text; a selection inside one frame changes only that frame (VectorCraft's own behavior; `textedit::remove_breaks_takes_the_whole_story_that_select_all_selected`, `textedit::convert_to_text_takes_the_whole_story_that_select_all_selected`). - - Switching from a Type tool to the Eyedropper keeps a whole story selected by Select All: a pick applies to the text of every frame that is not locked, hidden or on a locked artboard as one undo step, and switching back restores the story selection (VectorCraft's own behavior; `textedit::switching_to_the_eyedropper_keeps_the_whole_story_selected`). + - Switching from a Type tool to the Eyedropper keeps a whole story selected by Select All: a pick applies to the text of every frame as one undo step, and switching back restores the story selection (VectorCraft's own behavior; `textedit::switching_to_the_eyedropper_keeps_the_whole_story_selected`). - While the Type tool edits text, a character typed with AltGr is typed and runs no command. Firefox on Windows reports AltGr as Ctrl and Alt held, so in the web build AltGr+L (ł on a Polish layout) does not also open Layer Options for a new layer, and AltGr+Q (@ on a German layout) does not also run Reset Tracking and Kerning. A Ctrl+Alt chord that types no character runs its command. - On a layout without Latin letters, a one-key shortcut goes by the position of the key that typed the character, in the web build too, where the character arrives before its key press: on a Russian layout the P key (`з`) chooses the Pen, and two keys pressed in one frame each run their own shortcut. - With the Artboard tool, Cmd/Ctrl+A after a click on a row of the Artboards panel selects every artboard, as the macOS menu bar's Select All does then; the active artboard stays active, and the art's selection does not change. After a press outside the panel's list, Cmd/Ctrl+A selects every object again. It used to select every object after a click in the panel too. Public reference: [Select artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/select-artboards.html).