Fixed dark and light themes, ui.theme auto/dark/light
Colors no longer come from the terminal's palette, whose color 0 many color themes set to a grey that is not their background. A theme sets tview's default styles and the color names used in texts, all chosen from entries 16-255 of the 256-color table so terminals without 24-bit color show them exactly. "auto" picks light or dark from the background color the terminal reports. Settable in the Config view, applied at the next start (widgets copy their colors when created). Replaces using the terminal's own background and text colors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
+2
-84
@@ -2,16 +2,11 @@ package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/terminfo"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// terminalInfo is what the terminal said about itself at startup.
|
||||
@@ -19,8 +14,6 @@ type terminalInfo struct {
|
||||
graphics bool // the Kitty graphics protocol is supported
|
||||
bg color.RGBA // its background color, if hasBG
|
||||
hasBG bool
|
||||
fg color.RGBA // its text color, if hasFG
|
||||
hasFG bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -39,13 +32,12 @@ var terminalAnswers = func() terminalInfo {
|
||||
// parseTerminalAnswers reads the answers to probeTerminal's questions.
|
||||
func parseTerminalAnswers(got []byte) (info terminalInfo) {
|
||||
info.graphics = bytes.Contains(got, []byte("_Gi=31;OK"))
|
||||
info.fg, info.hasFG = parseColorAnswer(got, "10")
|
||||
info.bg, info.hasBG = parseColorAnswer(got, "11")
|
||||
return info
|
||||
}
|
||||
|
||||
// parseColorAnswer finds the answer to an OSC 10 (text) or 11 (background)
|
||||
// color query: ESC ] 11 ; rgb:RRRR/GGGG/BBBB, ended by ST or BEL. Each
|
||||
// parseColorAnswer finds the answer to an OSC color query such as 11
|
||||
// (background): ESC ] 11 ; rgb:RRRR/GGGG/BBBB, ended by ST or BEL. Each
|
||||
// component has 1 to 4 hex digits.
|
||||
func parseColorAnswer(got []byte, osc string) (color.RGBA, bool) {
|
||||
prefix := []byte("\x1b]" + osc + ";rgb:")
|
||||
@@ -72,80 +64,6 @@ func parseColorAnswer(got []byte, osc string) (color.RGBA, bool) {
|
||||
return color.RGBA{c[0], c[1], c[2], 255}, true
|
||||
}
|
||||
|
||||
// activeTabTag colors the name of the open view in the tab bar.
|
||||
var activeTabTag = "[black:white]"
|
||||
|
||||
// useTerminalBackground makes the terminal's own background color the
|
||||
// background of every pane, instead of color 0 of the palette ("black"), which
|
||||
// color themes often set to a grey that differs from their background (and
|
||||
// on which grey text almost disappears; in light themes color 0 is a dark
|
||||
// grey). On a light background the text, borders and fields switch to the
|
||||
// terminal's text color and shades between the two, since tview's defaults
|
||||
// (white text, blue fields) are made for dark ones. The accent colors (yellow,
|
||||
// green, red, grey) stay palette colors, which a theme picks to suit its
|
||||
// background. Needs true color, else the exact colors cannot be drawn. Must
|
||||
// run before any widget is created, they copy the styles. It returns a line
|
||||
// for the log that says what was decided and why.
|
||||
func useTerminalBackground(info terminalInfo, env func(string) string) string {
|
||||
if !info.hasBG {
|
||||
return "Background: palette color 0 (the terminal did not report its background color)"
|
||||
}
|
||||
hex := hexColor(info.bg)
|
||||
if !hasTrueColor(env) {
|
||||
return "Background: palette color 0 (the terminal's " + hex + " needs 24-bit color, which " + env("TERM") + " does not have; COLORTERM=truecolor enables it)"
|
||||
}
|
||||
st := &tview.Styles
|
||||
st.PrimitiveBackgroundColor = rgb(info.bg)
|
||||
if luminance(info.bg) <= 0.5 {
|
||||
return "Background: the terminal's own color " + hex
|
||||
}
|
||||
|
||||
fg := color.RGBA{0, 0, 0, 255}
|
||||
if info.hasFG && luminance(info.fg) < luminance(info.bg) {
|
||||
fg = info.fg
|
||||
}
|
||||
st.PrimaryTextColor, st.BorderColor, st.TitleColor, st.GraphicsColor = rgb(fg), rgb(fg), rgb(fg), rgb(fg)
|
||||
st.ContrastBackgroundColor = rgb(mix(info.bg, fg, 0.12)) // input fields, buttons
|
||||
st.ContrastSecondaryTextColor = rgb(mix(info.bg, fg, 0.55)) // placeholders, disabled buttons
|
||||
st.InverseTextColor = rgb(info.bg) // a pressed button: background on text color
|
||||
activeTabTag = "[" + hexColor(info.bg) + ":" + hexColor(fg) + "]"
|
||||
return "Background: the terminal's own color " + hex + ", light, with its text color " + hexColor(fg)
|
||||
}
|
||||
|
||||
func rgb(c color.RGBA) tcell.Color {
|
||||
return tcell.NewRGBColor(int32(c.R), int32(c.G), int32(c.B))
|
||||
}
|
||||
|
||||
func hexColor(c color.RGBA) string { return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B) }
|
||||
|
||||
// mix returns the color a fraction t of the way from a to b.
|
||||
func mix(a, b color.RGBA, t float64) color.RGBA {
|
||||
m := func(x, y uint8) uint8 { return uint8(float64(x) + (float64(y)-float64(x))*t + 0.5) }
|
||||
return color.RGBA{m(a.R, b.R), m(a.G, b.G), m(a.B, b.B), 255}
|
||||
}
|
||||
|
||||
// hasTrueColor reports whether tcell will draw 24-bit colors, by the same
|
||||
// rules: TCELL_TRUECOLOR, COLORTERM, or a terminal description with direct
|
||||
// color (e.g. xterm-ghostty and xterm-kitty, also without COLORTERM).
|
||||
func hasTrueColor(env func(string) string) bool {
|
||||
switch env("TCELL_TRUECOLOR") {
|
||||
case "":
|
||||
case "disable":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
switch strings.ToLower(env("COLORTERM")) {
|
||||
case "truecolor", "24bit", "24-bit":
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(env("TERM"), "-direct") {
|
||||
return true
|
||||
}
|
||||
ti, err := terminfo.LookupTerminfo(env("TERM"))
|
||||
return err == nil && (ti.TrueColor || ti.SetFgBgRGB != "")
|
||||
}
|
||||
|
||||
// luminance is the relative brightness of c, from 0 (black) to 1 (white).
|
||||
func luminance(c color.RGBA) float64 {
|
||||
return (0.2126*float64(c.R) + 0.7152*float64(c.G) + 0.0722*float64(c.B)) / 255
|
||||
|
||||
Reference in New Issue
Block a user