diff options
author | David Shin <dshin@mozilla.com> | 2022-07-09 17:33:58 +0000 |
---|---|---|
committer | Martin Robinson <mrobinson@igalia.com> | 2023-10-02 14:37:19 +0000 |
commit | bb0f857dfa42ae9cfdfd6868f23c48d3a2a63307 (patch) | |
tree | 213ea4c17e0d841fd6b4aa896d9435ca435ff273 | |
parent | 168c868330ad3f73c1712148ebcc104969971781 (diff) | |
download | servo-bb0f857dfa42ae9cfdfd6868f23c48d3a2a63307.tar.gz servo-bb0f857dfa42ae9cfdfd6868f23c48d3a2a63307.zip |
style: Port bezier edge cases handling from C++ to Rust
Differential Revision: https://phabricator.services.mozilla.com/D150569
-rw-r--r-- | components/style/bezier.rs | 56 | ||||
-rw-r--r-- | components/style/values/computed/easing.rs | 25 |
2 files changed, 68 insertions, 13 deletions
diff --git a/components/style/bezier.rs b/components/style/bezier.rs index bc3dc883b26..dd520ac0ed5 100644 --- a/components/style/bezier.rs +++ b/components/style/bezier.rs @@ -23,15 +23,65 @@ pub struct Bezier { } impl Bezier { - /// Create a unit cubic Bézier curve from the two middle control points. + /// Calculate the output of a unit cubic Bézier curve from the two middle control points. /// /// X coordinate is time, Y coordinate is function advancement. /// The nominal range for both is 0 to 1. /// /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation /// starts at 0% and ends at 100%. + pub fn calculate_bezier_output( + progress: f64, + epsilon: f64, + x1: f32, + y1: f32, + x2: f32, + y2: f32, + ) -> f64 { + // Check for a linear curve. + if x1 == y1 && x2 == y2 { + return progress; + } + + // Ensure that we return 0 or 1 on both edges. + if progress == 0.0 { + return 0.0; + } + if progress == 1.0 { + return 1.0; + } + + // For negative values, try to extrapolate with tangent (p1 - p0) or, + // if p1 is coincident with p0, with (p2 - p0). + if progress < 0.0 { + if x1 > 0.0 { + return progress * y1 as f64 / x1 as f64; + } + if y1 == 0.0 && x2 > 0.0 { + return progress * y2 as f64 / x2 as f64; + } + // If we can't calculate a sensible tangent, don't extrapolate at all. + return 0.0; + } + + // For values greater than 1, try to extrapolate with tangent (p2 - p3) or, + // if p2 is coincident with p3, with (p1 - p3). + if progress > 1.0 { + if x2 < 1.0 { + return 1.0 + (progress - 1.0) * (y2 as f64 - 1.0) / (x2 as f64 - 1.0); + } + if y2 == 1.0 && x1 < 1.0 { + return 1.0 + (progress - 1.0) * (y1 as f64 - 1.0) / (x1 as f64 - 1.0); + } + // If we can't calculate a sensible tangent, don't extrapolate at all. + return 1.0; + } + + Bezier::new(x1, y1, x2, y2).solve(progress, epsilon) + } + #[inline] - pub fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier { + fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier { let cx = 3. * x1 as f64; let bx = 3. * (x2 as f64 - x1 as f64) - cx; @@ -109,7 +159,7 @@ impl Bezier { /// Solve the bezier curve for a given `x` and an `epsilon`, that should be /// between zero and one. #[inline] - pub fn solve(&self, x: f64, epsilon: f64) -> f64 { + fn solve(&self, x: f64, epsilon: f64) -> f64 { self.sample_curve_y(self.solve_curve_x(x, epsilon)) } } diff --git a/components/style/values/computed/easing.rs b/components/style/values/computed/easing.rs index a90c6f18621..6bcf875527a 100644 --- a/components/style/values/computed/easing.rs +++ b/components/style/values/computed/easing.rs @@ -86,7 +86,7 @@ impl ComputedTimingFunction { pub fn calculate_output(&self, progress: f64, before_flag: BeforeFlag, epsilon: f64) -> f64 { match self { TimingFunction::CubicBezier { x1, y1, x2, y2 } => { - Bezier::new(*x1, *y1, *x2, *y2).solve(progress, epsilon) + Bezier::calculate_bezier_output(progress, epsilon, *x1, *y1, *x2, *y2) }, TimingFunction::Steps(steps, pos) => { Self::calculate_step_output(*steps, *pos, progress, before_flag) @@ -102,15 +102,20 @@ impl ComputedTimingFunction { .at(progress as f32) .into() }, - TimingFunction::Keyword(keyword) => { - let bezier = match keyword { - TimingKeyword::Linear => return progress, - TimingKeyword::Ease => Bezier::new(0.25, 0.1, 0.25, 1.), - TimingKeyword::EaseIn => Bezier::new(0.42, 0., 1., 1.), - TimingKeyword::EaseOut => Bezier::new(0., 0., 0.58, 1.), - TimingKeyword::EaseInOut => Bezier::new(0.42, 0., 0.58, 1.), - }; - bezier.solve(progress, epsilon) + TimingFunction::Keyword(keyword) => match keyword { + TimingKeyword::Linear => return progress, + TimingKeyword::Ease => { + Bezier::calculate_bezier_output(progress, epsilon, 0.25, 0.1, 0.25, 1.) + }, + TimingKeyword::EaseIn => { + Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 1., 1.) + }, + TimingKeyword::EaseOut => { + Bezier::calculate_bezier_output(progress, epsilon, 0., 0., 0.58, 1.) + }, + TimingKeyword::EaseInOut => { + Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 0.58, 1.) + }, }, } } |