1import chroma, { Color, Scale } from "chroma-js";
2import { fontWeights, } from "../../common";
3import { withOpacity } from "../../utils/color";
4import Theme, { buildPlayer, Syntax } from "./theme";
5
6export function colorRamp(color: Color): Scale {
7 let hue = color.hsl()[0];
8 let endColor = chroma.hsl(hue, 0.88, 0.96);
9 let startColor = chroma.hsl(hue, 0.68, 0.12);
10 return chroma.scale([startColor, color, endColor]).mode("hsl");
11}
12
13export function createTheme(
14 name: string,
15 isLight: boolean,
16 ramps: { [rampName: string]: Scale },
17): Theme {
18 if (isLight) {
19 for (var rampName in ramps) {
20 ramps[rampName] = ramps[rampName].domain([1, 0]);
21 }
22 ramps.neutral = ramps.neutral.domain([7, 0]);
23 } else {
24 ramps.neutral = ramps.neutral.domain([0, 7]);
25 }
26
27 let blend = isLight ? 0.12 : 0.24;
28
29 function sample(ramp: Scale, index: number): string {
30 return ramp(index).hex();
31 }
32 const darkest = ramps.neutral(isLight ? 7 : 0).hex();
33
34 const backgroundColor = {
35 // Title bar
36 100: {
37 base: sample(ramps.neutral, 1.25),
38 hovered: sample(ramps.neutral, 1.5),
39 active: sample(ramps.neutral, 1.75),
40 },
41 // Midground (panels, etc)
42 300: {
43 base: sample(ramps.neutral, 1),
44 hovered: sample(ramps.neutral, 1.25),
45 active: sample(ramps.neutral, 1.5),
46 },
47 // Editor
48 500: {
49 base: sample(ramps.neutral, 0),
50 hovered: sample(ramps.neutral, 0.25),
51 active: sample(ramps.neutral, 0.5),
52 },
53 on300: {
54 base: sample(ramps.neutral, 0),
55 hovered: sample(ramps.neutral, 0.25),
56 active: sample(ramps.neutral, 0.5),
57 },
58 on500: {
59 base: sample(ramps.neutral, 1.25),
60 hovered: sample(ramps.neutral, 1.5),
61 active: sample(ramps.neutral, 1.75),
62 },
63 ok: {
64 base: withOpacity(sample(ramps.green, 0.5), 0.15),
65 hovered: withOpacity(sample(ramps.green, 0.5), 0.2),
66 active: withOpacity(sample(ramps.green, 0.5), 0.25),
67 },
68 error: {
69 base: withOpacity(sample(ramps.red, 0.5), 0.15),
70 hovered: withOpacity(sample(ramps.red, 0.5), 0.2),
71 active: withOpacity(sample(ramps.red, 0.5), 0.25),
72 },
73 warning: {
74 base: withOpacity(sample(ramps.yellow, 0.5), 0.15),
75 hovered: withOpacity(sample(ramps.yellow, 0.5), 0.2),
76 active: withOpacity(sample(ramps.yellow, 0.5), 0.25),
77 },
78 info: {
79 base: withOpacity(sample(ramps.blue, 0.5), 0.15),
80 hovered: withOpacity(sample(ramps.blue, 0.5), 0.2),
81 active: withOpacity(sample(ramps.blue, 0.5), 0.25),
82 },
83 };
84
85 const borderColor = {
86 primary: sample(ramps.neutral, isLight ? 1.5 : 0),
87 secondary: sample(ramps.neutral, isLight ? 1.25 : 1),
88 muted: sample(ramps.neutral, isLight ? 1 : 3),
89 active: sample(ramps.neutral, isLight ? 4 : 3),
90 onMedia: withOpacity(darkest, 0.1),
91 ok: withOpacity(sample(ramps.green, 0.5), 0.15),
92 error: withOpacity(sample(ramps.red, 0.5), 0.15),
93 warning: withOpacity(sample(ramps.yellow, 0.5), 0.15),
94 info: withOpacity(sample(ramps.blue, 0.5), 0.15),
95 };
96
97 const textColor = {
98 primary: sample(ramps.neutral, 6),
99 secondary: sample(ramps.neutral, 5),
100 muted: sample(ramps.neutral, 5),
101 placeholder: sample(ramps.neutral, 4),
102 active: sample(ramps.neutral, 7),
103 feature: sample(ramps.blue, 0.5),
104 ok: sample(ramps.green, 0.5),
105 error: sample(ramps.red, 0.5),
106 warning: sample(ramps.yellow, 0.5),
107 info: sample(ramps.blue, 0.5),
108 onMedia: darkest,
109 };
110
111 const player = {
112 1: buildPlayer(sample(ramps.blue, 0.5)),
113 2: buildPlayer(sample(ramps.green, 0.5)),
114 3: buildPlayer(sample(ramps.magenta, 0.5)),
115 4: buildPlayer(sample(ramps.orange, 0.5)),
116 5: buildPlayer(sample(ramps.violet, 0.5)),
117 6: buildPlayer(sample(ramps.cyan, 0.5)),
118 7: buildPlayer(sample(ramps.red, 0.5)),
119 8: buildPlayer(sample(ramps.yellow, 0.5)),
120 };
121
122 const editor = {
123 background: backgroundColor[500].base,
124 indent_guide: borderColor.muted,
125 indent_guide_active: borderColor.secondary,
126 line: {
127 active: sample(ramps.neutral, 1),
128 highlighted: sample(ramps.neutral, 1.25), // TODO: Where is this used?
129 },
130 highlight: {
131 selection: player[1].selectionColor,
132 occurrence: withOpacity(sample(ramps.neutral, 3.5), blend),
133 activeOccurrence: withOpacity(sample(ramps.neutral, 3.5), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
134 matchingBracket: backgroundColor[500].active, // TODO: Not hooked up
135 match: sample(ramps.violet, 0.15),
136 activeMatch: withOpacity(sample(ramps.violet, 0.4), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
137 related: backgroundColor[500].hovered,
138 },
139 gutter: {
140 primary: textColor.placeholder,
141 active: textColor.active,
142 },
143 };
144
145 const syntax: Syntax = {
146 primary: {
147 color: sample(ramps.neutral, 7),
148 weight: fontWeights.normal,
149 },
150 comment: {
151 color: sample(ramps.neutral, 5),
152 weight: fontWeights.normal,
153 },
154 punctuation: {
155 color: sample(ramps.neutral, 6),
156 weight: fontWeights.normal,
157 },
158 constant: {
159 color: sample(ramps.neutral, 4),
160 weight: fontWeights.normal,
161 },
162 keyword: {
163 color: sample(ramps.blue, 0.5),
164 weight: fontWeights.normal,
165 },
166 function: {
167 color: sample(ramps.yellow, 0.5),
168 weight: fontWeights.normal,
169 },
170 type: {
171 color: sample(ramps.cyan, 0.5),
172 weight: fontWeights.normal,
173 },
174 constructor: {
175 color: sample(ramps.blue, 0.5),
176 weight: fontWeights.normal,
177 },
178 variant: {
179 color: sample(ramps.blue, 0.5),
180 weight: fontWeights.normal,
181 },
182 property: {
183 color: sample(ramps.blue, 0.5),
184 weight: fontWeights.normal,
185 },
186 enum: {
187 color: sample(ramps.orange, 0.5),
188 weight: fontWeights.normal,
189 },
190 operator: {
191 color: sample(ramps.orange, 0.5),
192 weight: fontWeights.normal,
193 },
194 string: {
195 color: sample(ramps.orange, 0.5),
196 weight: fontWeights.normal,
197 },
198 number: {
199 color: sample(ramps.green, 0.5),
200 weight: fontWeights.normal,
201 },
202 boolean: {
203 color: sample(ramps.green, 0.5),
204 weight: fontWeights.normal,
205 },
206 predictive: {
207 color: textColor.muted,
208 weight: fontWeights.normal,
209 },
210 title: {
211 color: sample(ramps.yellow, 0.5),
212 weight: fontWeights.bold,
213 },
214 emphasis: {
215 color: textColor.feature,
216 weight: fontWeights.normal,
217 },
218 "emphasis.strong": {
219 color: textColor.feature,
220 weight: fontWeights.bold,
221 },
222 linkUri: {
223 color: sample(ramps.green, 0.5),
224 weight: fontWeights.normal,
225 underline: true,
226 },
227 linkText: {
228 color: sample(ramps.orange, 0.5),
229 weight: fontWeights.normal,
230 italic: true,
231 },
232 };
233
234 const shadow = withOpacity(
235 ramps.neutral(isLight ? 7 : 0).darken().hex(),
236 blend);
237
238 return {
239 name,
240 backgroundColor,
241 borderColor,
242 textColor,
243 iconColor: textColor,
244 editor,
245 syntax,
246 player,
247 shadow,
248 };
249}