mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
Merge pull request #173 from carlitosteso3/fix/shortcut-modifiers-164
Shortcut recorder accepts modifier chords; Add Edit cuts only selected clips (#164, items 1 and 5)
This commit is contained in:
@@ -851,3 +851,27 @@ fn a_cyclic_subclip_chain_has_no_media_duration() {
|
||||
*parent = sub;
|
||||
assert_eq!(crate::media_duration(&s.project, &s.media, sub), None);
|
||||
}
|
||||
|
||||
/// #164: Add Edit with a clip selected cut every targeted track. Like Premiere it now cuts only
|
||||
/// the selected clips under the playhead; with nothing selected there, the targeted tracks.
|
||||
#[test]
|
||||
fn add_edit_cuts_only_the_selected_clips() {
|
||||
let mut s = demo();
|
||||
let count = |s: &Session| {
|
||||
let q = s.active_sequence().unwrap();
|
||||
(q.video_tracks.iter().map(|t| t.items.len()).sum::<usize>(), q.audio_tracks.iter().map(|t| t.items.len()).sum::<usize>())
|
||||
};
|
||||
// a V1 clip and a time inside it
|
||||
let first = v1(&s)[1].clone();
|
||||
let t = first.start + filmcraft_time::Tick(first.duration.0 / 2);
|
||||
let (v0, a0) = count(&s);
|
||||
s.state.selection = vec![first.id];
|
||||
s.execute("sequence.addEdit", json!({"time": t.0})).unwrap();
|
||||
let (v1n, a1n) = count(&s);
|
||||
assert_eq!((v1n, a1n), (v0 + 1, a0), "only the selected V1 clip is cut");
|
||||
// nothing selected under the playhead: every targeted track is cut
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
s.state.selection.clear();
|
||||
let r = s.execute("sequence.addEdit", json!({"time": t.0})).unwrap();
|
||||
assert!(r["cuts"].as_u64().unwrap() > 1, "{r}");
|
||||
}
|
||||
|
||||
@@ -1333,8 +1333,17 @@ fn build() -> Vec<CommandSpec> {
|
||||
}),
|
||||
cmd!("sequence.addEdit", "Add Edit", ["Sequence"], Some("Cmd+K"), r#"{"time":ticks?}"#, has_seq, |s, p| {
|
||||
let t = time_p(s, p, "").unwrap_or(s.playhead());
|
||||
let tg = s.targeting().targeted;
|
||||
let n = s.edit_sequence("Add Edit", |q, ctx, _| Ok(edit::razor(q, &tg, t, ctx)))?;
|
||||
// Like Premiere: selected clips under the playhead are cut, and only they (#164);
|
||||
// with none, the targeted tracks are.
|
||||
let sel = s.state.selection.clone();
|
||||
let selected_here =
|
||||
s.active_sequence().is_some_and(|q| sel.iter().any(|c| q.find_item(*c).is_some_and(|(_, it)| it.start < t && t < it.start + it.duration)));
|
||||
let n = if selected_here {
|
||||
s.edit_sequence("Add Edit", |q, ctx, _| Ok(edit::razor_items(q, &sel, t, ctx)))?
|
||||
} else {
|
||||
let tg = s.targeting().targeted;
|
||||
s.edit_sequence("Add Edit", |q, ctx, _| Ok(edit::razor(q, &tg, t, ctx)))?
|
||||
};
|
||||
Ok(json!({"cuts": n.len()}))
|
||||
}),
|
||||
cmd!("sequence.addEditAllTracks", "Add Edit to All Tracks", ["Sequence"], Some("Cmd+Shift+K"), r#"{"time":ticks?}"#, has_seq, |s, p| {
|
||||
|
||||
@@ -105,13 +105,21 @@ fn assign(app: &mut FilmcraftApp, command: &str, keys: &str, add: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the next key press while recording (Esc cancels).
|
||||
/// A modifier key on its own. egui reports pressing Shift, Ctrl, Alt or the Windows / Cmd key as a
|
||||
/// key event of its own, before the key it modifies arrives (#164).
|
||||
fn is_modifier_key(key: egui::Key) -> bool {
|
||||
use egui::Key::*;
|
||||
matches!(key, ShiftLeft | ShiftRight | ControlLeft | ControlRight | AltLeft | AltRight | SuperLeft | SuperRight)
|
||||
}
|
||||
|
||||
/// Take the next key press while recording (Esc cancels). Modifier keys on their own are skipped:
|
||||
/// the chord is recorded when the key they modify is pressed, with them as its modifiers.
|
||||
fn record(app: &mut FilmcraftApp, ctx: &egui::Context) {
|
||||
let Some((cmd, add)) = app.shortcut_editor.recording.clone() else { return };
|
||||
let got = ctx.input_mut(|i| {
|
||||
let mut got = None;
|
||||
i.events.retain(|e| match e {
|
||||
egui::Event::Key { key, pressed: true, modifiers, .. } if got.is_none() => {
|
||||
egui::Event::Key { key, pressed: true, modifiers, .. } if got.is_none() && !is_modifier_key(*key) => {
|
||||
got = Some((*key, *modifiers));
|
||||
false
|
||||
}
|
||||
|
||||
@@ -512,3 +512,33 @@ fn waveform_peaks_do_not_survive_opening_another_project() {
|
||||
let now = d.harness.state().tl.peaks.lock().unwrap().get(&item).cloned();
|
||||
assert!(now.is_none_or(|p| !std::sync::Arc::ptr_eq(&p, &old)), "peaks from the previous project were reused");
|
||||
}
|
||||
|
||||
/// #164: on a real keyboard, pressing Alt+Shift+J sends each modifier key on its own first (egui
|
||||
/// reports modifier keys as key events). The shortcut recorder took the Alt press and refused it
|
||||
/// ("AltLeft can't be used as a shortcut"), so no chord with Shift, Ctrl or Alt could be recorded.
|
||||
#[test]
|
||||
fn recording_a_shortcut_skips_the_modifier_key_presses() {
|
||||
let mut d = Driver::demo();
|
||||
d.ok("ui.menu.invoke", json!({"id": "app.keyboardShortcuts"}));
|
||||
d.frames(2);
|
||||
d.ok("ui.click", json!({"id": "shortcuts.search"}));
|
||||
d.ok("ui.type", json!({"text": "add edit"}));
|
||||
d.frames(2);
|
||||
d.ok("ui.click", json!({"id": "shortcuts.add.sequence.addEdit"}));
|
||||
d.frames(1);
|
||||
// what a real keyboard sends for Alt+Shift+J: Alt, then Shift, then J
|
||||
let alt = egui::Modifiers { alt: true, ..Default::default() };
|
||||
let alt_shift = egui::Modifiers { alt: true, shift: true, ..Default::default() };
|
||||
let key = |key, modifiers| egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers };
|
||||
let events = &mut d.harness.input_mut().events;
|
||||
events.push(key(egui::Key::AltLeft, alt));
|
||||
events.push(key(egui::Key::ShiftLeft, alt_shift));
|
||||
events.push(key(egui::Key::J, alt_shift));
|
||||
d.frames(2);
|
||||
d.ok("ui.click", json!({"id": "shortcuts.ok"}));
|
||||
d.frames(2);
|
||||
// added next to Cmd+K (`shortcuts.get` lists every chord of the command)
|
||||
let got = d.ok("engine.execute", json!({"command": "shortcuts.get", "params": {"command": "sequence.addEdit"}})).to_string();
|
||||
assert!(got.contains("Alt+Shift+J"), "{got}");
|
||||
assert!(got.contains("Cmd+K"), "{got}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user