From 39dfa75fe7a17faaa378ffa0af553b0f4dbbca2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 17 Jul 2024 17:22:43 +0530 Subject: [PATCH] Add test for steps easing function value calculation --- kitty/animation.c | 35 ++++++++++++++++++++++++++++++----- kitty/data-types.c | 2 ++ kitty/options/to-c.h | 6 +++--- kitty_tests/options.py | 19 ++++++++++++++++++- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/kitty/animation.c b/kitty/animation.c index dbb2709e5..8f8a40e26 100644 --- a/kitty/animation.c +++ b/kitty/animation.c @@ -41,6 +41,7 @@ typedef struct Animation { #include "animation.h" +#include "state.h" Animation* alloc_animation(void) { @@ -178,6 +179,8 @@ apply_easing_curve(const Animation *a, double val, monotonic_t duration) { if (!a->count) return val; size_t idx = MIN((size_t)(val * a->count), a->count - 1); animation_function *f = a->functions + idx; + double interval_size = 1. / a->count, interval_start = val - idx * interval_size; + val = (val - interval_start) / interval_size; double ans = f->curve(&f->params, val, duration); return f->y_at_start + unit_value(ans) * f->y_size; } @@ -244,13 +247,10 @@ add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t cou double jump_size = 1. / count, start_value = 0.; size_t num_of_buckets = count; switch (step) { - case EASING_STEP_START: - start_value = jump_size; - num_of_buckets--; - break; + case EASING_STEP_START: start_value = jump_size; break; case EASING_STEP_END: break; case EASING_STEP_NONE: - num_of_buckets--; + jump_size = 1. / (num_of_buckets - 1); break; case EASING_STEP_BOTH: num_of_buckets++; @@ -264,3 +264,28 @@ add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t cou animation_function *f = init_function(a, y_at_start, y_at_end, step_easing_curve); f->params = p; } + +static PyObject* +test_cursor_blink_easing_function(PyObject *self UNUSED, PyObject *args) { + Animation *a = OPT(animation.cursor); + if (!animation_is_valid(a)) { + PyErr_SetString(PyExc_RuntimeError, "must set a cursor blink animation on the global options object first"); + return NULL; + } + double t, duration_s = 0.5; + if (!PyArg_ParseTuple(args, "d|d", &t, &duration_s)) return NULL; + monotonic_t duration = s_double_to_monotonic_t(duration_s); + animation_function f = a->functions[0]; + return PyFloat_FromDouble(f.curve(f.params, t, duration)); +} + +static PyMethodDef module_methods[] = { + METHODB(test_cursor_blink_easing_function, METH_VARARGS), + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +bool init_animations(PyObject *module) { + if (PyModule_AddFunctions(module, module_methods) != 0) return false; + return true; +} diff --git a/kitty/data-types.c b/kitty/data-types.c index 64e3dbfb8..f23ebb0bd 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -488,6 +488,7 @@ extern bool init_child_monitor(PyObject *); extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); extern int init_Screen(PyObject *); +extern bool init_animations(PyObject*); extern bool init_fontconfig_library(PyObject*); extern bool init_crypto_library(PyObject*); extern bool init_desktop(PyObject*); @@ -573,6 +574,7 @@ PyInit_fast_data_types(void) { if (!init_loop_utils(m)) return NULL; if (!init_crypto_library(m)) return NULL; if (!init_systemd_module(m)) return NULL; + if (!init_animations(m)) return NULL; CellAttrs a; #define s(name, attr) { a.val = 0; a.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); } diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 1fc205551..9684f2a12 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -139,7 +139,7 @@ add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_en if (x) { double *y = x + count; for (size_t i = 0; i < count; i++) { - x[i] = D(linear_x, i); y[i] = D(y, i); + x[i] = D(linear_x, i); y[i] = D(linear_y, i); } add_linear_animation(a, y_at_start, y_at_end, count, x, y); } @@ -158,9 +158,9 @@ add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_en static inline void cursor_blink_interval(PyObject *src, Options *opts) { - free_animation(opts->animation.cursor); opts->cursor_blink_interval = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(src, 0)); - if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 1))) { + free_animation(opts->animation.cursor); + if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 1)) && (opts->animation.cursor = alloc_animation()) != NULL) { add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 1), 1, 0); if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 2))) { add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 2), 0, 1); diff --git a/kitty_tests/options.py b/kitty_tests/options.py index 0822e3a21..5a7700cce 100644 --- a/kitty_tests/options.py +++ b/kitty_tests/options.py @@ -2,7 +2,7 @@ # License: GPL v3 Copyright: 2018, Kovid Goyal -from kitty.fast_data_types import Color +from kitty.fast_data_types import Color, test_cursor_blink_easing_function from kitty.options.utils import DELETE_ENV_VAR, EasingFunction from kitty.utils import log_error @@ -155,3 +155,20 @@ class TestConfParsing(BaseTest): cb('linear(0, 0.25, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.5, 1.0), linear_y=(0, 0.25, 1.0))) cb('linear(0, 0.25 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.75, 1.0), linear_y=(0, 0.25, 1.0))) cb('linear(0, 0.25 25% 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.25, 0.75, 1.0), linear_y=(0, 0.25, 0.25, 1.0))) + + # test that easing functions give expected values + def ef(spec, tests, duration=0.5): + cfv = p('cursor_blink_interval ' + spec).cursor_blink_interval + self.set_options({'cursor_blink_interval': cfv}) + for t, expected in tests.items(): + actual = test_cursor_blink_easing_function(t, duration) + self.ae(expected, actual, f'Failed for {spec=} with {t=}: {expected} != {actual}') + + ef('linear(0, 0.25 25% 75%, 1)', {0: 0, 0.25: 0.25, 0.3: 0.25, 0.75: 0.25, 1:1}) + for spec in ('linear', 'linear(0, 1)'): + ef(spec, {0: 0, 1: 1, 0.1234: 0.1234, 0.6453: 0.6453}) + + ef('steps(5)', {0: 0, 0.1: 0, 0.3: 0.2, 0.9:0.8}) + ef('steps(5, start)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1}) + ef('steps(4, jump-both)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1}) + ef('steps(6, jump-none)', {0: 0, 0.1: 0.0, 0.3: 0.2, 0.9:1})