Merge pull request #5828 from mockydevs/feat/f2-rename-5324

Layers: F2 renames the selected layer, group or object (#5324)
This commit is contained in:
BFlat
2026-11-06 16:32:21 +08:00
committed by GitHub
6 changed files with 128 additions and 4 deletions
+1
View File
@@ -707,6 +707,7 @@ fn run_ui_part8(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
}
"ui.tileEdgeColor" => crate::dialogs::tile_edge_color::open(app),
"ui.layerOptions" => crate::dialogs::layer_options::open(app, p),
"ui.rename" => crate::panels::layers::rename(app, p),
"ui.newLayer" => crate::dialogs::layer_options::open_new(app, p),
"ui.newArtboard" => crate::dialogs::artboard_options::open_new(app),
"ui.layersPanelOptions" => crate::dialogs::layers_panel_options::open(app),
+6
View File
@@ -855,6 +855,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{ids?|id?} open Layer Options for Layers panel rows (default: the highlighted rows, else the current layer), filled in from the first: dialog `layerOptions`, fields name, color (a preset name such as \"Light Blue\" or #rrggbb), template, locked, visible, printable, preview, dimImages (bool), dimPercent (0–100); an object's row has name, visible and locked. OK runs layer.setProps (one undo step); for several rows it sends only the fields set in the dialog",
),
(
"ui.rename",
"Rename",
"",
"{id?} open the Layers panel's name field on the row of layer, group or object id, as double-clicking the row's name does (the panel is shown and the layers around the row open): Enter renames it with layer.setProps (one undo step), Escape keeps the name. Without id: the highlighted row when a Layers panel row was clicked last, else the one selected object or group, else the one highlighted row, else the current layer. Several rows or objects, or a row the panel's search, filter or Show Layers Only hides, is an error and nothing opens → {id, name}",
),
(
"ui.newLayer",
"New Layer Options…",
+55 -1
View File
@@ -250,7 +250,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if let Some(id) = located {
open_around(app, &doc, &[id]);
}
let reveal = located.or(reveal);
// A row Rename (`ui.rename`) opened the name field of comes into view first.
let renamed = ui.data_mut(|d| d.remove_temp::<NodeId>(key("rename-reveal")));
let reveal = renamed.or(located).or(reveal);
let Some(st) = app.session.active() else { return };
let list = rows_of_doc(ui, st, &app.ui.layers_panel, &query, &app.ui.layers_filter);
let view = View {
@@ -377,6 +379,58 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
see_selection(app, ui, selected);
}
/// The search typed into the Layers panel's Search All field, as the panel matches it.
fn search_query(ctx: &egui::Context) -> String {
ctx.data(|d| d.get_temp::<String>(egui::Id::unique("layers-search"))).unwrap_or_default().trim().to_lowercase()
}
/// `ui.rename {id?}`: open the Layers panel's name field on the row of layer, group or object
/// `id`, as double-clicking the row's name does: Enter renames it (`layer.setProps`, one undo
/// step), Escape leaves the name as it was. Without `id`, the row Rename names is the Layers
/// panel's highlighted row when a row was clicked last, else the one selected object or group,
/// else the one highlighted row, else the current layer. Several of them, or a row the panel's
/// search, filter or Show Layers Only leaves out, is an error that opens nothing.
pub fn rename(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let ctx = app.ui_ctx.clone().ok_or("the Layers panel is not shown")?;
let st = app.session.active().ok_or("no document open")?;
let one = |ids: &[NodeId]| match ids {
[id] => Ok(*id),
_ => Err("select one layer, group or object to rename".to_string()),
};
let id = match p.get("id") {
Some(v) => {
let id = NodeId(v.as_u64().ok_or_else(|| format!("`id` is an object id, not {v}"))?);
st.doc.node(id).map(|_| id).ok_or("no such layer or object")?
}
None => {
let rows = st.highlighted_rows();
let selected = &st.selection.objects;
let rows_first = super::has_row_keys(&ctx, ROW_KEYS) || selected.is_empty();
if rows_first && !rows.is_empty() {
one(&rows)?
} else if !selected.is_empty() {
one(selected)?
} else {
st.current_layer().ok_or("no layer to rename")?
}
}
};
let doc = st.doc.clone();
let node = doc.node(id).ok_or("no such layer or object")?;
if !listed_when_opened(&doc, &st.layers_open, &app.ui.layers_panel, &search_query(&ctx), &app.ui.layers_filter, id) {
return Err("the Layers panel's search, filter or Show Layers Only hides its row".into());
}
let name = node.identify_label(app.session.prefs.identify_objects_by == "xmlId");
crate::panel_docking::reveal_panel(app, "layers")?;
open_around(app, &doc, &[id]);
ctx.data_mut(|d| {
d.insert_temp(rename_id(), (id.0, name.clone()));
d.insert_temp(key("rename-reveal"), id);
});
ctx.request_repaint();
Ok(json!({ "id": id.0, "name": name }))
}
/// Open the layers and groups around rows `ids`.
fn open_around(app: &mut VectorcraftApp, doc: &Document, ids: &[NodeId]) {
let around: Vec<NodeId> =
+60
View File
@@ -469,6 +469,66 @@ fn shift_enter_renames_a_layer_and_keeps_the_field_open() {
assert!(ctx.data(|d| d.get_temp::<(u64, String)>(rename_id())).is_none(), "and closes the field");
}
/// Rename (`ui.rename`, F2) opens the name field on the selected object's row, as a
/// double-click on the name does: Enter renames it in one undo step.
#[test]
fn rename_opens_the_name_field_of_the_selected_object() {
let (mut app, _, [a, _]) = two_rects();
let ctx = egui::Context::default();
app.ui_ctx = Some(ctx.clone());
let name = |app: &VectorcraftApp| app.session.doc().unwrap().doc.node(a).unwrap().display_name();
let before = name(&app);
app.session.execute("select.set", &json!({"ids": [a.0]})).unwrap();
let opened = app.run("ui.rename", json!({})).unwrap();
assert_eq!(opened, json!({"id": a.0, "name": before}));
assert_eq!(ctx.data(|d| d.get_temp::<(u64, String)>(rename_id())), Some((a.0, before.clone())), "the field opens on its row");
let key = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers };
frame(&mut app, &ctx, vec![], false);
frame(&mut app, &ctx, vec![], false);
assert!(ctx.memory(|m| m.has_focus(rename_id().with("field"))), "with the keyboard");
frame(&mut app, &ctx, vec![key(egui::Key::A, egui::Modifiers::COMMAND), egui::Event::Text("Sun".into())], false);
frame(&mut app, &ctx, vec![key(egui::Key::Enter, egui::Modifiers::NONE)], false);
frame(&mut app, &ctx, vec![], false);
assert_eq!(name(&app), "Sun");
app.session.execute("edit.undo", &json!({})).unwrap();
assert_eq!(name(&app), before, "one undo step");
}
/// Rename (`ui.rename`) has no default key, F2 being free: it is listed in Keyboard Shortcuts,
/// where a key can be assigned to it.
#[test]
fn rename_has_no_default_key() {
use crate::shortcut_editor::{command_for_key, default_command_shortcut};
crate::shortcut_editor::sync(&crate::state::UiState::default());
assert_eq!(default_command_shortcut("ui.rename"), None);
assert_eq!(command_for_key("F2"), None);
let (mut app, _, _) = two_rects();
let listed = app.run("shortcuts.list", json!({"query": "ui.rename"})).unwrap();
let rows: Vec<_> = listed.as_array().expect("rows").iter().map(|r| r["label"].clone()).collect();
assert_eq!(rows, [json!("Rename")], "Keyboard Shortcuts lists it");
}
/// Without a selection Rename names the current layer; it refuses several objects and a row the
/// panel doesn't list, and opens nothing then.
#[test]
fn rename_names_the_current_layer_and_refuses_several_or_hidden_rows() {
let (mut app, layer, [a, b]) = two_rects();
let ctx = egui::Context::default();
app.ui_ctx = Some(ctx.clone());
let open = |ctx: &egui::Context| ctx.data(|d| d.get_temp::<(u64, String)>(rename_id())).map(|r| r.0);
assert_eq!(app.run("ui.rename", json!({})).unwrap()["id"], json!(layer.0), "nothing selected: the current layer");
ctx.data_mut(|d| d.remove::<(u64, String)>(rename_id()));
app.session.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
assert!(app.run("ui.rename", json!({})).is_err(), "two objects");
assert_eq!(open(&ctx), None);
app.ui.layers_panel.layers_only = true;
assert!(app.run("ui.rename", json!({"id": a.0})).is_err(), "Show Layers Only lists no object rows");
assert_eq!(open(&ctx), None);
assert!(app.run("ui.rename", json!({"id": 999_999})).is_err(), "no such object");
assert_eq!(app.run("ui.rename", json!({"id": layer.0})).unwrap()["id"], json!(layer.0));
assert_eq!(open(&ctx), Some(layer.0));
}
#[test]
fn an_open_rename_does_not_follow_into_another_document() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
+3 -1
View File
@@ -370,7 +370,9 @@ pub fn entries() -> &'static [Entry] {
is_tool: false,
}));
for (id, label, sc, params) in menus::UI_COMMANDS {
if !seen.insert(id) || (sc.is_empty() && !params.starts_with("{}") && !params.starts_with("{path")) {
// Rename has no default key, and a key press names the selection: it needs no `id`.
let key_runs_it = !sc.is_empty() || params.starts_with("{}") || params.starts_with("{path") || *id == "ui.rename";
if !seen.insert(id) || !key_runs_it {
continue;
}
v.push(Entry { key: id.to_string(), label: label.to_string(), group: id_group(id), is_tool: false });
+3 -2
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (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 (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
@@ -38,6 +38,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-06 | minor | Rename (`ui.rename`, no default key) opens the Layers panel's name field on the selected layer, group or object's row, as double-clicking its name does; a key can be assigned in Keyboard Shortcuts (#5324, a VectorCraft addition); scores and estimates unchanged |
| 2026-11-06 | minor | Properties panel in Edit Artboards (Shift+O): a Transform section with the shared reference point, X, Y, W and H, the preset and orientation, and Move and Scale Artwork with Artboard, above the Artboards section with the name, Artboard Options and Fill (#5219); scores and estimates unchanged |
| 2026-11-06 | minor | Scrubby labels: the Control bar's Stroke: and Opacity: and the Properties panel's Stroke, links to their panels, scrub the field after them as other labels do; a click still opens the panel (#4088); scores and estimates unchanged |
| 2026-11-05 | minor | Direct Selection: Shift-click picks several points of a blend's spine and dragging one moves them all, with the key objects on them, in one undo step (#5285); scores and estimates unchanged |
@@ -305,7 +306,7 @@ Cmd/Ctrl+/ (`appearance.addFill`) and Cmd/Ctrl+Alt+/ (`appearance.addStroke`) ad
selected objects, as the Appearance panel's Add New Fill and Add New Stroke buttons do.
~110 distinct modified shortcuts on menu commands plus single-key tool shortcuts for the default toolbar
(`crates/tools/src/catalog.rs`), a shortcut editor with chords, and saved sets. Cmd/Ctrl+Alt+L (`ui.newLayer`) opens Layer Options for a new layer, as an Alt-click on the Layers panel's New Layer button does, and Cmd/Ctrl+L still adds a layer without the dialog. F8 (`ui.symbolOptions`) opens Symbol Options for a new symbol made from the selection, as the Symbols panel's New Symbol button does. Not yet diffed item by item
(`crates/tools/src/catalog.rs`), a shortcut editor with chords, and saved sets. Cmd/Ctrl+Alt+L (`ui.newLayer`) opens Layer Options for a new layer, as an Alt-click on the Layers panel's New Layer button does, and Cmd/Ctrl+L still adds a layer without the dialog. F8 (`ui.symbolOptions`) opens Symbol Options for a new symbol made from the selection, as the Symbols panel's New Symbol button does. Rename (`ui.rename`, in the shortcut editor with no default key; a VectorCraft addition, #5324) opens the Layers panel's name field on the selected layer, group or object's row, as double-clicking its name does: Enter renames it in one undo step and Escape keeps the name; several selected objects, or a row the panel's search, filter or Show Layers Only hides, is refused with a message. Not yet diffed item by item
against Illustrator's published default shortcut list; estimated ~70% of it. Remaining: other panel shortcuts,
the type shortcuts (tracking/kerning/leading/baseline steps with Alt/Shift), and selection shortcuts in the
Layers panel. The shortcut editor also lists the commands that are in no menu but run without parameters (Edit › Duplicate,