git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

commit8ea59bb3e42991c9ee19976be572fd9561af4872
parenta55a5b0215
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 22:34
refactor(ramp): use cce-core's ramp functions, not a mirror

parse_spec and the monotone-cubic value_at were a hand-kept mirror of
cce-ui's, kept so this crate stayed dependency-minimal. They are
cce_core::ramp now, taken with default-features = false: no config half,
so no KDL or JSON — cce-core on log, web-time and libc. The curve
sculpted in the toolkit's widget is the same function evaluated here, so
nothing can drift. The ramp tests pass unchanged against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 Cargo.toml  |  2 ++
 src/ramp.rs | 90 +++++++------------------------------------------------------
 2 files changed, 11 insertions(+), 81 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 83b01da..916ebef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,8 @@ name = "cce_window_manager"
 path = "src/lib.rs"
 
 [dependencies]
+# The DE's ramp spec and its curve (`ramp.rs`), without cce-core's config half.
+cce-core = { git = "https://github.com/lsgalante/cce-core.git", rev = "39e989d6ebc7850a1e5aa96bf228fe8714e538e0", default-features = false }
 log = "0.4"
 serde = { version = "1", features = ["derive"] }
 
diff --git a/src/ramp.rs b/src/ramp.rs
index 6a0ae25..0503b6e 100644
--- a/src/ramp.rs
+++ b/src/ramp.rs
@@ -3,11 +3,11 @@
 // The input is the DE-wide ramp spec string written by cce-ui's Ramp widget
 // (`format_ramp_spec`): `"linear;0.000:0.100,0.500:1.000,1.000:0.050"` —
 // keys are `time:speed` pairs in [0,1]², and the `smooth` head draws a
-// monotone cubic through the keys instead of straight segments. The tiny
-// parser and the interpolation are MIRRORED from cce-ui
-// (`layout::sample_ramp_keys`; this crate stays dependency-minimal), so the
-// curve sculpted in the widget is exactly the curve evaluated here — keep
-// the two in step.
+// monotone cubic through the keys instead of straight segments. The parser
+// and the interpolation are cce-core's `ramp` (taken without its config
+// half, so this crate gains no KDL or JSON), the same functions cce-ui's
+// widget draws with, so the curve sculpted there is the curve evaluated here.
+// Until 2026-10-07 they were a hand-kept mirror of cce-ui's.
 //
 // The ramp is a SPEED profile over normalized time. Construction integrates
 // it once into a cumulative-progress table normalized to end at exactly 1,
@@ -21,82 +21,10 @@
 const SAMPLES: usize = 256;
 
 /// Parse a ramp spec string into `(keys, smooth)`; `None` for anything that
-/// doesn't yield at least two keys. Mirrors cce-ui's `parse_ramp_spec`.
-pub fn parse_spec(spec: &str) -> Option<(Vec<(f32, f32)>, bool)> {
-    let (head, body) = spec.split_once(';')?;
-    let smooth = head.trim() == "smooth";
-    let mut keys = Vec::new();
-    for part in body.split(',') {
-        let (p, v) = part.split_once(':')?;
-        keys.push((
-            p.trim().parse::<f32>().ok()?.clamp(0.0, 1.0),
-            v.trim().parse::<f32>().ok()?.clamp(0.0, 1.0),
-        ));
-    }
-    if keys.len() < 2 {
-        return None;
-    }
-    keys.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
-    Some((keys, smooth))
-}
-
-/// The ramp's value at `t` — endpoint-clamped; `smooth` is a monotone cubic
-/// through the keys (Fritsch–Butland tangents, zero at the ends and at local
-/// extrema, cubic Hermite segments), else straight segments. Mirrors cce-ui's
-/// `layout::sample_ramp_keys` exactly; a two-key smooth ramp is the plain
-/// smoothstep.
-fn value_at(keys: &[(f32, f32)], smooth: bool, t: f32) -> f32 {
-    if keys.is_empty() {
-        return 0.0;
-    }
-    if t <= keys[0].0 {
-        return keys[0].1;
-    }
-    if t >= keys[keys.len() - 1].0 {
-        return keys[keys.len() - 1].1;
-    }
-    for i in 0..keys.len() - 1 {
-        let ((x0, y0), (x1, y1)) = (keys[i], keys[i + 1]);
-        if t < x0 || t > x1 {
-            continue;
-        }
-        let h = x1 - x0;
-        if h.abs() < 0.0001 {
-            return y0;
-        }
-        let s = (t - x0) / h;
-        if !smooth {
-            return y0 + (y1 - y0) * s;
-        }
-        let (m0, m1) = (key_tangent(keys, i), key_tangent(keys, i + 1));
-        let (s2, s3) = (s * s, s * s * s);
-        let h00 = 2.0 * s3 - 3.0 * s2 + 1.0;
-        let h10 = s3 - 2.0 * s2 + s;
-        let h01 = -2.0 * s3 + 3.0 * s2;
-        let h11 = s3 - s2;
-        return h00 * y0 + h10 * h * m0 + h01 * y1 + h11 * h * m1;
-    }
-    keys[0].1
-}
-
-/// Tangent at key `i` for `value_at`'s smooth mode — mirrors cce-ui's
-/// `layout::ramp_key_tangent`.
-fn key_tangent(keys: &[(f32, f32)], i: usize) -> f32 {
-    if i == 0 || i + 1 >= keys.len() {
-        return 0.0;
-    }
-    let ((xp, yp), (x, y), (xn, yn)) = (keys[i - 1], keys[i], keys[i + 1]);
-    let (h0, h1) = (x - xp, xn - x);
-    if h0 <= 0.0001 || h1 <= 0.0001 {
-        return 0.0;
-    }
-    let (d0, d1) = ((y - yp) / h0, (yn - y) / h1);
-    if d0 * d1 <= 0.0 {
-        return 0.0;
-    }
-    let (w0, w1) = (2.0 * h1 + h0, h1 + 2.0 * h0);
-    (w0 + w1) / (w0 / d0 + w1 / d1)
-}
+/// doesn't yield at least two keys.
+pub use cce_core::ramp::parse_ramp_spec as parse_spec;
+/// The ramp's value at `t`: endpoint-clamped, a monotone cubic when `smooth`.
+use cce_core::ramp::sample_ramp_keys as value_at;
 
 /// A speed profile integrated into a normalized progress curve.
 #[derive(Debug, Clone)]