From c3eb48fc8afad5284263786e01d83728ea1c3d8b Mon Sep 17 00:00:00 2001 From: Rick Choi Date: Sat, 12 Oct 2024 19:15:40 +0900 Subject: [PATCH] add cursor trail --- kitty/child-monitor.c | 60 ++++++++++++++++++++++++++++++++++++++- kitty/data-types.h | 1 + kitty/fast_data_types.pyi | 3 ++ kitty/screen.c | 1 + kitty/shaders.c | 42 +++++++++++++++++++++++++-- kitty/shaders.py | 5 ++++ kitty/state.h | 8 ++++++ kitty/trail_fragment.glsl | 6 ++++ kitty/trail_vertex.glsl | 6 ++++ 9 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 kitty/trail_fragment.glsl create mode 100644 kitty/trail_vertex.glsl diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 2824679f3..3113ba52e 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -705,6 +705,63 @@ change_menubar_title(PyObject *title UNUSED) { #endif } +inline static float +norm(float x, float y) { + return sqrtf(x * x + y * y); +} + +static bool +update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now) { +#define WD w->render_data + // 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}}; + float cursor_edge_x[2], cursor_edge_y[2]; + cursor_edge_x[0] = WD.xstart + WD.screen->cursor_render_info.x * WD.dx; + cursor_edge_x[1] = cursor_edge_x[0] + WD.dx; + cursor_edge_y[0] = WD.ystart - WD.screen->cursor_render_info.y * WD.dy; + cursor_edge_y[1] = cursor_edge_y[0] - WD.dy; + + // todo - make these configurable + // the decay time for the trail to reach 1/1024 of its distance from the cursor corner + float decay_fast = 0.10f; + float decay_slow = 0.40f; + + if (OPT(input_delay) < now - WD.screen->cursor->updated_at && ct->updated_at < now) { + float cursor_center_x = (cursor_edge_x[0] + cursor_edge_x[1]) * 0.5f; + float cursor_center_y = (cursor_edge_y[0] + cursor_edge_y[1]) * 0.5f; + float cursor_diag_2 = norm(cursor_edge_x[1] - cursor_edge_x[0], cursor_edge_y[1] - cursor_edge_y[0]) * 0.5; + float dt = monotonic_t_to_s_double(now - ct->updated_at); + + for (int i = 0; i < 4; ++i) { + float dx = cursor_edge_x[ci[i][0]] - ct->corner_x[i]; + float dy = cursor_edge_y[ci[i][1]] - ct->corner_y[i]; + float dist = norm(dx, dy); + if (dist == 0) { + continue; + } + float dot = (dx * (cursor_edge_x[ci[i][0]] - cursor_center_x) + + dy * (cursor_edge_y[ci[i][1]] - cursor_center_y)) / + cursor_diag_2 / dist; + + 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 = cursor_edge_x[ci[i][0]] - ct->corner_x[i]; + float dy = cursor_edge_y[ci[i][1]] - ct->corner_y[i]; + if (dx * dx + dy * dy >= 1e-6) { + return true; + } + } + return false; +} + static bool prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) { #define TD os_window->tab_bar_render_data @@ -728,7 +785,6 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * color_type first_window_bg = 0; for (unsigned int i = 0; i < tab->num_windows; i++) { Window *w = tab->windows + i; -#define WD w->render_data if (w->visible && WD.screen) { screen_check_pause_rendering(WD.screen, now); *num_visible_windows += 1; @@ -751,6 +807,7 @@ 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 (true && update_cursor_trail(&tab->cursor_trail, w, now)) needs_render = true; } 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 +865,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 (true) draw_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; diff --git a/kitty/data-types.h b/kitty/data-types.h index a2d348ff6..8ec97ab92 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -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; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 8fcd806da..4b0e8a0ae 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -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 diff --git a/kitty/screen.c b/kitty/screen.c index 4abf3441e..cec584db1 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -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); } diff --git a/kitty/shaders.c b/kitty/shaders.c index 92957b674..25fb2f20d 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -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,41 @@ 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_trail(CursorTrail *trail) { + glEnable(GL_BLEND); + // BLEND_PREMULT + /*glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);*/ + BLEND_ONTO_OPAQUE; + + 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); + + // todo - get cursor color from opt + float trail_color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + glUniform4fv(trail_program_layout.uniforms.trail_color, 1, trail_color); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glDisable(GL_BLEND); + unbind_program(); +} + +// }}} + // Python API {{{ static bool @@ -1205,6 +1240,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 +1266,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 +1279,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); diff --git a/kitty/shaders.py b/kitty/shaders.py index fef40cde1..bea5fcb3c 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -27,9 +27,11 @@ from .fast_data_types import ( REVERSE, STRIKETHROUGH, TINT_PROGRAM, + TRAIL_PROGRAM, compile_program, get_options, init_cell_program, + init_trail_program, ) @@ -201,5 +203,8 @@ class LoadShaderPrograms: 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() diff --git a/kitty/state.h b/kitty/state.h index efae4d909..02afa7d40 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -212,11 +212,18 @@ typedef struct { ssize_t vao_idx; } BorderRects; +typedef struct { + monotonic_t updated_at; + float corner_x[4]; + float corner_y[4]; +} 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 +359,7 @@ 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_trail(CursorTrail *trail); void update_surface_size(int, int, uint32_t); void free_texture(uint32_t*); void free_framebuffer(uint32_t*); diff --git a/kitty/trail_fragment.glsl b/kitty/trail_fragment.glsl new file mode 100644 index 000000000..9b516b455 --- /dev/null +++ b/kitty/trail_fragment.glsl @@ -0,0 +1,6 @@ +uniform vec4 trail_color; +out vec4 color; + +void main() { + color = trail_color; +} diff --git a/kitty/trail_vertex.glsl b/kitty/trail_vertex.glsl new file mode 100644 index 000000000..781db8805 --- /dev/null +++ b/kitty/trail_vertex.glsl @@ -0,0 +1,6 @@ +uniform vec4 x_coords; +uniform vec4 y_coords; + +void main() { + gl_Position = vec4(x_coords[gl_VertexID], y_coords[gl_VertexID], -1.0, 1.0); +}