From 11740c9731723dd2dc2424fad54f2538a3f81237 Mon Sep 17 00:00:00 2001 From: theironchef <6278036+theironchef@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:51:46 -0700 Subject: [PATCH 1/2] Layers: renaming a layer or object commits on Enter or a click elsewhere The name field asked for focus every frame, which took it back from Enter, Escape or a click elsewhere, so lost_focus never fired and the rename was never committed. It now asks only while opening, as the Artboards panel's name field does. Fixes #165 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U9TrG4w49oSRYbP3cqxA3D --- crates/ui-egui/src/panels/layers.rs | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 2a86bbe3..228499fb 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -202,7 +202,11 @@ fn row( Some((rid, mut buf)) if rid == n.id.0 => { let mut child = ui.new_child(egui::UiBuilder::new().max_rect(name_rect)); let te = child.add(egui::TextEdit::singleline(&mut buf).desired_width(name_rect.width()).font(font.clone())); - te.request_focus(); + // Focus the field as it opens. Asking every frame took the focus back from Enter, + // Escape or a click elsewhere, so the rename never ended. + if !te.has_focus() && !te.lost_focus() { + te.request_focus(); + } if te.lost_focus() { ui.data_mut(|d| d.remove::<(u64, String)>(rename_id)); if child.input(|i| !i.key_pressed(egui::Key::Escape)) && buf != name { @@ -587,6 +591,35 @@ mod tests { assert_eq!(st.doc.node_count(), count); } + #[test] + fn renaming_a_layer_commits_on_enter_and_escape_cancels() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + let layer = app.session.doc().unwrap().doc.layers[0].id; + let name = |app: &VectorcraftApp| app.session.doc().unwrap().doc.layers[0].display_name(); + let ctx = egui::Context::default(); + let key = |key| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }; + let select_all = + egui::Event::Key { key: egui::Key::A, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::COMMAND }; + // What a double-click on the row does: the name field opens with the name. + let open = |app: &mut VectorcraftApp| { + ctx.data_mut(|d| d.insert_temp(egui::Id::new("layers-rename"), (layer.0, name(app)))); + frame(app, &ctx, vec![], false); + frame(app, &ctx, vec![], false); + }; + open(&mut app); + frame(&mut app, &ctx, vec![select_all.clone(), egui::Event::Text("Sky".into())], false); + frame(&mut app, &ctx, vec![key(egui::Key::Enter)], false); + frame(&mut app, &ctx, vec![], false); + assert_eq!(name(&app), "Sky", "Enter keeps the new name"); + assert!(ctx.data(|d| d.get_temp::<(u64, String)>(egui::Id::new("layers-rename"))).is_none(), "and closes the field"); + open(&mut app); + frame(&mut app, &ctx, vec![select_all, egui::Event::Text("Ground".into())], false); + frame(&mut app, &ctx, vec![key(egui::Key::Escape)], false); + frame(&mut app, &ctx, vec![], false); + assert_eq!(name(&app), "Sky", "Escape cancels"); + } + #[test] fn masks_underline_dashed_and_mask_editing_lists_only_the_mask() { let (mut app, _, [a, _]) = two_rects(); From 8c8ac373001ed984dd87f159ae4c38f33f04c56d Mon Sep 17 00:00:00 2001 From: theironchef <6278036+theironchef@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:58:54 -0700 Subject: [PATCH 2/2] Layers: an open rename stays with its document Node ids are per document, so a rename left open while another document came to the front renamed that document's node with the same id. The panel drops an open rename when the document it shows changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U9TrG4w49oSRYbP3cqxA3D --- crates/ui-egui/src/panels/layers.rs | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 228499fb..81985c7b 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -25,6 +25,18 @@ fn expanded_id() -> egui::Id { pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); let Some(st) = app.session.active() else { return }; + // A rename belongs to the document it started in (node ids are per document): drop it when + // another document shows, so it can't rename that document's node with the same id. + let shown_doc = egui::Id::new("layers-doc"); + let before = ui.data(|d| d.get_temp::(shown_doc)); + if before != Some(st.uid) { + ui.data_mut(|d| { + if before.is_some() { + d.remove::<(u64, String)>(egui::Id::new("layers-rename")); + } + d.insert_temp(shown_doc, st.uid); + }); + } let doc = st.doc.clone(); let sel: HashSet = st.selection.objects.iter().copied().collect(); let target = st.selection.target; @@ -620,6 +632,26 @@ mod tests { assert_eq!(name(&app), "Sky", "Escape cancels"); } + #[test] + fn an_open_rename_does_not_follow_into_another_document() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + let layer = app.session.doc().unwrap().doc.layers[0].id; + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![], false); + ctx.data_mut(|d| d.insert_temp(egui::Id::new("layers-rename"), (layer.0, "Renamed in the first".to_string()))); + frame(&mut app, &ctx, vec![], false); + // A second document, whose first layer has the same node id, comes to the front. + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + assert_eq!(app.session.doc().unwrap().doc.layers[0].id, layer); + let enter = egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }; + frame(&mut app, &ctx, vec![], false); + frame(&mut app, &ctx, vec![enter], false); + frame(&mut app, &ctx, vec![], false); + assert_eq!(app.session.doc().unwrap().doc.layers[0].display_name(), "Layer 1"); + assert!(ctx.data(|d| d.get_temp::<(u64, String)>(egui::Id::new("layers-rename"))).is_none()); + } + #[test] fn masks_underline_dashed_and_mask_editing_lists_only_the_mask() { let (mut app, _, [a, _]) = two_rects();