base16.ts

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