Open the Rename Clip dialog from Clip > Rename

Clip > Rename... and Cmd+Alt+Shift+R ran clip.rename without a name, so
they only put "`name` required" in the status line. They now open the
Rename Clip dialog, which only the clip's right-click menu opened so
far, for the selected clip (or the first clip the edit selection
touches).

The dialog's name field, and Rename Track's, now opens with the old
name selected, so typing replaces it instead of adding to it.
This commit is contained in:
grovebit 2026-10-10 14:01:33 +01:00
parent 3bc2d92b4e
commit aa4f038f1a
3 changed files with 61 additions and 4 deletions

View File

@ -100,6 +100,11 @@ impl Dialogs {
let s = soundcraft_engine::score::ScoreSetup::from_session(e.session());
Dialog::ScoreSetup { title: s.title, composer: s.composer, bars_per_system: s.bars_per_system, show_track_names: s.show_track_names }
}
"clip.rename" => {
let target = soundcraft_engine::cmd::clip_ids_param(e, &json!({})).first().and_then(|id| e.session().find_clip(*id));
let Some((_, clip)) = target else { return false };
Dialog::RenameClip { id: clip.id, name: clip.name.clone() }
}
_ => match path_dialog(id, &home, &name) {
Some(d) => d,
None => return false,
@ -273,15 +278,13 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
}
}
Dialog::RenameTrack { id, name } => {
let r = ui.text_edit_singleline(name);
r.request_focus();
name_field(ui, name);
if buttons(ui, "OK", enter) {
action = Some(("track.rename".into(), json!({"track": id.0, "name": name})));
}
}
Dialog::RenameClip { id, name } => {
let r = ui.text_edit_singleline(name);
r.request_focus();
name_field(ui, name);
if buttons(ui, "OK", enter) {
action = Some(("clip.rename".into(), json!({"clip": id.0, "name": name})));
}
@ -436,6 +439,17 @@ fn buttons(ui: &mut egui::Ui, ok: &str, enter: bool) -> bool {
pressed
}
/// A name field that takes the focus with its text selected, so typing replaces the old name.
fn name_field(ui: &mut egui::Ui, name: &mut String) {
let mut out = egui::TextEdit::singleline(name).show(ui);
if !out.response.has_focus() {
out.response.request_focus();
let all = egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(name.chars().count()));
out.state.cursor.set_char_range(Some(all));
out.state.store(ui.ctx(), out.response.id);
}
}
/// The command the tempo-change dialog runs on OK.
fn tempo_change_action(at: Samples, bpm: f64) -> (String, Value) {
("event.tempo".into(), json!({"bpm": bpm, "at": at}))
@ -515,6 +529,18 @@ mod tests {
assert!(matches!(d.open, Some(Dialog::ScoreSetup { bars_per_system: 4, .. })));
}
#[test]
fn clip_rename_opens_the_rename_dialog_for_the_selected_clip() {
let mut e = soundcraft_engine::demo::demo_engine();
let mut d = Dialogs::default();
e.execute("edit.select_none", &json!({})).unwrap();
assert!(!d.open_for_command(&e, "clip.rename"));
let clip = e.session().track_by_name("Kick").unwrap().clips()[0].clone();
e.execute("edit.select", &json!({"clips": [clip.id.0]})).unwrap();
assert!(d.open_for_command(&e, "clip.rename"));
assert!(matches!(d.open, Some(Dialog::RenameClip { id, ref name }) if id == clip.id && *name == clip.name));
}
#[test]
fn tempo_change_dialog_runs_a_real_command_at_its_position() {
let (cmd, params) = tempo_change_action(96_000, 133.5);

View File

@ -159,6 +159,7 @@ const DIALOG_COMMANDS: &[&str] = &[
"event.quantize",
"event.transpose",
"edit.strip_silence",
"clip.rename",
];
/// Shared menu hierarchy for egui and native desktop hosts.

View File

@ -0,0 +1,30 @@
use egui::{Event, Key, Modifiers, vec2};
use egui_kittest::Harness;
use serde_json::json;
use soundcraft_ui_egui::{Services, SoundApp, menus};
#[test]
fn typing_in_the_rename_clip_dialog_replaces_the_old_name() {
let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default());
let clip = app.engine.session().track_by_name("Kick").unwrap().clips()[0].clone();
app.run("edit.select", json!({"clips": [clip.id.0]})).unwrap();
menus::invoke_menu(&mut app, "clip.rename", "Clip > Rename...");
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state(
|ui, app: &mut SoundApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
app,
);
for _ in 0..3 {
h.step();
}
h.input_mut().events.push(Event::Text("Intro".into()));
h.step();
h.input_mut().events.push(Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE });
h.step();
let session = h.state().engine.session();
let renamed = session.tracks.iter().flat_map(|t| t.clips()).find(|c| c.id == clip.id).map(|c| c.name.clone());
assert_eq!(renamed.as_deref(), Some("Intro"));
}