From 039e7ad1c19f85273d4fb20c910da2a4d9c8361f Mon Sep 17 00:00:00 2001 From: Kristian Myram Date: Tue, 27 Oct 2026 20:35:53 +0000 Subject: [PATCH 1/3] #2459: use text-field Undo and Redo labels while typing --- crates/ui-egui/src/native_menu.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index 9288ec4e1..6fbbdad9a 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -131,18 +131,28 @@ impl NativeMenu { /// otherwise follows the project clipboard and would be greyed out with nothing copied there, /// while the field can paste. pub fn build_now(app: &EffectcraftApp, ctx: &egui::Context) -> NativeMenu { - fn enable(nodes: &mut [NativeNode]) { + fn enable(app: &EffectcraftApp, nodes: &mut [NativeNode]) { for node in nodes { match node { - NativeNode::Item(item) if edits_text(&item.command) => item.enabled = true, - NativeNode::Submenu { children, .. } => enable(children), + NativeNode::Item(item) if edits_text(&item.command) => { + item.enabled = true; + let source = match item.command.as_str() { + "edit.undo" => Some("Undo"), + "edit.redo" => Some("Redo"), + _ => None, + }; + if let Some(source) = source { + item.label = crate::i18n::label(app, &item.command, source).into(); + } + } + NativeNode::Submenu { children, .. } => enable(app, children), _ => {} } } } let mut menu = build(app); if text_field_window(ctx).is_some() { - enable(&mut menu.menus); + enable(app, &mut menu.menus); } menu } From 6a8e28126ed54f0f50c7417148e6f62a22af7b34 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 09:16:34 +0000 Subject: [PATCH 2/3] #2459: test the text-field Undo and Redo labels in the native Edit menu Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01RDvxCS8xcR28VArjzn1x7U --- crates/ui-egui/src/native_menu.rs | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index 6fbbdad9a..e89aae5c9 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -745,4 +745,41 @@ mod tests { assert!(edits_text("edit.copy") && edits_text("edit.paste")); assert!(!edits_text("layer.newNull")); } + /// #2459: while a text field has the keyboard the Edit menu's Undo and Redo act on the field and + /// read plain "Undo" / "Redo"; the project history labels come back once focus leaves. + #[test] + fn a_focused_text_field_gets_plain_undo_and_redo_labels() { + fn label(menu: &NativeMenu, id: &str) -> String { + menu.items().into_iter().find(|i| i.command == id).map(|i| i.label.to_string()).unwrap_or_default() + } + let mut app = app(); + app.session.execute("layer.newNull", json!({})).unwrap(); + app.session.execute("layer.newNull", json!({})).unwrap(); + app.session.execute("edit.undo", json!({})).unwrap(); + let ctx = egui::Context::default(); + let history = label(&build_now(&app, &ctx), "edit.undo"); + let redo_history = label(&build_now(&app, &ctx), "edit.redo"); + assert!(history.starts_with("Undo") && history != "Undo", "{history}"); + assert!(redo_history.starts_with("Redo") && redo_history != "Redo", "{redo_history}"); + let mut text = String::new(); + let mut id = None; + for _ in 0..2 { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + let r = ui.text_edit_singleline(&mut text); + r.request_focus(); + id = Some(r.id); + }); + out.textures_delta.clear(); + } + assert!(text_field_window(&ctx).is_some()); + let typing = build_now(&app, &ctx); + assert_eq!(label(&typing, "edit.undo"), "Undo"); + assert_eq!(label(&typing, "edit.redo"), "Redo"); + assert!(typing.items().into_iter().any(|i| i.command == "edit.undo" && i.enabled)); + ctx.memory_mut(|m| m.surrender_focus(id.unwrap_or(egui::Id::NULL))); + assert!(text_field_window(&ctx).is_none()); + let after = build_now(&app, &ctx); + assert_eq!(label(&after, "edit.undo"), history); + assert_eq!(label(&after, "edit.redo"), redo_history); + } } From c9688ef796a6f51dc17ccdf370ce41cfb00e7d08 Mon Sep 17 00:00:00 2001 From: Kristian Myram Date: Thu, 29 Oct 2026 19:47:46 +0100 Subject: [PATCH 3/3] #2459: cover localized text-field Undo and Redo labels --- crates/ui-egui/src/native_menu.rs | 60 ++++++++++++++++--------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index e89aae5c9..e6b552742 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -752,34 +752,38 @@ mod tests { fn label(menu: &NativeMenu, id: &str) -> String { menu.items().into_iter().find(|i| i.command == id).map(|i| i.label.to_string()).unwrap_or_default() } - let mut app = app(); - app.session.execute("layer.newNull", json!({})).unwrap(); - app.session.execute("layer.newNull", json!({})).unwrap(); - app.session.execute("edit.undo", json!({})).unwrap(); - let ctx = egui::Context::default(); - let history = label(&build_now(&app, &ctx), "edit.undo"); - let redo_history = label(&build_now(&app, &ctx), "edit.redo"); - assert!(history.starts_with("Undo") && history != "Undo", "{history}"); - assert!(redo_history.starts_with("Redo") && redo_history != "Redo", "{redo_history}"); - let mut text = String::new(); - let mut id = None; - for _ in 0..2 { - let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { - let r = ui.text_edit_singleline(&mut text); - r.request_focus(); - id = Some(r.id); - }); - out.textures_delta.clear(); + for (language, undo, redo) in [("en", "Undo", "Redo"), ("ja", "取り消し", "やり直し")] { + let mut app = app(); + app.session.execute("prefs.set", json!({"key": "general.language", "value": language})).unwrap(); + app.session.execute("layer.newNull", json!({})).unwrap(); + app.session.execute("layer.newNull", json!({})).unwrap(); + app.session.execute("edit.undo", json!({})).unwrap(); + let ctx = egui::Context::default(); + let history = label(&build_now(&app, &ctx), "edit.undo"); + let redo_history = label(&build_now(&app, &ctx), "edit.redo"); + assert!(history.starts_with(undo) && history != undo, "{history}"); + assert!(redo_history.starts_with(redo) && redo_history != redo, "{redo_history}"); + let mut text = String::new(); + let mut id = None; + for _ in 0..2 { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + let r = ui.text_edit_singleline(&mut text); + r.request_focus(); + id = Some(r.id); + }); + out.textures_delta.clear(); + } + assert!(text_field_window(&ctx).is_some()); + let typing = build_now(&app, &ctx); + assert_eq!(label(&typing, "edit.undo"), undo); + assert_eq!(label(&typing, "edit.redo"), redo); + assert!(typing.items().into_iter().any(|i| i.command == "edit.undo" && i.enabled)); + assert!(typing.items().into_iter().any(|i| i.command == "edit.redo" && i.enabled)); + ctx.memory_mut(|m| m.surrender_focus(id.unwrap_or(egui::Id::NULL))); + assert!(text_field_window(&ctx).is_none()); + let after = build_now(&app, &ctx); + assert_eq!(label(&after, "edit.undo"), history); + assert_eq!(label(&after, "edit.redo"), redo_history); } - assert!(text_field_window(&ctx).is_some()); - let typing = build_now(&app, &ctx); - assert_eq!(label(&typing, "edit.undo"), "Undo"); - assert_eq!(label(&typing, "edit.redo"), "Redo"); - assert!(typing.items().into_iter().any(|i| i.command == "edit.undo" && i.enabled)); - ctx.memory_mut(|m| m.surrender_focus(id.unwrap_or(egui::Id::NULL))); - assert!(text_field_window(&ctx).is_none()); - let after = build_now(&app, &ctx); - assert_eq!(label(&after, "edit.undo"), history); - assert_eq!(label(&after, "edit.redo"), redo_history); } }