GUI-free half of the cce toolkit: config, input, IPC, spec parsers
git clone https://git.lucas.co/cce-core.git
src/ramp.rs (4.4K)
1 //! The DE's ramp spec — `"smooth;0.000:0.500,0.200:1.000,…"` — and the curve it draws:
2 //! what cce-ui's Ramp widget writes, its relief profiles read, and the window manager's
3 //! camera transitions evaluate. `cce_ui::widget` and `cce_ui::layout` re-export these.
4
5 /// Serialize ramp keys + line type as the DE's ramp spec string:
6 /// `"smooth;0.000:0.500,0.200:1.000,…"` (`"linear;…"` for straight segments) —
7 /// the format ramp-valued params travel in (`ParametersBg` "ramp" rows,
8 /// project files, `cce_ui::layout::set_bevel_profile_keys` consumers).
9 pub fn format_ramp_spec(keys: &[(f32, f32)], smooth: bool) -> String {
10 let body: Vec<String> =
11 keys.iter().map(|(p, v)| format!("{:.3}:{:.3}", p, v)).collect();
12 format!("{};{}", if smooth { "smooth" } else { "linear" }, body.join(","))
13 }
14
15 /// Parse a ramp spec string ([`format_ramp_spec`]) into `(keys, smooth)`.
16 /// `None` for anything that doesn't yield at least two keys.
17 pub fn parse_ramp_spec(spec: &str) -> Option<(Vec<(f32, f32)>, bool)> {
18 let (head, body) = spec.split_once(';')?;
19 let smooth = head.trim() == "smooth";
20 let mut keys = Vec::new();
21 for part in body.split(',') {
22 let (p, v) = part.split_once(':')?;
23 keys.push((
24 p.trim().parse::<f32>().ok()?.clamp(0.0, 1.0),
25 v.trim().parse::<f32>().ok()?.clamp(0.0, 1.0),
26 ));
27 }
28 if keys.len() < 2 {
29 return None;
30 }
31 keys.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
32 Some((keys, smooth))
33 }
34
35 /// Evaluate a ramp key list at `t` — THE ramp interpolation of the DE.
36 /// `cce_ui::widget::Ramp` draws it, `RampPreview` previews it, the relief
37 /// profile LUTs sample it, and cce-window-manager's camera speed ramp mirrors
38 /// it verbatim (that crate stays dependency-minimal), so a curve sculpted in
39 /// the widget is exactly the curve every consumer evaluates. Keys are
40 /// `(pos, value)` sorted by pos; outside the key range the end values hold.
41 ///
42 /// `smooth` is the widget's curved line type: a **monotone cubic** through
43 /// the keys (Fritsch–Butland tangents, cubic Hermite segments) — C1, passes
44 /// through every key, never overshoots a key, and flattens only at the ends
45 /// and at genuine local extrema. It used to be a smoothstep blend PER
46 /// SEGMENT, which forces zero slope at every key: a curve with more than two
47 /// keys came out as a chain of little bumps, and a wall profile built from
48 /// it read as jagged and uneven where a smooth slope was drawn. A two-key
49 /// ramp is unchanged — zero tangents at both ends make the single Hermite
50 /// segment exactly the old smoothstep — so the identity sentinel and every
51 /// simple ease keep their look. `false` is straight segments.
52 pub fn sample_ramp_keys(keys: &[(f32, f32)], smooth: bool, t: f32) -> f32 {
53 let Some(first) = keys.first() else { return 0.0 };
54 let last = keys.last().unwrap();
55 if t <= first.0 {
56 return first.1;
57 }
58 if t >= last.0 {
59 return last.1;
60 }
61 for i in 0..keys.len() - 1 {
62 let ((x0, y0), (x1, y1)) = (keys[i], keys[i + 1]);
63 if t < x0 || t > x1 {
64 continue;
65 }
66 let h = x1 - x0;
67 if h.abs() < 0.0001 {
68 return y0;
69 }
70 let s = (t - x0) / h;
71 if !smooth {
72 return y0 + (y1 - y0) * s;
73 }
74 let (m0, m1) = (ramp_key_tangent(keys, i), ramp_key_tangent(keys, i + 1));
75 let (s2, s3) = (s * s, s * s * s);
76 let h00 = 2.0 * s3 - 3.0 * s2 + 1.0;
77 let h10 = s3 - 2.0 * s2 + s;
78 let h01 = -2.0 * s3 + 3.0 * s2;
79 let h11 = s3 - s2;
80 return h00 * y0 + h10 * h * m0 + h01 * y1 + h11 * h * m1;
81 }
82 first.1
83 }
84
85 /// The monotone cubic's tangent (dy/dpos) at key `i`: zero at either end and
86 /// at any local extremum (so the curve never overshoots a key), otherwise the
87 /// Fritsch–Butland weighted harmonic mean of the two neighbouring secants —
88 /// the shape-preserving choice, which keeps every segment monotone whenever
89 /// its keys are.
90 fn ramp_key_tangent(keys: &[(f32, f32)], i: usize) -> f32 {
91 if i == 0 || i + 1 >= keys.len() {
92 return 0.0;
93 }
94 let ((xp, yp), (x, y), (xn, yn)) = (keys[i - 1], keys[i], keys[i + 1]);
95 let (h0, h1) = (x - xp, xn - x);
96 if h0 <= 0.0001 || h1 <= 0.0001 {
97 return 0.0;
98 }
99 let (d0, d1) = ((y - yp) / h0, (yn - y) / h1);
100 if d0 * d1 <= 0.0 {
101 return 0.0;
102 }
103 let (w0, w1) = (2.0 * h1 + h0, h1 + 2.0 * h0);
104 (w0 + w1) / (w0 / d0 + w1 / d1)
105 }