Bohdan's terminal configuration

ghostty: enabled Neovide-like custom cursor shader

bpavuk.neocities.org 34fabf00 80a9b743

verified
Changed files
+308
ghostty
.config
ghostty
+2
ghostty/.config/ghostty/config
··· 51 51 window-decoration = none 52 52 53 53 theme = Gruvbox Dark Hard 54 + 55 + custom-shader = 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 + }