mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-26 18:22:09 +02:00
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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user