Repository: Textualize/textual

Issue:
Add more easing functions
There are a number of 'easing functions' used by the animation system. At the top of _animator.py you will see the following:

```python
EASING = {
    "none": lambda x: 1.0,
    "round": lambda x: 0.0 if x < 0.5 else 1.0,
    "linear": lambda x: x,
    "in_cubic": lambda x: x * x * x,
    "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
    "out_cubic": lambda x: 1 - pow(1 - x, 3),
}
```

These function have been copied from https://easings.net/ and translated from Javascript to Python. I would like to add the remaining functions to the EASING dict above.

See the animation.py example as a way of testing the above functions. 


Discussion:
I'm having a go at this. Some of the easing functions will look ugly as one-line lambdas. I'll use short `def`s for those and then you'll see how it looks. Does that make sense?
Sounds good! Agree re not always using lambda.
Can you assign yourself to this? Want to avoid more than one person jumping on a task.

Resolving patch:
diff --git a/src/textual/_animator.py b/src/textual/_animator.py
--- a/src/textual/_animator.py
+++ b/src/textual/_animator.py
@@ -8,6 +8,7 @@
 
 from dataclasses import dataclass
 
+from ._easing import DEFAULT_EASING, EASING
 from ._timer import Timer
 from ._types import MessageTarget
 
@@ -27,19 +28,6 @@ def blend(self: T, destination: T, factor: float) -> T:
         ...
 
 
-# https://easings.net/
-EASING = {
-    "none": lambda x: 1.0,
-    "round": lambda x: 0.0 if x < 0.5 else 1.0,
-    "linear": lambda x: x,
-    "in_cubic": lambda x: x * x * x,
-    "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
-    "out_cubic": lambda x: 1 - pow(1 - x, 3),
-}
-
-DEFAULT_EASING = "in_out_cubic"
-
-
 @dataclass
 class Animation:
     obj: object
diff --git a/src/textual/_easing.py b/src/textual/_easing.py
new file mode 100644
--- /dev/null
+++ b/src/textual/_easing.py
@@ -0,0 +1,130 @@
+"""
+Define a series of easing functions for more natural-looking animations.
+Taken from https://easings.net/ and translated from JavaScript.
+"""
+
+from math import pi, cos, sin, sqrt
+
+
+def _in_out_expo(x: float) -> float:
+    """https://easings.net/#easeInOutExpo"""
+    if 0 < x < 0.5:
+        return pow(2, 20 * x - 10) / 2
+    elif 0.5 <= x < 1:
+        return (2 - pow(2, -20 * x + 10)) / 2
+    else:
+        return x  # x in (0, 1)
+
+
+def _in_out_circ(x: float) -> float:
+    """https://easings.net/#easeInOutCirc"""
+    if x < 0.5:
+        return (1 - sqrt(1 - pow(2 * x, 2))) / 2
+    else:
+        return (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2
+
+
+def _in_out_back(x: float) -> float:
+    """https://easings.net/#easeInOutBack"""
+    c = 1.70158 * 1.525
+    if x < 0.5:
+        return (pow(2 * x, 2) * ((c + 1) * 2 * x - c)) / 2
+    else:
+        return (pow(2 * x - 2, 2) * ((c + 1) * (x * 2 - 2) + c) + 2) / 2
+
+
+def _in_elastic(x: float) -> float:
+    """https://easings.net/#easeInElastic"""
+    c = 2 * pi / 3
+    if 0 < x < 1:
+        return -pow(2, 10 * x - 10) * sin((x * 10 - 10.75) * c)
+    else:
+        return x  # x in (0, 1)
+
+
+def _in_out_elastic(x: float) -> float:
+    """https://easings.net/#easeInOutElastic"""
+    c = 2 * pi / 4.5
+    if 0 < x < 0.5:
+        return -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c)) / 2
+    elif 0.5 <= x < 1:
+        return (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c)) / 2 + 1
+    else:
+        return x  # x in (0, 1)
+
+
+def _out_elastic(x: float) -> float:
+    """https://easings.net/#easeInOutElastic"""
+    c = 2 * pi / 3
+    if 0 < x < 1:
+        return pow(2, -10 * x) * sin((x * 10 - 0.75) * c) + 1
+    else:
+        return x  # x in (0, 1)
+
+
+def _out_bounce(x: float) -> float:
+    """https://easings.net/#easeOutBounce"""
+    n, d = 7.5625, 2.75
+    if x < 1 / d:
+        return n * x * x
+    elif x < 2 / d:
+        x_ = x - 1.5 / d
+        return n * x_ * x_ + 0.75
+    elif x < 2.5 / d:
+        x_ = x - 2.25 / d
+        return n * x_ * x_ + 0.9375
+    else:
+        x_ = x - 2.625 / d
+        return n * x_ * x_ + 0.984375
+
+
+def _in_bounce(x: float) -> float:
+    """https://easings.net/#easeInBounce"""
+    return 1 - _out_bounce(1 - x)
+
+
+def _in_out_bounce(x: float) -> float:
+    """https://easings.net/#easeInOutBounce"""
+    if x < 0.5:
+        return (1 - _out_bounce(1 - 2 * x)) / 2
+    else:
+        return (1 + _out_bounce(2 * x - 1)) / 2
+
+
+EASING = {
+    "none": lambda x: 1.0,
+    "round": lambda x: 0.0 if x < 0.5 else 1.0,
+    "linear": lambda x: x,
+    "in_sine": lambda x: 1 - cos((x * pi) / 2),
+    "in_out_sine": lambda x: -(cos(x * pi) - 1) / 2,
+    "out_sine": lambda x: sin((x * pi) / 2),
+    "in_quad": lambda x: x * x,
+    "in_out_quad": lambda x: 2 * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 2) / 2,
+    "out_quad": lambda x: 1 - pow(1 - x, 2),
+    "in_cubic": lambda x: x * x * x,
+    "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
+    "out_cubic": lambda x: 1 - pow(1 - x, 3),
+    "in_quart": lambda x: pow(x, 4),
+    "in_out_quart": lambda x: 8 * pow(x, 4) if x < 0.5 else 1 - pow(-2 * x + 2, 4) / 2,
+    "out_quart": lambda x: 1 - pow(1 - x, 4),
+    "in_quint": lambda x: pow(x, 5),
+    "in_out_quint": lambda x: 16 * pow(x, 5) if x < 0.5 else 1 - pow(-2 * x + 2, 5) / 2,
+    "out_quint": lambda x: 1 - pow(1 - x, 5),
+    "in_expo": lambda x: pow(2, 10 * x - 10) if x else 0,
+    "in_out_expo": _in_out_expo,
+    "out_expo": lambda x: 1 - pow(2, -10 * x) if x != 1 else 1,
+    "in_circ": lambda x: 1 - sqrt(1 - pow(x, 2)),
+    "in_out_circ": _in_out_circ,
+    "out_circ": lambda x: sqrt(1 - pow(x - 1, 2)),
+    "in_back": lambda x: 2.70158 * pow(x, 3) - 1.70158 * pow(x, 2),
+    "in_out_back": _in_out_back,
+    "out_back": lambda x: 1 + 2.70158 * pow(x - 1, 3) + 1.70158 * pow(x - 1, 2),
+    "in_elastic": _in_elastic,
+    "in_out_elastic": _in_out_elastic,
+    "out_elastic": _out_elastic,
+    "in_bounce": _in_bounce,
+    "in_out_bounce": _in_out_bounce,
+    "out_bounce": _out_bounce,
+}
+
+DEFAULT_EASING = "in_out_cubic"


Test patch:
diff --git a/tests/test_easing.py b/tests/test_easing.py
new file mode 100644
--- /dev/null
+++ b/tests/test_easing.py
@@ -0,0 +1,741 @@
+"""
+Test the easing functions by sampling the range [0, 1] uniformly in 21 points.
+Then, a TypeScript script was used to sample all the original functions,
+the data was collected, and is here tested against the Python implementation.
+"""
+
+import pytest
+
+from textual._easing import EASING
+
+
+POINTS = [
+    0.0,
+    0.05,
+    0.1,
+    0.15,
+    0.2,
+    0.25,
+    0.3,
+    0.35,
+    0.4,
+    0.45,
+    0.5,
+    0.55,
+    0.6,
+    0.65,
+    0.7,
+    0.75,
+    0.8,
+    0.85,
+    0.9,
+    0.95,
+    1.0,
+]
+
+DATA = {
+    "none": [1] * len(POINTS),
+    "round": [0] * POINTS.index(0.5) + [1] * (len(POINTS) - POINTS.index(0.5)),
+    "linear": POINTS[::],
+    "in_sine": [
+        0,
+        0.003082666266872036,
+        0.01231165940486223,
+        0.027630079602323443,
+        0.04894348370484647,
+        0.07612046748871326,
+        0.1089934758116321,
+        0.1473598356459077,
+        0.19098300562505255,
+        0.23959403439996907,
+        0.2928932188134524,
+        0.35055195166981645,
+        0.41221474770752686,
+        0.4775014352840511,
+        0.5460095002604531,
+        0.6173165676349102,
+        0.6909830056250525,
+        0.7665546361440945,
+        0.843565534959769,
+        0.921540904272155,
+        0.9999999999999999,
+    ],
+    "out_sine": [
+        0,
+        0.07845909572784494,
+        0.15643446504023087,
+        0.2334453638559054,
+        0.3090169943749474,
+        0.3826834323650898,
+        0.45399049973954675,
+        0.5224985647159488,
+        0.5877852522924731,
+        0.6494480483301837,
+        0.7071067811865475,
+        0.760405965600031,
+        0.8090169943749475,
+        0.8526401643540922,
+        0.8910065241883678,
+        0.9238795325112867,
+        0.9510565162951535,
+        0.9723699203976766,
+        0.9876883405951378,
+        0.996917333733128,
+        1,
+    ],
+    "in_out_sine": [
+        0,
+        0.006155829702431115,
+        0.024471741852423234,
+        0.05449673790581605,
+        0.09549150281252627,
+        0.1464466094067262,
+        0.20610737385376343,
+        0.27300475013022657,
+        0.3454915028125263,
+        0.4217827674798845,
+        0.49999999999999994,
+        0.5782172325201155,
+        0.6545084971874737,
+        0.7269952498697734,
+        0.7938926261462365,
+        0.8535533905932737,
+        0.9045084971874737,
+        0.9455032620941839,
+        0.9755282581475768,
+        0.9938441702975689,
+        1,
+    ],
+    "in_quad": [
+        0,
+        0.0025000000000000005,
+        0.010000000000000002,
+        0.0225,
+        0.04000000000000001,
+        0.0625,
+        0.09,
+        0.12249999999999998,
+        0.16000000000000003,
+        0.2025,
+        0.25,
+        0.30250000000000005,
+        0.36,
+        0.42250000000000004,
+        0.48999999999999994,
+        0.5625,
+        0.6400000000000001,
+        0.7224999999999999,
+        0.81,
+        0.9025,
+        1,
+    ],
+    "out_quad": [
+        0,
+        0.09750000000000003,
+        0.18999999999999995,
+        0.2775000000000001,
+        0.3599999999999999,
+        0.4375,
+        0.51,
+        0.5774999999999999,
+        0.64,
+        0.6975,
+        0.75,
+        0.7975000000000001,
+        0.84,
+        0.8775000000000001,
+        0.9099999999999999,
+        0.9375,
+        0.96,
+        0.9775,
+        0.99,
+        0.9974999999999999,
+        1,
+    ],
+    "in_out_quad": [
+        0,
+        0.005000000000000001,
+        0.020000000000000004,
+        0.045,
+        0.08000000000000002,
+        0.125,
+        0.18,
+        0.24499999999999997,
+        0.32000000000000006,
+        0.405,
+        0.5,
+        0.5950000000000001,
+        0.6799999999999999,
+        0.755,
+        0.82,
+        0.875,
+        0.92,
+        0.955,
+        0.98,
+        0.995,
+        1,
+    ],
+    "in_cubic": [
+        0,
+        0.00012500000000000003,
+        0.0010000000000000002,
+        0.003375,
+        0.008000000000000002,
+        0.015625,
+        0.027,
+        0.04287499999999999,
+        0.06400000000000002,
+        0.09112500000000001,
+        0.125,
+        0.16637500000000005,
+        0.216,
+        0.27462500000000006,
+        0.3429999999999999,
+        0.421875,
+        0.5120000000000001,
+        0.6141249999999999,
+        0.7290000000000001,
+        0.8573749999999999,
+        1,
+    ],
+    "out_cubic": [
+        0,
+        0.1426250000000001,
+        0.2709999999999999,
+        0.3858750000000001,
+        0.4879999999999999,
+        0.578125,
+        0.657,
+        0.7253749999999999,
+        0.784,
+        0.833625,
+        0.875,
+        0.908875,
+        0.9359999999999999,
+        0.957125,
+        0.973,
+        0.984375,
+        0.992,
+        0.996625,
+        0.999,
+        0.999875,
+        1,
+    ],
+    "in_out_cubic": [
+        0,
+        0.0005000000000000001,
+        0.004000000000000001,
+        0.0135,
+        0.03200000000000001,
+        0.0625,
+        0.108,
+        0.17149999999999996,
+        0.25600000000000006,
+        0.36450000000000005,
+        0.5,
+        0.6355000000000002,
+        0.744,
+        0.8285,
+        0.8919999999999999,
+        0.9375,
+        0.968,
+        0.9865,
+        0.996,
+        0.9994999999999999,
+        1,
+    ],
+    "in_quart": [
+        0,
+        0.000006250000000000002,
+        0.00010000000000000003,
+        0.00050625,
+        0.0016000000000000005,
+        0.00390625,
+        0.0081,
+        0.015006249999999995,
+        0.025600000000000008,
+        0.04100625000000001,
+        0.0625,
+        0.09150625000000004,
+        0.1296,
+        0.17850625000000006,
+        0.24009999999999992,
+        0.31640625,
+        0.40960000000000013,
+        0.5220062499999999,
+        0.6561000000000001,
+        0.8145062499999999,
+        1,
+    ],
+    "out_quart": [
+        0,
+        0.18549375,
+        0.3438999999999999,
+        0.4779937500000001,
+        0.5903999999999998,
+        0.68359375,
+        0.7599,
+        0.8214937499999999,
+        0.8704000000000001,
+        0.90849375,
+        0.9375,
+        0.95899375,
+        0.9743999999999999,
+        0.98499375,
+        0.9919,
+        0.99609375,
+        0.9984,
+        0.99949375,
+        0.9999,
+        0.99999375,
+        1,
+    ],
+    "in_out_quart": [
+        0,
+        0.000050000000000000016,
+        0.0008000000000000003,
+        0.00405,
+        0.012800000000000004,
+        0.03125,
+        0.0648,
+        0.12004999999999996,
+        0.20480000000000007,
+        0.32805000000000006,
+        0.5,
+        0.6719500000000002,
+        0.7951999999999999,
+        0.87995,
+        0.9351999999999999,
+        0.96875,
+        0.9872,
+        0.99595,
+        0.9992,
+        0.99995,
+        1,
+    ],
+    "in_quint": [
+        0,
+        3.1250000000000013e-7,
+        0.000010000000000000004,
+        0.0000759375,
+        0.00032000000000000013,
+        0.0009765625,
+        0.00243,
+        0.005252187499999998,
+        0.010240000000000004,
+        0.018452812500000006,
+        0.03125,
+        0.050328437500000024,
+        0.07776,
+        0.11602906250000004,
+        0.16806999999999994,
+        0.2373046875,
+        0.32768000000000014,
+        0.4437053124999999,
+        0.5904900000000002,
+        0.7737809374999999,
+        1,
+    ],
+    "out_quint": [
+        0,
+        0.2262190625,
+        0.4095099999999998,
+        0.5562946875000001,
+        0.6723199999999998,
+        0.7626953125,
+        0.8319300000000001,
+        0.8839709375,
+        0.92224,
+        0.9496715625,
+        0.96875,
+        0.9815471875,
+        0.98976,
+        0.9947478125,
+        0.99757,
+        0.9990234375,
+        0.99968,
+        0.9999240625,
+        0.99999,
+        0.9999996875,
+        1,
+    ],
+    "in_out_quint": [
+        0,
+        0.000005000000000000002,
+        0.00016000000000000007,
+        0.001215,
+        0.005120000000000002,
+        0.015625,
+        0.03888,
+        0.08403499999999997,
+        0.16384000000000007,
+        0.2952450000000001,
+        0.5,
+        0.7047550000000002,
+        0.8361599999999999,
+        0.915965,
+        0.96112,
+        0.984375,
+        0.99488,
+        0.998785,
+        0.99984,
+        0.999995,
+        1,
+    ],
+    "in_expo": [
+        0,
+        0.0013810679320049757,
+        0.001953125,
+        0.0027621358640099515,
+        0.00390625,
+        0.005524271728019903,
+        0.0078125,
+        0.011048543456039806,
+        0.015625,
+        0.02209708691207961,
+        0.03125,
+        0.04419417382415922,
+        0.0625,
+        0.08838834764831845,
+        0.125,
+        0.1767766952966369,
+        0.25,
+        0.3535533905932738,
+        0.5,
+        0.7071067811865476,
+        1,
+    ],
+    "out_expo": [
+        0,
+        0.2928932188134524,
+        0.5,
+        0.6464466094067263,
+        0.75,
+        0.8232233047033631,
+        0.875,
+        0.9116116523516815,
+        0.9375,
+        0.9558058261758408,
+        0.96875,
+        0.9779029130879204,
+        0.984375,
+        0.9889514565439602,
+        0.9921875,
+        0.99447572827198,
+        0.99609375,
+        0.99723786413599,
+        0.998046875,
+        0.998618932067995,
+        1,
+    ],
+    "in_out_expo": [
+        0,
+        0.0009765625,
+        0.001953125,
+        0.00390625,
+        0.0078125,
+        0.015625,
+        0.03125,
+        0.0625,
+        0.125,
+        0.25,
+        0.5,
+        0.75,
+        0.875,
+        0.9375,
+        0.96875,
+        0.984375,
+        0.9921875,
+        0.99609375,
+        0.998046875,
+        0.9990234375,
+        1,
+    ],
+    "in_circ": [
+        0,
+        0.0012507822280910519,
+        0.005012562893380035,
+        0.011314003335740508,
+        0.020204102886728803,
+        0.031754163448145745,
+        0.04606079858305434,
+        0.06325030024024025,
+        0.08348486100883201,
+        0.10697144502541245,
+        0.1339745962155614,
+        0.16483534557549673,
+        0.19999999999999996,
+        0.24006579232146685,
+        0.285857157145715,
+        0.3385621722338523,
+        0.40000000000000013,
+        0.473217312357363,
+        0.5641101056459328,
+        0.6877501000800801,
+        1,
+    ],
+    "out_circ": [
+        0,
+        0.31224989991991997,
+        0.4358898943540673,
+        0.526782687642637,
+        0.5999999999999999,
+        0.6614378277661477,
+        0.714142842854285,
+        0.7599342076785331,
+        0.8,
+        0.8351646544245033,
+        0.8660254037844386,
+        0.8930285549745877,
+        0.916515138991168,
+        0.9367496997597597,
+        0.9539392014169457,
+        0.9682458365518543,
+        0.9797958971132712,
+        0.9886859966642595,
+        0.99498743710662,
+        0.998749217771909,
+        1,
+    ],
+    "in_out_circ": [
+        0,
+        0.0025062814466900174,
+        0.010102051443364402,
+        0.02303039929152717,
+        0.041742430504416006,
+        0.0669872981077807,
+        0.09999999999999998,
+        0.1429285785728575,
+        0.20000000000000007,
+        0.2820550528229664,
+        0.5,
+        0.7179449471770338,
+        0.7999999999999999,
+        0.8570714214271424,
+        0.8999999999999999,
+        0.9330127018922193,
+        0.958257569495584,
+        0.9769696007084728,
+        0.9898979485566356,
+        0.9974937185533099,
+        1,
+    ],
+    "in_back": [
+        0,
+        -0.003916252500000001,
+        -0.014314220000000004,
+        -0.029167717499999996,
+        -0.046450560000000016,
+        -0.06413656250000001,
+        -0.08019953999999999,
+        -0.09261330750000002,
+        -0.09935168000000005,
+        -0.09838847249999999,
+        -0.08769750000000004,
+        -0.06525257750000002,
+        -0.029027519999999973,
+        0.023003857500000002,
+        0.09286773999999975,
+        0.18259031249999969,
+        0.29419775999999986,
+        0.4297162674999999,
+        0.5911720200000001,
+        0.7805912024999997,
+        0.9999999999999998,
+    ],
+    "out_back": [
+        2.220446049250313e-16,
+        0.21940879750000053,
+        0.40882797999999987,
+        0.5702837325000001,
+        0.7058022400000001,
+        0.8174096875000002,
+        0.9071322600000001,
+        0.9769961425,
+        1.02902752,
+        1.0652525775,
+        1.0876975,
+        1.0983884725,
+        1.09935168,
+        1.0926133075,
+        1.08019954,
+        1.0641365625,
+        1.04645056,
+        1.0291677175,
+        1.01431422,
+        1.0039162525,
+        1,
+    ],
+    "in_out_back": [
+        -0,
+        -0.011177092750000001,
+        -0.037518552000000004,
+        -0.06823964924999999,
+        -0.092555656,
+        -0.09968184375,
+        -0.07883348399999998,
+        -0.019225848250000063,
+        0.0899257920000001,
+        0.25940616525,
+        0.5,
+        0.7405938347500003,
+        0.9100742079999999,
+        1.01922584825,
+        1.078833484,
+        1.09968184375,
+        1.092555656,
+        1.06823964925,
+        1.0375185519999999,
+        1.0111770927500001,
+        1,
+    ],
+    "in_elastic": [
+        0,
+        0.0006905339660024924,
+        0.001953125,
+        0.0013810679320049725,
+        -0.0019531250000000126,
+        -0.005524271728019903,
+        -0.0039062499999999918,
+        0.00552427172801992,
+        0.015625,
+        0.011048543456039785,
+        -0.015625000000000045,
+        -0.04419417382415922,
+        -0.031249999999999948,
+        0.04419417382415934,
+        0.125,
+        0.08838834764831832,
+        -0.1250000000000001,
+        -0.3535533905932738,
+        -0.24999999999999986,
+        0.353553390593274,
+        1,
+    ],
+    "out_elastic": [
+        0,
+        0.6464466094067263,
+        1.25,
+        1.3535533905932737,
+        1.125,
+        0.9116116523516816,
+        0.875,
+        0.9558058261758408,
+        1.03125,
+        1.0441941738241591,
+        1.015625,
+        0.9889514565439602,
+        0.984375,
+        0.99447572827198,
+        1.00390625,
+        1.00552427172802,
+        1.001953125,
+        0.998618932067995,
+        0.998046875,
+        0.9993094660339975,
+        1,
+    ],
+    "in_out_elastic": [
+        0,
+        0.0009765625,
+        0.000339156597005722,
+        -0.0036706742999449557,
+        -0.0039062499999999935,
+        0.011969444423734044,
+        0.023938888847468056,
+        -0.03125000000000003,
+        -0.11746157759823853,
+        0.04341204441673268,
+        0.5,
+        0.9565879555832674,
+        1.1174615775982386,
+        1.03125,
+        0.9760611111525319,
+        0.988030555576266,
+        1.00390625,
+        1.003670674299945,
+        0.9996608434029943,
+        0.9990234375,
+        1,
+    ],
+    "in_bounce": [
+        0,
+        0.015468750000000031,
+        0.01187500000000008,
+        0.05484375000000008,
+        0.06000000000000005,
+        0.02734375,
+        0.06937499999999996,
+        0.1673437499999999,
+        0.22750000000000004,
+        0.24984375000000003,
+        0.234375,
+        0.18109375000000005,
+        0.09000000000000019,
+        0.07359375000000024,
+        0.31937499999999985,
+        0.52734375,
+        0.6975000000000001,
+        0.82984375,
+        0.9243750000000001,
+        0.98109375,
+        1,
+    ],
+    "out_bounce": [
+        0,
+        0.018906250000000003,
+        0.07562500000000001,
+        0.17015624999999998,
+        0.30250000000000005,
+        0.47265625,
+        0.6806249999999999,
+        0.9264062499999998,
+        0.9099999999999998,
+        0.81890625,
+        0.765625,
+        0.75015625,
+        0.7725,
+        0.8326562500000001,
+        0.930625,
+        0.97265625,
+        0.94,
+        0.9451562499999999,
+        0.9881249999999999,
+        0.98453125,
+        1,
+    ],
+    "in_out_bounce": [
+        0,
+        0.00593750000000004,
+        0.030000000000000027,
+        0.03468749999999998,
+        0.11375000000000002,
+        0.1171875,
+        0.045000000000000095,
+        0.15968749999999993,
+        0.34875000000000006,
+        0.46218750000000003,
+        0.5,
+        0.5378125,
+        0.6512499999999999,
+        0.8403125,
+        0.9550000000000001,
+        0.8828125,
+        0.88625,
+        0.9653125,
+        0.97,
+        0.9940624999999998,
+        1,
+    ],
+}
+
+
+def test_coverage():
+    assert set(DATA.keys()) == set(EASING.keys())
+
+
+def test_easings():
+    for name, values in DATA.items():
+        func = EASING[name]
+        for point, value in zip(POINTS, values):
+            assert value == pytest.approx(func(point))
diff --git a/tools/gen_easings_tests.ts b/tools/gen_easings_tests.ts
new file mode 100644
--- /dev/null
+++ b/tools/gen_easings_tests.ts
@@ -0,0 +1,233 @@
+/*
+ * Easing functions from http://easings.net;
+ * This script generates the data points used to test the _easing.py functions.
+ */
+
+function easeInSine(x: number): number {
+    return 1 - cos((x * PI) / 2);
+}
+
+function easeOutSine(x: number): number {
+    return sin((x * PI) / 2);
+}
+
+function easeInOutSine(x: number): number {
+    return -(cos(PI * x) - 1) / 2;
+}
+
+
+function easeInQuad(x: number): number {
+    return x * x;
+}
+
+function easeOutQuad(x: number): number {
+    return 1 - (1 - x) * (1 - x);
+}
+
+function easeInOutQuad(x: number): number {
+    return x < 0.5 ? 2 * x * x : 1 - pow(-2 * x + 2, 2) / 2;
+}
+
+
+function easeInCubic(x: number): number {
+    return x * x * x;
+}
+
+function easeOutCubic(x: number): number {
+    return 1 - pow(1 - x, 3);
+}
+
+function easeInOutCubic(x: number): number {
+    return x < 0.5 ? 4 * x * x * x : 1 - pow(-2 * x + 2, 3) / 2;
+}
+
+
+function easeInQuart(x: number): number {
+    return x * x * x * x;
+}
+
+function easeOutQuart(x: number): number {
+    return 1 - pow(1 - x, 4);
+}
+
+function easeInOutQuart(x: number): number {
+    return x < 0.5 ? 8 * x * x * x * x : 1 - pow(-2 * x + 2, 4) / 2;
+}
+
+
+function easeInQuint(x: number): number {
+    return x * x * x * x * x;
+}
+
+function easeOutQuint(x: number): number {
+    return 1 - pow(1 - x, 5);
+}
+
+function easeInOutQuint(x: number): number {
+    return x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5) / 2;
+}
+
+
+function easeInExpo(x: number): number {
+    return x === 0 ? 0 : pow(2, 10 * x - 10);
+}
+
+function easeOutExpo(x: number): number {
+    return x === 1 ? 1 : 1 - pow(2, -10 * x);
+}
+
+function easeInOutExpo(x: number): number {
+    return x === 0
+      ? 0
+      : x === 1
+      ? 1
+      : x < 0.5 ? pow(2, 20 * x - 10) / 2
+      : (2 - pow(2, -20 * x + 10)) / 2;
+}
+
+
+function easeInCirc(x: number): number {
+    return 1 - sqrt(1 - pow(x, 2));
+}
+
+function easeOutCirc(x: number): number {
+    return sqrt(1 - pow(x - 1, 2));
+}
+
+function easeInOutCirc(x: number): number {
+    return x < 0.5
+      ? (1 - sqrt(1 - pow(2 * x, 2))) / 2
+      : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2;
+}
+
+
+function easeInBack(x: number): number {
+    const c1 = 1.70158;
+    const c3 = c1 + 1;
+
+    return c3 * x * x * x - c1 * x * x;
+}
+
+function easeOutBack(x: number): number {
+    const c1 = 1.70158;
+    const c3 = c1 + 1;
+
+    return 1 + c3 * pow(x - 1, 3) + c1 * pow(x - 1, 2);
+}
+
+function easeInOutBack(x: number): number {
+    const c1 = 1.70158;
+    const c2 = c1 * 1.525;
+
+    return x < 0.5
+      ? (pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2
+      : (pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
+}
+
+
+function easeInElastic(x: number): number {
+    const c4 = (2 * Math.PI) / 3;
+
+    return x === 0
+      ? 0
+      : x === 1
+      ? 1
+      : -pow(2, 10 * x - 10) * sin((x * 10 - 10.75) * c4);
+}
+
+function easeOutElastic(x: number): number {
+    const c4 = (2 * Math.PI) / 3;
+
+    return x === 0
+      ? 0
+      : x === 1
+      ? 1
+      : pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1;
+}
+
+function easeInOutElastic(x: number): number {
+    const c5 = (2 * Math.PI) / 4.5;
+
+    return x === 0
+      ? 0
+      : x === 1
+      ? 1
+      : x < 0.5
+      ? -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c5)) / 2
+      : (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c5)) / 2 + 1;
+
+}
+
+
+function easeInBounce(x: number): number {
+    return 1 - easeOutBounce(1 - x);
+}
+
+function easeOutBounce(x: number): number {
+    const n1 = 7.5625;
+    const d1 = 2.75;
+
+    if (x < 1 / d1) {
+        return n1 * x * x;
+    } else if (x < 2 / d1) {
+        return n1 * (x -= 1.5 / d1) * x + 0.75;
+    } else if (x < 2.5 / d1) {
+        return n1 * (x -= 2.25 / d1) * x + 0.9375;
+    } else {
+        return n1 * (x -= 2.625 / d1) * x + 0.984375;
+    }
+}
+
+function easeInOutBounce(x: number): number {
+    return x < 0.5
+      ? (1 - easeOutBounce(1 - 2 * x)) / 2
+      : (1 + easeOutBounce(2 * x - 1)) / 2;
+}
+
+
+const funcs = [
+    easeInSine, easeOutSine, easeInOutSine, easeInQuad, easeOutQuad, easeInOutQuad,
+    easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart,
+    easeInQuint, easeOutQuint, easeInOutQuint, easeInExpo, easeOutExpo, easeInOutExpo,
+    easeInCirc, easeOutCirc, easeInOutCirc, easeInBack, easeOutBack, easeInOutBack,
+    easeInElastic, easeOutElastic, easeInOutElastic, easeInBounce, easeOutBounce, easeInOutBounce
+];
+
+const points = [
+    0.0,
+    0.05,
+    0.1,
+    0.15,
+    0.2,
+    0.25,
+    0.3,
+    0.35,
+    0.4,
+    0.45,
+    0.5,
+    0.55,
+    0.6,
+    0.65,
+    0.7,
+    0.75,
+    0.8,
+    0.85,
+    0.9,
+    0.95,
+    1.0
+];
+
+let PI = Math.PI;
+let cos = Math.cos;
+let sin = Math.sin;
+let pow = Math.pow;
+let sqrt = Math.sqrt;
+
+for (func of funcs) {
+    console.log(func.name);
+    let values = [];
+    for (point of points) {
+        values.push(func(point));
+    }
+    console.log(values);
+}