Allow controlling the easing curves used for the visual bell

This commit is contained in:
Kovid Goyal 2024-07-19 11:35:00 +05:30
parent 3cf07e27a1
commit 43769bc3e0
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
12 changed files with 70 additions and 41 deletions

View file

@ -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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -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:

View file

@ -18,6 +18,7 @@ typedef enum {
FONTCONFIG_CLEANUP_FUNC,
FREETYPE_CLEANUP_FUNC,
SYSTEMD_CLEANUP_FUNC,
SHADERS_CLEANUP_FUNC,
NUM_CLEANUP_FUNCS
} AtExitCleanupFunc;

View file

@ -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 <https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function>`.
'''
)

View file

@ -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)

View file

@ -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

View file

@ -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");

View file

@ -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

View file

@ -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)

View file

@ -7,6 +7,7 @@
#include "fonts.h"
#include "gl.h"
#include "cleanup.h"
#include "colors.h"
#include <stddef.h>
#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;
}
// }}}

View file

@ -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

View file

@ -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 {