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.
This commit is contained in:
grovebit 2026-10-10 14:03:28 +01:00
parent aa4f038f1a
commit bd00c3855c
3 changed files with 32 additions and 1 deletions

View File

@ -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;

View File

@ -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<String> {
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)

View File

@ -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);
}