From dfff9ba2dc030287cbf303d565bb2bb2ddbd6cc8 Mon Sep 17 00:00:00 2001 From: pravdomil <2387356+pravdomil@users.noreply.github.com> Date: Mon, 12 Oct 2026 17:49:55 +0200 Subject: [PATCH] Alt+B / Alt+Shift+B toggle layer sampling layer.sampling takes `toggle`: layers that all have the sampling already get the other one. The Bilinear and Bicubic menu entries, which carry the keys, pass it. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/commands/layer_menu.rs | 9 ++++++++- crates/engine/src/menus.rs | 4 ++-- crates/engine/src/tests_menu_cmds.rs | 24 ++++++++++++++++++++++++ crates/engine/src/tests_settings.rs | 4 ++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/commands/layer_menu.rs b/crates/engine/src/commands/layer_menu.rs index 755dd898..cac33088 100644 --- a/crates/engine/src/commands/layer_menu.rs +++ b/crates/engine/src/commands/layer_menu.rs @@ -76,6 +76,13 @@ fn sampling(s: &mut Session, p: &Value) -> Result { "bicubic" => Sampling::Bicubic, q => return Err(bad("layer.sampling", format!("sampling must be bilinear|bicubic, not `{q}`"))), }; + // `toggle`: layers that all have it already get the other one. + let all = s.project.comp(cid).is_some_and(|c| c.layers.iter().filter(|l| ids.contains(&l.id)).all(|l| l.switches.sampling == q)); + let q = match q { + Sampling::Bilinear if all && b_p(p, "toggle").unwrap_or(false) => Sampling::Bicubic, + Sampling::Bicubic if all && b_p(p, "toggle").unwrap_or(false) => Sampling::Bilinear, + q => q, + }; s.edit("Layer Sampling", None, |proj, _| { for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id)) { l.switches.sampling = q; @@ -800,7 +807,7 @@ fn sequence(s: &mut Session, p: &Value) -> Result { pub fn specs() -> Vec { vec![ cmd!("layer.quality", "Quality", [], None, "{layers?, quality: best|draft|wireframe}", has_layers, quality), - cmd!("layer.sampling", "Sampling", [], None, "{layers?, sampling: bilinear|bicubic}", has_layers, sampling), + cmd!("layer.sampling", "Sampling", [], None, "{layers?, sampling: bilinear|bicubic, toggle?}", has_layers, sampling), cmd!("layer.frameBlending", "Frame Blending", [], None, "{layers?, mode: off|frameMix|pixelMotion}", has_layers, frame_blending), cmd!("layer.hideOtherVideo", "Hide Other Video", ["Layer", "Switches"], Some("Cmd+Alt+Shift+V"), "{layers?}", has_layers, hide_other_video), cmd!("layer.showAllVideo", "Show All Video", ["Layer", "Switches"], None, "{comp?}", has_comp, show_all_video), diff --git a/crates/engine/src/menus.rs b/crates/engine/src/menus.rs index 1fb215a3..47106540 100644 --- a/crates/engine/src/menus.rs +++ b/crates/engine/src/menus.rs @@ -785,8 +785,8 @@ Layer Draft | layer.quality {"quality":"draft"} | Cmd+Shift+U Wireframe | layer.quality {"quality":"wireframe"} | Cmd+Alt+Shift+U --- - Bilinear | layer.sampling {"sampling":"bilinear"} | Alt+B - Bicubic | layer.sampling {"sampling":"bicubic"} | Alt+Shift+B + Bilinear | layer.sampling {"sampling":"bilinear","toggle":true} | Alt+B + Bicubic | layer.sampling {"sampling":"bicubic","toggle":true} | Alt+Shift+B Switches Hide Other Video | layer.hideOtherVideo Show All Video | layer.showAllVideo diff --git a/crates/engine/src/tests_menu_cmds.rs b/crates/engine/src/tests_menu_cmds.rs index cb83c163..098bd856 100644 --- a/crates/engine/src/tests_menu_cmds.rs +++ b/crates/engine/src/tests_menu_cmds.rs @@ -52,6 +52,30 @@ fn layer_quality_sampling_frame_blending_and_undo() { assert!(s.execute("layer.quality", json!({"quality": "fancy"})).is_err()); } +/// `layer.sampling {toggle}` (Alt+B, Alt+Shift+B): layers that all have the sampling already get +/// the other one; without `toggle` it is only set. +#[test] +fn layer_sampling_toggles_when_every_layer_has_it() { + let mut s = comp(); + let a = solid(&mut s, "#ff0000"); + let b = solid(&mut s, "#00ff00"); + let sampling = |s: &Session| [layer(s, a).switches.sampling, layer(s, b).switches.sampling]; + let bicubic = json!({"layers": [a, b], "sampling": "bicubic", "toggle": true}); + s.execute("layer.sampling", bicubic.clone()).unwrap(); + assert_eq!(sampling(&s), [Sampling::Bicubic; 2]); + s.execute("layer.sampling", bicubic.clone()).unwrap(); + assert_eq!(sampling(&s), [Sampling::Bilinear; 2]); + s.execute("layer.sampling", json!({"layers": [a, b], "sampling": "bilinear", "toggle": true})).unwrap(); + assert_eq!(sampling(&s), [Sampling::Bicubic; 2]); + // Mixed layers all get the one asked for. + s.execute("layer.sampling", json!({"layers": [a], "sampling": "bilinear"})).unwrap(); + s.execute("layer.sampling", bicubic).unwrap(); + assert_eq!(sampling(&s), [Sampling::Bicubic; 2]); + // Without `toggle` it stays. + s.execute("layer.sampling", json!({"layers": [a, b], "sampling": "bicubic"})).unwrap(); + assert_eq!(sampling(&s), [Sampling::Bicubic; 2]); +} + /// `layer.setSwitch {others: true}` (Alt-click on an A/V feature): the layer stands alone, the /// other layers come back the next time. #[test] diff --git a/crates/engine/src/tests_settings.rs b/crates/engine/src/tests_settings.rs index fea7709e..dd68664d 100644 --- a/crates/engine/src/tests_settings.rs +++ b/crates/engine/src/tests_settings.rs @@ -418,8 +418,8 @@ fn default_keys_for_commands_that_had_none() { for (id, params, key) in [ ("edit.pasteReversedKeyframes", json!(null), "Cmd+Shift+V"), ("edit.purge", json!({"what": "snapshot"}), "Cmd+Shift+F5"), - ("layer.sampling", json!({"sampling": "bilinear"}), "Alt+B"), - ("layer.sampling", json!({"sampling": "bicubic"}), "Alt+Shift+B"), + ("layer.sampling", json!({"sampling": "bilinear", "toggle": true}), "Alt+B"), + ("layer.sampling", json!({"sampling": "bicubic", "toggle": true}), "Alt+Shift+B"), ("view.res.custom", json!(null), "Cmd+Alt+J"), ("file.setProxy", json!(null), "Cmd+Alt+P"), ("file.openRecent", json!(null), "Cmd+Alt+Shift+P"),