diff --git a/src/components/theme-creator/ColorPicker.tsx b/src/components/theme-creator/ColorPicker.tsx index 8bf2be105..e45a46dd7 100644 --- a/src/components/theme-creator/ColorPicker.tsx +++ b/src/components/theme-creator/ColorPicker.tsx @@ -385,9 +385,11 @@ function Popover({ export function ColorPicker({ value, onChange, + unset = false, }: { value: string; onChange: (hex: string) => void; + unset?: boolean; }) { const [open, setOpen] = useState(false); const [hsva, setHsvaRaw] = useState<[number, number, number, number]>(() => hexToHsva(value)); @@ -444,9 +446,9 @@ export function ColorPicker({ padding: 0, overflow: "hidden" }} - title={value} + title={unset ? undefined : value} > -
+ {!unset &&
} {open && anchorRect && ( void; +}) { + return ( + + ); +} + +function ColorRow({ field, label, picked, color, text, unset, onChange, children }: { + field: string; + label: string; + picked: boolean; + color: string; + text: string; + unset?: boolean; + onChange: (hex: string) => void; + children?: React.ReactNode; +}) { + return ( +
+ + {label} + {text} + {children} +
+ ); +} + function ColorEditor({ draft, setDraft, @@ -328,13 +387,11 @@ function ColorEditor({ const setUiColor = (field: keyof UITheme, value: string) => setDraft((d) => ({ ...d, ui: { ...d.ui, [field]: value } })); - const setTermColor = (field: keyof TerminalTheme, value: string) => + const setTermColor = (field: keyof TerminalTheme, value: string | undefined) => setDraft((d) => ({ ...d, terminal: { ...d.terminal, [field]: value } })); const [searchVar, setSearchVar] = useState(null); - const ui = draft.ui as unknown as Record; - const term = draft.terminal as unknown as Record; const scrollRef = useRef(null); // Scroll to first match when pickedFields changes @@ -344,17 +401,6 @@ function ColorEditor({ first?.scrollIntoView({ behavior: "smooth", block: "center" }); }, [pickedFields]); - const rowStyle = (field: string): React.CSSProperties => - pickedFields.has(field) - ? { - borderRadius: 4, - boxShadow: "inset 0 0 0 1px var(--t-accent)", - background: "color-mix(in srgb, var(--t-accent) 12%, transparent)", - padding: "2px 4px", - margin: "0 -4px", - } - : { padding: "2px 4px", margin: "0 -4px" }; - return ( <> {searchVar && setSearchVar(null)} />} @@ -397,38 +443,27 @@ function ColorEditor({ {uiGroups.map((group) => (

{group.label}

- {group.fields.map((field) => ( -
- { + const varName = fieldToVar(field); + return ( + setUiColor(field, hex)} - /> - {fieldLabels[field] ?? field} - {ui[field as string]} - -
- ))} + setSearchVar(searchVar === varName ? null : varName)} + /> + + ); + })}
))} @@ -475,23 +510,30 @@ function ColorEditor({ {terminalGroups.map((group) => (

{group.label}

- {group.fields.map((field) => ( -
- = 7 - ? term[field as string].slice(0, 7) : "#000000"} - onChange={(hex) => setTermColor(field, hex)} - /> - {fieldLabels[field] ?? field} - {term[field as string].slice(0, 7)} -
- ))} + {group.fields.map((field) => { + const value = draft.terminal[field]; + const hex = value?.startsWith("#") && value.length >= 7 ? value.slice(0, 7) : undefined; + return ( + setTermColor(field, next)} + > + {value && OPTIONAL_TERMINAL_FIELDS.has(field) && ( + setTermColor(field, undefined)} + /> + )} + + ); + })}
))}
diff --git a/src/components/theme-creator/colorGroups.ts b/src/components/theme-creator/colorGroups.ts index 8b73aef48..18f2996fa 100644 --- a/src/components/theme-creator/colorGroups.ts +++ b/src/components/theme-creator/colorGroups.ts @@ -31,11 +31,13 @@ export function getUiGroups(t: TFunction): { label: string; fields: (keyof UIThe ]; } +export const OPTIONAL_TERMINAL_FIELDS: ReadonlySet = new Set(["selectionForeground"]); + export function getTerminalGroups(t: TFunction): { label: string; fields: (keyof TerminalTheme)[] }[] { return [ { label: t("themeCreator.groups.terminalBase"), - fields: ["background", "foreground", "cursor", "selectionBackground"], + fields: ["background", "foreground", "cursor", "selectionBackground", "selectionForeground"], }, { label: t("themeCreator.groups.ansiColors"), @@ -63,6 +65,7 @@ export function getFieldLabels(t: TFunction): Record { textNotice: t("themeCreator.fields.textNotice"), background: t("themeCreator.fields.background"), foreground: t("themeCreator.fields.foreground"), cursor: t("themeCreator.fields.cursor"), selectionBackground: t("themeCreator.fields.selectionBackground"), + selectionForeground: t("themeCreator.fields.selectionForeground"), black: t("themeCreator.fields.black"), red: t("themeCreator.fields.red"), green: t("themeCreator.fields.green"), yellow: t("themeCreator.fields.yellow"), blue: t("themeCreator.fields.blue"), magenta: t("themeCreator.fields.magenta"), cyan: t("themeCreator.fields.cyan"), white: t("themeCreator.fields.white"), brightBlack: t("themeCreator.fields.brightBlack"), brightRed: t("themeCreator.fields.brightRed"), brightGreen: t("themeCreator.fields.brightGreen"), diff --git a/src/i18n/locales/cs/themeCreator.json b/src/i18n/locales/cs/themeCreator.json index 5d1568fff..81b477b93 100644 --- a/src/i18n/locales/cs/themeCreator.json +++ b/src/i18n/locales/cs/themeCreator.json @@ -34,7 +34,8 @@ "sizePx": "Velikost (px)", "lineHeight": "Výška řádku", "terminalFont": "Písmo terminálu", - "highlightUsing": "Zvýraznit prvky používající {{varName}}" + "highlightUsing": "Zvýraznit prvky používající {{varName}}", + "resetColor": "Obnovit výchozí" }, "groups": { "backgrounds": "Pozadí", @@ -84,6 +85,7 @@ "foreground": "Popředí", "cursor": "Kurzor", "selectionBackground": "Výběr", + "selectionForeground": "Vybraný text", "black": "Černá", "red": "Červená", "green": "Zelená", diff --git a/src/i18n/locales/en/themeCreator.json b/src/i18n/locales/en/themeCreator.json index 27e80a271..e63e48a59 100644 --- a/src/i18n/locales/en/themeCreator.json +++ b/src/i18n/locales/en/themeCreator.json @@ -32,7 +32,8 @@ "sizePx": "Size (px)", "lineHeight": "Line height", "terminalFont": "Terminal Font", - "highlightUsing": "Highlight elements using {{varName}}" + "highlightUsing": "Highlight elements using {{varName}}", + "resetColor": "Reset to default" }, "groups": { "backgrounds": "Backgrounds", @@ -82,6 +83,7 @@ "foreground": "Foreground", "cursor": "Cursor", "selectionBackground": "Selection", + "selectionForeground": "Selected Text", "black": "Black", "red": "Red", "green": "Green", diff --git a/src/i18n/locales/fr/themeCreator.json b/src/i18n/locales/fr/themeCreator.json index 43d1bf0ad..87261f977 100644 --- a/src/i18n/locales/fr/themeCreator.json +++ b/src/i18n/locales/fr/themeCreator.json @@ -33,7 +33,8 @@ "sizePx": "Taille (px)", "lineHeight": "Hauteur de ligne", "terminalFont": "Police du terminal", - "highlightUsing": "Mettre en surbrillance les éléments utilisant {{varName}}" + "highlightUsing": "Mettre en surbrillance les éléments utilisant {{varName}}", + "resetColor": "Réinitialiser" }, "groups": { "backgrounds": "Arrière-plans", @@ -83,6 +84,7 @@ "foreground": "Premier plan", "cursor": "Curseur", "selectionBackground": "Sélection", + "selectionForeground": "Texte sélectionné", "black": "Noir", "red": "Rouge", "green": "Vert", diff --git a/src/i18n/locales/ru/themeCreator.json b/src/i18n/locales/ru/themeCreator.json index 5bc19a6f7..ee8f0a7d5 100644 --- a/src/i18n/locales/ru/themeCreator.json +++ b/src/i18n/locales/ru/themeCreator.json @@ -34,7 +34,8 @@ "sizePx": "Размер (px)", "lineHeight": "Межстрочный интервал", "terminalFont": "Шрифт терминала", - "highlightUsing": "Подсветить элементы, использующие {{varName}}" + "highlightUsing": "Подсветить элементы, использующие {{varName}}", + "resetColor": "Сбросить" }, "groups": { "backgrounds": "Фоны", @@ -84,6 +85,7 @@ "foreground": "Передний план", "cursor": "Курсор", "selectionBackground": "Выделение", + "selectionForeground": "Выделенный текст", "black": "Чёрный", "red": "Красный", "green": "Зелёный", diff --git a/src/i18n/locales/tr/themeCreator.json b/src/i18n/locales/tr/themeCreator.json index 23ccc2007..9ea6ee1a7 100644 --- a/src/i18n/locales/tr/themeCreator.json +++ b/src/i18n/locales/tr/themeCreator.json @@ -32,7 +32,8 @@ "sizePx": "Boyut (px)", "lineHeight": "Satır yüksekliği", "terminalFont": "Terminal yazı tipi", - "highlightUsing": "{{varName}} kullanan öğeleri vurgula" + "highlightUsing": "{{varName}} kullanan öğeleri vurgula", + "resetColor": "Varsayılana sıfırla" }, "groups": { "backgrounds": "Arka planlar", @@ -82,6 +83,7 @@ "foreground": "Ön plan", "cursor": "İmleç", "selectionBackground": "Seçim", + "selectionForeground": "Seçili Metin", "black": "Siyah", "red": "Kırmızı", "green": "Yeşil", diff --git a/src/i18n/locales/zh/themeCreator.json b/src/i18n/locales/zh/themeCreator.json index de71cf986..da449aa0d 100644 --- a/src/i18n/locales/zh/themeCreator.json +++ b/src/i18n/locales/zh/themeCreator.json @@ -31,7 +31,8 @@ "sizePx": "大小(px)", "lineHeight": "行高", "terminalFont": "终端字体", - "highlightUsing": "高亮使用 {{varName}} 的元素" + "highlightUsing": "高亮使用 {{varName}} 的元素", + "resetColor": "恢复默认" }, "groups": { "backgrounds": "背景", @@ -81,6 +82,7 @@ "foreground": "前景", "cursor": "光标", "selectionBackground": "选择", + "selectionForeground": "选中文本", "black": "黑色", "red": "红色", "green": "绿色", diff --git a/src/themes/types.ts b/src/themes/types.ts index fc467c3e6..2944e9827 100644 --- a/src/themes/types.ts +++ b/src/themes/types.ts @@ -3,6 +3,8 @@ export interface TerminalTheme { foreground: string; cursor: string; selectionBackground: string; + /** Unset: selected text keeps its own colours. */ + selectionForeground?: string; black: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; white: string; brightBlack: string; brightRed: string; brightGreen: string; diff --git a/src/utils/terminalColors.test.ts b/src/utils/terminalColors.test.ts index f6654c858..200a0bbec 100644 --- a/src/utils/terminalColors.test.ts +++ b/src/utils/terminalColors.test.ts @@ -22,6 +22,17 @@ describe("terminalColorsFor", () => { }); }); + it("uses the theme's selected-text colour when it sets one", () => { + expect(terminalColorsFor(theme({ selectionForeground: "#21b568" }))).toMatchObject({ + fg: "#e2e8f0", + selectionFg: "#21b568", + }); + }); + + it("falls back to the foreground when the selected-text colour is unparseable", () => { + expect(terminalColorsFor(theme({ selectionForeground: "green" }))).toMatchObject({ selectionFg: "#e2e8f0" }); + }); + it("gives up on colours it cannot parse", () => { expect(terminalColorsFor(theme({ background: "rgb(1, 2, 3)" }))).toBeNull(); }); diff --git a/src/utils/terminalColors.ts b/src/utils/terminalColors.ts index b04d11d24..7ba615643 100644 --- a/src/utils/terminalColors.ts +++ b/src/utils/terminalColors.ts @@ -25,8 +25,9 @@ export function terminalColorsFor(theme: TerminalTheme): TerminalColors | null { const bg = parseHex(theme.background); const sel = parseHex(theme.selectionBackground); if (!fg || !bg || !sel) return null; + const selFg = (theme.selectionForeground && parseHex(theme.selectionForeground)) || fg; const blended = sel.rgb.map((c, i) => c * sel.alpha + bg.rgb[i] * (1 - sel.alpha)); - return { fg: toHex(fg.rgb), bg: toHex(bg.rgb), selectionFg: toHex(fg.rgb), selectionBg: toHex(blended) }; + return { fg: toHex(fg.rgb), bg: toHex(bg.rgb), selectionFg: toHex(selFg.rgb), selectionBg: toHex(blended) }; } export function currentTerminalColors(): TerminalColors | null {