+2
ghostty/.config/ghostty/config
+2
ghostty/.config/ghostty/config
+306
ghostty/.config/ghostty/shaders/cursor_warp.glsl
+306
ghostty/.config/ghostty/shaders/cursor_warp.glsl
···
1
+
// Copyright (c) 2025 Sahaj Bhatt
2
+
// original source located at https://github.com/sahaj-b/ghostty-cursor-shaders
3
+
4
+
// --- CONFIGURATION ---
5
+
vec4 TRAIL_COLOR = iCurrentCursorColor; // can change to eg: vec4(0.2, 0.6, 1.0, 0.5);
6
+
const float DURATION = 0.2; // total animation time
7
+
const float TRAIL_SIZE = 0.8; // 0.0 = all corners move together. 1.0 = max smear (leading corners jump instantly)
8
+
const float THRESHOLD_MIN_DISTANCE = 1.5; // min distance to show trail (units of cursor height)
9
+
const float BLUR = 1.0; // blur size in pixels (for antialiasing)
10
+
const float TRAIL_THICKNESS = 1.0; // 1.0 = full cursor height, 0.0 = zero height, >1.0 = funky aah
11
+
const float TRAIL_THICKNESS_X = 0.9;
12
+
13
+
const float FADE_ENABLED = 0.0; // 1.0 to enable fade gradient along the trail, 0.0 to disable
14
+
const float FADE_EXPONENT = 5.0; // exponent for fade gradient along the trail
15
+
16
+
// --- CONSTANTS for easing functions ---
17
+
const float PI = 3.14159265359;
18
+
const float C1_BACK = 1.70158;
19
+
const float C2_BACK = C1_BACK * 1.525;
20
+
const float C3_BACK = C1_BACK + 1.0;
21
+
const float C4_ELASTIC = (2.0 * PI) / 3.0;
22
+
const float C5_ELASTIC = (2.0 * PI) / 4.5;
23
+
const float SPRING_STIFFNESS = 9.0;
24
+
const float SPRING_DAMPING = 0.9;
25
+
26
+
// --- EASING FUNCTIONS ---
27
+
28
+
// // Linear
29
+
// float ease(float x) {
30
+
// return x;
31
+
// }
32
+
33
+
// // EaseOutQuad
34
+
// float ease(float x) {
35
+
// return 1.0 - (1.0 - x) * (1.0 - x);
36
+
// }
37
+
38
+
// // EaseOutCubic
39
+
// float ease(float x) {
40
+
// return 1.0 - pow(1.0 - x, 3.0);
41
+
// }
42
+
43
+
// // EaseOutQuart
44
+
// float ease(float x) {
45
+
// return 1.0 - pow(1.0 - x, 4.0);
46
+
// }
47
+
48
+
// // EaseOutQuint
49
+
// float ease(float x) {
50
+
// return 1.0 - pow(1.0 - x, 5.0);
51
+
// }
52
+
53
+
// // EaseOutSine
54
+
// float ease(float x) {
55
+
// return sin((x * PI) / 2.0);
56
+
// }
57
+
58
+
// // EaseOutExpo
59
+
// float ease(float x) {
60
+
// return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);
61
+
// }
62
+
63
+
// EaseOutCirc
64
+
float ease(float x) {
65
+
return sqrt(1.0 - pow(x - 1.0, 2.0));
66
+
}
67
+
68
+
// // EaseOutBack
69
+
// float ease(float x) {
70
+
// return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);
71
+
// }
72
+
73
+
// // EaseOutElastic
74
+
// float ease(float x) {
75
+
// return x == 0.0 ? 0.0
76
+
// : x == 1.0 ? 1.0
77
+
// : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;
78
+
// }
79
+
80
+
// // Parametric Spring
81
+
// float ease(float x) {
82
+
// x = clamp(x, 0.0, 1.0);
83
+
// float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);
84
+
// float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));
85
+
// float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);
86
+
// return 1.0 - decay * osc;
87
+
// }
88
+
89
+
float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
90
+
{
91
+
vec2 d = abs(p - xy) - b;
92
+
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
93
+
}
94
+
95
+
// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
96
+
// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
97
+
float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
98
+
vec2 e = b - a;
99
+
vec2 w = p - a;
100
+
vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
101
+
float segd = dot(p - proj, p - proj);
102
+
d = min(d, segd);
103
+
104
+
float c0 = step(0.0, p.y - a.y);
105
+
float c1 = 1.0 - step(0.0, p.y - b.y);
106
+
float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
107
+
float allCond = c0 * c1 * c2;
108
+
float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
109
+
float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
110
+
s *= flip;
111
+
return d;
112
+
}
113
+
114
+
float getSdfConvexQuad(in vec2 p, in vec2 v1, in vec2 v2, in vec2 v3, in vec2 v4) {
115
+
float s = 1.0;
116
+
float d = dot(p - v1, p - v1);
117
+
118
+
d = seg(p, v1, v2, s, d);
119
+
d = seg(p, v2, v3, s, d);
120
+
d = seg(p, v3, v4, s, d);
121
+
d = seg(p, v4, v1, s, d);
122
+
123
+
return s * sqrt(d);
124
+
}
125
+
126
+
vec2 normalize(vec2 value, float isPosition) {
127
+
return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
128
+
}
129
+
130
+
float antialising(float distance, float blurAmount) {
131
+
return 1. - smoothstep(0., normalize(vec2(blurAmount, blurAmount), 0.).x, distance);
132
+
}
133
+
134
+
// Determines animation duration based on a corner's alignment with the move direction(dot product)
135
+
// dot_val will be in [-2, 2]
136
+
// > 0.5 (1 or 2) = Leading
137
+
// > -0.5 (0) = Side
138
+
// <= -0.5 (-1 or -2) = Trailing
139
+
float getDurationFromDot(float dot_val, float DURATION_LEAD, float DURATION_SIDE, float DURATION_TRAIL) {
140
+
float isLead = step(0.5, dot_val);
141
+
float isSide = step(-0.5, dot_val) * (1.0 - isLead);
142
+
143
+
// Start with trailing duration
144
+
float duration = mix(DURATION_TRAIL, DURATION_SIDE, isSide);
145
+
// Mix in leading duration
146
+
duration = mix(duration, DURATION_LEAD, isLead);
147
+
return duration;
148
+
}
149
+
150
+
void mainImage(out vec4 fragColor, in vec2 fragCoord){
151
+
#if !defined(WEB)
152
+
fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
153
+
#endif
154
+
155
+
// normalization & setup(-1, 1 coords)
156
+
vec2 vu = normalize(fragCoord, 1.);
157
+
vec2 offsetFactor = vec2(-.5, 0.5);
158
+
159
+
vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
160
+
vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
161
+
162
+
vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);
163
+
vec2 halfSizeCC = currentCursor.zw * 0.5;
164
+
vec2 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);
165
+
vec2 halfSizeCP = previousCursor.zw * 0.5;
166
+
167
+
float sdfCurrentCursor = getSdfRectangle(vu, centerCC, halfSizeCC);
168
+
169
+
float lineLength = distance(centerCC, centerCP);
170
+
float minDist = currentCursor.w * THRESHOLD_MIN_DISTANCE;
171
+
172
+
vec4 newColor = vec4(fragColor);
173
+
174
+
float baseProgress = iTime - iTimeCursorChange;
175
+
176
+
if (lineLength > minDist && baseProgress < DURATION - 0.001) {
177
+
// defining corners of cursors
178
+
179
+
// Y (Height) with TRAIL_THICKNESS
180
+
float cc_half_height = currentCursor.w * 0.5;
181
+
float cc_center_y = currentCursor.y - cc_half_height;
182
+
float cc_new_half_height = cc_half_height * TRAIL_THICKNESS;
183
+
float cc_new_top_y = cc_center_y + cc_new_half_height;
184
+
float cc_new_bottom_y = cc_center_y - cc_new_half_height;
185
+
186
+
// X (Width) with TRAIL_THICKNESS
187
+
float cc_half_width = currentCursor.z * 0.5;
188
+
float cc_center_x = currentCursor.x + cc_half_width;
189
+
float cc_new_half_width = cc_half_width * TRAIL_THICKNESS_X;
190
+
float cc_new_left_x = cc_center_x - cc_new_half_width;
191
+
float cc_new_right_x = cc_center_x + cc_new_half_width;
192
+
193
+
vec2 cc_tl = vec2(cc_new_left_x, cc_new_top_y);
194
+
vec2 cc_tr = vec2(cc_new_right_x, cc_new_top_y);
195
+
vec2 cc_bl = vec2(cc_new_left_x, cc_new_bottom_y);
196
+
vec2 cc_br = vec2(cc_new_right_x, cc_new_bottom_y);
197
+
198
+
// same thing for previous cursor
199
+
float cp_half_height = previousCursor.w * 0.5;
200
+
float cp_center_y = previousCursor.y - cp_half_height;
201
+
float cp_new_half_height = cp_half_height * TRAIL_THICKNESS;
202
+
float cp_new_top_y = cp_center_y + cp_new_half_height;
203
+
float cp_new_bottom_y = cp_center_y - cp_new_half_height;
204
+
205
+
float cp_half_width = previousCursor.z * 0.5;
206
+
float cp_center_x = previousCursor.x + cp_half_width;
207
+
float cp_new_half_width = cp_half_width * TRAIL_THICKNESS_X;
208
+
float cp_new_left_x = cp_center_x - cp_new_half_width;
209
+
float cp_new_right_x = cp_center_x + cp_new_half_width;
210
+
211
+
vec2 cp_tl = vec2(cp_new_left_x, cp_new_top_y);
212
+
vec2 cp_tr = vec2(cp_new_right_x, cp_new_top_y);
213
+
vec2 cp_bl = vec2(cp_new_left_x, cp_new_bottom_y);
214
+
vec2 cp_br = vec2(cp_new_right_x, cp_new_bottom_y);
215
+
216
+
// calculating durations for every corner
217
+
const float DURATION_TRAIL = DURATION;
218
+
const float DURATION_LEAD = DURATION * (1.0 - TRAIL_SIZE);
219
+
const float DURATION_SIDE = (DURATION_LEAD + DURATION_TRAIL) / 2.0;
220
+
221
+
vec2 moveVec = centerCC - centerCP;
222
+
vec2 s = sign(moveVec);
223
+
224
+
// dot products for each corner, determining alignment with movement direction
225
+
float dot_tl = dot(vec2(-1., 1.), s);
226
+
float dot_tr = dot(vec2( 1., 1.), s);
227
+
float dot_bl = dot(vec2(-1.,-1.), s);
228
+
float dot_br = dot(vec2( 1.,-1.), s);
229
+
230
+
// assign durations based on dot products
231
+
float dur_tl = getDurationFromDot(dot_tl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
232
+
float dur_tr = getDurationFromDot(dot_tr, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
233
+
float dur_bl = getDurationFromDot(dot_bl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
234
+
float dur_br = getDurationFromDot(dot_br, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
235
+
236
+
// check direction of horizontal movement
237
+
float isMovingRight = step(0.5, s.x);
238
+
float isMovingLeft = step(0.5, -s.x);
239
+
240
+
// calculate vertical-rail durations
241
+
float dot_right_edge = (dot_tr + dot_br) * 0.5;
242
+
float dur_right_rail = getDurationFromDot(dot_right_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
243
+
244
+
float dot_left_edge = (dot_tl + dot_bl) * 0.5;
245
+
float dur_left_rail = getDurationFromDot(dot_left_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
246
+
247
+
float final_dur_tl = mix(dur_tl, dur_left_rail, isMovingLeft);
248
+
float final_dur_bl = mix(dur_bl, dur_left_rail, isMovingLeft);
249
+
250
+
float final_dur_tr = mix(dur_tr, dur_right_rail, isMovingRight);
251
+
float final_dur_br = mix(dur_br, dur_right_rail, isMovingRight);
252
+
253
+
// calculate progress for each corner based on the duration and time since cursor change
254
+
float prog_tl = ease(clamp(baseProgress / final_dur_tl, 0.0, 1.0));
255
+
float prog_tr = ease(clamp(baseProgress / final_dur_tr, 0.0, 1.0));
256
+
float prog_bl = ease(clamp(baseProgress / final_dur_bl, 0.0, 1.0));
257
+
float prog_br = ease(clamp(baseProgress / final_dur_br, 0.0, 1.0));
258
+
259
+
// get the trial corner positions based on progress
260
+
vec2 v_tl = mix(cp_tl, cc_tl, prog_tl);
261
+
vec2 v_tr = mix(cp_tr, cc_tr, prog_tr);
262
+
vec2 v_br = mix(cp_br, cc_br, prog_br);
263
+
vec2 v_bl = mix(cp_bl, cc_bl, prog_bl);
264
+
265
+
// DRAWING THE TRAIL
266
+
float sdfTrail = getSdfConvexQuad(vu, v_tl, v_tr, v_br, v_bl);
267
+
268
+
// --- FADE GRADIENT CALCULATION ---
269
+
vec2 fragVec = vu - centerCP;
270
+
271
+
// project fragment onto movement vector, normalize to [0, 1]
272
+
// 0.0 at tail, 1.0 at head
273
+
// tiny epsilon to avoid division by zero if moveVec is (0,0)
274
+
float fadeProgress = clamp(dot(fragVec, moveVec) / (dot(moveVec, moveVec) + 1e-6), 0.0, 1.0);
275
+
276
+
vec4 trail = TRAIL_COLOR;
277
+
278
+
float effectiveBlur = BLUR;
279
+
if (BLUR < 2.5) {
280
+
// no antialising on horizontal/vertical movement, fixes 'pulse' like thing on end cursor
281
+
float isDiagonal = abs(s.x) * abs(s.y); // 1.0 if diagonal, 0.0 if H/V
282
+
float effectiveBlur = mix(0.0, BLUR, isDiagonal);
283
+
}
284
+
float shapeAlpha = antialising(sdfTrail, effectiveBlur); // shape mask
285
+
286
+
if (FADE_ENABLED > 0.5) {
287
+
// apply fade gradient along the trail
288
+
// float fadeStart = 0.2;
289
+
// float easedProgress = smoothstep(fadeStart, 1.0, fadeProgress);
290
+
// easedProgress = pow(2.0, 10.0 * (fadeProgress - 1.0));
291
+
float easedProgress = pow(fadeProgress, FADE_EXPONENT);
292
+
trail.a *= easedProgress;
293
+
}
294
+
295
+
float finalAlpha = trail.a * shapeAlpha;
296
+
297
+
// newColor.a to preserve the background alpha.
298
+
newColor = mix(newColor, vec4(trail.rgb, newColor.a), finalAlpha);
299
+
300
+
// punch hole on the trail, so current cursor is drawn on top
301
+
newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
302
+
303
+
}
304
+
305
+
fragColor = newColor;
306
+
}