mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-15 01:14:30 +00:00
Number fields: a typed +n adds n to each selected layer, even where the edited one is clamped (#3195)
A typed +10 on several selected layers went through the same-change offset path, so when the edited layer's range cut its change short (Opacity 95 + 10 stops at 100) the others moved by 5. Send the typed operand as prop.set addend: each other layer takes its own value plus it, within its own range; scrubs keep the offset path.
This commit is contained in:
@@ -371,24 +371,30 @@ fn set_selected(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
let follow: Vec<usize> =
|
||||
p.get("follow").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_u64).filter_map(|d| usize::try_from(d).ok()).collect();
|
||||
// A typed `*2` or `/2` (#3195): each layer's own value times it, not the edited layer's result.
|
||||
// A typed `*2`, `/2` (`factor`) or `+10` (`addend`) (#3195): each layer's own value times it
|
||||
// or plus it, not the edited layer's result (which its range may have clamped).
|
||||
let factor = p.get("factor").and_then(Value::as_f64).filter(|k| k.is_finite() && delta.is_none());
|
||||
let addend = p.get("addend").and_then(Value::as_f64).filter(|n| n.is_finite() && delta.is_none());
|
||||
let own_step = |v: f64| -> Option<f64> {
|
||||
let r = match (factor, addend) {
|
||||
(Some(k), _) => v * k,
|
||||
(None, Some(n)) => v + n,
|
||||
(None, None) => return None,
|
||||
};
|
||||
r.is_finite().then_some(r)
|
||||
};
|
||||
// Each layer's own value where `set` writes it (a `time` other than the CTI keys there).
|
||||
let typed = |s: &Session, q: &mut Value, olid: LayerId, ouid: Uid| {
|
||||
let at = snapped_key_time(s, cid, olid, q, "prop.set").ok().flatten();
|
||||
if let (true, Some(k)) = (scalar, factor)
|
||||
&& let Some(own) = components_at(s, cid, olid, ouid, at).and_then(|c| c.first().copied())
|
||||
&& (own * k).is_finite()
|
||||
{
|
||||
q["value"] = json!(own * k);
|
||||
if scalar && let Some(v) = components_at(s, cid, olid, ouid, at).and_then(|c| c.first().copied()).and_then(own_step) {
|
||||
q["value"] = json!(v);
|
||||
}
|
||||
if let (Some(dims), Some(n)) = (&dims, &new)
|
||||
&& let Some(mut c) = components_at(s, cid, olid, ouid, at)
|
||||
{
|
||||
// The typed components' new values on this layer.
|
||||
let typed_value = |c: &[f64], d: usize| match factor {
|
||||
Some(k) => c.get(d).map(|v| v * k).filter(|v| v.is_finite()),
|
||||
None => n.get(d).copied(),
|
||||
let typed_value = |c: &[f64], d: usize| {
|
||||
if factor.is_some() || addend.is_some() { c.get(d).copied().and_then(own_step) } else { n.get(d).copied() }
|
||||
};
|
||||
let change = dims.first().and_then(|&d| Some(typed_value(&c, d)? - c.get(d)?)).unwrap_or(0.0);
|
||||
let values: Vec<Option<f64>> = dims.iter().map(|&d| typed_value(&c, d)).collect();
|
||||
@@ -1459,7 +1465,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Set Property Value",
|
||||
[],
|
||||
None,
|
||||
"{layer?, path|prop, value, time? (layer s by default), timeBase?: layer|comp, merge?, selected? (also the other selected layers), offset? (them by the same change), proportional? (with offset: Proportional Scrubbing, each layer by its share in selection order, the first none, the last all), dims? (with selected, a typed value: the components edited, e.g. [2] for Z; the other layers keep the rest of their own value), follow? (with dims: components that move on each other layer by the change its first typed component takes), factor? (with selected, a typed `*` or `/`: the other layers take their own value times it, in the dims components of a vector)}",
|
||||
"{layer?, path|prop, value, time? (layer s by default), timeBase?: layer|comp, merge?, selected? (also the other selected layers), offset? (them by the same change), proportional? (with offset: Proportional Scrubbing, each layer by its share in selection order, the first none, the last all), dims? (with selected, a typed value: the components edited, e.g. [2] for Z; the other layers keep the rest of their own value), follow? (with dims: components that move on each other layer by the change its first typed component takes), factor? (with selected, a typed `*` or `/`: the other layers take their own value times it, in the dims components of a vector), addend? (the same for a typed `+`: their own value plus it)}",
|
||||
has_layers,
|
||||
set_selected
|
||||
),
|
||||
|
||||
@@ -1552,6 +1552,17 @@ fn a_typed_factor_scales_each_selected_layers_own_value() {
|
||||
s.execute_checked("prop.set", json!({"layer": a, "prop": position, "value": [5.0, 20.0], "selected": true, "dims": [0], "factor": 0.5})).unwrap();
|
||||
assert_eq!(prop(&s, a, "transform/position").value.components()[..2], [5.0, 20.0]);
|
||||
assert_eq!(prop(&s, b, "transform/position").value.components()[..2], [50.0, 50.0], "B halves its own X, keeps its Y");
|
||||
// A typed `+10` (`addend`): each layer's own value plus 10, within its own range, even where
|
||||
// the edited layer's range cut its change short (95 + 10 stops at 100).
|
||||
s.execute("prop.set", json!({"layer": a, "path": "transform/opacity", "value": 95.0})).unwrap();
|
||||
s.execute("prop.set", json!({"layer": b, "path": "transform/opacity", "value": 30.0})).unwrap();
|
||||
s.execute_checked("prop.set", json!({"layer": a, "prop": opacity, "value": 100.0, "selected": true, "addend": 10.0})).unwrap();
|
||||
assert_eq!(prop(&s, b, "transform/opacity").value.as_f64(), 40.0, "B adds 10, not the 5 A moved");
|
||||
s.execute("prop.set", json!({"layer": b, "path": "transform/opacity", "value": 95.0})).unwrap();
|
||||
s.execute_checked("prop.set", json!({"layer": a, "prop": opacity, "value": 100.0, "selected": true, "addend": 10.0})).unwrap();
|
||||
assert_eq!(prop(&s, b, "transform/opacity").value.as_f64(), 100.0, "B's own range still applies");
|
||||
s.execute_checked("prop.set", json!({"layer": a, "prop": position, "value": [15.0, 20.0], "selected": true, "dims": [0], "addend": 10.0})).unwrap();
|
||||
assert_eq!(prop(&s, b, "transform/position").value.components()[..2], [60.0, 50.0], "B adds 10 to its own X");
|
||||
}
|
||||
|
||||
/// #3195: Mask Feather is never negative on the other selected layers either, when a typed `*-1`
|
||||
|
||||
@@ -164,14 +164,15 @@ pub fn hot_number_step_at(
|
||||
|
||||
/// Mark a queued `prop.set` (with `selected`) as the typed `step` it came from, so every other
|
||||
/// selected layer takes that step from its own value rather than the edited layer's result
|
||||
/// (#3195): `+n` by the same change (`offset`), `*n` and `/n` by the same `factor`.
|
||||
/// (#3195): `+n` adds `n` to each (`addend`; not the edited layer's change, which its range may
|
||||
/// have clamped), `*n` and `/n` multiply each by the same `factor`.
|
||||
pub fn mark_step(action: Option<&mut (String, serde_json::Value)>, step: Option<crate::num_math::Step>) {
|
||||
let (Some((_, a)), Some(step)) = (action, step) else { return };
|
||||
match step.factor() {
|
||||
Some(k) => a["factor"] = serde_json::json!(k),
|
||||
None if matches!(step, crate::num_math::Step::Add(_)) => a["offset"] = serde_json::json!(true),
|
||||
match (step, step.factor()) {
|
||||
(crate::num_math::Step::Add(n), _) => a["addend"] = serde_json::json!(n),
|
||||
(_, Some(k)) => a["factor"] = serde_json::json!(k),
|
||||
// (A factor too large to send: the other layers take the typed result.)
|
||||
None => {}
|
||||
(_, None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +480,16 @@ fn a_typed_step_applies_to_each_selected_layer() {
|
||||
key(&mut h, Key::Enter);
|
||||
assert_eq!((opacity_of(&h, a), opacity_of(&h, b)), expected, "{text}");
|
||||
}
|
||||
// `+10` where the edited layer stops at 100: the others still add 10, not the 5 it moved.
|
||||
for (l, v) in [(a, 95.0), (b, 30.0)] {
|
||||
h.state_mut().session.execute("prop.set", json!({"layer": l, "path": "transform/opacity", "value": v})).unwrap();
|
||||
}
|
||||
h.run_steps(5);
|
||||
let p = center(&h, &format!("timeline.prop.{opacity}.value"));
|
||||
click(&mut h, p, 1);
|
||||
type_text(&mut h, "+10");
|
||||
key(&mut h, Key::Enter);
|
||||
assert_eq!((opacity_of(&h, a), opacity_of(&h, b)), (100.0, 40.0));
|
||||
}
|
||||
|
||||
/// A click at `p` with `modifiers` held.
|
||||
|
||||
Reference in New Issue
Block a user