-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcolors.nix
More file actions
200 lines (181 loc) · 5.24 KB
/
colors.nix
File metadata and controls
200 lines (181 loc) · 5.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# Shared color utility library.
#
# Takes a raw colors attrset (bare hex strings without #), an ansiColors
# attrset (16 named ANSI slots to bare hex strings), and a paletteToAnsi
# attrset (bare hex -> ANSI slot name covering the full palette), and returns
# the colors enriched with mix/lighten/darken functions, an `ansi` sub-attrset,
# and a `hexToAnsiName` function for mapping any palette hex to an Augment
# ANSI color name.
#
# Both the top-level attrset and withHashtag carry these functions and the
# `ansi` sub-attrset, operating on bare and #-prefixed strings respectively.
#
# Fractions are passed as (num, den) pairs to avoid Nix integer division
# truncation — Nix has no float literals in pure evaluation contexts.
#
# Usage:
# colorLib = import ./colors.nix colors ansiColors paletteToAnsi;
# colorLib.mix "585b70" "cdd6f4" 1 2 # => "9398b2"
# colorLib.withHashtag.mix "#585b70" "#cdd6f4" 1 2 # => "#9398b2"
# colorLib.ansi.black # => "1e1e2e"
# colorLib.withHashtag.ansi.black # => "#1e1e2e"
# colorLib.hexToAnsiName "89b4fa" # => "blue" or null
# colorLib.withHashtag.hexToAnsiName "#89b4fa" # => "blue" or null
# colorLib.lighten "585b70" 1 4 # => "9da0b3"
# colorLib.darken "cdd6f4" 1 4 # => "9aa0b7"
colors: ansiColors_: paletteToAnsi_:
let
ansiColors = if ansiColors_ == null then { } else ansiColors_;
paletteToAnsi = if paletteToAnsi_ == null then { } else paletteToAnsi_;
hexDigits = {
"0" = 0;
"1" = 1;
"2" = 2;
"3" = 3;
"4" = 4;
"5" = 5;
"6" = 6;
"7" = 7;
"8" = 8;
"9" = 9;
"a" = 10;
"b" = 11;
"c" = 12;
"d" = 13;
"e" = 14;
"f" = 15;
"A" = 10;
"B" = 11;
"C" = 12;
"D" = 13;
"E" = 14;
"F" = 15;
};
intDigits = [
"0"
"1"
"2"
"3"
"4"
"5"
"6"
"7"
"8"
"9"
"a"
"b"
"c"
"d"
"e"
"f"
];
# Parse a two-character hex byte string into an integer 0-255.
parseByte = s: hexDigits.${builtins.substring 0 1 s} * 16 + hexDigits.${builtins.substring 1 1 s};
# Encode an integer 0-255 as a two-character lowercase hex string.
encodeByte =
n: builtins.elemAt intDigits (n / 16) + builtins.elemAt intDigits (builtins.bitAnd n 15);
# Parse a bare 6-character hex color string into { r, g, b } integers.
parseColor = hex: {
r = parseByte (builtins.substring 0 2 hex);
g = parseByte (builtins.substring 2 2 hex);
b = parseByte (builtins.substring 4 2 hex);
};
# Encode { r, g, b } integers into a bare 6-character hex string.
encodeColor = c: encodeByte c.r + encodeByte c.g + encodeByte c.b;
# Clamp an integer to [0, 255].
clamp =
n:
if n < 0 then
0
else if n > 255 then
255
else
n;
# Linear interpolation between two integers: a + (b - a) * num / den.
lerpInt =
a: b: num: den:
clamp (a + (b - a) * num / den);
# Mix two bare hex color strings. t = num/den in [0, 1].
# mix c1 c2 0 1 = c1, mix c1 c2 1 1 = c2.
mix =
c1: c2: num: den:
let
a = parseColor c1;
b = parseColor c2;
in
encodeColor {
r = lerpInt a.r b.r num den;
g = lerpInt a.g b.g num den;
b = lerpInt a.b b.b num den;
};
# Lighten a bare hex color string by mixing toward white by amount/den.
lighten =
c: amount: den:
mix c "ffffff" amount den;
# Darken a bare hex color string by mixing toward black by amount/den.
darken =
c: amount: den:
mix c "000000" amount den;
# Strip a leading # from a color string.
stripHash = s: builtins.substring 1 (builtins.stringLength s - 1) s;
# Variants of the functions that accept and return #-prefixed strings.
mixH =
c1: c2: num: den:
"#" + mix (stripHash c1) (stripHash c2) num den;
lightenH =
c: amount: den:
"#" + lighten (stripHash c) amount den;
darkenH =
c: amount: den:
"#" + darken (stripHash c) amount den;
# Map ANSI slot names to the 8 Augment color names.
# Bright variants collapse to their base name.
slotToAugmentName = {
black = "black";
brightBlack = "black";
red = "red";
brightRed = "red";
green = "green";
brightGreen = "green";
yellow = "yellow";
brightYellow = "yellow";
blue = "blue";
brightBlue = "blue";
magenta = "magenta";
brightMagenta = "magenta";
cyan = "cyan";
brightCyan = "cyan";
white = "white";
brightWhite = "white";
};
# Look up a bare hex string in paletteToAnsi, then map the slot name to an
# Augment color name. Returns null if the hex is not in the palette.
hexToAnsiName =
hex:
let
slot = paletteToAnsi.${hex} or null;
in
if slot == null then null else slotToAugmentName.${slot};
# Look up a #-prefixed hex string; strips the # before lookup.
hexToAnsiNameH = hex: hexToAnsiName (stripHash hex);
ansi = ansiColors;
ansiWithHashtag = builtins.mapAttrs (_: v: "#" + v) ansiColors;
withHashtag = builtins.mapAttrs (_: v: "#" + v) colors // {
ansi = ansiWithHashtag;
hexToAnsiName = hexToAnsiNameH;
mix = mixH;
lighten = lightenH;
darken = darkenH;
};
in
colors
// {
inherit
ansi
hexToAnsiName
withHashtag
mix
lighten
darken
;
}