#3087: a placeholder's float slider keeps its range

It arrived as a plain number, so Effect Controls neither dragged it over its
slider range nor clamped an edit to its valid range, as After Effects does.
This commit is contained in:
arnav-hiwarkar committed 2026-11-12 13:54:03 +05:30
1 parent 04bbc67528
commit e7a396856d
3 files changed
+31 -2

No files matched your search

+12 -1
View File
@@ -111,7 +111,7 @@ pub(crate) fn placeholder(
/// parameter), its control and its value until one is read. `None` for a type that is not kept.
fn shape(p: &EffectParam<'_, '_>, size: (u32, u32)) -> Option<(Option<Kind>, ParamUi, Value)> {
let (kind, ui, default) = match p.kind {
ParamKind::IntSlider | ParamKind::FixedSlider | ParamKind::FloatSlider => (Kind::EffectNumber(1.0), ParamUi::Number, Value::Scalar(0.0)),
ParamKind::IntSlider | ParamKind::FixedSlider | ParamKind::FloatSlider => (Kind::EffectNumber(1.0), slider_ui(p), Value::Scalar(0.0)),
ParamKind::Angle => (Kind::EffectNumber(1.0), ParamUi::Angle, Value::Scalar(0.0)),
ParamKind::Checkbox => (Kind::EffectBool, ParamUi::Checkbox, Value::Bool(false)),
ParamKind::Color => (Kind::EffectColor, ParamUi::Color, Value::Color([0.0, 0.0, 0.0, 1.0])),
@@ -131,6 +131,17 @@ fn shape(p: &EffectParam<'_, '_>, size: (u32, u32)) -> Option<(Option<Kind>, Par
Some((Some(kind), ui, default))
}
/// A slider's control: a float slider's valid range and drag range when they are ranges (only a
/// float slider's are decoded), else a plain number.
fn slider_ui(p: &EffectParam<'_, '_>) -> ParamUi {
match (p.valid_min(), p.valid_max(), p.slider_range()) {
(Some(min), Some(max), Some((smin, smax))) if [min, max, smin, smax].iter().all(|v| v.is_finite()) && min <= max && smin <= smax => {
ParamUi::Slider { min, max, slider_min: smin.clamp(min, max), slider_max: smax.clamp(min, max), decimals: 2 }
}
_ => ParamUi::Number,
}
}
/// How many items a popup has: the file's names, else the count in its definition.
fn popup_len(p: &EffectParam<'_, '_>) -> Option<usize> {
p.items.as_ref().map(Vec::len).or_else(|| p.popup_item_count().map(usize::from))
@@ -109,6 +109,24 @@ fn a_pseudo_effect_is_kept_with_its_parameter_names() {
assert_eq!(path.as_deref().and_then(|p| layer.prop(p)).map(|p| p.name.as_str()), Some("Speed"), "{path:?}");
}
/// A float slider keeps its valid range and slider range, so Effect Controls drags and clamps it
/// as After Effects does; a range that is not a range stays a plain number.
#[test]
fn a_float_slider_keeps_its_range() {
let fx = Fx {
match_name: "Pseudo/r",
name: "Rig",
defs: vec![slider(K::FloatSlider, "Speed", 1.0, (-10.0, 100.0)), slider(K::FloatSlider, "Odd", 1.0, (5.0, 1.0))],
values: vec![],
data: vec![],
keys: vec![],
};
let (g, _) = placeholder_of(&fx);
let ui = |id: &str| g.prop(id).map(|p| p.ui.clone());
assert_eq!(ui("param1"), Some(ParamUi::Slider { min: -10.0, max: 100.0, slider_min: -10.0, slider_max: 100.0, decimals: 2 }));
assert_eq!(ui("param2"), Some(ParamUi::Number));
}
/// A popup whose definition is borrowed (the project copy) and has no value record: its default
/// slot holds another copy's selection, so the first item is used and that is reported.
#[test]
+1 -1
View File
@@ -324,7 +324,7 @@ parameter gets the file's value:
is that name as one path segment, `Pseudo_0_25`). Like a missing plug-in it renders nothing and is listed by Find Missing
Effects. It holds the effect's parameters flat (without the twirl-down group markers), named as
in After Effects, each with its value, keys and expression, in the units After Effects'
expressions read (sliders and angles as stored, points in layer pixels, colours, checkboxes,
expressions read (sliders and angles as stored, a float slider with its range, points in layer pixels, colours, checkboxes,
popups over the file's items with separators, read 1-based; layer parameters name their layer,
without a record the layer itself as for Layer Control, and their keys and expression are
reported),