Switch the theme live

Widgets copy their colors when they are created, so they now get role
tokens (one unique RGB value per role) instead of colors, and a wrapper
around the tcell screen replaces the tokens with the current theme's
colors in SetContent. Changing the theme in the Config view swaps that
table; the next draw recolors everything. Poster colors that equal a
token are nudged off it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
2026-10-21 12:11:19 +02:00
co-authored by Claude Opus 5.5
parent 24f7c38a5b
commit 67f5cf6ce7
11 changed files with 171 additions and 38 deletions
+45 -9
View File
@@ -51,29 +51,65 @@ func TestChooseTheme(t *testing.T) {
}
}
func TestThemeApply(t *testing.T) {
oldStyles, oldCurrent := tview.Styles, current
func TestUseTokens(t *testing.T) {
oldStyles := tview.Styles
oldNames := map[string]tcell.Color{}
for k, v := range tcell.ColorNames {
oldNames[k] = v
}
defer func() {
tview.Styles, current = oldStyles, oldCurrent
tview.Styles = oldStyles
for k, v := range oldNames {
tcell.ColorNames[k] = v
}
}()
lightTheme.apply()
if tview.Styles.PrimitiveBackgroundColor != lightTheme.bg || tview.Styles.PrimaryTextColor != lightTheme.text {
useTokens()
if tview.Styles.PrimitiveBackgroundColor != tokens.bg || tview.Styles.PrimaryTextColor != tokens.text {
t.Error("tview styles not set")
}
// Color names in texts mean the theme's colors.
if named("yellow") != lightTheme.yellow || tcell.GetColor("black") != lightTheme.bg || tcell.GetColor("white") != lightTheme.text {
// Color names in texts mean the roles.
if named("yellow") != tokens.yellow || tcell.GetColor("black") != tokens.bg || tcell.GetColor("white") != tokens.text {
t.Error("color names not set")
}
if current.name != "light" {
t.Error("current theme not set")
}
func TestThemedScreenSwitchesLive(t *testing.T) {
sim := tcell.NewSimulationScreen("")
if err := sim.Init(); err != nil {
t.Fatal(err)
}
defer sim.Fini()
sim.SetSize(4, 1)
s := newThemedScreen(sim, darkTheme)
poster := tcell.NewRGBColor(10, 20, 30)
draw := func() {
s.SetContent(0, 0, 'a', nil, tcell.StyleDefault.Foreground(tokens.yellow).Background(tokens.bg))
s.SetContent(1, 0, 'b', nil, tcell.StyleDefault.Foreground(poster).Background(poster))
}
check := func(th theme) {
_, _, st, _ := sim.GetContent(0, 0)
if fg, bg, _ := st.Decompose(); fg != th.yellow || bg != th.bg {
t.Errorf("%s: role colors drawn as %v on %v", th.name, fg, bg)
}
_, _, st, _ = sim.GetContent(1, 0)
if fg, bg, _ := st.Decompose(); fg != poster || bg != poster {
t.Errorf("%s: other colors changed to %v on %v", th.name, fg, bg)
}
}
draw()
check(darkTheme)
s.setTheme(lightTheme)
draw()
check(lightTheme)
// A poster color that equals a role color is nudged off it.
if c := notToken(tokens.red); c == tokens.red {
t.Error("role color in a picture not nudged")
}
if notToken(poster) != poster {
t.Error("ordinary color changed")
}
}