The widget half of the widget catalogue's later work, ported onto makepad-widgets-core and the family crates. - libs/tween: a dependency-free tween and timeline engine -- nested timelines with positions and labels, stagger, repeat and yoyo, seek and reverse, time scale, a ticker with lag smoothing, a global pause and reduced motion, colour-space interpolation, keyframes and motion paths. The core binds it to the widget tree (tween, a widget's TweenHost), gives script a mod.tween (tween_script) and a live inspector (tween_inspect, tween_inspector). The Animator's Ease now answers from the engine's easing, so the curves have one source; the Animator keeps its states, its keyframe timelines and its own clock. - libs/gizmo and a Gizmo3d / ViewCube widget. - core: the ease editor, the sequencer, diagonal headings, hotkeys, the hotkey editor and keyboard map, the overlay layer host, a conceding row and the width a widget would take without drawing; a theme mix and a person's own theme edits that survive a style reload (theme_mix, set_theme_edit), both taken off by set_base_theme. - the surface material: buttons, check boxes, toggles, sliders and knobs carry the moulded material behind one uniform, a press idiom per sign of material_press, and four sheets that raise it: neumorphic, molded, glossy and milled. - the sheets: a row of fields is one height under every sheet. A sheet line that names a widget the app did not register (a family left out) is skipped when the sheet installs, so the date fields a sheet sizes need the dates family only where it is linked. - extras: the gradient editor, registered with the colour picker it builds on. - tweaker: the designer (a widget's literal located and edited in its source as hunks), the theme lab and the theme builder. The window reads the docked panel's band through the tweaker's hooks. - the panel no longer wears the app's theme (the design surface keeps its own palette), so the worn-theme tests go with it. Five theme builder tests fail here as they do on the branch this comes from (every_built_theme_reads and four more on text contrast); radial_menu's tab_under_an_open_menu_finds_nowhere_to_go fails here as it does on dev.
521 lines
15 KiB
Rust
521 lines
15 KiB
Rust
//! Shared test helpers. The oracle below is a verbatim copy of Makepad's
|
|
//! `animator::Ease` and `Ease::map` (widgets/src/animator.rs), with only the
|
|
//! script derives and `#[live]` / `#[pick]` attributes removed: the parity
|
|
//! family of `Easing` must reproduce it bit for bit.
|
|
//!
|
|
//! Every test binary that declares `mod common;` uses a different subset of
|
|
//! these items, so unused ones are expected per binary.
|
|
#![allow(dead_code)]
|
|
|
|
use makepad_tween::Easing;
|
|
use std::f64::consts::PI;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum Ease {
|
|
Linear,
|
|
None,
|
|
Constant(f64),
|
|
InQuad,
|
|
OutQuad,
|
|
InOutQuad,
|
|
InCubic,
|
|
OutCubic,
|
|
InOutCubic,
|
|
InQuart,
|
|
OutQuart,
|
|
InOutQuart,
|
|
InQuint,
|
|
OutQuint,
|
|
InOutQuint,
|
|
InSine,
|
|
OutSine,
|
|
InOutSine,
|
|
InExp,
|
|
OutExp,
|
|
InOutExp,
|
|
InCirc,
|
|
OutCirc,
|
|
InOutCirc,
|
|
InElastic,
|
|
OutElastic,
|
|
InOutElastic,
|
|
InBack,
|
|
OutBack,
|
|
InOutBack,
|
|
InBounce,
|
|
OutBounce,
|
|
InOutBounce,
|
|
ExpDecay {
|
|
d1: f64,
|
|
d2: f64,
|
|
max: usize,
|
|
},
|
|
|
|
Pow {
|
|
begin: f64,
|
|
end: f64,
|
|
},
|
|
Bezier {
|
|
cp0: f64,
|
|
cp1: f64,
|
|
cp2: f64,
|
|
cp3: f64,
|
|
},
|
|
}
|
|
|
|
impl Ease {
|
|
pub fn map(&self, t: f64) -> f64 {
|
|
match self {
|
|
Self::ExpDecay { d1, d2, max } => {
|
|
// there must be a closed form for this
|
|
if t > 0.999 {
|
|
return 1.0;
|
|
}
|
|
|
|
// first we count the number of steps we'd need to decay
|
|
let mut di = *d1;
|
|
let mut dt = 1.0;
|
|
let max_steps = (*max).min(1000);
|
|
let mut steps = 0;
|
|
// for most of the settings we use this takes max 15 steps or so
|
|
while dt > 0.001 && steps < max_steps {
|
|
steps = steps + 1;
|
|
dt = dt * di;
|
|
di *= d2;
|
|
}
|
|
// then we know how to find the step, and lerp it
|
|
let step = t * (steps as f64);
|
|
let mut di = *d1;
|
|
let mut dt = 1.0;
|
|
let max_steps = max_steps as f64;
|
|
let mut steps = 0.0;
|
|
while dt > 0.001 && steps < max_steps {
|
|
steps += 1.0;
|
|
if steps >= step {
|
|
// right step
|
|
let fac = steps - step;
|
|
return 1.0 - (dt * fac + (dt * di) * (1.0 - fac));
|
|
}
|
|
dt = dt * di;
|
|
di *= d2;
|
|
}
|
|
1.0
|
|
}
|
|
Self::Linear => {
|
|
return t.max(0.0).min(1.0);
|
|
}
|
|
Self::Constant(t) => {
|
|
return t.max(0.0).min(1.0);
|
|
}
|
|
Self::None => {
|
|
return 1.0;
|
|
}
|
|
Self::Pow { begin, end } => {
|
|
if t < 0. {
|
|
return 0.;
|
|
}
|
|
if t > 1. {
|
|
return 1.;
|
|
}
|
|
let a = -1. / (begin * begin).max(1.0);
|
|
let b = 1. + 1. / (end * end).max(1.0);
|
|
let t2 = (((a - 1.) * -b) / (a * (1. - b))).powf(t);
|
|
return (-a * b + b * a * t2) / (a * t2 - b);
|
|
}
|
|
|
|
Self::InQuad => {
|
|
return t * t;
|
|
}
|
|
Self::OutQuad => {
|
|
return t * (2.0 - t);
|
|
}
|
|
Self::InOutQuad => {
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
return 0.5 * t * t;
|
|
} else {
|
|
let t = t - 1.;
|
|
return -0.5 * (t * (t - 2.) - 1.);
|
|
}
|
|
}
|
|
Self::InCubic => {
|
|
return t * t * t;
|
|
}
|
|
Self::OutCubic => {
|
|
let t2 = t - 1.0;
|
|
return t2 * t2 * t2 + 1.0;
|
|
}
|
|
Self::InOutCubic => {
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
return 0.5 * t * t * t;
|
|
} else {
|
|
let t = t - 2.;
|
|
return 1. / 2. * (t * t * t + 2.);
|
|
}
|
|
}
|
|
Self::InQuart => return t * t * t * t,
|
|
Self::OutQuart => {
|
|
let t = t - 1.;
|
|
return -(t * t * t * t - 1.);
|
|
}
|
|
Self::InOutQuart => {
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
return 0.5 * t * t * t * t;
|
|
} else {
|
|
let t = t - 2.;
|
|
return -0.5 * (t * t * t * t - 2.);
|
|
}
|
|
}
|
|
Self::InQuint => {
|
|
return t * t * t * t * t;
|
|
}
|
|
Self::OutQuint => {
|
|
let t = t - 1.;
|
|
return t * t * t * t * t + 1.;
|
|
}
|
|
Self::InOutQuint => {
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
return 0.5 * t * t * t * t * t;
|
|
} else {
|
|
let t = t - 2.;
|
|
return 0.5 * (t * t * t * t * t + 2.);
|
|
}
|
|
}
|
|
Self::InSine => {
|
|
return -(t * PI * 0.5).cos() + 1.;
|
|
}
|
|
Self::OutSine => {
|
|
return (t * PI * 0.5).sin();
|
|
}
|
|
Self::InOutSine => {
|
|
return -0.5 * ((t * PI).cos() - 1.);
|
|
}
|
|
Self::InExp => {
|
|
if t < 0.001 {
|
|
return 0.;
|
|
} else {
|
|
return 2.0f64.powf(10. * (t - 1.));
|
|
}
|
|
}
|
|
Self::OutExp => {
|
|
if t > 0.999 {
|
|
return 1.;
|
|
} else {
|
|
return -(2.0f64.powf(-10. * t)) + 1.;
|
|
}
|
|
}
|
|
Self::InOutExp => {
|
|
if t < 0.001 {
|
|
return 0.;
|
|
}
|
|
if t > 0.999 {
|
|
return 1.;
|
|
}
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
return 0.5 * 2.0f64.powf(10. * (t - 1.));
|
|
} else {
|
|
let t = t - 1.;
|
|
return 0.5 * (-(2.0f64.powf(-10. * t)) + 2.);
|
|
}
|
|
}
|
|
Self::InCirc => {
|
|
return -((1. - t * t).sqrt() - 1.);
|
|
}
|
|
Self::OutCirc => {
|
|
let t = t - 1.;
|
|
return (1. - t * t).sqrt();
|
|
}
|
|
Self::InOutCirc => {
|
|
let t = t * 2.;
|
|
if t < 1. {
|
|
return -0.5 * ((1. - t * t).sqrt() - 1.);
|
|
} else {
|
|
let t = t - 2.;
|
|
return 0.5 * ((1. - t * t).sqrt() + 1.);
|
|
}
|
|
}
|
|
Self::InElastic => {
|
|
let p = 0.3;
|
|
let s = p / 4.0; // c = 1.0, b = 0.0, d = 1.0
|
|
if t < 0.001 {
|
|
return 0.;
|
|
}
|
|
if t > 0.999 {
|
|
return 1.;
|
|
}
|
|
let t = t - 1.0;
|
|
return -(2.0f64.powf(10.0 * t) * ((t - s) * (2.0 * PI) / p).sin());
|
|
}
|
|
Self::OutElastic => {
|
|
let p = 0.3;
|
|
let s = p / 4.0; // c = 1.0, b = 0.0, d = 1.0
|
|
|
|
if t < 0.001 {
|
|
return 0.;
|
|
}
|
|
if t > 0.999 {
|
|
return 1.;
|
|
}
|
|
return 2.0f64.powf(-10.0 * t) * ((t - s) * (2.0 * PI) / p).sin() + 1.0;
|
|
}
|
|
Self::InOutElastic => {
|
|
let p = 0.3;
|
|
let s = p / 4.0; // c = 1.0, b = 0.0, d = 1.0
|
|
if t < 0.001 {
|
|
return 0.;
|
|
}
|
|
if t > 0.999 {
|
|
return 1.;
|
|
}
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
let t = t - 1.0;
|
|
return -0.5 * (2.0f64.powf(10.0 * t) * ((t - s) * (2.0 * PI) / p).sin());
|
|
} else {
|
|
let t = t - 1.0;
|
|
return 0.5 * 2.0f64.powf(-10.0 * t) * ((t - s) * (2.0 * PI) / p).sin() + 1.0;
|
|
}
|
|
}
|
|
Self::InBack => {
|
|
let s = 1.70158;
|
|
return t * t * ((s + 1.) * t - s);
|
|
}
|
|
Self::OutBack => {
|
|
let s = 1.70158;
|
|
let t = t - 1.;
|
|
return t * t * ((s + 1.) * t + s) + 1.;
|
|
}
|
|
Self::InOutBack => {
|
|
let s = 1.70158;
|
|
let t = t * 2.0;
|
|
if t < 1. {
|
|
let s = s * 1.525;
|
|
return 0.5 * (t * t * ((s + 1.) * t - s));
|
|
} else {
|
|
let t = t - 2.;
|
|
return 0.5 * (t * t * ((s + 1.) * t + s) + 2.);
|
|
}
|
|
}
|
|
Self::InBounce => {
|
|
return 1.0 - Self::OutBounce.map(1.0 - t);
|
|
}
|
|
Self::OutBounce => {
|
|
if t < (1. / 2.75) {
|
|
return 7.5625 * t * t;
|
|
}
|
|
if t < (2. / 2.75) {
|
|
let t = t - (1.5 / 2.75);
|
|
return 7.5625 * t * t + 0.75;
|
|
}
|
|
if t < (2.5 / 2.75) {
|
|
let t = t - (2.25 / 2.75);
|
|
return 7.5625 * t * t + 0.9375;
|
|
}
|
|
let t = t - (2.625 / 2.75);
|
|
return 7.5625 * t * t + 0.984375;
|
|
}
|
|
Self::InOutBounce => {
|
|
if t < 0.5 {
|
|
return Self::InBounce.map(t * 2.) * 0.5;
|
|
} else {
|
|
return Self::OutBounce.map(t * 2. - 1.) * 0.5 + 0.5;
|
|
}
|
|
}
|
|
Self::Bezier { cp0, cp1, cp2, cp3 } => {
|
|
if t < 0. {
|
|
return 0.;
|
|
}
|
|
if t > 1. {
|
|
return 1.;
|
|
}
|
|
|
|
if (cp0 - cp1).abs() < 0.001 && (cp2 - cp3).abs() < 0.001 {
|
|
return t;
|
|
}
|
|
|
|
let epsilon = 1.0 / 200.0 * t;
|
|
let cx = 3.0 * cp0;
|
|
let bx = 3.0 * (cp2 - cp0) - cx;
|
|
let ax = 1.0 - cx - bx;
|
|
let cy = 3.0 * cp1;
|
|
let by = 3.0 * (cp3 - cp1) - cy;
|
|
let ay = 1.0 - cy - by;
|
|
let mut u = t;
|
|
|
|
for _i in 0..6 {
|
|
let x = ((ax * u + bx) * u + cx) * u - t;
|
|
if x.abs() < epsilon {
|
|
return ((ay * u + by) * u + cy) * u;
|
|
}
|
|
let d = (3.0 * ax * u + 2.0 * bx) * u + cx;
|
|
if d.abs() < 1e-6 {
|
|
break;
|
|
}
|
|
u = u - x / d;
|
|
}
|
|
|
|
if t > 1. {
|
|
return (ay + by) + cy;
|
|
}
|
|
if t < 0. {
|
|
return 0.0;
|
|
}
|
|
|
|
let mut w = 0.0;
|
|
let mut v = 1.0;
|
|
u = t;
|
|
for _i in 0..8 {
|
|
let x = ((ax * u + bx) * u + cx) * u;
|
|
if (x - t).abs() < epsilon {
|
|
return ((ay * u + by) * u + cy) * u;
|
|
}
|
|
|
|
if t > x {
|
|
w = u;
|
|
} else {
|
|
v = u;
|
|
}
|
|
u = (v - w) * 0.5 + w;
|
|
}
|
|
|
|
return ((ay * u + by) * u + cy) * u;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The `Easing` that stands for an oracle `Ease` (the future
|
|
/// `From<&Ease> for Easing` of the widgets adapter, test-local here).
|
|
pub fn to_easing(e: &Ease) -> Easing {
|
|
match *e {
|
|
Ease::Linear => Easing::Linear,
|
|
Ease::None => Easing::Instant,
|
|
Ease::Constant(c) => Easing::Constant(c),
|
|
Ease::InQuad => Easing::InQuad,
|
|
Ease::OutQuad => Easing::OutQuad,
|
|
Ease::InOutQuad => Easing::InOutQuad,
|
|
Ease::InCubic => Easing::InCubic,
|
|
Ease::OutCubic => Easing::OutCubic,
|
|
Ease::InOutCubic => Easing::InOutCubic,
|
|
Ease::InQuart => Easing::InQuart,
|
|
Ease::OutQuart => Easing::OutQuart,
|
|
Ease::InOutQuart => Easing::InOutQuart,
|
|
Ease::InQuint => Easing::InQuint,
|
|
Ease::OutQuint => Easing::OutQuint,
|
|
Ease::InOutQuint => Easing::InOutQuint,
|
|
Ease::InSine => Easing::InSine,
|
|
Ease::OutSine => Easing::OutSine,
|
|
Ease::InOutSine => Easing::InOutSine,
|
|
Ease::InExp => Easing::InExp,
|
|
Ease::OutExp => Easing::OutExp,
|
|
Ease::InOutExp => Easing::InOutExp,
|
|
Ease::InCirc => Easing::InCirc,
|
|
Ease::OutCirc => Easing::OutCirc,
|
|
Ease::InOutCirc => Easing::InOutCirc,
|
|
Ease::InElastic => Easing::InElastic,
|
|
Ease::OutElastic => Easing::OutElastic,
|
|
Ease::InOutElastic => Easing::InOutElastic,
|
|
Ease::InBack => Easing::InBack,
|
|
Ease::OutBack => Easing::OutBack,
|
|
Ease::InOutBack => Easing::InOutBack,
|
|
Ease::InBounce => Easing::InBounce,
|
|
Ease::OutBounce => Easing::OutBounce,
|
|
Ease::InOutBounce => Easing::InOutBounce,
|
|
Ease::ExpDecay { d1, d2, max } => Easing::exp_decay(d1, d2, max),
|
|
Ease::Pow { begin, end } => Easing::pow(begin, end),
|
|
Ease::Bezier { cp0, cp1, cp2, cp3 } => Easing::Bezier {
|
|
x1: cp0,
|
|
y1: cp1,
|
|
x2: cp2,
|
|
y2: cp3,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Every parity arm without a payload.
|
|
pub const PLAIN_ARMS: [Ease; 32] = [
|
|
Ease::Linear,
|
|
Ease::None,
|
|
Ease::InQuad,
|
|
Ease::OutQuad,
|
|
Ease::InOutQuad,
|
|
Ease::InCubic,
|
|
Ease::OutCubic,
|
|
Ease::InOutCubic,
|
|
Ease::InQuart,
|
|
Ease::OutQuart,
|
|
Ease::InOutQuart,
|
|
Ease::InQuint,
|
|
Ease::OutQuint,
|
|
Ease::InOutQuint,
|
|
Ease::InSine,
|
|
Ease::OutSine,
|
|
Ease::InOutSine,
|
|
Ease::InExp,
|
|
Ease::OutExp,
|
|
Ease::InOutExp,
|
|
Ease::InCirc,
|
|
Ease::OutCirc,
|
|
Ease::InOutCirc,
|
|
Ease::InElastic,
|
|
Ease::OutElastic,
|
|
Ease::InOutElastic,
|
|
Ease::InBack,
|
|
Ease::OutBack,
|
|
Ease::InOutBack,
|
|
Ease::InBounce,
|
|
Ease::OutBounce,
|
|
Ease::InOutBounce,
|
|
];
|
|
|
|
/// The eight theme motion eases (widgets/src/theme_desktop_dark.rs).
|
|
pub const THEME_EASES: [Ease; 8] = [
|
|
Ease::Bezier {
|
|
cp0: 0.2,
|
|
cp1: 0.0,
|
|
cp2: 0.0,
|
|
cp3: 1.0,
|
|
},
|
|
Ease::Bezier {
|
|
cp0: 0.0,
|
|
cp1: 0.0,
|
|
cp2: 0.0,
|
|
cp3: 1.0,
|
|
},
|
|
Ease::Bezier {
|
|
cp0: 0.3,
|
|
cp1: 0.0,
|
|
cp2: 1.0,
|
|
cp3: 1.0,
|
|
},
|
|
Ease::Bezier {
|
|
cp0: 0.05,
|
|
cp1: 0.7,
|
|
cp2: 0.1,
|
|
cp3: 1.0,
|
|
},
|
|
Ease::Bezier {
|
|
cp0: 0.3,
|
|
cp1: 0.0,
|
|
cp2: 0.8,
|
|
cp3: 0.15,
|
|
},
|
|
Ease::Linear,
|
|
Ease::OutElastic,
|
|
Ease::OutBounce,
|
|
];
|
|
|
|
/// A small deterministic generator for seeded test inputs.
|
|
pub struct Rng(pub u64);
|
|
|
|
impl Rng {
|
|
/// The next value in [0, 1).
|
|
pub fn next_f64(&mut self) -> f64 {
|
|
self.0 = makepad_tween::splitmix64(self.0);
|
|
(self.0 >> 11) as f64 / (1u64 << 53) as f64
|
|
}
|
|
}
|