292 lines
7.0 KiB
TypeScript
292 lines
7.0 KiB
TypeScript
import { Scale, Color } from "chroma-js"
|
|
import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
|
|
export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
|
|
import {
|
|
ThemeConfig,
|
|
ThemeAppearance,
|
|
ThemeConfigInputColors,
|
|
} from "./theme_config"
|
|
import { get_ramps } from "./ramps"
|
|
|
|
export interface Theme {
|
|
name: string
|
|
is_light: boolean
|
|
|
|
/**
|
|
* App background, other elements that should sit directly on top of the background.
|
|
*/
|
|
lowest: Layer
|
|
/**
|
|
* Panels, tabs, other UI surfaces that sit on top of the background.
|
|
*/
|
|
middle: Layer
|
|
/**
|
|
* Editors like code buffers, conversation editors, etc.
|
|
*/
|
|
highest: Layer
|
|
|
|
ramps: RampSet
|
|
|
|
popover_shadow: Shadow
|
|
modal_shadow: Shadow
|
|
|
|
players: Players
|
|
syntax?: Partial<ThemeSyntax>
|
|
}
|
|
|
|
export interface Meta {
|
|
name: string
|
|
author: string
|
|
url: string
|
|
license: License
|
|
}
|
|
|
|
export interface License {
|
|
SPDX: SPDXExpression
|
|
}
|
|
|
|
// License name -> License text
|
|
export interface Licenses {
|
|
[key: string]: string
|
|
}
|
|
|
|
// FIXME: Add support for the SPDX expression syntax
|
|
export type SPDXExpression = "MIT"
|
|
|
|
export interface Player {
|
|
cursor: string
|
|
selection: string
|
|
}
|
|
|
|
export interface Players {
|
|
"0": Player
|
|
"1": Player
|
|
"2": Player
|
|
"3": Player
|
|
"4": Player
|
|
"5": Player
|
|
"6": Player
|
|
"7": Player
|
|
}
|
|
|
|
export interface Shadow {
|
|
blur: number
|
|
color: string
|
|
offset: number[]
|
|
}
|
|
|
|
export type StyleSets = keyof Layer
|
|
export interface Layer {
|
|
base: StyleSet
|
|
variant: StyleSet
|
|
on: StyleSet
|
|
accent: StyleSet
|
|
positive: StyleSet
|
|
warning: StyleSet
|
|
negative: StyleSet
|
|
}
|
|
|
|
export interface RampSet {
|
|
neutral: Scale
|
|
red: Scale
|
|
orange: Scale
|
|
yellow: Scale
|
|
green: Scale
|
|
cyan: Scale
|
|
blue: Scale
|
|
violet: Scale
|
|
magenta: Scale
|
|
}
|
|
|
|
export type Styles = keyof StyleSet
|
|
export interface StyleSet {
|
|
default: Style
|
|
active: Style
|
|
disabled: Style
|
|
hovered: Style
|
|
pressed: Style
|
|
inverted: Style
|
|
}
|
|
|
|
export interface Style {
|
|
background: string
|
|
border: string
|
|
foreground: string
|
|
}
|
|
|
|
export function create_theme(theme: ThemeConfig): Theme {
|
|
const {
|
|
name,
|
|
appearance,
|
|
input_color,
|
|
override: { syntax },
|
|
} = theme
|
|
|
|
const is_light = appearance === ThemeAppearance.Light
|
|
const color_ramps: ThemeConfigInputColors = input_color
|
|
|
|
// Chromajs scales from 0 to 1 flipped if is_light is true
|
|
const ramps = get_ramps(is_light, color_ramps)
|
|
const lowest = lowest_layer(ramps)
|
|
const middle = middle_layer(ramps)
|
|
const highest = highest_layer(ramps)
|
|
|
|
const popover_shadow = {
|
|
blur: 4,
|
|
color: ramps
|
|
.neutral(is_light ? 7 : 0)
|
|
.darken()
|
|
.alpha(0.2)
|
|
.hex(), // TODO used blend previously. Replace with something else
|
|
offset: [1, 2],
|
|
}
|
|
|
|
const modal_shadow = {
|
|
blur: 16,
|
|
color: ramps
|
|
.neutral(is_light ? 7 : 0)
|
|
.darken()
|
|
.alpha(0.2)
|
|
.hex(), // TODO used blend previously. Replace with something else
|
|
offset: [0, 2],
|
|
}
|
|
|
|
const players = {
|
|
"0": player(ramps.blue),
|
|
"1": player(ramps.green),
|
|
"2": player(ramps.magenta),
|
|
"3": player(ramps.orange),
|
|
"4": player(ramps.violet),
|
|
"5": player(ramps.cyan),
|
|
"6": player(ramps.red),
|
|
"7": player(ramps.yellow),
|
|
}
|
|
|
|
return {
|
|
name,
|
|
is_light,
|
|
|
|
ramps,
|
|
|
|
lowest,
|
|
middle,
|
|
highest,
|
|
|
|
popover_shadow,
|
|
modal_shadow,
|
|
|
|
players,
|
|
syntax,
|
|
}
|
|
}
|
|
|
|
function player(ramp: Scale): Player {
|
|
return {
|
|
selection: ramp(0.5).alpha(0.24).hex(),
|
|
cursor: ramp(0.5).hex(),
|
|
}
|
|
}
|
|
|
|
function lowest_layer(ramps: RampSet): Layer {
|
|
return {
|
|
base: build_style_set(ramps.neutral, 0.2, 1),
|
|
variant: build_style_set(ramps.neutral, 0.2, 0.7),
|
|
on: build_style_set(ramps.neutral, 0.1, 1),
|
|
accent: build_style_set(ramps.blue, 0.1, 0.5),
|
|
positive: build_style_set(ramps.green, 0.1, 0.5),
|
|
warning: build_style_set(ramps.yellow, 0.1, 0.5),
|
|
negative: build_style_set(ramps.red, 0.1, 0.5),
|
|
}
|
|
}
|
|
|
|
function middle_layer(ramps: RampSet): Layer {
|
|
return {
|
|
base: build_style_set(ramps.neutral, 0.1, 1),
|
|
variant: build_style_set(ramps.neutral, 0.1, 0.7),
|
|
on: build_style_set(ramps.neutral, 0, 1),
|
|
accent: build_style_set(ramps.blue, 0.1, 0.5),
|
|
positive: build_style_set(ramps.green, 0.1, 0.5),
|
|
warning: build_style_set(ramps.yellow, 0.1, 0.5),
|
|
negative: build_style_set(ramps.red, 0.1, 0.5),
|
|
}
|
|
}
|
|
|
|
function highest_layer(ramps: RampSet): Layer {
|
|
return {
|
|
base: build_style_set(ramps.neutral, 0, 1),
|
|
variant: build_style_set(ramps.neutral, 0, 0.7),
|
|
on: build_style_set(ramps.neutral, 0.1, 1),
|
|
accent: build_style_set(ramps.blue, 0.1, 0.5),
|
|
positive: build_style_set(ramps.green, 0.1, 0.5),
|
|
warning: build_style_set(ramps.yellow, 0.1, 0.5),
|
|
negative: build_style_set(ramps.red, 0.1, 0.5),
|
|
}
|
|
}
|
|
|
|
function build_style_set(
|
|
ramp: Scale,
|
|
background_base: number,
|
|
foreground_base: number,
|
|
step = 0.08
|
|
): StyleSet {
|
|
const style_definitions = build_style_definition(
|
|
background_base,
|
|
foreground_base,
|
|
step
|
|
)
|
|
|
|
function color_string(index_or_color: number | Color): string {
|
|
if (typeof index_or_color === "number") {
|
|
return ramp(index_or_color).hex()
|
|
} else {
|
|
return index_or_color.hex()
|
|
}
|
|
}
|
|
|
|
function build_style(style: Styles): Style {
|
|
return {
|
|
background: color_string(style_definitions.background[style]),
|
|
border: color_string(style_definitions.border[style]),
|
|
foreground: color_string(style_definitions.foreground[style]),
|
|
}
|
|
}
|
|
|
|
return {
|
|
default: build_style("default"),
|
|
hovered: build_style("hovered"),
|
|
pressed: build_style("pressed"),
|
|
active: build_style("active"),
|
|
disabled: build_style("disabled"),
|
|
inverted: build_style("inverted"),
|
|
}
|
|
}
|
|
|
|
function build_style_definition(bg_base: number, fg_base: number, step = 0.08) {
|
|
return {
|
|
background: {
|
|
default: bg_base,
|
|
hovered: bg_base + step,
|
|
pressed: bg_base + step * 1.5,
|
|
active: bg_base + step * 2.2,
|
|
disabled: bg_base,
|
|
inverted: fg_base + step * 6,
|
|
},
|
|
border: {
|
|
default: bg_base + step * 1,
|
|
hovered: bg_base + step,
|
|
pressed: bg_base + step,
|
|
active: bg_base + step * 3,
|
|
disabled: bg_base + step * 0.5,
|
|
inverted: bg_base - step * 3,
|
|
},
|
|
foreground: {
|
|
default: fg_base,
|
|
hovered: fg_base,
|
|
pressed: fg_base,
|
|
active: fg_base + step * 6,
|
|
disabled: bg_base + step * 4,
|
|
inverted: bg_base + step * 2,
|
|
},
|
|
}
|
|
}
|