Implement proper unit bezier easing function

Code based on WebKit
https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/UnitBezier.h
This commit is contained in:
Kovid Goyal
2024-07-17 10:06:18 +05:30
parent f090c9a895
commit fc13b06b35
8 changed files with 308 additions and 66 deletions

View File

@@ -8,16 +8,27 @@
#include "data-types.h" #include "data-types.h"
#define ANIMATION_INTERNAL_API #define ANIMATION_INTERNAL_API
typedef struct easing_curve_parameters { typedef struct LinearParameters {
size_t count; size_t count;
double extra0, extra1, extra2, extra3; double buf[];
const double *params, *positions; } LinearParameters;
} easing_curve_parameters;
typedef double(*easing_curve)(easing_curve_parameters*, double); typedef struct StepsParameters {
size_t num_of_buckets;
double jump_size, start_value;
} StepsParameters;
static const double bezier_epsilon = 1e-7;
static const int max_newton_iterations = 4;
typedef struct BezierParameters {
double ax, bx, cx, ay, by, cy, start_gradient, end_gradient, spline_samples[11];
} BezierParameters;
typedef double(*easing_curve)(void*, double, monotonic_t);
typedef struct animation_function { typedef struct animation_function {
easing_curve_parameters params; void *params;
easing_curve curve; easing_curve curve;
double y_at_start, y_size; double y_at_start, y_size;
} animation_function; } animation_function;
@@ -42,90 +53,194 @@ animation_is_valid(const Animation* a) { return a != NULL && a->count > 0; }
Animation* Animation*
free_animation(Animation *a) { free_animation(Animation *a) {
if (a) { if (a) {
for (size_t i = 0; i < a->count; i++) free((void*)a->functions[i].params.params); for (size_t i = 0; i < a->count; i++) free(a->functions[i].params);
free(a->functions); free(a->functions);
free(a); free(a);
} }
return NULL; return NULL;
} }
static double static double
unit_value(double x) { return MAX(0., MIN(x, 1.)); } unit_value(double x) { return MAX(0., MIN(x, 1.)); }
static double static double
linear_easing_curve(easing_curve_parameters *p, double val) { linear_easing_curve(void *p_, double val, monotonic_t duration UNUSED) {
LinearParameters *p = p_;
double start_pos = 0, stop_pos = 1, start_val = 0, stop_val = 1; double start_pos = 0, stop_pos = 1, start_val = 0, stop_val = 1;
double *x = p->buf, *y = p->buf + p->count;
for (size_t i = 0; i < p->count; i++) { for (size_t i = 0; i < p->count; i++) {
if (p->positions[i] >= val) { if (x[i] >= val) {
stop_pos = p->positions[i]; stop_pos = x[i];
stop_val = p->params[i]; stop_val = y[i];
if (i > 0) { if (i > 0) {
start_val = p->params[i-1]; start_val = y[i-1];
start_pos = p->positions[i-1]; start_pos = x[i-1];
} }
break; break;
} }
} }
double frac = (val - start_pos) / (stop_pos - start_pos); if (stop_pos > start_pos) {
return start_val + frac * (stop_val - start_val); double frac = (val - start_pos) / (stop_pos - start_pos);
return start_val + frac * (stop_val - start_val);
}
return stop_val;
}
// Cubic Bezier {{{
static double
sample_curve_x(const BezierParameters *p, double t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((p->ax * t + p->bx) * t + p->cx) * t;
} }
static double static double
cubic_bezier_easing_curve(easing_curve_parameters *p, double t) { sample_curve_y(const BezierParameters *p, double t) {
const double u = 1. - t, uu = u * u, uuu = uu * u, tt = t * t, ttt = tt * t; return ((p->ay * t + p->by) * t + p->cy) * t;
// p0 is start, p3 is end. p1, p2 are control points
return uuu * p->extra0 + 3 * uu * t * p->extra1 + 3 * u * tt * p->extra2 + ttt * p->extra3;
} }
static double static double
step_easing_curve(easing_curve_parameters *p, double t) { sample_derivative_x(const BezierParameters *p, double t) {
double num_of_buckets = p->extra0, start_value = p->extra2, jump_size = p->extra1; return (3.0 * p->ax * t + 2.0 * p->bx) * t + p->cx;
size_t val_bucket = (size_t)(t * num_of_buckets);
return start_value + val_bucket * jump_size;
} }
static double
solve_curve_x(const BezierParameters *p, double x, double epsilon) {
// Given an x value, find a parametric value it came from.
double t0 = 0.0, t1 = 0.0, t2 = x, x2 = 0.0, d2 = 0.0;
// Linear interpolation of spline curve for initial guess.
static const size_t num_samples = arraysz(p->spline_samples);
double delta = 1.0 / (num_samples - 1);
for (size_t i = 1; i < num_samples; i++) {
if (x <= p->spline_samples[i]) {
t1 = delta * i;
t0 = t1 - delta;
t2 = t0 + (t1 - t0) * (x - p->spline_samples[i - 1]) / (p->spline_samples[i] - p->spline_samples[i - 1]);
break;
}
}
// Perform a few iterations of Newton's method -- normally very fast.
// See https://en.wikipedia.org/wiki/Newton%27s_method.
double newton_epsilon = MIN(bezier_epsilon, epsilon);
for (size_t i = 0; i < max_newton_iterations; i++) {
x2 = sample_curve_x(p, t2) - x;
if (fabs(x2) < newton_epsilon) return t2;
d2 = sample_derivative_x(p, t2);
if (fabs(d2) < bezier_epsilon) break;
t2 = t2 - x2 / d2;
}
if (fabs(x2) < epsilon) return t2;
// Fall back to the bisection method for reliability.
while (t0 < t1) {
x2 = sample_curve_x(p, t2);
if (fabs(x2 - x) < epsilon) return t2;
if (x > x2) t0 = t2;
else t1 = t2;
t2 = (t1 + t0) * .5;
}
// Failure.
return t2;
}
static double
solve_unit_bezier(const BezierParameters *p, double x, double epsilon) {
if (x < 0.0) return 0.0 + p->start_gradient * x;
if (x > 1.0) return 1.0 + p->end_gradient * (x - 1.0);
return sample_curve_y(p, solve_curve_x(p, x, epsilon));
}
static double
cubic_bezier_easing_curve(void *p_, double t, monotonic_t duration) {
BezierParameters *p = p_;
// The longer the animation, the more precision we need
double epsilon = 1.0 / monotonic_t_to_ms(duration);
return solve_unit_bezier(p, t, epsilon);
}
// }}}
static double
step_easing_curve(void *p_, double t, monotonic_t duration UNUSED) {
StepsParameters *p = p_;
size_t val_bucket = (size_t)(t * p->num_of_buckets);
return p->start_value + val_bucket * p->jump_size;
}
static double
identity_easing_curve(void *p_ UNUSED, double t, monotonic_t duration UNUSED) { return t; }
double double
apply_easing_curve(const Animation *a, double val) { apply_easing_curve(const Animation *a, double val, monotonic_t duration) {
val = unit_value(val); val = unit_value(val);
if (!a->count) return val; if (!a->count) return val;
size_t idx = MIN((size_t)(val * a->count), a->count - 1); size_t idx = MIN((size_t)(val * a->count), a->count - 1);
animation_function *f = a->functions + idx; animation_function *f = a->functions + idx;
double ans = f->curve(&f->params, val); double ans = f->curve(&f->params, val, duration);
return f->y_at_start + unit_value(ans) * f->y_size; return f->y_at_start + unit_value(ans) * f->y_size;
} }
static animation_function* static animation_function*
init_function(Animation *a, double y_at_start, double y_at_end, easing_curve curve, size_t count) { init_function(Animation *a, double y_at_start, double y_at_end, easing_curve curve) {
ensure_space_for(a, functions, animation_function, a->count + 1, capacity, 4, false); ensure_space_for(a, functions, animation_function, a->count + 1, capacity, 4, false);
animation_function *f = a->functions + a->count++; animation_function *f = a->functions + a->count++;
zero_at_ptr(f); zero_at_ptr(f);
f->y_at_start = y_at_start; f->y_size = y_at_end - y_at_start; f->curve = curve; f->y_at_start = y_at_start; f->y_size = y_at_end - y_at_start; f->curve = curve;
if (count) {
double *p = calloc(count*2, sizeof(double));
if (!p) fatal("Out of memory");
f->params.params = p;
f->params.positions = p + count;
f->params.count = 0;
}
return f; return f;
} }
void void
add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double start, double p1, double p2, double end) { add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double p1x, double p1y, double p2x, double p2y) {
animation_function *f = init_function(a, y_at_start, y_at_end, cubic_bezier_easing_curve, 4); p1x = unit_value(p1x); p2x = unit_value(p2x);
f->params.extra0 = start; f->params.extra1 = p1; f->params.extra2 = p2; f->params.extra3 = end; if (p1x == 0 && p1y == 0 && p2x == 1 && p2y == 1) {
init_function(a, y_at_start, y_at_end, identity_easing_curve);
return;
}
BezierParameters *p = calloc(1, sizeof(BezierParameters));
if (!p) fatal("Out of memory");
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
p->cx = 3.0 * p1x;
p->bx = 3.0 * (p2x - p1x) - p->cx;
p->ax = 1.0 - p->cx - p->bx;
p->cy = 3.0 * p1y;
p->by = 3.0 * (p2y - p1y) - p->cy;
p->ay = 1.0 - p->cy - p->by;
// Calculate gradients used for values outside the unit interval
if (p1x > 0) p->start_gradient = p1y / p1x;
else if (p1y == 0 && p2x > 0) p->start_gradient = p2y / p2x;
else if (p1y == 0 && p2y == 0) p->start_gradient = 1;
else p->start_gradient = 0;
if (p2x < 1) p->end_gradient = (p2y - 1) / (p2x - 1);
else if (p2y == 1 && p1x < 1) p->end_gradient = (p1y - 1) / (p1x - 1);
else if (p2y == 1 && p1y == 1) p->end_gradient = 1;
else p->end_gradient = 0;
size_t num_samples = arraysz(p->spline_samples);
double delta = 1. / num_samples;
for (size_t i = 0; i < num_samples; i++) p->spline_samples[i] = sample_curve_x(p, i * delta);
animation_function *f = init_function(a, y_at_start, y_at_end, cubic_bezier_easing_curve);
f->params = p;
} }
void void
add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *params, const double *positions) { add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *x, const double *y) {
animation_function *f = init_function(a, y_at_start, y_at_end, linear_easing_curve, count);
const size_t sz = count * sizeof(double); const size_t sz = count * sizeof(double);
memcpy((void*)f->params.params, params, sz); memcpy((void*)f->params.positions, positions, sz); LinearParameters *p = calloc(1, sizeof(LinearParameters) + 2 * sz);
if (!p) fatal("Out of memory");
p->count = count;
double *px = p->buf, *py = px + count;
memcpy(px, x, sz); memcpy(py, y, sz);
animation_function *f = init_function(a, y_at_start, y_at_end, linear_easing_curve);
f->params = p;
} }
void void
add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step) { add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step) {
animation_function *f = init_function(a, y_at_start, y_at_end, step_easing_curve, 0);
double jump_size = 1. / count, start_value = 0.; double jump_size = 1. / count, start_value = 0.;
size_t num_of_buckets = count; size_t num_of_buckets = count;
switch (step) { switch (step) {
@@ -143,5 +258,9 @@ add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t cou
start_value = jump_size; start_value = jump_size;
break; break;
} }
f->params.extra0 = num_of_buckets; f->params.extra1 = jump_size; f->params.extra2 = start_value; StepsParameters *p = malloc(sizeof(StepsParameters));
if (!p) fatal("Out of memory");
p->num_of_buckets = num_of_buckets; p->jump_size = jump_size; p->start_value = start_value;
animation_function *f = init_function(a, y_at_start, y_at_end, step_easing_curve);
f->params = p;
} }

View File

@@ -9,15 +9,16 @@
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#include "monotonic.h"
typedef enum { EASING_STEP_START, EASING_STEP_END, EASING_STEP_NONE, EASING_STEP_BOTH } EasingStep; typedef enum { EASING_STEP_START, EASING_STEP_END, EASING_STEP_NONE, EASING_STEP_BOTH } EasingStep;
#ifndef ANIMATION_INTERNAL_API #ifndef ANIMATION_INTERNAL_API
typedef struct {int x;} *Animation; typedef struct {int x;} *Animation;
#endif #endif
Animation* alloc_animation(void); Animation* alloc_animation(void);
double apply_easing_curve(const Animation *a, double t /* must be between 0 and 1*/); double apply_easing_curve(const Animation *a, double t /* must be between 0 and 1*/, monotonic_t duration);
bool animation_is_valid(const Animation *a); bool animation_is_valid(const Animation *a);
void add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double start, double p1, double p2, double end); void add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double p1_x, double p1_y, double p2_x, double p2_y);
void add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *params, const double *positions); void add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *x, const double *y);
void add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step); void add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step);
Animation* free_animation(Animation *a); Animation* free_animation(Animation *a);

View File

@@ -679,7 +679,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
monotonic_t den = OPT(cursor_blink_interval) * 2; monotonic_t den = OPT(cursor_blink_interval) * 2;
monotonic_t time_into_cycle = time_since_start_blink % den; monotonic_t time_into_cycle = time_since_start_blink % den;
double frac_into_cycle = (double)time_into_cycle / (double)den; double frac_into_cycle = (double)time_into_cycle / (double)den;
ans->opacity = (float)apply_easing_curve(OPT(animation.cursor), frac_into_cycle); ans->opacity = (float)apply_easing_curve(OPT(animation.cursor), frac_into_cycle, den);
set_maximum_wait(ms_to_monotonic_t(75)); set_maximum_wait(ms_to_monotonic_t(75));
} else { } else {
monotonic_t n = time_since_start_blink / OPT(cursor_blink_interval); monotonic_t n = time_since_start_blink / OPT(cursor_blink_interval);

View File

@@ -56,6 +56,10 @@ def positive_float(x: ConvertibleToNumbers) -> float:
return max(0, float(x)) return max(0, float(x))
def percent(x: str) -> float:
return float(x.rstrip('%')) / 100.
def to_color(x: str) -> Color: def to_color(x: str) -> Color:
ans = as_color(x, validate=True) ans = as_color(x, validate=True)
if ans is None: # this is only for type-checking if ans is None: # this is only for type-checking

View File

@@ -6,13 +6,11 @@
*/ */
#include "state.h" #include "state.h"
#include "screen.h"
#include "charsets.h" #include "charsets.h"
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include "glfw-wrapper.h" #include "glfw-wrapper.h"
#include "control-codes.h" #include "control-codes.h"
#include "monotonic.h"
extern PyTypeObject Screen_Type; extern PyTypeObject Screen_Type;

View File

@@ -127,25 +127,31 @@ static void
add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_end) { add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_end) {
#define G(name) RAII_PyObject(name, PyObject_GetAttrString(e, #name)) #define G(name) RAII_PyObject(name, PyObject_GetAttrString(e, #name))
#define D(container, idx) PyFloat_AsDouble(PyTuple_GET_ITEM(container, idx)) #define D(container, idx) PyFloat_AsDouble(PyTuple_GET_ITEM(container, idx))
#define EQ(x, val) (PyUnicode_CompareWithASCIIString((x), val) == 0)
G(type); G(type);
if (PyUnicode_CompareWithASCIIString(type, "cubic-bezier")) { if (EQ(type, "cubic-bezier")) {
G(cubic_bezier_points); G(cubic_bezier_points);
add_cubic_bezier_animation(a, y_at_start, y_at_end, D(cubic_bezier_points, 0), D(cubic_bezier_points, 1), D(cubic_bezier_points, 2), D(cubic_bezier_points, 3)); add_cubic_bezier_animation(a, y_at_start, y_at_end, D(cubic_bezier_points, 0), D(cubic_bezier_points, 1), D(cubic_bezier_points, 2), D(cubic_bezier_points, 3));
} else if (PyUnicode_CompareWithASCIIString(type, "linear")) { } else if (EQ(type, "linear")) {
G(linear_count); G(linear_params); G(linear_positions); G(linear_x); G(linear_y);
size_t count = PyLong_AsSize_t(linear_count); size_t count = PyTuple_GET_SIZE(linear_x);
RAII_ALLOC(double, params, malloc(2 * sizeof(double) * count)); RAII_ALLOC(double, x, malloc(2 * sizeof(double) * count));
if (params) { if (x) {
double *positions = params + count; double *y = x + count;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
params[i] = D(linear_params, i); positions[i] = D(linear_positions, i); x[i] = D(linear_x, i); y[i] = D(y, i);
} }
add_linear_animation(a, y_at_start, y_at_end, count, params, positions); add_linear_animation(a, y_at_start, y_at_end, count, x, y);
} }
} else if (PyUnicode_CompareWithASCIIString(type, "steps")) { } else if (EQ(type, "steps")) {
G(num_steps); G(jump_type); G(num_steps); G(jump_type);
add_steps_animation(a, y_at_start, y_at_end, PyLong_AsSize_t(num_steps), PyLong_AsLong(jump_type)); EasingStep jt = EASING_STEP_END;
if (EQ(jump_type, "start")) jt = EASING_STEP_START;
else if (EQ(jump_type, "none")) jt = EASING_STEP_NONE;
else if (EQ(jump_type, "both")) jt = EASING_STEP_BOTH;
add_steps_animation(a, y_at_start, y_at_end, PyLong_AsSize_t(num_steps), jt);
} }
#undef EQ
#undef D #undef D
#undef G #undef G
} }

View File

@@ -6,6 +6,7 @@ import enum
import re import re
import sys import sys
from collections import defaultdict from collections import defaultdict
from contextlib import suppress
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
from functools import lru_cache from functools import lru_cache
from typing import ( from typing import (
@@ -34,6 +35,7 @@ from kitty.conf.utils import (
KeyAction, KeyAction,
KeyFuncWrapper, KeyFuncWrapper,
currently_parsing, currently_parsing,
percent,
positive_float, positive_float,
positive_int, positive_int,
python_string, python_string,
@@ -1393,14 +1395,13 @@ def parse_font_spec(spec: str) -> FontSpec:
class EasingFunction(NamedTuple): class EasingFunction(NamedTuple):
type: str = '' type: Literal['steps', 'linear', 'cubic-bezier', ''] = ''
num_steps: int = 0 num_steps: int = 0
jump_type: int = 0 jump_type: Literal['start', 'end', 'none', 'both'] = 'end'
linear_count: int = 0 linear_x: Tuple[float, ...] = ()
linear_params: Tuple[float, ...] = () linear_y: Tuple[float, ...] = ()
linear_positions: Tuple[float, ...] = ()
cubic_bezier_points: Tuple[float, ...] = () cubic_bezier_points: Tuple[float, ...] = ()
@@ -1411,13 +1412,125 @@ class EasingFunction(NamedTuple):
def __bool__(self) -> bool: def __bool__(self) -> bool:
return bool(self.type) return bool(self.type)
@classmethod
def cubic_bezier(cls, params: str) -> 'EasingFunction':
parts = params.replace(',', ' ').split()
if len(parts) != 4:
raise ValueError('cubic-bezier easing function must have four points')
return cls(type='cubic-bezier', cubic_bezier_points=(
unit_float(parts[0]), float(parts[1]), unit_float(parts[2]), float(parts[2])))
@classmethod
def linear(cls, params: str) -> 'EasingFunction':
parts = params.split(',')
if len(parts) < 2:
raise ValueError('Must specify at least two points for the linear easing function')
xaxis: List[float] = []
yaxis: List[float] = []
def balance(end: float) -> None:
extra = len(yaxis) - len(xaxis)
if extra <= 0:
return
start = xaxis[-1] if xaxis else 0
delta = (end - start) / (extra + 1)
if delta <= 0:
raise ValueError(f'Linear easing curve must have strictly increasing points: {params} does not')
for i in range(extra):
xaxis.append((i+1) * delta)
def add_point(y: float, x: Optional[float] = None) -> None:
if x is None:
yaxis.append(y)
else:
x = unit_float(x)
balance(x)
xaxis.append(x)
yaxis.append(y)
for r in parts:
points = r.strip().split()
y = unit_float(points[0])
if len(points) == 1:
add_point(y)
elif len(points) == 2:
add_point(y, percent(points[1]))
elif len(points) == 3:
add_point(y, percent(points[1]))
add_point(y, percent(points[2]))
else:
raise ValueError(f'{r} has too many points for a linear easing curve parameter')
balance(1)
return cls(type='linear', linear_x=tuple(xaxis), linear_y=tuple(yaxis))
@classmethod
def steps(cls, params: str) -> 'EasingFunction':
parts = params.replace(',', ' ').split()
jump_type = 'end'
if len(parts) == 2:
n = int(parts[0])
jt = parts[1]
try:
jump_type = {
'jump-start': 'start', 'start': 'start', 'end': 'end', 'jump-end': 'end', 'jump-none': 'none', 'jump-both': 'both'
}[jt.lower()]
except KeyError:
raise KeyError(f'{jt} is not a valid jump type for a linear easing function')
if jump_type == 'none':
n = max(2, n)
else:
n = max(1, n)
else:
n = max(1, int(parts[0]))
return cls(type='steps', jump_type=jump_type, num_steps=n) # type: ignore
def cursor_blink_interval(spec: str) -> Tuple[float, EasingFunction, EasingFunction]: def cursor_blink_interval(spec: str) -> Tuple[float, EasingFunction, EasingFunction]:
try: interval: float = -1
with suppress(Exception):
interval = float(spec) interval = float(spec)
return interval, EasingFunction(), EasingFunction() return interval, EasingFunction(), EasingFunction()
except Exception:
return -1, EasingFunction(), EasingFunction() m = [EasingFunction(), EasingFunction()]
def parse_func(func_name: str, params: str) -> None:
idx = 1 if m[0] else 0
if m[idx]:
raise ValueError(f'{spec} specified more than two easing functions')
if func_name == 'cubic-bezier':
m[idx] = EasingFunction.cubic_bezier(params)
elif func_name == 'linear':
m[idx] = EasingFunction.linear(params)
elif func_name == 'steps':
m[idx] = EasingFunction.steps(params)
else:
raise KeyError(f'{func_name} is not a valid easing function')
for match in re.finditer(r'([-+.0-9a-zA-Z]+)(?:\(([^)]*)\)){0,1}', spec):
func_name, params = match.group(1, 2)
if params:
parse_func(func_name, params)
continue
with suppress(Exception):
interval = float(func_name)
continue
if func_name == 'ease-in-out':
parse_func('cubic-bezier', '0.42, 0, 0.58, 1')
elif func_name == 'linear':
parse_func('cubic-bezier', '0, 0, 1, 1')
elif func_name == 'ease':
parse_func('cubic-bezier', '0.25, 0.1, 0.25, 1')
elif func_name == 'ease-out':
parse_func('cubic-bezier', '0, 0, 0.58, 1')
elif func_name == 'ease-in':
parse_func('cubic-bezier', '0.42, 0, 1, 1')
elif func_name == 'step-start':
parse_func('steps', '1, start')
elif func_name == 'step-end':
parse_func('steps', '1, end')
else:
raise KeyError(f'{func_name} is not a valid easing function')
return interval, m[0], m[1]
def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None: def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None:

View File

@@ -5,6 +5,7 @@
*/ */
#pragma once #pragma once
#include "data-types.h"
#include "animation.h" #include "animation.h"
#include "screen.h" #include "screen.h"
#include "monotonic.h" #include "monotonic.h"