Control bar: the Global Edit Options dropdown shows while Start Global Edit can run

While Select > Start Global Edit can run, the Control bar shows a Global Edit
Options dropdown after Recolor Artwork. It opens the Properties panel's Global
Edit Options (Match: Appearance, Size; On Artboards; Include Objects On
Canvas; Result(s)), so both set the same options, and it is off while Global
Edit is on, as the Properties dropdown is. The bar leaves it out when no room
is left for it.

Public reference:
https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html

The dropdown takes room ahead of the inline X/Y/W/H fields, so the ruler-origin
tests that read X/Y from the Control bar draw it 1700 px wide.
This commit is contained in:
dotCooCoo
2026-11-04 15:41:15 +07:00
committed by bflatastic
parent a176ffae46
commit f4469e16d6
6 changed files with 106 additions and 4 deletions
+22
View File
@@ -135,6 +135,27 @@ fn isolation_button(app: &mut VectorcraftApp, ui: &mut Ui) {
}
}
/// The size of the Control bar's Global Edit Options button.
const GLOBAL_EDIT_OPTIONS_WIDTH: f32 = 24.0;
/// The Control bar's Global Edit Options dropdown, shown while `select.globalEdit` can run. The help
/// page places the options in the Control panel as well as in Properties
/// (<https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html>).
/// It opens the Properties panel's Global Edit Options, which set the same session options, and is
/// off while Global Edit is on, as that panel's dropdown is. It sits after Recolor Artwork, ahead of
/// the X/Y/W/H fields that the bar leaves out when it runs short of room, and is left out only when
/// no room is left for it.
fn global_edit_options(app: &mut VectorcraftApp, ui: &mut Ui) {
if !crate::menus::enabled(app, "select.globalEdit") || ui.available_width() < GLOBAL_EDIT_OPTIONS_WIDTH {
return;
}
let on = app.session.active().is_some_and(|d| d.global_edit.is_some());
let options = widgets::icon_button_enabled(ui, "chevron-down", tl!("Global Edit Options"), false, !on, GLOBAL_EDIT_OPTIONS_WIDTH);
egui::Popup::menu(&options)
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| crate::panels::properties::global_edit_options(app, ui));
}
/// The room the Control bar's inline X/Y/W/H fields need; with less, only the Transform link shows.
const INLINE_TRANSFORM_WIDTH: f32 = 440.0;
@@ -237,6 +258,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
if colored && widgets::icon_button(ui, "palette", tl!("Recolor Artwork"), false, 24.0).clicked() {
crate::menus::invoke(app, "ui.recolorDialog", json!({}));
}
global_edit_options(app, ui);
}
ui.separator();
crate::panels::character::control_bar(app, ui);
+78
View File
@@ -1803,3 +1803,81 @@ fn the_control_bar_shows_the_distribute_buttons_for_two_or_more_objects() {
};
assert_eq!((left(&app, &a), left(&app, &b), left(&app, &c)), (10.0, 105.0, 200.0), "{}", app.ui.status);
}
/// The Control bar shows the Global Edit Options dropdown while Start Global Edit can run. Its
/// options are the session's, which the Properties panel's dropdown also sets: Match Size from
/// the Control bar narrows the Results and the objects Start Global Edit takes. The dropdown is off
/// while Global Edit is on, and absent when the selection cannot start it.
#[test]
fn the_control_bar_shows_the_global_edit_options() {
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let rect = |app: &mut crate::VectorcraftApp, x: f64, size: f64| {
app.run("shape.rectangle", json!({"x": x, "y": 20, "width": size, "height": size})).unwrap()["id"].clone()
};
let a = rect(&mut app, 20.0, 40.0);
rect(&mut app, 100.0, 40.0);
rect(&mut app, 200.0, 80.0);
let ctx = egui::Context::default();
ctx.enable_accesskit();
crate::theme::install_fonts(&ctx);
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 300.0));
let frame = |app: &mut crate::VectorcraftApp, events: Vec<egui::Event>| {
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(screen), ..Default::default() }, |ui| super::control_bar(app, ui));
out.textures_delta.clear();
out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default()
};
type Nodes = Vec<(egui::accesskit::NodeId, egui::accesskit::Node)>;
let find = |nodes: &Nodes, name: &str| nodes.iter().find(|(_, n)| n.label() == Some(name) || n.value() == Some(name)).map(|(_, n)| n.clone());
let center = |n: &egui::accesskit::Node| {
let b = n.bounds().unwrap_or_else(|| panic!("{n:?}"));
egui::pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
};
let click = |app: &mut crate::VectorcraftApp, at: egui::Pos2| {
let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, vec![egui::Event::PointerMoved(at), press(true)]);
frame(app, vec![press(false)]);
frame(app, vec![]);
frame(app, vec![])
};
app.run("select.set", json!({"ids": []})).unwrap();
frame(&mut app, vec![]);
assert!(find(&frame(&mut app, vec![]), "Global Edit Options").is_none(), "nothing selected: no dropdown");
app.run("select.set", json!({"ids": [a]})).unwrap();
frame(&mut app, vec![]);
let nodes = frame(&mut app, vec![]);
let button = find(&nodes, "Global Edit Options").unwrap_or_else(|| panic!("one rectangle: the dropdown shows"));
let nodes = click(&mut app, center(&button));
assert!(find(&nodes, "Results: 3").is_some(), "the options open with their Results");
let size = find(&nodes, "Size").unwrap_or_else(|| panic!("the Size check"));
let nodes = click(&mut app, center(&size));
assert!(app.session.global_edit_options.size, "Match Size is the session's option, shared with Properties");
assert!(find(&nodes, "Results: 2").is_some(), "Size narrows the Results");
app.run("select.globalEdit", json!({})).unwrap();
let ge = app.session.active().unwrap().global_edit.as_ref().map(|g| g.members.len());
assert_eq!(ge, Some(2), "Start Global Edit takes the two rectangles of the same size");
let nodes = frame(&mut app, vec![]);
let button = find(&nodes, "Global Edit Options").unwrap_or_else(|| panic!("Global Edit on: the dropdown still shows"));
assert!(button.is_disabled(), "the options are off while Global Edit is on");
// At any width the button either fits inside the bar or is left out, and a wider bar never
// leaves it out once a narrower one showed it.
app.run("select.globalEdit", json!({})).unwrap();
let mut seen = false;
for width in (600..=2400).step_by(10) {
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width as f32, 300.0));
let mut nodes = Vec::new();
for _ in 0..2 {
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| super::control_bar(&mut app, ui));
out.textures_delta.clear();
nodes = out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default();
}
let shown = find(&nodes, "Global Edit Options").and_then(|n| n.bounds());
if let Some(b) = shown {
assert!(b.x1 <= f64::from(width), "{width}: {b:?}");
}
assert!(!seen || shown.is_some(), "{width}: left out after a narrower bar showed it");
seen |= shown.is_some();
}
assert!(seen, "a wide bar has room for it");
}
+3 -2
View File
@@ -245,8 +245,9 @@ fn global_edit_row(app: &mut VectorcraftApp, ui: &mut Ui) {
}
/// The Global Edit Options: what else similar objects must share (Match), where they may lie (On
/// Artboards) and how many objects that takes now.
fn global_edit_options(app: &mut VectorcraftApp, ui: &mut Ui) {
/// Artboards) and how many objects that takes now. The Control bar's Global Edit Options dropdown
/// shows them too.
pub(crate) fn global_edit_options(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.set_min_width(220.0);
let o = app.session.global_edit_options.clone();
let set = |app: &mut VectorcraftApp, p: serde_json::Value| crate::menus::invoke(app, "select.globalEditOptions", p);
+1 -1
View File
@@ -24,7 +24,7 @@ fn context() -> egui::Context {
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<Event>, draw: fn(&mut VectorcraftApp, &mut Ui)) -> Vec<(String, Rect)> {
let raw = egui::RawInput {
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1600.0, 900.0))),
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1700.0, 900.0))),
time: Some(ctx.input(|i| i.time) + 0.05),
events,
..Default::default()
+1
View File
@@ -498,6 +498,7 @@ since 2026-10-01.
- **Clipping paths on their outline (G1):** the selection tools (Selection, Direct Selection, Group Selection), Object Highlighting and a right-click take a clipping path on its outline before the art it clips; Select Behind lists the same; the eyedropper, the tools that act on what is drawn and the selection tools' double-clicks, loaded text and guide release take the art. Doc and tool regression tests; score unchanged.
- **Escape in Selection tool drags:** Escape during a Selection tool move or Alt-drag copy, a bounding-box scale or rotation, a marquee, or a corner widget, pie widget or bracket drag cancels it: the art returns to its state at the press, no copy is left, no undo step is added, the release does nothing, and the tool stays chosen. This is VectorCraft's own behavior. Regression tests: `escape_cancels_*` in `select/tests.rs` and `pie.rs`, and `escape_cancels_a_selection_tool_move_copy_scale_or_rotation` in `tests/selectescape.rs`.
- **Global Edit: Shift-click leaves a match out:** while Global Edit is on, a Shift-click with the Selection tool on a highlighted match, or on an object inside a matched group, leaves that match out (`select.globalEditExclude`): it is no longer highlighted and later edits leave it as it is; the object the edit started from stays, and Global Edit ends when fewer than two matches are left ([Update assets quickly with Global Edit](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)). Regression tests: `a_shift_click_*`, `an_object_left_out_*`, `a_group_left_out_*` and `leaving_out_all_but_one_*` in `crates/engine/src/tests/globaledit.rs`.
- **Global Edit Options in the Control bar:** while Select › Start Global Edit can run, the Control bar shows a Global Edit Options dropdown that opens the same options as the Properties panel's (Match: Appearance, Size; On Artboards; Include Objects On Canvas; Result(s)); both set the same options. The dropdown is off while Global Edit is on; it sits after Recolor Artwork, ahead of the X/Y/W/H fields ([Edit similar objects globally](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)). Regression test: `the_control_bar_shows_the_global_edit_options` in `crates/ui-egui/src/chrome/tests.rs`.
- **Pattern-only backquote drags:** With Selection move, bounding-box scale or rotate, or the Rotate, Reflect, Scale or Shear tool, backquote held at mouse release applies the drag matrix to selected pattern paint while object geometry stays in place. The key may be pressed after mouse press; if released before the mouse button, the drag commits as an ordinary transform. Objects without pattern paint stay still; stroke-only patterns and patterned objects in mixed selections take the same matrix. Regression tests: `backquote_move_uses_release_state_and_one_commit` and `backquote_box_scale_and_rotate_use_pattern_matrix_at_release` in `select/tests.rs`, `backquote_drag_previews_and_commits_pattern_only_for_each_transform_tool` in `xform/transform.rs`, `selection_backquote_drag_changes_pattern_fill_and_stroke_without_moving_mixed_art` and `selection_backquote_drag_changes_stroke_only_pattern` in `tests/pattern.rs`, `egui_backtick_drag_uses_key_state_at_release` in `canvas/tests.rs`, and `synthetic_pointer_backquote_keeps_solid_object_in_place` in `control/tests.rs`. Scores and hours unchanged.
- **Existing-guide Escape cancellation (G1):** Escape clears a pending guide press or drag through Selection and Direct/Group Selection, cancelling a begun preview. Eight app-input regressions cover both axes, move/copy, temporary selection-tool borrowing from idle Pen, preselected-guide document/interaction-start selection and Undo/Redo/saved-state preservation, later on-canvas motion/release, Shift-press cancellation, and ordinary-release/Enter/idle controls. Historical focused checks on `8ab574b7` recorded five expected RED failures with three passing controls, then all eight GREEN tests and the Direct Selection Escape control passed. All nine also passed in current full CI. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure. The original 1280×960 headless app capture from `b076025a` was reviewed for one vertical Selection guide after Escape; source review found its scene unchanged on `a0671777`. Scores and hours unchanged.
- **Escape in more tool drags:** Escape during an Anchor Point drag, a Mesh tool drag, a Direct Selection drag of a mesh point, a corner widget or the brackets of type on a path, or a Shape Builder drag cancels it: the art goes back to how the press found it, no undo step is added, the release does nothing, and the tool stays chosen. Escape during a Measure tool drag brings back the measurement before it. Regression tests: `escape_cancels_*` in `draw2/anchor/tests.rs`, `meshedit.rs`, `direct/tests.rs`, `builder/tests.rs` and `xform/measure.rs`, and `escape_cancels_an_anchor_point_mesh_shape_builder_or_measure_drag` in `tests/toolescape.rs`.
+1 -1
View File
@@ -325,7 +325,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Actions panel: Delete Selection (bottom bar) deletes the selected set with its actions and keeps the other sets ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html)) | partial | `ui.actionSet.delete`; `panels/actions.rs` tests. VectorCraft's own rules, which the overview does not cover: deleting a set asks first (a selected action is still deleted at once), Shift-click on a set's name selects a range of sets, ≡ › Delete does the same as the button, and Default Actions and the last set can be deleted. Undo does not restore a deleted set, and recorded commands cannot be selected or deleted |
| Actions panel: ≡ › Duplicate copies the selected action or set ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html)) | partial | `ui.actionSet.duplicate`; `panels/actions.rs` tests. VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected, and a copied set holds copies of all its actions. Undo does not remove the copy, and recorded commands cannot be duplicated |
| Properties panel per context (every selection type's Quick Actions) | partial | one clip group: Edit Clipping Path and Edit Contents, at the top of the panel rather than in Quick Actions (G1); the overview's additional controls ([Properties panel overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/properties-panel-overview.html)): Blend Options (one blend selected, with any tool, #4817) and Merge Live Paint (a Live Paint group with more) in Quick Actions, the Width Profile list (Width tool or a variable-width stroke) and an applied effect's Delete icon in Appearance, Corner and Corner Radius for corners selected with Direct Selection; Variable Font was already there |
| Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs; a Shift-click on a highlighted match leaves it out | partial | #3521, `select.globalEdit` / `select.globalEditOptions` / `select.globalEditExclude`, `crates/engine/src/tests/globaledit.rs`; paths, compound paths, symbol instances and groups of them (similar groups, #4939: edits inside one group reach the same object in the others; [public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-groups-with-similar-objects.html)); Shift-click to leave a match out ([public source](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)); the Control panel's copy of the options, rotated copies and highlighting matches before the start remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) |
| Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs; a Shift-click on a highlighted match leaves it out | partial | #3521, `select.globalEdit` / `select.globalEditOptions` / `select.globalEditExclude`, `crates/engine/src/tests/globaledit.rs`; paths, compound paths, symbol instances and groups of them (similar groups, #4939: edits inside one group reach the same object in the others; [public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-groups-with-similar-objects.html)); Shift-click to leave a match out ([public source](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)); the Control bar's Global Edit Options dropdown opens the same options; rotated copies and highlighting matches before the start remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) |
| Contextual task bar | done | |
| Control bar with Transform and Stroke popovers | done | |
| Pathfinder shape results keep their source names and SVG IDs | done | #1293; Minus Front keeps the back name, Minus Back the front name; Unite/Intersect/Exclude use the backmost named input; compound output carries it once; geometry and paint unchanged |