add cursor trail

This commit is contained in:
Rick Choi 2024-10-12 19:15:40 +09:00
parent b07d4059d3
commit c3eb48fc8a
9 changed files with 129 additions and 3 deletions

View file

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

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

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
uniform vec4 trail_color;
out vec4 color;
void main() {
color = trail_color;
}

6
kitty/trail_vertex.glsl Normal file
View file

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