Text Range Selector: Start, End and Offset show as counts with Units = Index (#1814)

With Units set to Index the selector already counts characters (words,
lines), but the timeline kept showing Start, End and Offset as
percentages ("3.0%"), so switching looked like it did nothing. They are
now shown without the % sign while Units is Index
(render::text::in_index_units).
This commit is contained in:
SBaksa
2026-10-19 21:08:16 +11:00
parent ef74d82902
commit c17f9eb959
3 changed files with 29 additions and 1 deletions
+8
View File
@@ -187,6 +187,14 @@ pub fn range_value_of(f: f64, offset: f64, units_index: bool, n: usize) -> f64 {
if units_index { (v * n as f64).round() } else { v * 100.0 }
}
/// Whether property `uid` of `layer` is a Range Selector's Start, End or Offset while its Units
/// are Index: the value is then a count of characters (words, lines), not a percentage.
pub fn in_index_units(layer: &Layer, uid: effectcraft_project::Uid) -> bool {
let Some(sel) = layer.props.parent_of(uid).filter(|g| g.match_id == "rangeSelector") else { return false };
let is_range_value = sel.props().any(|p| p.uid == uid && matches!(p.match_id.as_str(), "start" | "end" | "offset"));
is_range_value && sel.prop("advanced/units").is_some_and(|u| u.value == Value::Enum(1))
}
/// Selection of every glyph by an animator's selectors, per dimension.
pub fn animator_selection(ctx: &EvalCtx, layer: &Layer, anim: &PropGroup, lay: &TextLayout) -> Vec<[f64; 3]> {
let sels: Vec<&PropGroup> = anim.sub("selectors").map(|s| s.groups().filter(|g| g.enabled).collect()).unwrap_or_default();
+18
View File
@@ -99,6 +99,24 @@ 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:?}");
}
/// With Units = Index a Range Selector's Start, End and Offset are character counts, not
/// percentages (#1814); the selector's other values, and other selectors, are unaffected.
#[test]
fn index_units_mark_start_end_and_offset() {
let (mut p, _cid, comp) = setup(400, 200);
let mut l = text_layer(&mut p, &comp, doc("ABCDEFG", 30.0));
animator(&mut p, &mut l, &["opacity"], &["range", "wiggly"]);
let uid = |l: &Layer, path: &str| l.props.prop(path).unwrap_or_else(|| panic!("no {path}")).uid;
let base = "text/animators/#1/selectors/#1";
let range_values = ["start", "end", "offset"].map(|k| uid(&l, &format!("{base}/{k}")));
assert!(range_values.iter().all(|u| !in_index_units(&l, *u)), "Percentage, the default");
set(&mut l, &format!("{base}/advanced/units"), Value::Enum(1));
assert!(range_values.iter().all(|u| in_index_units(&l, *u)));
assert!(!in_index_units(&l, uid(&l, &format!("{base}/advanced/amount"))), "Amount stays a percentage");
assert!(!in_index_units(&l, uid(&l, "text/animators/#1/properties/opacity")));
assert!(!in_index_units(&l, uid(&l, "transform/opacity")));
}
#[test]
fn range_start_end_and_offset_are_capped() {
let (mut p, cid, comp) = setup(400, 200);
+3 -1
View File
@@ -4117,7 +4117,9 @@ fn value_editor(
set(actions, json!(nv));
}
} else {
let suffix = if matches!(prop.ui, ParamUi::Percent) || prop.match_id == "opacity" { "%" } else { "" };
// A Range Selector's Start, End and Offset count characters with Units = Index (#1814).
let pct = (matches!(prop.ui, ParamUi::Percent) || prop.match_id == "opacity") && !effectcraft_engine::render::text::in_index_units(layer, uid);
let suffix = if pct { "%" } else { "" };
let (r, nv, _) = widgets::hot_number_at(ui, pos2(x, y), egui::Id::new(("v", uid, 0)), *v, speed, (min, max), dec.max(1), suffix, &t);
app.auto.add(&format!("timeline.prop.{uid}.value"), r, &prop.name);
if let Some(nv) = nv {