This commit is contained in:
Kovid Goyal 2024-10-18 10:52:34 +05:30
commit cbccda8061
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
16 changed files with 269 additions and 3 deletions

View file

@ -751,6 +751,10 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
WD.screen->cursor_render_info.is_focused = os_window->is_focused;
set_os_window_title_from_window(w, os_window);
*active_window_bg = window_bg;
if (OPT(enable_cursor_trail) && update_cursor_trail(&tab->cursor_trail, w, now, os_window)) {
needs_render = true;
set_maximum_wait(OPT(repaint_delay));
}
} else {
if (WD.screen->cursor_render_info.render_even_when_unfocused) {
if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true;
@ -808,6 +812,7 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co
w->cursor_opacity_at_last_render = WD.screen->cursor_render_info.opacity; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
}
}
if (OPT(enable_cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail);
if (os_window->live_resize.in_progress) draw_resizing_text(os_window);
swap_window_buffers(os_window);
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;

106
kitty/cursor_trail.c Normal file
View file

@ -0,0 +1,106 @@
#include "state.h"
inline static float
norm(float x, float y) {
return sqrtf(x * x + y * y);
}
inline static bool
get_cursor_edge(float *left, float *right, float *top, float *bottom, Window *w) {
#define WD w->render_data
*left = WD.xstart + WD.screen->cursor_render_info.x * WD.dx;
*bottom = WD.ystart - (WD.screen->cursor_render_info.y + 1) * WD.dy;
switch (WD.screen->cursor_render_info.shape) {
case CURSOR_BLOCK:
case CURSOR_HOLLOW:
*right = *left + WD.dx;
*top = *bottom + WD.dy;
return true;
case CURSOR_BEAM:
*right = *left + WD.dx / WD.screen->cell_size.width * OPT(cursor_beam_thickness);
*top = *bottom + WD.dy;
return true;
case CURSOR_UNDERLINE:
*right = *left + WD.dx;
*top = *bottom + WD.dy / WD.screen->cell_size.height * OPT(cursor_underline_thickness);
return true;
default:
return false;
}
}
bool
update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window) {
// the trail corners move towards the cursor corner at a speed proportional to their distance from the cursor corner.
// equivalent to exponential ease out animation.
static const int ci[4][2] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}};
const float dx_threshold = WD.dx / WD.screen->cell_size.width;
const float dy_threshold = WD.dy / WD.screen->cell_size.height;
bool needs_render_prev = ct->needs_render;
ct->needs_render = false;
#define EDGE(axis, index) ct->cursor_edge_##axis[index]
if (!WD.screen->paused_rendering.expires_at && !get_cursor_edge(&EDGE(x, 0), &EDGE(x, 1), &EDGE(y, 0), &EDGE(y, 1), w)) {
return needs_render_prev;
}
// the decay time for the trail to reach 1/1024 of its distance from the cursor corner
float decay_fast = OPT(cursor_trail_decay_fast);
float decay_slow = OPT(cursor_trail_decay_slow);
if (os_window->live_resize.in_progress) {
for (int i = 0; i < 4; ++i) {
ct->corner_x[i] = EDGE(x, ci[i][0]);
ct->corner_y[i] = EDGE(y, ci[i][1]);
}
}
else if (OPT(input_delay) < now - WD.screen->cursor->updated_at && ct->updated_at < now) {
float cursor_center_x = (EDGE(x, 0) + EDGE(x, 1)) * 0.5f;
float cursor_center_y = (EDGE(y, 0) + EDGE(y, 1)) * 0.5f;
float cursor_diag_2 = norm(EDGE(x, 1) - EDGE(x, 0), EDGE(y, 1) - EDGE(y, 0)) * 0.5f;
float dt = (float)monotonic_t_to_s_double(now - ct->updated_at);
for (int i = 0; i < 4; ++i) {
float dx = EDGE(x, ci[i][0]) - ct->corner_x[i];
float dy = EDGE(y, ci[i][1]) - ct->corner_y[i];
if (fabsf(dx) < dx_threshold && fabsf(dy) < dy_threshold) {
ct->corner_x[i] = EDGE(x, ci[i][0]);
ct->corner_y[i] = EDGE(y, ci[i][1]);
continue;
}
// Corner that is closer to the cursor moves faster.
// It creates dynamic effect that looks like the trail is being pulled towards the cursor.
float dot = (dx * (EDGE(x, ci[i][0]) - cursor_center_x) +
dy * (EDGE(y, ci[i][1]) - cursor_center_y)) /
cursor_diag_2 / norm(dx, dy);
float decay_seconds = decay_slow + (decay_fast - decay_slow) * (1.0f + dot) * 0.5f;
float step = 1.0f - 1.0f / exp2f(10.0f * dt / decay_seconds);
ct->corner_x[i] += dx * step;
ct->corner_y[i] += dy * step;
}
}
ct->updated_at = now;
for (int i = 0; i < 4; ++i) {
float dx = fabsf(EDGE(x, ci[i][0]) - ct->corner_x[i]);
float dy = fabsf(EDGE(y, ci[i][1]) - ct->corner_y[i]);
if (dx_threshold <= dx || dy_threshold <= dy) {
ct->needs_render = true;
break;
}
}
if (ct->needs_render) {
ColorProfile *cp = WD.screen->color_profile;
ct->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color).rgb;
}
#undef WD
#undef EDGE
// returning true here will cause the cells to be drawn
return ct->needs_render || needs_render_prev;
}

View file

@ -297,6 +297,7 @@ typedef struct {
PyObject_HEAD
bool bold, italic, reverse, strikethrough, dim, non_blinking;
monotonic_t updated_at;
unsigned int x, y;
uint8_t decoration;
CursorShape shape;

View file

@ -296,6 +296,7 @@ FC_WIDTH_NORMAL: int
FC_SLANT_ROMAN: int
FC_SLANT_ITALIC: int
BORDERS_PROGRAM: int
TRAIL_PROGRAM: int
PRESS: int
RELEASE: int
DRAG: int
@ -540,6 +541,8 @@ def add_borders_rect(
def init_borders_program() -> None:
pass
def init_trail_program() -> None:
pass
def os_window_has_background_image(os_window_id: int) -> bool:
pass

View file

@ -350,6 +350,28 @@
'''
)
opt('enable_cursor_trail', 'no',
option_type='to_bool', ctype='bool',
long_text='''
Enables or disables the cursor trail effect. When set to `yes`, a trailing
effect is rendered behind the cursor as it moves, creating a motion trail.
'''
)
opt('cursor_trail_decay', '0.1 0.3',
option_type='cursor_trail_decay',
ctype='!cursor_trail_decay',
long_text='''
Controls the decay times for the cursor trail effect when :code:`enable_cursor_trail`
is set to :code:`yes`. This option accepts two positive float values specifying the
fastest and slowest decay times in seconds. The first value corresponds to the
fastest decay time (minimum), and the second value corresponds to the slowest
decay time (maximum). The second value must be equal to or greater than the
first value. Smaller values result in a faster decay of the cursor trail.
Adjust these values to control how quickly the cursor trail fades away.
''',
)
egr() # }}}

View file

@ -10,7 +10,7 @@
action_alias, active_tab_title_template, allow_hyperlinks, bell_on_tab, box_drawing_scale,
clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, clone_source_strategies,
config_or_absolute_path, copy_on_select, cursor_blink_interval, cursor_text_color,
deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases,
cursor_trail_decay, deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases,
deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures,
edge_width, env, filter_notification, font_features, hide_window_decorations, macos_option_as_alt,
macos_titlebar_color, menu_map, modify_font, narrow_symbols, notify_on_cmd_finish,
@ -931,6 +931,9 @@ def cursor_stop_blinking_after(self, val: str, ans: typing.Dict[str, typing.Any]
def cursor_text_color(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['cursor_text_color'] = cursor_text_color(val)
def cursor_trail_decay(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['cursor_trail_decay'] = cursor_trail_decay(val)
def cursor_underline_thickness(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['cursor_underline_thickness'] = positive_float(val)
@ -963,6 +966,9 @@ def editor(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
def enable_audio_bell(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['enable_audio_bell'] = to_bool(val)
def enable_cursor_trail(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['enable_cursor_trail'] = to_bool(val)
def enabled_layouts(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['enabled_layouts'] = to_layout_names(val)

View file

@ -161,6 +161,32 @@ convert_from_opts_cursor_stop_blinking_after(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_enable_cursor_trail(PyObject *val, Options *opts) {
opts->enable_cursor_trail = PyObject_IsTrue(val);
}
static void
convert_from_opts_enable_cursor_trail(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "enable_cursor_trail");
if (ret == NULL) return;
convert_from_python_enable_cursor_trail(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_cursor_trail_decay(PyObject *val, Options *opts) {
cursor_trail_decay(val, opts);
}
static void
convert_from_opts_cursor_trail_decay(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_trail_decay");
if (ret == NULL) return;
convert_from_python_cursor_trail_decay(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollback_indicator_opacity(PyObject *val, Options *opts) {
opts->scrollback_indicator_opacity = PyFloat_AsFloat(val);
@ -1123,6 +1149,10 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_cursor_stop_blinking_after(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_enable_cursor_trail(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_cursor_trail_decay(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollback_indicator_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollback_pager_history_size(py_opts, opts);

View file

@ -180,6 +180,11 @@ visual_bell_duration(PyObject *src, Options *opts) {
#undef parse_animation
static inline void
cursor_trail_decay(PyObject *src, Options *opts) {
opts->cursor_trail_decay_fast = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 0));
opts->cursor_trail_decay_slow = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 1));
}
static void
parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) {

View file

@ -334,6 +334,7 @@
'cursor_shape_unfocused',
'cursor_stop_blinking_after',
'cursor_text_color',
'cursor_trail_decay',
'cursor_underline_thickness',
'default_pointer_shape',
'detect_urls',
@ -343,6 +344,7 @@
'dynamic_background_opacity',
'editor',
'enable_audio_bell',
'enable_cursor_trail',
'enabled_layouts',
'env',
'exe_search_path',
@ -510,6 +512,7 @@ class Options:
cursor_shape_unfocused: int = 4
cursor_stop_blinking_after: float = 15.0
cursor_text_color: typing.Optional[kitty.fast_data_types.Color] = Color(17, 17, 17)
cursor_trail_decay: typing.Tuple[float, float] = (0.1, 0.3)
cursor_underline_thickness: float = 2.0
default_pointer_shape: choices_for_default_pointer_shape = 'beam'
detect_urls: bool = True
@ -519,6 +522,7 @@ class Options:
dynamic_background_opacity: bool = False
editor: str = '.'
enable_audio_bell: bool = True
enable_cursor_trail: bool = False
enabled_layouts: typing.List[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical']
file_transfer_confirmation_bypass: str = ''
focus_follows_mouse: bool = False

View file

@ -558,6 +558,11 @@ def to_cursor_unfocused_shape(x: str) -> int:
)
)
def cursor_trail_decay(x: str) -> Tuple[float, float]:
fast, slow = map(positive_float, x.split())
slow = max(slow, fast)
return fast, slow
def scrollback_lines(x: str) -> int:
ans = int(x)

View file

@ -1765,6 +1765,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
line += self->margin_top;
line = MAX(self->margin_top, MIN(line, self->margin_bottom));
}
self->cursor->updated_at = monotonic();
self->cursor->x = column; self->cursor->y = line;
screen_ensure_bounds(self, false, in_margins);
}

View file

@ -18,7 +18,7 @@
#define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, NUM_PROGRAMS };
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, TRAIL_PROGRAM, NUM_PROGRAMS };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT };
// Sprites {{{
@ -1133,6 +1133,36 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
// }}}
// Cursor Trail {{{
typedef struct {
TrailUniforms uniforms;
} TrailProgramLayout;
static TrailProgramLayout trail_program_layout;
static void
init_trail_program(void) {
get_uniform_locations_trail(TRAIL_PROGRAM, &trail_program_layout.uniforms);
}
void
draw_cursor_trail(CursorTrail *trail) {
bind_program(TRAIL_PROGRAM);
glUniform4fv(trail_program_layout.uniforms.x_coords, 1, trail->corner_x);
glUniform4fv(trail_program_layout.uniforms.y_coords, 1, trail->corner_y);
glUniform2fv(trail_program_layout.uniforms.cursor_edge_x, 1, trail->cursor_edge_x);
glUniform2fv(trail_program_layout.uniforms.cursor_edge_y, 1, trail->cursor_edge_y);
color_vec3(trail_program_layout.uniforms.trail_color, trail->color);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
unbind_program();
}
// }}}
// Python API {{{
static bool
@ -1205,6 +1235,8 @@ NO_ARG(init_borders_program)
NO_ARG(init_cell_program)
NO_ARG(init_trail_program)
static PyObject*
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
unsigned int w, h;
@ -1229,6 +1261,7 @@ static PyMethodDef module_methods[] = {
MW(unbind_program, METH_NOARGS),
MW(init_borders_program, METH_NOARGS),
MW(init_cell_program, METH_NOARGS),
MW(init_trail_program, METH_NOARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
@ -1241,7 +1274,7 @@ finalize(void) {
bool
init_shaders(PyObject *module) {
#define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; }
C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM);
C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM);
C(GLSL_VERSION);
C(GL_VERSION);
C(GL_VENDOR);

View file

@ -27,9 +27,11 @@
REVERSE,
STRIKETHROUGH,
TINT_PROGRAM,
TRAIL_PROGRAM,
compile_program,
get_options,
init_cell_program,
init_trail_program,
)
@ -201,5 +203,8 @@ def resolve_graphics_fragment_defines(which: str, f: str) -> str:
program_for('tint').compile(TINT_PROGRAM, allow_recompile)
init_cell_program()
program_for('trail').compile(TRAIL_PROGRAM, allow_recompile)
init_trail_program()
load_shader_programs = LoadShaderPrograms()

View file

@ -46,6 +46,9 @@ typedef struct {
CursorShape cursor_shape, cursor_shape_unfocused;
float cursor_beam_thickness;
float cursor_underline_thickness;
bool enable_cursor_trail;
float cursor_trail_decay_fast;
float cursor_trail_decay_slow;
unsigned int url_style;
unsigned int scrollback_pager_history_size;
bool scrollback_fill_enlarged_window;
@ -212,11 +215,22 @@ typedef struct {
ssize_t vao_idx;
} BorderRects;
typedef struct {
bool needs_render;
monotonic_t updated_at;
color_type color;
float corner_x[4];
float corner_y[4];
float cursor_edge_x[2];
float cursor_edge_y[2];
} CursorTrail;
typedef struct {
id_type id;
unsigned int active_window, num_windows, capacity;
Window *windows;
BorderRects border_rects;
CursorTrail cursor_trail;
} Tab;
enum RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY };
@ -352,6 +366,8 @@ ssize_t create_border_vao(void);
bool send_cell_data_to_gpu(ssize_t, float, float, float, float, Screen *, OSWindow *);
void draw_cells(ssize_t, const WindowRenderData*, OSWindow *, bool, bool, bool, Window*);
void draw_centered_alpha_mask(OSWindow *w, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float);
void draw_cursor_trail(CursorTrail *trail);
bool update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window);
void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*);
void free_framebuffer(uint32_t*);

15
kitty/trail_fragment.glsl Normal file
View file

@ -0,0 +1,15 @@
uniform vec2 cursor_edge_x;
uniform vec2 cursor_edge_y;
uniform vec3 trail_color;
in vec2 frag_pos;
out vec4 final_color;
void main() {
if (cursor_edge_x[0] <= frag_pos.x && frag_pos.x <= cursor_edge_x[1] &&
cursor_edge_y[1] <= frag_pos.y && frag_pos.y <= cursor_edge_y[0]) {
discard;
} else {
final_color = vec4(trail_color, 1.0);
}
}

9
kitty/trail_vertex.glsl Normal file
View file

@ -0,0 +1,9 @@
uniform vec4 x_coords;
uniform vec4 y_coords;
out vec2 frag_pos;
void main() {
frag_pos = vec2(x_coords[gl_VertexID], y_coords[gl_VertexID]);
gl_Position = vec4(x_coords[gl_VertexID], y_coords[gl_VertexID], 1.0, 1.0);
}