@kecan0406/ttheme 1.0.33 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -35
- package/bin/ttheme.js +138 -135
- package/bin/ttheme.js.map +1 -0
- package/dist/manifest.json +1 -16
- package/package.json +3 -5
- package/shell/adapters/_osc.zsh +2 -0
- package/shell/adapters/ghostty.zsh +14 -1
- package/shell/launch-tab.zsh +1 -1
- package/shell/ttheme.zsh +126 -54
- package/dist/ghostty/ttheme.conf +0 -9
- package/ghostty/shaders/LICENSE +0 -21
- package/ghostty/shaders/README.md +0 -31
- package/ghostty/shaders/cursor_tail.glsl +0 -243
- package/ghostty/shaders/ripple_cursor.glsl +0 -141
package/dist/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.35",
|
|
3
3
|
"gate": [
|
|
4
4
|
{
|
|
5
5
|
"rule": "foreground",
|
|
@@ -46,21 +46,6 @@
|
|
|
46
46
|
"margin": 0.03,
|
|
47
47
|
"stands": 12
|
|
48
48
|
},
|
|
49
|
-
"font": {
|
|
50
|
-
"family": "JetBrainsMono Nerd Font",
|
|
51
|
-
"size": 14,
|
|
52
|
-
"codepointMap": [
|
|
53
|
-
{
|
|
54
|
-
"range": "U+AC00-U+D7A3",
|
|
55
|
-
"family": "D2Coding"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"range": "U+3130-U+318F",
|
|
59
|
-
"family": "D2Coding"
|
|
60
|
-
}
|
|
61
|
-
]
|
|
62
|
-
},
|
|
63
|
-
"shader": "cursor_tail.glsl",
|
|
64
49
|
"palettes": [
|
|
65
50
|
{
|
|
66
51
|
"name": "neutral",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kecan0406/ttheme",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ttheme": "bin/ttheme.js"
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin",
|
|
10
10
|
"dist/manifest.json",
|
|
11
|
-
"dist/ghostty/ttheme.conf",
|
|
12
11
|
"shell",
|
|
13
|
-
"!shell/check.zsh"
|
|
14
|
-
"ghostty/shaders"
|
|
12
|
+
"!shell/check.zsh"
|
|
15
13
|
],
|
|
16
|
-
"description": "Character terminal palettes for
|
|
14
|
+
"description": "Character terminal palettes for Ghostty, kitty, Alacritty, WezTerm, iTerm2, Windows Terminal and Warp",
|
|
17
15
|
"license": "MIT",
|
|
18
16
|
"author": "kecan0406",
|
|
19
17
|
"repository": {
|
package/shell/adapters/_osc.zsh
CHANGED
|
@@ -4,6 +4,19 @@ __tt_keepable() { return 0 }
|
|
|
4
4
|
|
|
5
5
|
__tt_bg_shown() { __tt_ghostty_shown }
|
|
6
6
|
|
|
7
|
+
__tt_reset_reloaded() {
|
|
8
|
+
local REPLY
|
|
9
|
+
integer i
|
|
10
|
+
__tt_osc_reset
|
|
11
|
+
[[ -n $1 ]] && (( ${TTHEME_TERMINALS[(Ie)ghostty]} && ! TTHEME_TMUX )) || return 0
|
|
12
|
+
for (( i = 0; i < 20; i++ )); do
|
|
13
|
+
__tt_query_bg || return 0
|
|
14
|
+
[[ ${(L)REPLY} == ${(L)1} ]] || return 0
|
|
15
|
+
sleep 0.05
|
|
16
|
+
__tt_osc_reset
|
|
17
|
+
done
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
__tt_shown() {
|
|
8
21
|
local dir=${TTHEME_CONFIG:h}/backgrounds was REPLY
|
|
9
22
|
__tt_bg_shown
|
|
@@ -17,7 +30,7 @@ __tt_shown() {
|
|
|
17
30
|
__tt_bg_cells() {
|
|
18
31
|
local fd saved resp="" line c v f
|
|
19
32
|
local -i px=2 py=2 fs=0 sc=2
|
|
20
|
-
for f in ${XDG_CONFIG_HOME:-$HOME/.config}/ghostty/config
|
|
33
|
+
for f in ${XDG_CONFIG_HOME:-$HOME/.config}/ghostty/config; do
|
|
21
34
|
[[ -r $f ]] || continue
|
|
22
35
|
for line in "${(@f)$(<$f)}"; do
|
|
23
36
|
v=${${line#*=}// /}
|
package/shell/launch-tab.zsh
CHANGED
package/shell/ttheme.zsh
CHANGED
|
@@ -12,8 +12,36 @@ __tt_palettes_load() {
|
|
|
12
12
|
|
|
13
13
|
__tt_fresh() {
|
|
14
14
|
local -a at
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
local was=$TTHEME_STARTUP
|
|
16
|
+
if ! zstat -F %s.%N -A at +mtime -- $TTHEME_HOME/palettes.zsh 2>/dev/null; then
|
|
17
|
+
[[ -e $TTHEME_HOME ]] || __tt_gone
|
|
18
|
+
return 0
|
|
19
|
+
fi
|
|
20
|
+
[[ $at[1] == "$TTHEME_PALETTES_AT" ]] && return 0
|
|
21
|
+
__tt_palettes_load && __tt_reloaded
|
|
22
|
+
[[ -n $was && -z $TTHEME_STARTUP && -n $TTHEME_SPEC ]] && __tt_active && __tt_off_here
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
__tt_gone() {
|
|
26
|
+
local bg=${TTHEME_SPEC%% *} hook
|
|
27
|
+
TTHEME_SPEC=
|
|
28
|
+
__tt_reload
|
|
29
|
+
__tt_reset_reloaded $bg
|
|
30
|
+
print -n $'\e[?1004l'
|
|
31
|
+
for hook in precmd:__tt_fresh precmd:__tt_precmd precmd:__tt_unmux preexec:__tt_preexec preexec:__tt_mux chpwd:__tt_chpwd; do
|
|
32
|
+
add-zsh-hook -d ${hook%%:*} ${hook#*:}
|
|
33
|
+
done
|
|
34
|
+
unfunction ttheme
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
__tt_off_here() {
|
|
38
|
+
local bg=${TTHEME_SPEC%% *}
|
|
39
|
+
TTHEME_SPEC=
|
|
40
|
+
if __tt_shown "" || [[ $1 == reload ]]; then
|
|
41
|
+
__tt_reload
|
|
42
|
+
fi
|
|
43
|
+
__tt_unshown force
|
|
44
|
+
__tt_reset_reloaded $bg
|
|
17
45
|
}
|
|
18
46
|
|
|
19
47
|
if [[ -r $TTHEME_HOME/palettes.zsh ]]; then
|
|
@@ -134,7 +162,7 @@ __tt_spec() {
|
|
|
134
162
|
|
|
135
163
|
__tt_name_of() { REPLY=${${(k)TTHEME_PALETTE[(re)$1]}:-custom} }
|
|
136
164
|
|
|
137
|
-
__tt_color() { [[ -t 1 && -z $NO_COLOR ]] }
|
|
165
|
+
__tt_color() { [[ -t 1 && -z $NO_COLOR && $TERM != dumb ]] }
|
|
138
166
|
|
|
139
167
|
__tt_palette_line() {
|
|
140
168
|
local name=$1 marker=$2
|
|
@@ -379,34 +407,60 @@ __tt_menu() {
|
|
|
379
407
|
[[ $k == $cur ]] && mark=$gutter
|
|
380
408
|
__tt_palette_line "$k" "$mark"
|
|
381
409
|
done
|
|
382
|
-
printf '\n\033[2mttheme <
|
|
410
|
+
printf '\n\033[2mttheme use <palette> paints this tab · ttheme preview · ttheme help\033[0m\n'
|
|
383
411
|
}
|
|
384
412
|
|
|
385
413
|
__tt_help() {
|
|
386
|
-
|
|
414
|
+
local v
|
|
415
|
+
print -r -- $'ttheme — character terminal palettes\n'
|
|
416
|
+
print -r -- " ${(r:16:):-ttheme}list every palette, grouped, with previews"
|
|
417
|
+
for v in $TTHEME_VERBS; do
|
|
418
|
+
if [[ $v == - ]]; then
|
|
419
|
+
print
|
|
420
|
+
else
|
|
421
|
+
print -r -- " ${(r:16:)${:-ttheme $v}}$TTHEME_VERB_ABOUT[$v]"
|
|
422
|
+
fi
|
|
423
|
+
done
|
|
424
|
+
print -r -- $'\nttheme <command> --help describes one command · ttheme --version prints the version'
|
|
425
|
+
}
|
|
387
426
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
ttheme next advance this tab to the next palette
|
|
392
|
-
ttheme default make a palette the one new tabs open with
|
|
393
|
-
ttheme off open new tabs in the terminal'"'"'s own colors — ttheme on wears the default again
|
|
394
|
-
ttheme pin pick a palette for this directory — cd into it repaints, cd out restores
|
|
395
|
-
ttheme unpin drop the palette pinned to this directory
|
|
396
|
-
ttheme config edit settings in $EDITOR — they apply in new tabs
|
|
427
|
+
__tt_usage() {
|
|
428
|
+
print -r -- "Usage: ttheme ${${:-$1 $TTHEME_VERB_ARGS[$1]}% }"$'\n\n'"$TTHEME_VERB_ABOUT[$1]"
|
|
429
|
+
}
|
|
397
430
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
431
|
+
__tt_verb_help() {
|
|
432
|
+
if (( ${TTHEME_SHELL_VERBS[(Ie)$1]} )); then
|
|
433
|
+
__tt_usage $1
|
|
434
|
+
elif [[ -n $1 && -n ${TTHEME_VERB_ABOUT[$1]} ]]; then
|
|
435
|
+
__tt_cli $1 --help
|
|
436
|
+
else
|
|
437
|
+
__tt_help
|
|
438
|
+
fi
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
__tt_misuse() {
|
|
442
|
+
print -u2 -r -- "ttheme $1: $2"$'\n'
|
|
443
|
+
__tt_usage $1 >&2
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
__tt_arity() {
|
|
448
|
+
local verb=$1
|
|
449
|
+
shift
|
|
450
|
+
local -a want=(${=TTHEME_VERB_ARGS[$verb]})
|
|
451
|
+
local least=${#${(M)want:#\<*}} most=${#want}
|
|
452
|
+
(( ${#${(M)want:#*...*}} )) && most=$#
|
|
453
|
+
(( $# >= least )) || { __tt_misuse $verb "missing $want[$# + 1]"; return }
|
|
454
|
+
(( $# <= most )) || { __tt_misuse $verb "too many arguments: ${argv[most + 1, -1]}"; return }
|
|
403
455
|
}
|
|
404
456
|
|
|
405
457
|
__tt_cli() {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
458
|
+
local k
|
|
459
|
+
local -a pass=()
|
|
460
|
+
for k in $TTHEME_SETTINGS; do
|
|
461
|
+
(( $+parameters[$k] )) && pass+=("$k=${(P)k}")
|
|
462
|
+
done
|
|
463
|
+
env $pass node $TTHEME_HOME/ttheme.js "$@"
|
|
410
464
|
}
|
|
411
465
|
|
|
412
466
|
__tt_catalog() {
|
|
@@ -421,18 +475,20 @@ __tt_catalog() {
|
|
|
421
475
|
|
|
422
476
|
__tt_switch() {
|
|
423
477
|
local spec
|
|
424
|
-
|
|
425
|
-
|
|
478
|
+
__tt_cli "$@" || return
|
|
479
|
+
[[ -r $TTHEME_HOME/palettes.zsh ]] && __tt_palettes_load && __tt_reloaded
|
|
480
|
+
if ! __tt_active; then
|
|
481
|
+
__tt_reload
|
|
482
|
+
return 0
|
|
483
|
+
fi
|
|
426
484
|
if [[ $1 == off ]]; then
|
|
427
|
-
|
|
428
|
-
TTHEME_SPEC=
|
|
429
|
-
__tt_shown "" && __tt_reload
|
|
430
|
-
__tt_unshown force
|
|
485
|
+
__tt_off_here reload
|
|
431
486
|
elif [[ -n $TTHEME_STARTUP ]]; then
|
|
432
487
|
spec=${TTHEME_PALETTE[$TTHEME_STARTUP]}
|
|
433
488
|
__tt_apply "$spec"
|
|
434
489
|
TTHEME_SPEC=$spec
|
|
435
|
-
__tt_shown "$TTHEME_STARTUP" force
|
|
490
|
+
__tt_shown "$TTHEME_STARTUP" force
|
|
491
|
+
__tt_reload
|
|
436
492
|
fi
|
|
437
493
|
}
|
|
438
494
|
|
|
@@ -1559,7 +1615,21 @@ __tt_preview() {
|
|
|
1559
1615
|
}
|
|
1560
1616
|
|
|
1561
1617
|
ttheme() {
|
|
1562
|
-
if
|
|
1618
|
+
if (( $# > 1 && ${${argv[2,-1]}[(I)(-h|--help)]} )); then
|
|
1619
|
+
__tt_verb_help $1
|
|
1620
|
+
return
|
|
1621
|
+
fi
|
|
1622
|
+
case $1 in
|
|
1623
|
+
-h|--help) __tt_help; return 0 ;;
|
|
1624
|
+
-V|--version) __tt_cli --version; return ;;
|
|
1625
|
+
help) __tt_verb_help $2; return 0 ;;
|
|
1626
|
+
-*) print -u2 "ttheme: unknown option $1 — see \`ttheme help\`"; return 1 ;;
|
|
1627
|
+
esac
|
|
1628
|
+
if (( $# )) && [[ -z ${TTHEME_VERB_ABOUT[$1]} ]]; then
|
|
1629
|
+
print -u2 "ttheme: unknown command '$1' — see \`ttheme help\`"
|
|
1630
|
+
return 1
|
|
1631
|
+
fi
|
|
1632
|
+
if [[ $TTHEME_ADAPTER == warp && $1 != (browse|list|add|remove|update|config|default|on|off) ]]; then
|
|
1563
1633
|
print -u2 "ttheme: Warp wears one theme app-wide and paints no tab background of its own — \`ttheme default <palette>\` puts one on every Warp window"
|
|
1564
1634
|
return 1
|
|
1565
1635
|
fi
|
|
@@ -1570,41 +1640,43 @@ ttheme() {
|
|
|
1570
1640
|
fi
|
|
1571
1641
|
|
|
1572
1642
|
case $1 in
|
|
1573
|
-
-h|--help|help) __tt_help; return 0 ;;
|
|
1574
1643
|
browse|list|add|remove|update) __tt_catalog "$@"; return ;;
|
|
1575
|
-
|
|
1644
|
+
on|off) __tt_switch "$@"; return ;;
|
|
1576
1645
|
esac
|
|
1577
|
-
|
|
1646
|
+
__tt_arity "$@" || return
|
|
1647
|
+
[[ $1 == config ]] && { __tt_config; return }
|
|
1578
1648
|
(( ${#TTHEME_PALETTE} )) || { __tt_empty; return }
|
|
1579
1649
|
|
|
1650
|
+
local REPLY spec
|
|
1580
1651
|
case $1 in
|
|
1581
|
-
|
|
1652
|
+
use)
|
|
1653
|
+
__tt_resolve "$2" || return 1
|
|
1654
|
+
spec=${TTHEME_PALETTE[$REPLY]}
|
|
1655
|
+
__tt_apply "$spec"
|
|
1656
|
+
TTHEME_SPEC=$spec
|
|
1657
|
+
__tt_shown "$REPLY" force && __tt_reload
|
|
1658
|
+
__tt_announce ;;
|
|
1659
|
+
next) __tt_rotate ;;
|
|
1582
1660
|
default)
|
|
1583
|
-
[[ -n $2 ]] || { print -u2 "ttheme default: name a palette — see \`ttheme help\`"; return 1 }
|
|
1584
|
-
local REPLY
|
|
1585
1661
|
__tt_resolve "$2" || return 1
|
|
1586
|
-
__tt_keep "$REPLY"
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
pin) __tt_preview pin; return ;;
|
|
1591
|
-
unpin) __tt_unpin; return ;;
|
|
1592
|
-
-*) print -u2 "ttheme: unknown option $1 — see \`ttheme help\`"; return 1 ;;
|
|
1662
|
+
__tt_keep "$REPLY" ;;
|
|
1663
|
+
preview) __tt_preview ;;
|
|
1664
|
+
pin) __tt_preview pin ;;
|
|
1665
|
+
unpin) __tt_unpin ;;
|
|
1593
1666
|
esac
|
|
1594
|
-
|
|
1595
|
-
local REPLY
|
|
1596
|
-
__tt_resolve "$1" || return 1
|
|
1597
|
-
local spec=${TTHEME_PALETTE[$REPLY]}
|
|
1598
|
-
__tt_apply "$spec"
|
|
1599
|
-
TTHEME_SPEC=$spec
|
|
1600
|
-
__tt_shown "$REPLY" force && __tt_reload
|
|
1601
|
-
__tt_announce
|
|
1602
1667
|
}
|
|
1603
1668
|
|
|
1604
1669
|
if (( $+functions[compdef] )); then
|
|
1605
1670
|
__tt_complete() {
|
|
1606
|
-
|
|
1607
|
-
|
|
1671
|
+
local -a reply
|
|
1672
|
+
if (( CURRENT == 2 )); then
|
|
1673
|
+
compadd -- ${TTHEME_VERBS:#-} help
|
|
1674
|
+
elif [[ $words[2] == remove || ( $words[2] == (use|default) && CURRENT == 3 ) ]]; then
|
|
1675
|
+
compadd -- $TTHEME_ORDER
|
|
1676
|
+
elif [[ $words[2] == add ]]; then
|
|
1677
|
+
reply=(${${${(M)${(f)"$(__tt_cli list --json 2>/dev/null)"}:#*\"name\": *}#*\"name\": \"}%%\"*})
|
|
1678
|
+
compadd -- ${reply:|TTHEME_ORDER}
|
|
1679
|
+
fi
|
|
1608
1680
|
}
|
|
1609
1681
|
compdef __tt_complete ttheme
|
|
1610
1682
|
fi
|
package/dist/ghostty/ttheme.conf
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# ttheme — shared font and shader, identical for every palette.
|
|
2
|
-
# Pick colors with: theme = <palette>
|
|
3
|
-
font-family = JetBrainsMono Nerd Font
|
|
4
|
-
font-size = 14
|
|
5
|
-
font-codepoint-map = U+AC00-U+D7A3=D2Coding
|
|
6
|
-
font-codepoint-map = U+3130-U+318F=D2Coding
|
|
7
|
-
|
|
8
|
-
custom-shader = ~/.config/ghostty/shaders/cursor_tail.glsl
|
|
9
|
-
custom-shader-animation = true
|
package/ghostty/shaders/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Sahaj Bhatt
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Shaders
|
|
2
|
-
|
|
3
|
-
Ghostty is the only Tier-1 terminal that runs GLSL, so nothing here applies to
|
|
4
|
-
kitty, Alacritty, WezTerm or iTerm2. A theme picks one with `[ghostty] shader`
|
|
5
|
-
in its TOML; the built config wires it up as `custom-shader`.
|
|
6
|
-
|
|
7
|
-
Shaders apply to the whole Ghostty instance, not per tab — so switching a tab's
|
|
8
|
-
palette does not switch its shader.
|
|
9
|
-
|
|
10
|
-
## Included
|
|
11
|
-
|
|
12
|
-
Both come from [sahaj-b/ghostty-cursor-shaders][sahaj] (MIT — see `LICENSE`).
|
|
13
|
-
|
|
14
|
-
| File | Effect | Local change |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| `cursor_tail.glsl` | comet trail behind the cursor | `DURATION` 0.09 → 0.05 (shorter tail) |
|
|
17
|
-
| `ripple_cursor.glsl` | expanding ring on cursor move | fixed `COLOR` → theme cursor via `iCurrentCursorColor` |
|
|
18
|
-
|
|
19
|
-
Trail length lives in `DURATION` and `MAX_TRAIL_LENGTH` inside the file.
|
|
20
|
-
|
|
21
|
-
## Not included
|
|
22
|
-
|
|
23
|
-
These are in use locally but ship no license, so they are not redistributed
|
|
24
|
-
here. Drop them into this directory yourself if you want them:
|
|
25
|
-
|
|
26
|
-
- `bloom.glsl`, `bloom-subtle.glsl` — [qwerasd205's gist][bloom]
|
|
27
|
-
- `gradient-background.glsl`, `animated-gradient-shader.glsl` — [unkn0wncode][unk]
|
|
28
|
-
|
|
29
|
-
[sahaj]: https://github.com/sahaj-b/ghostty-cursor-shaders
|
|
30
|
-
[bloom]: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
|
|
31
|
-
[unk]: https://github.com/unkn0wncode
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
// cursor_tail.glsl — from sahaj-b/ghostty-cursor-shaders (MIT).
|
|
2
|
-
// https://github.com/sahaj-b/ghostty-cursor-shaders
|
|
3
|
-
// See LICENSE in this directory. Local change: DURATION tuned.
|
|
4
|
-
|
|
5
|
-
// sRGB -> Linear conversion (needed because Ghostty passes sRGB values but the shader pipeline operates in linear color space)
|
|
6
|
-
vec3 sRGBToLinear(vec3 c) {
|
|
7
|
-
return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// --- CONFIGURATION ---
|
|
11
|
-
vec4 TRAIL_COLOR = vec4(sRGBToLinear(iCurrentCursorColor.rgb), iCurrentCursorColor.a); // for custom color: vec4(0.2, 0.6, 1.0, 0.5); (wrap in sRGBToLinear for correct brightness)
|
|
12
|
-
const float DURATION = 0.05; // in seconds
|
|
13
|
-
const float MAX_TRAIL_LENGTH = 0.2;
|
|
14
|
-
const float THRESHOLD_MIN_DISTANCE = 1.5; // min distance to show trail (units of cursor width)
|
|
15
|
-
const float BLUR = 2.0; // blur size in pixels (for antialiasing)
|
|
16
|
-
|
|
17
|
-
// --- CONSTANTS for easing functions ---
|
|
18
|
-
const float PI = 3.14159265359;
|
|
19
|
-
const float C1_BACK = 1.70158;
|
|
20
|
-
const float C2_BACK = C1_BACK * 1.525;
|
|
21
|
-
const float C3_BACK = C1_BACK + 1.0;
|
|
22
|
-
const float C4_ELASTIC = (2.0 * PI) / 3.0;
|
|
23
|
-
const float C5_ELASTIC = (2.0 * PI) / 4.5;
|
|
24
|
-
const float SPRING_STIFFNESS = 9.0;
|
|
25
|
-
const float SPRING_DAMPING = 0.9;
|
|
26
|
-
|
|
27
|
-
// --- EASING FUNCTIONS ---
|
|
28
|
-
|
|
29
|
-
// // Linear
|
|
30
|
-
// float ease(float x) {
|
|
31
|
-
// return x;
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
// // EaseOutQuad
|
|
35
|
-
// float ease(float x) {
|
|
36
|
-
// return 1.0 - (1.0 - x) * (1.0 - x);
|
|
37
|
-
// }
|
|
38
|
-
|
|
39
|
-
// // EaseOutCubic
|
|
40
|
-
// float ease(float x) {
|
|
41
|
-
// return 1.0 - pow(1.0 - x, 3.0);
|
|
42
|
-
// }
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// // EaseOutQuart
|
|
46
|
-
// float ease(float x) {
|
|
47
|
-
// return 1.0 - pow(1.0 - x, 4.0);
|
|
48
|
-
// }
|
|
49
|
-
|
|
50
|
-
// // EaseOutQuint
|
|
51
|
-
// float ease(float x) {
|
|
52
|
-
// return 1.0 - pow(1.0 - x, 5.0);
|
|
53
|
-
// }
|
|
54
|
-
|
|
55
|
-
// // EaseOutSine
|
|
56
|
-
// float ease(float x) {
|
|
57
|
-
// return sin((x * PI) / 2.0);
|
|
58
|
-
// }
|
|
59
|
-
|
|
60
|
-
// // EaseOutExpo
|
|
61
|
-
// float ease(float x) {
|
|
62
|
-
// return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);
|
|
63
|
-
// }
|
|
64
|
-
|
|
65
|
-
// EaseOutCirc
|
|
66
|
-
float ease(float x) {
|
|
67
|
-
return sqrt(1.0 - pow(x - 1.0, 2.0));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// // EaseOutBack
|
|
71
|
-
// float ease(float x) {
|
|
72
|
-
// return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);
|
|
73
|
-
// }
|
|
74
|
-
|
|
75
|
-
// // EaseOutElastic
|
|
76
|
-
// float ease(float x) {
|
|
77
|
-
// return x == 0.0 ? 0.0
|
|
78
|
-
// : x == 1.0 ? 1.0
|
|
79
|
-
// : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;
|
|
80
|
-
// }
|
|
81
|
-
|
|
82
|
-
// Parametric Spring
|
|
83
|
-
// float ease(float x) {
|
|
84
|
-
// x = clamp(x, 0.0, 1.0);
|
|
85
|
-
// float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);
|
|
86
|
-
// float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));
|
|
87
|
-
// float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);
|
|
88
|
-
// return 1.0 - decay * osc;
|
|
89
|
-
// }
|
|
90
|
-
|
|
91
|
-
float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
|
|
92
|
-
{
|
|
93
|
-
vec2 d = abs(p - xy) - b;
|
|
94
|
-
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
|
|
98
|
-
// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
|
|
99
|
-
|
|
100
|
-
float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
|
|
101
|
-
vec2 e = b - a;
|
|
102
|
-
vec2 w = p - a;
|
|
103
|
-
vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
|
|
104
|
-
float segd = dot(p - proj, p - proj);
|
|
105
|
-
d = min(d, segd);
|
|
106
|
-
|
|
107
|
-
float c0 = step(0.0, p.y - a.y);
|
|
108
|
-
float c1 = 1.0 - step(0.0, p.y - b.y);
|
|
109
|
-
float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
|
|
110
|
-
float allCond = c0 * c1 * c2;
|
|
111
|
-
float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
|
|
112
|
-
float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
|
|
113
|
-
s *= flip;
|
|
114
|
-
return d;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
float getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {
|
|
118
|
-
float s = 1.0;
|
|
119
|
-
float d = dot(p - v0, p - v0);
|
|
120
|
-
|
|
121
|
-
d = seg(p, v0, v3, s, d);
|
|
122
|
-
d = seg(p, v1, v0, s, d);
|
|
123
|
-
d = seg(p, v2, v1, s, d);
|
|
124
|
-
d = seg(p, v3, v2, s, d);
|
|
125
|
-
|
|
126
|
-
return s * sqrt(d);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
vec2 normalize(vec2 value, float isPosition) {
|
|
130
|
-
return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
float antialising(float distance) {
|
|
134
|
-
return 1. - smoothstep(0., normalize(vec2(BLUR, BLUR), 0.).x, distance);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
float determineIfTopRightIsLeading(vec2 a, vec2 b) {
|
|
138
|
-
float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
|
|
139
|
-
float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
|
|
140
|
-
|
|
141
|
-
// if neither condition is met, return 1 (else case)
|
|
142
|
-
return 1.0 - max(condition1, condition2);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
vec2 getRectangleCenter(vec4 rectangle) {
|
|
146
|
-
return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));
|
|
147
|
-
}
|
|
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 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);
|
|
164
|
-
|
|
165
|
-
vec2 delta = centerCP - centerCC;
|
|
166
|
-
float lineLength = length(delta);
|
|
167
|
-
|
|
168
|
-
float sdfCurrentCursor = getSdfRectangle(vu, centerCC, currentCursor.zw * 0.5);
|
|
169
|
-
|
|
170
|
-
vec4 newColor = vec4(fragColor);
|
|
171
|
-
|
|
172
|
-
float minDist = currentCursor.w * THRESHOLD_MIN_DISTANCE;
|
|
173
|
-
float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);
|
|
174
|
-
if (lineLength > minDist) {
|
|
175
|
-
// ANIMATION logic
|
|
176
|
-
|
|
177
|
-
float head_eased = 0.0;
|
|
178
|
-
float tail_eased = 0.0;
|
|
179
|
-
|
|
180
|
-
float tail_delay_factor = MAX_TRAIL_LENGTH / lineLength;
|
|
181
|
-
|
|
182
|
-
float isLongMove = step(MAX_TRAIL_LENGTH, lineLength);
|
|
183
|
-
|
|
184
|
-
float head_eased_short = ease(progress);
|
|
185
|
-
float tail_eased_short = ease(smoothstep(tail_delay_factor, 1.0, progress));
|
|
186
|
-
float head_eased_long = 1.0;
|
|
187
|
-
float tail_eased_long = ease(progress);
|
|
188
|
-
|
|
189
|
-
head_eased = mix(head_eased_long, head_eased_short, isLongMove);
|
|
190
|
-
tail_eased = mix(tail_eased_long, tail_eased_short, isLongMove);
|
|
191
|
-
|
|
192
|
-
// detect straight moves
|
|
193
|
-
vec2 delta_abs = abs(centerCC - centerCP);
|
|
194
|
-
float threshold = 0.001;
|
|
195
|
-
float isHorizontal = step(delta_abs.y, threshold);
|
|
196
|
-
float isVertical = step(delta_abs.x, threshold);
|
|
197
|
-
float isStraightMove = max(isHorizontal, isVertical);
|
|
198
|
-
|
|
199
|
-
// -- Making the parallelogram sdf (diagonal move) --
|
|
200
|
-
|
|
201
|
-
// animate the TOP-LEFT corners
|
|
202
|
-
vec2 head_pos_tl = mix(previousCursor.xy, currentCursor.xy, head_eased);
|
|
203
|
-
vec2 tail_pos_tl = mix(previousCursor.xy, currentCursor.xy, tail_eased);
|
|
204
|
-
|
|
205
|
-
float isTopRightLeading = determineIfTopRightIsLeading(currentCursor.xy, previousCursor.xy);
|
|
206
|
-
float isBottomLeftLeading = 1.0 - isTopRightLeading;
|
|
207
|
-
|
|
208
|
-
// v0, v1 : "front" of the trail (head)
|
|
209
|
-
vec2 v0 = vec2(head_pos_tl.x + currentCursor.z * isTopRightLeading, head_pos_tl.y - currentCursor.w);
|
|
210
|
-
vec2 v1 = vec2(head_pos_tl.x + currentCursor.z * isBottomLeftLeading, head_pos_tl.y);
|
|
211
|
-
|
|
212
|
-
// v2, v3: "back" of the trail (tail)
|
|
213
|
-
vec2 v2 = vec2(tail_pos_tl.x + currentCursor.z * isBottomLeftLeading, tail_pos_tl.y);
|
|
214
|
-
vec2 v3 = vec2(tail_pos_tl.x + currentCursor.z * isTopRightLeading, tail_pos_tl.y - previousCursor.w);
|
|
215
|
-
|
|
216
|
-
float sdfTrail_diag = getSdfParallelogram(vu, v0, v1, v2, v3);
|
|
217
|
-
|
|
218
|
-
// -- Making the rectangle sdf (straight move) --
|
|
219
|
-
|
|
220
|
-
vec2 head_center = mix(centerCP, centerCC, head_eased);
|
|
221
|
-
vec2 tail_center = mix(centerCP, centerCC, tail_eased);
|
|
222
|
-
|
|
223
|
-
vec2 min_center = min(head_center, tail_center);
|
|
224
|
-
vec2 max_center = max(head_center, tail_center);
|
|
225
|
-
|
|
226
|
-
vec2 box_size = (max_center - min_center) + currentCursor.zw;
|
|
227
|
-
vec2 box_center = (min_center + max_center) * 0.5;
|
|
228
|
-
|
|
229
|
-
float sdfTrail_rect = getSdfRectangle(vu, box_center, box_size * 0.5);
|
|
230
|
-
|
|
231
|
-
// -- FINAL SELECTING AND DRAWING --
|
|
232
|
-
float sdfTrail = mix(sdfTrail_diag, sdfTrail_rect, isStraightMove);
|
|
233
|
-
|
|
234
|
-
vec4 trail = TRAIL_COLOR;
|
|
235
|
-
float trailAlpha = antialising(sdfTrail);
|
|
236
|
-
newColor = mix(newColor, trail, trailAlpha);
|
|
237
|
-
|
|
238
|
-
// punch hole
|
|
239
|
-
newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
fragColor = newColor;
|
|
243
|
-
}
|