cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Fix scheme contrast by blending toward the text color

Dami committed 7e2d997
Replacing a color with the palette entry nearest in lightness turned
muted text red and body text teal on some schemes; moving it toward
the scheme's text color keeps grays gray and hues close. Config
warnings name files relative to the config directory, and paths in
notes use ~.
commit
7e2d997efe21c40a1aa5fc8717a73e805e928807
parent
446a78d920b98bb9e1ff565a32f4dd2a72230f02
author
Dami <dami.etoile@gmail.com>
7 files changed, +63 -37
Mcmd/012/main.go4+-
Minternal/config/config.go15+++++++++
Minternal/config/edit.go13+++++++
Minternal/ui/prefs.go4+-
Minternal/ui/theme/contrast.go52++++++++++++++----------------
Minternal/ui/theme/scheme.go10+++---
Minternal/ui/themepicker.go2+-
M cmd/012/main.go +2 -2
M internal/config/config.go +15 -0
M internal/config/edit.go +13 -0
M internal/ui/prefs.go +2 -2
M internal/ui/theme/contrast.go +25 -27
@@ -6,9 +6,8 @@ import (
66 )77 88 // Contrast correction for color schemes, adapted from puzzletea's theme9-// package (same author): WCAG 2 contrast, and a readable replacement for10-// a foreground that falls short, taken from the scheme's own colors when11-// one qualifies.9+// package (same author): WCAG 2 contrast, and a foreground that falls10+// short moved just far enough to be readable.1211 1312 // Contrast minimums.1413 const (
@@ -39,27 +38,35 @@ func contrast(a, b color.Color) float64 {
3938 return (la + 0.05) / (lb + 0.05)4039 }4140 42-// readable returns fg, or when it has less than min contrast with bg,43-// the scheme color closest to it in lightness that has enough, or failing44-// that the nearest color toward black or white that does.45-func readable(fg, bg color.Color, p *Palette, min float64) color.Color {41+// readable returns fg, or when it has less than min contrast with bg, fg42+// moved just far enough toward toward (the scheme's text color, which43+// keeps grays gray and hues close) to reach it, or failing that toward44+// black or white.45+func readable(fg, bg, toward color.Color, min float64) color.Color {4646 if contrast(fg, bg) >= min {4747 return fg4848 }49- target := luminance(fg)50- var best color.Color51- bestDist := math.Inf(1)52- for _, c := range append(p.ANSI[:], p.Foreground, p.Background) {53- if d := math.Abs(luminance(c) - target); contrast(c, bg) >= min && d < bestDist {54- best, bestDist = c, d55- }56- }57- if best != nil {58- return best49+ if toward != nil && contrast(toward, bg) >= min {50+ return blendUntil(fg, toward, bg, min)5951 }6052 return nudge(fg, bg, min)6153 }6254 55+// blendUntil moves fg toward end until it has min contrast with bg; end56+// itself must have it.57+func blendUntil(fg, end, bg color.Color, min float64) color.Color {58+ lo, hi := 0.0, 1.059+ for range 24 {60+ mid := (lo + hi) / 261+ if contrast(blend(fg, end, mid), bg) >= min {62+ hi = mid63+ } else {64+ lo = mid65+ }66+ }67+ return blend(fg, end, hi)68+}69+6370 // nudge blends fg toward white or black, whichever moves it away from6471 // bg, just far enough to reach min contrast.6572 func nudge(fg, bg color.Color, min float64) color.Color {
@@ -74,16 +81,7 @@ func nudge(fg, bg color.Color, min float64) color.Color {
7481 end = color.White7582 }7683 }77- lo, hi := 0.0, 1.078- for range 24 {79- mid := (lo + hi) / 280- if contrast(blend(fg, end, mid), bg) >= min {81- hi = mid82- } else {83- lo = mid84- }85- }86- return blend(fg, end, hi)84+ return blendUntil(fg, end, bg, min)8785 }8886 8987 // distinct returns bg, or when it's too close to the screen to show, bg
M internal/ui/theme/scheme.go +5 -5
@@ -24,12 +24,12 @@ const barBlend = 0.12
2424 // - a background role too close to the screen's is moved apart;2525 // - text that falls short of its contrast minimum (4.5:1 for text, 3:12626 // for hints and lines, 2:1 for unavailable items) against its27-// background is replaced by the scheme color nearest it in lightness28-// that meets it, or nudged toward black or white.27+// background is moved toward the scheme's text color just far enough28+// to meet it, or toward black or white.2929 func FromPalette(p Palette) Theme {3030 t := New(p.Dark)3131 t.Name, t.Palette = p.Name, &p32- text := readable(p.Foreground, p.Background, &p, minText)32+ text := readable(p.Foreground, p.Background, nil, minText)3333 t.Screen = lipgloss.NewStyle().Background(p.Background).Foreground(text)3434 eachRole(&t, func(_ string, s *lipgloss.Style) { *s = mapStyle(*s, &p) })3535 if p.Selection != nil && contrast(p.Selection, p.Background) >= minDistinct {
@@ -119,11 +119,11 @@ func fixContrast(role string, s lipgloss.Style, text color.Color, p *Palette) li
119119 }120120 }121121 if c := s.GetForeground(); !isNoColor(c) && role != "SeriesBg" {122- s = s.Foreground(readable(c, bg, p, minContrast(role)))122+ s = s.Foreground(readable(c, bg, text, minContrast(role)))123123 } else if !isNoColor(s.GetBackground()) && role != "SeriesBg" {124124 // Text on a background role with no color of its own is drawn in125125 // the screen's text color.126- s = s.Foreground(readable(text, bg, p, minContrast(role)))126+ s = s.Foreground(readable(text, bg, nil, minContrast(role)))127127 }128128 return s129129 }
M internal/ui/themepicker.go +1 -1