refactor(tui): extract system theme generation (#36753)
This commit is contained in:
parent
748b0d8836
commit
4c45a5cf23
10 changed files with 206 additions and 268 deletions
|
|
@ -7,7 +7,8 @@ import {
|
|||
} from "@opentui/core"
|
||||
import { extend, useRenderer } from "@opentui/solid"
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
import { tint, useTheme } from "../context/theme"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { tint } from "../theme/color"
|
||||
import { GoUpsellArtPainter } from "./bg-pulse-render"
|
||||
|
||||
type GoUpsellArtOptions = RenderableOptions<FrameBufferRenderable> & {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { RGBA, TextAttributes } from "@opentui/core"
|
||||
import { For, type JSX } from "solid-js"
|
||||
import { tint, useTheme } from "../context/theme"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { tint } from "../theme/color"
|
||||
import { logo } from "../logo"
|
||||
|
||||
export function Logo() {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ import path from "path"
|
|||
import { fileURLToPath } from "url"
|
||||
import { useLocal } from "../../context/local"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { tint, useTheme } from "../../context/theme"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { tint } from "../../theme/color"
|
||||
import { EmptyBorder, SplitBorder } from "../../ui/border"
|
||||
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
||||
import { useClipboard } from "../../context/clipboard"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
addTheme,
|
||||
allThemes,
|
||||
generateSyntax,
|
||||
generateSystem,
|
||||
hasTheme,
|
||||
isTheme,
|
||||
resolveTheme,
|
||||
|
|
@ -13,11 +12,10 @@ import {
|
|||
setCustomThemes,
|
||||
setSystemTheme,
|
||||
subscribeThemes,
|
||||
terminalMode,
|
||||
tint,
|
||||
upsertTheme,
|
||||
type ThemeJson,
|
||||
} from "../theme"
|
||||
import { generateSystem, terminalMode } from "../theme/system"
|
||||
import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
|
|
@ -63,13 +61,10 @@ export {
|
|||
addTheme,
|
||||
allThemes,
|
||||
generateSyntax,
|
||||
generateSystem,
|
||||
hasTheme,
|
||||
isTheme,
|
||||
resolveTheme,
|
||||
selectedForeground,
|
||||
terminalMode,
|
||||
tint,
|
||||
upsertTheme,
|
||||
type Theme,
|
||||
type ThemeJson,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/** @jsxImportSource @opentui/solid */
|
||||
import type { ColorInput, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { tint } from "../../context/theme"
|
||||
import { tint } from "../../theme/color"
|
||||
import { createEffect, createMemo, For, Match, Switch } from "solid-js"
|
||||
import { buildFileTree, flattenFileTree, type FileTreeItem, type FileTreeRow } from "./diff-viewer-file-tree-utils"
|
||||
import { Panel } from "./diff-viewer-ui"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-j
|
|||
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
|
||||
import open from "open"
|
||||
import { selectedForeground, tint, useTheme } from "../../context/theme"
|
||||
import { selectedForeground, useTheme } from "../../context/theme"
|
||||
import { tint } from "../../theme/color"
|
||||
import type { FormField, FormValue } from "@opencode-ai/client"
|
||||
import type { FormWithLocation } from "../../context/data"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
|
|
|
|||
48
packages/tui/src/theme/color.ts
Normal file
48
packages/tui/src/theme/color.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { RGBA } from "@opentui/core"
|
||||
|
||||
export function ansiToRgba(code: number): RGBA {
|
||||
if (code < 16) {
|
||||
const ansiColors = [
|
||||
"#000000",
|
||||
"#800000",
|
||||
"#008000",
|
||||
"#808000",
|
||||
"#000080",
|
||||
"#800080",
|
||||
"#008080",
|
||||
"#c0c0c0",
|
||||
"#808080",
|
||||
"#ff0000",
|
||||
"#00ff00",
|
||||
"#ffff00",
|
||||
"#0000ff",
|
||||
"#ff00ff",
|
||||
"#00ffff",
|
||||
"#ffffff",
|
||||
]
|
||||
return RGBA.fromHex(ansiColors[code] ?? "#000000")
|
||||
}
|
||||
|
||||
if (code < 232) {
|
||||
const index = code - 16
|
||||
const b = index % 6
|
||||
const g = Math.floor(index / 6) % 6
|
||||
const r = Math.floor(index / 36)
|
||||
const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
|
||||
return RGBA.fromInts(val(r), val(g), val(b))
|
||||
}
|
||||
|
||||
if (code < 256) {
|
||||
const gray = (code - 232) * 10 + 8
|
||||
return RGBA.fromInts(gray, gray, gray)
|
||||
}
|
||||
|
||||
return RGBA.fromInts(0, 0, 0)
|
||||
}
|
||||
|
||||
export function tint(base: RGBA, overlay: RGBA, alpha: number): RGBA {
|
||||
const r = base.r + (overlay.r - base.r) * alpha
|
||||
const g = base.g + (overlay.g - base.g) * alpha
|
||||
const b = base.b + (overlay.b - base.b) * alpha
|
||||
return RGBA.fromInts(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255))
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
|
||||
import { SyntaxStyle, RGBA } from "@opentui/core"
|
||||
import { ansiToRgba } from "./color"
|
||||
import aura from "./assets/aura.json" with { type: "json" }
|
||||
import ayu from "./assets/ayu.json" with { type: "json" }
|
||||
import carbonfox from "./assets/carbonfox.json" with { type: "json" }
|
||||
|
|
@ -297,261 +298,6 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
|||
} as Theme
|
||||
}
|
||||
|
||||
function ansiToRgba(code: number): RGBA {
|
||||
// Standard ANSI colors (0-15)
|
||||
if (code < 16) {
|
||||
const ansiColors = [
|
||||
"#000000", // Black
|
||||
"#800000", // Red
|
||||
"#008000", // Green
|
||||
"#808000", // Yellow
|
||||
"#000080", // Blue
|
||||
"#800080", // Magenta
|
||||
"#008080", // Cyan
|
||||
"#c0c0c0", // White
|
||||
"#808080", // Bright Black
|
||||
"#ff0000", // Bright Red
|
||||
"#00ff00", // Bright Green
|
||||
"#ffff00", // Bright Yellow
|
||||
"#0000ff", // Bright Blue
|
||||
"#ff00ff", // Bright Magenta
|
||||
"#00ffff", // Bright Cyan
|
||||
"#ffffff", // Bright White
|
||||
]
|
||||
return RGBA.fromHex(ansiColors[code] ?? "#000000")
|
||||
}
|
||||
|
||||
// 6x6x6 Color Cube (16-231)
|
||||
if (code < 232) {
|
||||
const index = code - 16
|
||||
const b = index % 6
|
||||
const g = Math.floor(index / 6) % 6
|
||||
const r = Math.floor(index / 36)
|
||||
|
||||
const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
|
||||
return RGBA.fromInts(val(r), val(g), val(b))
|
||||
}
|
||||
|
||||
// Grayscale Ramp (232-255)
|
||||
if (code < 256) {
|
||||
const gray = (code - 232) * 10 + 8
|
||||
return RGBA.fromInts(gray, gray, gray)
|
||||
}
|
||||
|
||||
// Fallback for invalid codes
|
||||
return RGBA.fromInts(0, 0, 0)
|
||||
}
|
||||
|
||||
export function tint(base: RGBA, overlay: RGBA, alpha: number): RGBA {
|
||||
const r = base.r + (overlay.r - base.r) * alpha
|
||||
const g = base.g + (overlay.g - base.g) * alpha
|
||||
const b = base.b + (overlay.b - base.b) * alpha
|
||||
return RGBA.fromInts(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255))
|
||||
}
|
||||
|
||||
export function terminalMode(colors: TerminalColors): "dark" | "light" | undefined {
|
||||
const bg = colors.defaultBackground
|
||||
if (!bg) return
|
||||
const { r, g, b } = RGBA.fromHex(bg)
|
||||
return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? "light" : "dark"
|
||||
}
|
||||
|
||||
export function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJson {
|
||||
const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
|
||||
const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
|
||||
const transparent = RGBA.fromValues(bg.r, bg.g, bg.b, 0)
|
||||
const isDark = mode == "dark"
|
||||
|
||||
const col = (i: number) => {
|
||||
const value = colors.palette[i]
|
||||
if (value) return RGBA.fromHex(value)
|
||||
return ansiToRgba(i)
|
||||
}
|
||||
|
||||
// Generate gray scale based on terminal background
|
||||
const grays = generateGrayScale(bg, isDark)
|
||||
const textMuted = generateMutedTextColor(bg, isDark)
|
||||
|
||||
// ANSI color references
|
||||
const ansiColors = {
|
||||
black: col(0),
|
||||
red: col(1),
|
||||
green: col(2),
|
||||
yellow: col(3),
|
||||
blue: col(4),
|
||||
magenta: col(5),
|
||||
cyan: col(6),
|
||||
white: col(7),
|
||||
redBright: col(9),
|
||||
greenBright: col(10),
|
||||
}
|
||||
|
||||
const diffAlpha = isDark ? 0.22 : 0.14
|
||||
const diffAddedBg = tint(bg, ansiColors.green, diffAlpha)
|
||||
const diffRemovedBg = tint(bg, ansiColors.red, diffAlpha)
|
||||
const diffContextBg = grays[2]
|
||||
const diffAddedLineNumberBg = tint(diffContextBg, ansiColors.green, diffAlpha)
|
||||
const diffRemovedLineNumberBg = tint(diffContextBg, ansiColors.red, diffAlpha)
|
||||
const diffLineNumber = textMuted
|
||||
|
||||
return {
|
||||
theme: {
|
||||
// Primary colors using ANSI
|
||||
primary: ansiColors.cyan,
|
||||
secondary: ansiColors.magenta,
|
||||
accent: ansiColors.cyan,
|
||||
|
||||
// Status colors using ANSI
|
||||
error: ansiColors.red,
|
||||
warning: ansiColors.yellow,
|
||||
success: ansiColors.green,
|
||||
info: ansiColors.cyan,
|
||||
|
||||
// Text colors
|
||||
text: fg,
|
||||
textMuted,
|
||||
selectedListItemText: bg,
|
||||
|
||||
// Background colors - use transparent to respect terminal transparency
|
||||
background: transparent,
|
||||
backgroundPanel: grays[2],
|
||||
backgroundElement: grays[3],
|
||||
backgroundMenu: grays[3],
|
||||
|
||||
// Border colors
|
||||
borderSubtle: grays[6],
|
||||
border: grays[7],
|
||||
borderActive: grays[8],
|
||||
|
||||
// Diff colors
|
||||
diffAdded: ansiColors.green,
|
||||
diffRemoved: ansiColors.red,
|
||||
diffContext: grays[7],
|
||||
diffHunkHeader: grays[7],
|
||||
diffHighlightAdded: ansiColors.greenBright,
|
||||
diffHighlightRemoved: ansiColors.redBright,
|
||||
diffAddedBg,
|
||||
diffRemovedBg,
|
||||
diffContextBg,
|
||||
diffLineNumber,
|
||||
diffAddedLineNumberBg,
|
||||
diffRemovedLineNumberBg,
|
||||
|
||||
// Markdown colors
|
||||
markdownText: fg,
|
||||
markdownHeading: fg,
|
||||
markdownLink: ansiColors.blue,
|
||||
markdownLinkText: ansiColors.cyan,
|
||||
markdownCode: ansiColors.green,
|
||||
markdownBlockQuote: ansiColors.yellow,
|
||||
markdownEmph: ansiColors.yellow,
|
||||
markdownStrong: fg,
|
||||
markdownHorizontalRule: grays[7],
|
||||
markdownListItem: ansiColors.blue,
|
||||
markdownListEnumeration: ansiColors.cyan,
|
||||
markdownImage: ansiColors.blue,
|
||||
markdownImageText: ansiColors.cyan,
|
||||
markdownCodeBlock: fg,
|
||||
|
||||
// Syntax colors
|
||||
syntaxComment: textMuted,
|
||||
syntaxKeyword: ansiColors.magenta,
|
||||
syntaxFunction: ansiColors.blue,
|
||||
syntaxVariable: fg,
|
||||
syntaxString: ansiColors.green,
|
||||
syntaxNumber: ansiColors.yellow,
|
||||
syntaxType: ansiColors.cyan,
|
||||
syntaxOperator: ansiColors.cyan,
|
||||
syntaxPunctuation: fg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> {
|
||||
const grays: Record<number, RGBA> = {}
|
||||
|
||||
// RGBA stores floats in range 0-1, convert to 0-255
|
||||
const bgR = bg.r * 255
|
||||
const bgG = bg.g * 255
|
||||
const bgB = bg.b * 255
|
||||
|
||||
const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
const factor = i / 12.0
|
||||
|
||||
let grayValue: number
|
||||
let newR: number
|
||||
let newG: number
|
||||
let newB: number
|
||||
|
||||
if (isDark) {
|
||||
if (luminance < 10) {
|
||||
grayValue = Math.floor(factor * 0.4 * 255)
|
||||
newR = grayValue
|
||||
newG = grayValue
|
||||
newB = grayValue
|
||||
} else {
|
||||
const newLum = luminance + (255 - luminance) * factor * 0.4
|
||||
|
||||
const ratio = newLum / luminance
|
||||
newR = Math.min(bgR * ratio, 255)
|
||||
newG = Math.min(bgG * ratio, 255)
|
||||
newB = Math.min(bgB * ratio, 255)
|
||||
}
|
||||
} else {
|
||||
if (luminance > 245) {
|
||||
grayValue = Math.floor(255 - factor * 0.4 * 255)
|
||||
newR = grayValue
|
||||
newG = grayValue
|
||||
newB = grayValue
|
||||
} else {
|
||||
const newLum = luminance * (1 - factor * 0.4)
|
||||
|
||||
const ratio = newLum / luminance
|
||||
newR = Math.max(bgR * ratio, 0)
|
||||
newG = Math.max(bgG * ratio, 0)
|
||||
newB = Math.max(bgB * ratio, 0)
|
||||
}
|
||||
}
|
||||
|
||||
grays[i] = RGBA.fromInts(Math.floor(newR), Math.floor(newG), Math.floor(newB))
|
||||
}
|
||||
|
||||
return grays
|
||||
}
|
||||
|
||||
function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA {
|
||||
// RGBA stores floats in range 0-1, convert to 0-255
|
||||
const bgR = bg.r * 255
|
||||
const bgG = bg.g * 255
|
||||
const bgB = bg.b * 255
|
||||
|
||||
const bgLum = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
|
||||
|
||||
let grayValue: number
|
||||
|
||||
if (isDark) {
|
||||
if (bgLum < 10) {
|
||||
// Very dark/black background
|
||||
grayValue = 180 // #b4b4b4
|
||||
} else {
|
||||
// Scale up for lighter dark backgrounds
|
||||
grayValue = Math.min(Math.floor(160 + bgLum * 0.3), 200)
|
||||
}
|
||||
} else {
|
||||
if (bgLum > 245) {
|
||||
// Very light/white background
|
||||
grayValue = 75 // #4b4b4b
|
||||
} else {
|
||||
// Scale down for darker light backgrounds
|
||||
grayValue = Math.max(Math.floor(100 - (255 - bgLum) * 0.2), 60)
|
||||
}
|
||||
}
|
||||
|
||||
return RGBA.fromInts(grayValue, grayValue, grayValue)
|
||||
}
|
||||
|
||||
export function generateSyntax(theme: Theme) {
|
||||
return SyntaxStyle.fromTheme(getSyntaxRules(theme))
|
||||
}
|
||||
|
|
|
|||
144
packages/tui/src/theme/system.ts
Normal file
144
packages/tui/src/theme/system.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { RGBA, type TerminalColors } from "@opentui/core"
|
||||
import { ansiToRgba, tint } from "./color"
|
||||
|
||||
export function terminalMode(colors: TerminalColors): "dark" | "light" | undefined {
|
||||
const bg = colors.defaultBackground
|
||||
if (!bg) return
|
||||
const { r, g, b } = RGBA.fromHex(bg)
|
||||
return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? "light" : "dark"
|
||||
}
|
||||
|
||||
export function generateSystem(colors: TerminalColors, mode: "dark" | "light") {
|
||||
const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
|
||||
const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
|
||||
const transparent = RGBA.fromValues(bg.r, bg.g, bg.b, 0)
|
||||
const isDark = mode === "dark"
|
||||
|
||||
const col = (index: number) => {
|
||||
const value = colors.palette[index]
|
||||
if (value) return RGBA.fromHex(value)
|
||||
return ansiToRgba(index)
|
||||
}
|
||||
|
||||
const grays = generateGrayScale(bg, isDark)
|
||||
const textMuted = generateMutedTextColor(bg, isDark)
|
||||
const ansiColors = {
|
||||
red: col(1),
|
||||
green: col(2),
|
||||
yellow: col(3),
|
||||
blue: col(4),
|
||||
magenta: col(5),
|
||||
cyan: col(6),
|
||||
redBright: col(9),
|
||||
greenBright: col(10),
|
||||
}
|
||||
|
||||
const diffAlpha = isDark ? 0.22 : 0.14
|
||||
const diffAddedBg = tint(bg, ansiColors.green, diffAlpha)
|
||||
const diffRemovedBg = tint(bg, ansiColors.red, diffAlpha)
|
||||
const diffContextBg = grays[2]
|
||||
const diffAddedLineNumberBg = tint(diffContextBg, ansiColors.green, diffAlpha)
|
||||
const diffRemovedLineNumberBg = tint(diffContextBg, ansiColors.red, diffAlpha)
|
||||
|
||||
return {
|
||||
theme: {
|
||||
primary: ansiColors.cyan,
|
||||
secondary: ansiColors.magenta,
|
||||
accent: ansiColors.cyan,
|
||||
error: ansiColors.red,
|
||||
warning: ansiColors.yellow,
|
||||
success: ansiColors.green,
|
||||
info: ansiColors.cyan,
|
||||
text: fg,
|
||||
textMuted,
|
||||
selectedListItemText: bg,
|
||||
background: transparent,
|
||||
backgroundPanel: grays[2],
|
||||
backgroundElement: grays[3],
|
||||
backgroundMenu: grays[3],
|
||||
borderSubtle: grays[6],
|
||||
border: grays[7],
|
||||
borderActive: grays[8],
|
||||
diffAdded: ansiColors.green,
|
||||
diffRemoved: ansiColors.red,
|
||||
diffContext: grays[7],
|
||||
diffHunkHeader: grays[7],
|
||||
diffHighlightAdded: ansiColors.greenBright,
|
||||
diffHighlightRemoved: ansiColors.redBright,
|
||||
diffAddedBg,
|
||||
diffRemovedBg,
|
||||
diffContextBg,
|
||||
diffLineNumber: textMuted,
|
||||
diffAddedLineNumberBg,
|
||||
diffRemovedLineNumberBg,
|
||||
markdownText: fg,
|
||||
markdownHeading: fg,
|
||||
markdownLink: ansiColors.blue,
|
||||
markdownLinkText: ansiColors.cyan,
|
||||
markdownCode: ansiColors.green,
|
||||
markdownBlockQuote: ansiColors.yellow,
|
||||
markdownEmph: ansiColors.yellow,
|
||||
markdownStrong: fg,
|
||||
markdownHorizontalRule: grays[7],
|
||||
markdownListItem: ansiColors.blue,
|
||||
markdownListEnumeration: ansiColors.cyan,
|
||||
markdownImage: ansiColors.blue,
|
||||
markdownImageText: ansiColors.cyan,
|
||||
markdownCodeBlock: fg,
|
||||
syntaxComment: textMuted,
|
||||
syntaxKeyword: ansiColors.magenta,
|
||||
syntaxFunction: ansiColors.blue,
|
||||
syntaxVariable: fg,
|
||||
syntaxString: ansiColors.green,
|
||||
syntaxNumber: ansiColors.yellow,
|
||||
syntaxType: ansiColors.cyan,
|
||||
syntaxOperator: ansiColors.cyan,
|
||||
syntaxPunctuation: fg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> {
|
||||
const grays: Record<number, RGBA> = {}
|
||||
const bgR = bg.r * 255
|
||||
const bgG = bg.g * 255
|
||||
const bgB = bg.b * 255
|
||||
const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
const factor = i / 12
|
||||
|
||||
if (isDark && luminance < 10) {
|
||||
const gray = Math.floor(factor * 0.4 * 255)
|
||||
grays[i] = RGBA.fromInts(gray, gray, gray)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!isDark && luminance > 245) {
|
||||
const gray = Math.floor(255 - factor * 0.4 * 255)
|
||||
grays[i] = RGBA.fromInts(gray, gray, gray)
|
||||
continue
|
||||
}
|
||||
|
||||
const next = isDark ? luminance + (255 - luminance) * factor * 0.4 : luminance * (1 - factor * 0.4)
|
||||
const ratio = next / luminance
|
||||
grays[i] = RGBA.fromInts(
|
||||
Math.floor(Math.min(Math.max(bgR * ratio, 0), 255)),
|
||||
Math.floor(Math.min(Math.max(bgG * ratio, 0), 255)),
|
||||
Math.floor(Math.min(Math.max(bgB * ratio, 0), 255)),
|
||||
)
|
||||
}
|
||||
|
||||
return grays
|
||||
}
|
||||
|
||||
function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA {
|
||||
const luminance = 0.299 * bg.r * 255 + 0.587 * bg.g * 255 + 0.114 * bg.b * 255
|
||||
if (isDark) {
|
||||
const gray = luminance < 10 ? 180 : Math.min(Math.floor(160 + luminance * 0.3), 200)
|
||||
return RGBA.fromInts(gray, gray, gray)
|
||||
}
|
||||
|
||||
const gray = luminance > 245 ? 75 : Math.max(Math.floor(100 - (255 - luminance) * 0.2), 60)
|
||||
return RGBA.fromInts(gray, gray, gray)
|
||||
}
|
||||
|
|
@ -2,8 +2,9 @@ import { expect, test } from "bun:test"
|
|||
import { mkdir, writeFile } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import type { TerminalColors } from "@opentui/core"
|
||||
import { DEFAULT_THEMES, addTheme, allThemes, hasTheme, resolveTheme, terminalMode } from "../src/theme"
|
||||
import { DEFAULT_THEMES, addTheme, allThemes, hasTheme, resolveTheme } from "../src/theme"
|
||||
import { discoverThemes } from "../src/context/theme"
|
||||
import { terminalMode } from "../src/theme/system"
|
||||
import { tmpdir } from "./fixture/fixture"
|
||||
|
||||
test("addTheme writes into module theme store", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue