From bd00c3855ccca4d73d8a0620ea1296279f57743e Mon Sep 17 00:00:00 2001 From: grovebit <337789029+grovebit@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:03:28 +0100 Subject: [PATCH] Name the step in the Edit menu's Undo and Redo The Edit menu always read "Can't Undo" and "Can't Redo", the incumbent's labels for an empty history, even when there was something to undo. The items now name the step they would take ("Undo Mute", "Redo Mute"), in the in-window menu bar and the macOS system menu alike, and read "Can't Undo" / "Can't Redo" only when there is nothing. --- apps/soundcraft/src/native_menu.rs | 5 +++++ crates/ui-egui/src/menus.rs | 13 ++++++++++++- crates/ui-egui/tests/edit_menu.rs | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 crates/ui-egui/tests/edit_menu.rs diff --git a/apps/soundcraft/src/native_menu.rs b/apps/soundcraft/src/native_menu.rs index 9cc72de..82cd25a 100644 --- a/apps/soundcraft/src/native_menu.rs +++ b/apps/soundcraft/src/native_menu.rs @@ -105,6 +105,11 @@ impl NativeMenu { self.last_sync = Some(now); for entry in &mut self.entries { let (enabled, checked) = menus::item_state(app, &entry.path, entry.command.as_deref()); + if let Some(label) = menus::live_label(app, entry.command.as_deref()) + && entry.item.text() != label + { + entry.item.set_text(label); + } if enabled != entry.enabled { entry.item.set_enabled(enabled); entry.enabled = enabled; diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 0e540e0..5b8f079 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -259,7 +259,8 @@ fn menu_node(app: &mut SoundApp, ui: &mut egui::Ui, n: &MenuNode, extra: &[(&str } let id = command_for_path(&n.path, extra); let (enabled, checked) = item_state(app, &n.path, id.as_deref()); - let label = if checked { format!("✔ {}", n.label) } else { n.label.clone() }; + let label = live_label(app, id.as_deref()).unwrap_or_else(|| n.label.clone()); + let label = if checked { format!("✔ {label}") } else { label }; let shortcut = id.as_deref().and_then(soundcraft_engine::find_command).and_then(|c| c.shortcut).unwrap_or(""); let btn = egui::Button::new(label).shortcut_text(crate::shortcuts::shortcut_label(shortcut, ui.ctx().os().is_mac())); if ui.add_enabled(enabled, btn).clicked() @@ -387,6 +388,16 @@ const MENU_WINDOWS: &[(&str, &str)] = &[ ("Event > MIDI Real-Time Properties", "window.rtp"), ]; +/// Undo and Redo name the step they would undo or redo; other items keep their catalog label. +pub fn live_label(app: &SoundApp, id: Option<&str>) -> Option { + let e = &app.engine; + match id? { + "edit.undo" => Some(e.undo_label().map_or_else(|| "Can't Undo".into(), |l| format!("Undo {l}"))), + "edit.redo" => Some(e.redo_label().map_or_else(|| "Can't Redo".into(), |l| format!("Redo {l}"))), + _ => None, + } +} + /// Menu-style invocation: may open a dialog. pub fn invoke_menu(app: &mut SoundApp, id: &str, path: &str) { if let Some((_, w)) = MENU_WINDOWS.iter().find(|(p, _)| *p == path) diff --git a/crates/ui-egui/tests/edit_menu.rs b/crates/ui-egui/tests/edit_menu.rs new file mode 100644 index 0000000..8fdbb52 --- /dev/null +++ b/crates/ui-egui/tests/edit_menu.rs @@ -0,0 +1,15 @@ +use serde_json::json; +use soundcraft_ui_egui::{Services, SoundApp, menus}; + +#[test] +fn undo_and_redo_name_the_step_they_would_take() { + let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default()); + let label = |app: &SoundApp, id| menus::live_label(app, Some(id)); + assert_eq!(label(&app, "edit.undo").as_deref(), Some("Can't Undo")); + app.run("mix.mute", json!({"track": "Kick"})).unwrap(); + assert_eq!(label(&app, "edit.undo").as_deref(), Some("Undo Mute")); + assert_eq!(label(&app, "edit.redo").as_deref(), Some("Can't Redo")); + app.run("edit.undo", json!({})).unwrap(); + assert_eq!(label(&app, "edit.redo").as_deref(), Some("Redo Mute")); + assert_eq!(label(&app, "edit.copy"), None); +}