mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 12:13:19 +00:00
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:
@@ -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),
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user