From 43769bc3e01e04b8cc21814e5795972cca2da643 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 Jul 2024 11:35:00 +0530 Subject: [PATCH] Allow controlling the easing curves used for the visual bell --- docs/changelog.rst | 2 ++ docs/glossary.rst | 3 ++- kitty/cleanup.h | 1 + kitty/options/definition.py | 10 +++++++-- kitty/options/parse.py | 5 +++-- kitty/options/to-c-generated.h | 2 +- kitty/options/to-c.h | 31 ++++++++++++++++--------- kitty/options/types.py | 2 +- kitty/options/utils.py | 11 +++++++-- kitty/shaders.c | 41 +++++++++++++++++----------------- kitty/state.c | 1 + kitty/state.h | 2 +- 12 files changed, 70 insertions(+), 41 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index dab251b6d..bbfdbf56b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -96,6 +96,8 @@ Detailed list of changes - Add support for in-band window resize notifications (:iss:`7642`) +- Allow controlling the easing curves used for :opt:`visual_bell_duration` + 0.35.2 [2024-06-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/glossary.rst b/docs/glossary.rst index ac432ea4a..eae4f6f08 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -58,7 +58,8 @@ Glossary Commonly used easing functions are :code:`linear` for a constant rate animation and :code:`ease-in-out` for an animation that starts slow, becomes fast in the middle and ends slowly. These are used to control - various animations in kitty, such as :opt:`cursor_blink_interval`. + various animations in kitty, such as :opt:`cursor_blink_interval` and + :opt:`visual_bell_duration`. .. _env_vars: diff --git a/kitty/cleanup.h b/kitty/cleanup.h index 58ae26608..f5ac9f54e 100644 --- a/kitty/cleanup.h +++ b/kitty/cleanup.h @@ -18,6 +18,7 @@ typedef enum { FONTCONFIG_CLEANUP_FUNC, FREETYPE_CLEANUP_FUNC, SYSTEMD_CLEANUP_FUNC, + SHADERS_CLEANUP_FUNC, NUM_CLEANUP_FUNCS } AtExitCleanupFunc; diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 0007ad223..fba34b497 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -897,10 +897,16 @@ ) opt('visual_bell_duration', '0.0', - option_type='positive_float', ctype='time', + option_type='visual_bell_duration', ctype='!visual_bell_duration', long_text=''' The visual bell duration (in seconds). Flash the screen when a bell occurs for -the specified number of seconds. Set to zero to disable. +the specified number of seconds. Set to zero to disable. The flash is animated, fading +in and out over the specified duration. The :term:`easing function` used for the fading can be controlled. +For example, :code:`2.0 linear` will casuse the flash to fade in and out linearly. The default +if unspecified is to use :code:`ease-in-out` which fades slowly at the start, middle and end. +You can specify different easing functions for the fade-in and fade-out parts, like this: +:code:`2.0 ease-in linear`. kitty +supports all the :link:`CSS easing functions `. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 6c6c4bbfe..2ad54127d 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -19,7 +19,8 @@ tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size, to_layout_names, to_modifiers, url_prefixes, url_style, - visual_window_select_characters, window_border_width, window_logo_scale, window_size + visual_bell_duration, visual_window_select_characters, window_border_width, window_logo_scale, + window_size ) @@ -1353,7 +1354,7 @@ def visual_bell_color(self, val: str, ans: typing.Dict[str, typing.Any]) -> None ans['visual_bell_color'] = to_color_or_none(val) def visual_bell_duration(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['visual_bell_duration'] = positive_float(val) + ans['visual_bell_duration'] = visual_bell_duration(val) def visual_window_select_characters(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['visual_window_select_characters'] = visual_window_select_characters(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 308bb44da..a375696a3 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -488,7 +488,7 @@ convert_from_opts_enable_audio_bell(PyObject *py_opts, Options *opts) { static void convert_from_python_visual_bell_duration(PyObject *val, Options *opts) { - opts->visual_bell_duration = parse_s_double_to_monotonic_t(val); + visual_bell_duration(val, opts); } static void diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 55f5d48e7..1240ba678 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -156,20 +156,31 @@ add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_en #undef G } +#define parse_animation(duration, name, start, end) \ + opts->duration = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(src, 0)); \ + opts->animation.name = free_animation(opts->animation.name); \ + if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 1)) && (opts->animation.name = alloc_animation()) != NULL) { \ + add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 1), start, end); \ + if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 2))) { \ + add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 2), end, start); \ + } else { \ + add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 1), end, start); \ + } \ + } \ + static inline void cursor_blink_interval(PyObject *src, Options *opts) { - opts->cursor_blink_interval = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(src, 0)); - opts->animation.cursor = 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); - } else { - add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 1), 0, 1); - } - } + parse_animation(cursor_blink_interval, cursor, 1, 0); } +static inline void +visual_bell_duration(PyObject *src, Options *opts) { + parse_animation(visual_bell_duration, visual_bell, 0, 1); +} + +#undef parse_animation + + static void parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) { PyObject *mv = PyObject_GetAttrString(val, "mod_value"); diff --git a/kitty/options/types.py b/kitty/options/types.py index 03718792e..b1424f20e 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -612,7 +612,7 @@ class Options: url_prefixes: typing.Tuple[str, ...] = ('file', 'ftp', 'ftps', 'gemini', 'git', 'gopher', 'http', 'https', 'irc', 'ircs', 'kitty', 'mailto', 'news', 'sftp', 'ssh') url_style: int = 3 visual_bell_color: typing.Optional[kitty.fast_data_types.Color] = None - visual_bell_duration: float = 0 + visual_bell_duration: typing.Tuple[float, kitty.options.utils.EasingFunction, kitty.options.utils.EasingFunction] = (0.0, kitty.options.utils.EasingFunction(), kitty.options.utils.EasingFunction()) visual_window_select_characters: str = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ' wayland_enable_ime: bool = True wayland_titlebar_color: int = 0 diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 415c0f2d5..987c31bfb 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -1493,8 +1493,7 @@ def steps(cls, params: str) -> 'EasingFunction': return cls(type='steps', jump_type=jump_type, num_steps=n) -def cursor_blink_interval(spec: str) -> Tuple[float, EasingFunction, EasingFunction]: - interval: float = -1 +def parse_animation(spec: str, interval: float = -1.) -> Tuple[float, EasingFunction, EasingFunction]: with suppress(Exception): interval = float(spec) return interval, EasingFunction(), EasingFunction() @@ -1540,6 +1539,14 @@ def parse_func(func_name: str, params: str) -> None: return interval, m[0], m[1] +def cursor_blink_interval(spec: str) -> Tuple[float, EasingFunction, EasingFunction]: + return parse_animation(spec) + + +def visual_bell_duration(spec: str) -> Tuple[float, EasingFunction, EasingFunction]: + return parse_animation(spec, interval=0.) + + def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None: if not hasattr(deprecated_hide_window_decorations_aliases, key): setattr(deprecated_hide_window_decorations_aliases, key, True) diff --git a/kitty/shaders.c b/kitty/shaders.c index 4060fad2d..74d2c5448 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -7,6 +7,7 @@ #include "fonts.h" #include "gl.h" +#include "cleanup.h" #include "colors.h" #include #include "window_logo.h" @@ -973,31 +974,23 @@ send_cell_data_to_gpu(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat d return changed; } -static float -ease_out_cubic(float phase) { - return 1.0f - powf(1.0f - phase, 3.0f); -} - -static float -ease_in_out_cubic(float phase) { - return phase < 0.5f ? - 4.0f * powf(phase, 3.0f) : - 1.0f - powf(-2.0f * phase + 2.0f, 3.0f) / 2.0f; -} - -static float -visual_bell_intensity(float phase) { - static const float peak = 0.2f; - const float fade = 1.0f - peak; - return phase < peak ? ease_out_cubic(phase / peak) : ease_in_out_cubic((1.0f - phase) / fade); -} +static Animation *default_visual_bell_animation = NULL; static float get_visual_bell_intensity(Screen *screen) { if (screen->start_visual_bell_at > 0) { - monotonic_t progress = monotonic() - screen->start_visual_bell_at; - monotonic_t duration = OPT(visual_bell_duration); - if (progress <= duration) return visual_bell_intensity((float)progress / duration); + if (!default_visual_bell_animation) { + default_visual_bell_animation = alloc_animation(); + if (!default_visual_bell_animation) fatal("Out of memory"); + add_cubic_bezier_animation(default_visual_bell_animation, 0, 1, 0.42, 0, 0.58, 1); + add_cubic_bezier_animation(default_visual_bell_animation, 1, 0, 0.42, 0, 0.58, 1); + } + const monotonic_t progress = monotonic() - screen->start_visual_bell_at; + const monotonic_t duration = OPT(visual_bell_duration) / 2; + if (progress <= duration) { + Animation *a = animation_is_valid(OPT(animation.visual_bell)) ? OPT(animation.visual_bell) : default_visual_bell_animation; + return (float)apply_easing_curve(a, progress / (double)duration, duration); + } screen->start_visual_bell_at = 0; } return 0.0f; @@ -1244,6 +1237,11 @@ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; +static void +finalize(void) { + default_visual_bell_animation = free_animation(default_visual_bell_animation); +} + bool init_shaders(PyObject *module) { #define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; } @@ -1277,6 +1275,7 @@ init_shaders(PyObject *module) { #undef C if (PyModule_AddFunctions(module, module_methods) != 0) return false; + register_at_exit_cleanup_func(SHADERS_CLEANUP_FUNC, finalize); return true; } // }}} diff --git a/kitty/state.c b/kitty/state.c index c8f379c5b..f396c9a67 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1463,6 +1463,7 @@ finalize(void) { #undef F Py_CLEAR(options_object); free_animation(OPT(animation.cursor)); + free_animation(OPT(animation.visual_bell)); // we leak the texture here since it is not guaranteed // that freeing the texture will work during shutdown and // the GPU driver should take care of it when the OpenGL context is diff --git a/kitty/state.h b/kitty/state.h index 7df66e696..b8afcba79 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -119,7 +119,7 @@ typedef struct { hb_feature_t *features; } *entries; } font_features; - struct { Animation *cursor; } animation; + struct { Animation *cursor, *visual_bell; } animation; } Options; typedef struct WindowLogoRenderData {