From 8927ae57817078bf86d60cc65d53d93af2bb3fa9 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:37:32 +0200 Subject: [PATCH] Cap text animator range selector Start, End and Offset (#908) --- crates/render/src/text.rs | 7 ++++--- crates/render/src/text/tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index 7d7f7615..61d97e0b 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -149,9 +149,10 @@ fn range_params(ctx: &EvalCtx, layer: &Layer, sel: &PropGroup, n: usize) -> Rang let units_index = adv.map(|a| ctx.e(layer, a, "units") == 1).unwrap_or(false); let div = if units_index { n.max(1) as f64 } else { 100.0 }; Range { - start: ctx.f(layer, sel, "start", 0.0) / div, - end: ctx.f(layer, sel, "end", if units_index { n as f64 } else { 100.0 }) / div, - offset: ctx.f(layer, sel, "offset", 0.0) / div, + // Start and End stay within 0..100% (0..n in Index units); Offset within -100..100%. + start: ctx.f(layer, sel, "start", 0.0).clamp(0.0, div) / div, + end: ctx.f(layer, sel, "end", if units_index { n as f64 } else { 100.0 }).clamp(0.0, div) / div, + offset: ctx.f(layer, sel, "offset", 0.0).clamp(-div, div) / div, amount: af("amount", 100.0) / 100.0, shape: Shape::from_index(adv.map(|a| ctx.e(layer, a, "shape")).unwrap_or(0)), smoothness: af("smoothness", 100.0) / 100.0, diff --git a/crates/render/src/text/tests.rs b/crates/render/src/text/tests.rs index 835f9d33..1e578b74 100644 --- a/crates/render/src/text/tests.rs +++ b/crates/render/src/text/tests.rs @@ -99,6 +99,29 @@ fn range_based_on_words_and_lines() { assert_eq!(s, vec![0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0], "{s:?}"); } +#[test] +fn range_start_end_and_offset_are_capped() { + let (mut p, cid, comp) = setup(400, 200); + let mut l = text_layer(&mut p, &comp, doc("ABCD", 30.0)); + let a = animator(&mut p, &mut l, &["opacity"], &["range"]); + set(&mut l, "text/animators/#1/selectors/#1/advanced/smoothness", Value::Scalar(0.0)); + // End 300% is capped at 100% (Offset -150% at -100%): the range is -100%..0% and selects nothing. + // Uncapped it would be -150%..150% and select everything. + set(&mut l, "text/animators/#1/selectors/#1/end", Value::Scalar(300.0)); + set(&mut l, "text/animators/#1/selectors/#1/offset", Value::Scalar(-150.0)); + p.comp_mut(cid).unwrap().layers.push(l); + assert_eq!(selection(&p, cid, 0.0, a), vec![0.0; 4]); + // Start -200% is capped at 0%: with Offset 100% the range is 100%..150% and selects nothing. + // Uncapped it would be -100%..150% and select everything. + let l = &mut p.comp_mut(cid).unwrap().layers[0]; + set(l, "text/animators/#1/selectors/#1/start", Value::Scalar(-200.0)); + set(l, "text/animators/#1/selectors/#1/end", Value::Scalar(50.0)); + set(l, "text/animators/#1/selectors/#1/offset", Value::Scalar(100.0)); + assert_eq!(selection(&p, cid, 0.0, a), vec![0.0; 4]); + set(l, "text/animators/#1/selectors/#1/offset", Value::Scalar(0.0)); + assert_eq!(selection(&p, cid, 0.0, a), vec![1.0, 1.0, 0.0, 0.0]); +} + #[test] fn selector_modes_and_randomize() { let (mut p, cid, comp) = setup(400, 200);