commit cdda64c
delthas
·
2025-02-19 16:03:59 +0000 UTC
parent 1e2ffe2
Use alternative gray color on color schemes with bg == gray With these heuristics, "gray" text should always be readable regardless of the theme, even for 8-color themes. Fixes: https://todo.sr.ht/~delthas/senpai/160
M
app.go
+5,
-4
1@@ -179,7 +179,7 @@ func NewApp(cfg Config) (app *App, err error) {
2
3 mouse := cfg.Mouse
4
5- app.win, err = ui.New(ui.Config{
6+ app.win, app.cfg.Colors, err = ui.New(ui.Config{
7 NickColWidth: cfg.NickColWidth,
8 ChanColWidth: cfg.ChanColWidth,
9 ChanColEnabled: cfg.ChanColEnabled,
10@@ -199,6 +199,7 @@ func NewApp(cfg Config) (app *App, err error) {
11 if err != nil {
12 return
13 }
14+
15 ui.NotifyStart(func(ev *ui.NotifyEvent) {
16 app.events <- event{
17 src: "*",
18@@ -1865,7 +1866,7 @@ func (app *App) formatEvent(ev irc.Event) ui.Line {
19 var body ui.StyledStringBuilder
20 body.Grow(len(ev.User) + 1)
21 body.SetStyle(vaxis.Style{
22- Foreground: vaxis.IndexColor(2),
23+ Foreground: ui.ColorGreen,
24 })
25 body.WriteByte('+')
26 body.SetStyle(vaxis.Style{
27@@ -1959,7 +1960,7 @@ func (app *App) formatEvent(ev irc.Event) ui.Line {
28 body.WriteByte('-')
29 } else {
30 body.SetStyle(vaxis.Style{
31- Foreground: vaxis.IndexColor(2),
32+ Foreground: ui.ColorGreen,
33 })
34 body.WriteByte('+')
35 }
36@@ -2060,7 +2061,7 @@ func (app *App) formatMessage(s *irc.Session, ev irc.MessageEvent) (buffer strin
37 }
38
39 head := ev.User
40- headColor := vaxis.IndexColor(15)
41+ var headColor vaxis.Color
42 if isAction || isNotice {
43 head = "*"
44 } else {
+1,
-1
1@@ -560,7 +560,7 @@ func commandDoNames(app *App, args []string) (err error) {
2 for _, name := range s.Names(buffer) {
3 if name.PowerLevel != "" {
4 sb.SetStyle(vaxis.Style{
5- Foreground: vaxis.IndexColor(2),
6+ Foreground: ui.ColorGreen,
7 })
8 sb.WriteString(name.PowerLevel)
9 sb.SetStyle(vaxis.Style{
+6,
-6
1@@ -73,7 +73,7 @@ func parseColor(s string, c *vaxis.Color) error {
2 }
3
4 if code == -1 {
5- *c = vaxis.Color(0)
6+ *c = ui.ColorDefault
7 return nil
8 }
9
10@@ -149,13 +149,13 @@ func Defaults() Config {
11 TextMaxWidth: 0,
12 StatusEnabled: true,
13 Colors: ui.ConfigColors{
14- Status: ui.ColorGray,
15- Prompt: vaxis.Color(0),
16- Unread: vaxis.Color(0),
17+ Status: ui.ColorDefault, // Overriden by UI later.
18+ Prompt: ui.ColorDefault,
19+ Unread: ui.ColorDefault,
20 Nicks: ui.ColorScheme{
21 Type: ui.ColorSchemeBase,
22- Others: vaxis.Color(0),
23- Self: vaxis.Color(9),
24+ Others: ui.ColorDefault,
25+ Self: ui.ColorRed,
26 },
27 },
28 Debug: false,
M
go.mod
+1,
-1
1@@ -4,7 +4,7 @@ go 1.18
2
3 require (
4 codeberg.org/emersion/go-scfg v0.1.0
5- git.sr.ht/~rockorager/vaxis v0.12.0
6+ git.sr.ht/~rockorager/vaxis v0.12.1-0.20250218200016-0e88f197413a
7 github.com/delthas/go-libnp v0.0.0-20250105150050-96674b98150e
8 github.com/delthas/go-localeinfo v0.0.0-20240813094314-e5413e186769
9 github.com/disintegration/imaging v1.6.2
M
go.sum
+2,
-2
1@@ -1,7 +1,7 @@
2 codeberg.org/emersion/go-scfg v0.1.0 h1:6dnGU0ZI4gX+O5rMjwhoaySItzHG710eXL5TIQKl+uM=
3 codeberg.org/emersion/go-scfg v0.1.0/go.mod h1:0nooW1ufBB4SlJEdTtiVN9Or+bnNM1icOkQ6Tbrq6O0=
4-git.sr.ht/~rockorager/vaxis v0.12.0 h1:7zM2If+mHTliDMLWhermHLnIfNkEIuDs3sHYh1MjdF8=
5-git.sr.ht/~rockorager/vaxis v0.12.0/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
6+git.sr.ht/~rockorager/vaxis v0.12.1-0.20250218200016-0e88f197413a h1:duJRKIJCDSWNUx1A7GmlGK2HcRxbVFrFiXSqAW2EBHE=
7+git.sr.ht/~rockorager/vaxis v0.12.1-0.20250218200016-0e88f197413a/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
8 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
9 github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
10 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+8,
-8
1@@ -828,7 +828,7 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
2 }
3
4 width--
5- drawVerticalLine(vx, x0+width, y0, height)
6+ bs.ui.drawVerticalLine(vx, x0+width, y0, height)
7 clearArea(vx, x0, y0, width, height)
8
9 indexPadding := 1 + int(math.Ceil(math.Log10(float64(len(bs.list)))))
10@@ -844,7 +844,7 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
11 if bi == bs.current || bi == bs.clicked {
12 st.Attribute |= vaxis.AttrReverse
13 } else if b.muted {
14- st.Foreground = ColorGray
15+ st.Foreground = bs.ui.config.Colors.Gray
16 }
17
18 var title string
19@@ -859,7 +859,7 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
20 continue
21 }
22 indexSt := st
23- indexSt.Foreground = ColorGray
24+ indexSt.Foreground = bs.ui.config.Colors.Gray
25 indexText := fmt.Sprintf("%d:", bi+1)
26 printString(vx, &x, y, Styled(indexText, indexSt))
27 x = x0 + indexPadding
28@@ -1043,7 +1043,7 @@ func (bs *BufferList) DrawHorizontalBufferList(vx *Vaxis, x0, y0, width int, off
29 if i == bs.clicked {
30 st.Attribute |= vaxis.AttrReverse
31 } else if b.muted {
32- st.Foreground = ColorGray
33+ st.Foreground = bs.ui.config.Colors.Gray
34 }
35
36 var title string
37@@ -1171,7 +1171,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
38 }
39 }
40 y0++
41- drawHorizontalLine(vx, x0, y0, bs.tlInnerWidth+nickColWidth+9)
42+ bs.ui.drawHorizontalLine(vx, x0, y0, bs.tlInnerWidth+nickColWidth+9)
43 y0++
44
45 if bs.textWidth < bs.tlInnerWidth {
46@@ -1195,10 +1195,10 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
47 if isRead && yi > y0 {
48 yi--
49 st := vaxis.Style{
50- Foreground: ColorGray,
51+ Foreground: bs.ui.config.Colors.Gray,
52 }
53 printIdent(vx, x0+7, yi, nickColWidth, Styled("--", st))
54- drawHorizontalLine(vx, x0, yi, 9+nickColWidth+bs.tlInnerWidth)
55+ bs.ui.drawHorizontalLine(vx, x0, yi, 9+nickColWidth+bs.tlInnerWidth)
56 rulerDrawn = true
57 }
58 }
59@@ -1228,7 +1228,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
60 }
61 if showTime {
62 st := vaxis.Style{
63- Foreground: ColorGray,
64+ Foreground: bs.ui.config.Colors.Gray,
65 }
66 printTime(vx, x0, yi, st, line.At.Local())
67 }
+2,
-1
1@@ -7,8 +7,9 @@ import (
2 "git.sr.ht/~rockorager/vaxis"
3 )
4
5+var ColorDefault = vaxis.Color(0)
6+var ColorGreen = vaxis.IndexColor(2)
7 var ColorRed = vaxis.IndexColor(9)
8-var ColorGray = vaxis.IndexColor(8)
9
10 type ColorSchemeType int
11
+0,
-14
1@@ -261,17 +261,3 @@ func printTime(vx *Vaxis, x int, y int, st vaxis.Style, t time.Time) {
2 func clearArea(vx *Vaxis, x0, y0, width, height int) {
3 vx.window.New(x0, y0, width, height).Clear()
4 }
5-
6-func drawHorizontalLine(vx *Vaxis, x0, y, width int) {
7- for x := x0; x < x0+width; x++ {
8- setCell(vx, x, y, '─', vaxis.Style{
9- Foreground: ColorGray,
10- })
11- }
12-}
13-
14-func drawVerticalLine(vx *Vaxis, x, y0, height int) {
15- for y := y0; y < y0+height; y++ {
16- setCell(vx, x, y, '│', vaxis.Style{})
17- }
18-}
+1,
-2
1@@ -624,8 +624,7 @@ func (e *Editor) Draw(vx *Vaxis, x0, y int, hint string) {
2 i := 0
3 for i < len(display) {
4 s := vaxis.Style{
5- Background: vaxis.IndexColor(0),
6- Attribute: vaxis.AttrReverse,
7+ Attribute: vaxis.AttrReverse,
8 }
9 if ci+autoOff == e.autoCacheIdx {
10 s.Attribute |= vaxis.AttrBold
+10,
-12
1@@ -47,7 +47,7 @@ var hexCodes = []uint32{
2
3 func colorFromCode(code int) (color vaxis.Color) {
4 if code < 0 || 99 <= code {
5- color = vaxis.Color(0)
6+ color = ColorDefault
7 } else if code < 16 {
8 color = baseCodes[code]
9 } else {
10@@ -219,16 +219,16 @@ func parseColor(raw string) (fg, bg vaxis.Color, n int) {
11 raw = raw[n:]
12
13 if len(raw) == 0 || raw[0] != ',' {
14- return fg, vaxis.Color(0), n
15+ return fg, ColorDefault, n
16 }
17
18 n++
19 bg, p := parseColorNumber(raw[1:])
20 n += p
21
22- if bg == vaxis.Color(0) {
23+ if bg == ColorDefault {
24 // Lone comma, do not parse as part of a color code.
25- return fg, vaxis.Color(0), n - 1
26+ return fg, ColorDefault, n - 1
27 }
28
29 return fg, bg, n
30@@ -253,16 +253,16 @@ func parseHexColor(raw string) (fg, bg vaxis.Color, n int) {
31 raw = raw[n:]
32
33 if len(raw) == 0 || raw[0] != ',' {
34- return fg, vaxis.Color(0), n
35+ return fg, ColorDefault, n
36 }
37
38 n++
39 bg, p := parseHexColorNumber(raw[1:])
40 n += p
41
42- if bg == vaxis.Color(0) {
43+ if bg == ColorDefault {
44 // Lone comma, do not parse as part of a color code.
45- return fg, vaxis.Color(0), n - 1
46+ return fg, ColorDefault, n - 1
47 }
48
49 return fg, bg, n
50@@ -302,11 +302,9 @@ func IRCString(raw string) StyledString {
51 }
52 raw = raw[n:]
53 if n == 0 {
54- // Both `fg` and `bg` are equal to
55- // tcell.ColorDefault.
56- current.Foreground = vaxis.Color(0)
57- current.Background = vaxis.Color(0)
58- } else if bg == vaxis.Color(0) {
59+ current.Foreground = ColorDefault
60+ current.Background = ColorDefault
61+ } else if bg == ColorDefault {
62 current.Foreground = fg
63 } else {
64 current.Foreground = fg
M
ui/ui.go
+55,
-12
1@@ -3,6 +3,7 @@ package ui
2 import (
3 "fmt"
4 "image"
5+ "reflect"
6 "strings"
7 "sync/atomic"
8 "time"
9@@ -29,6 +30,7 @@ type Config struct {
10 }
11
12 type ConfigColors struct {
13+ Gray vaxis.Color
14 Status vaxis.Color
15 Prompt vaxis.Color
16 Unread vaxis.Color
17@@ -79,7 +81,7 @@ type UI struct {
18 colorThemeMode vaxis.ColorThemeMode
19 }
20
21-func New(config Config) (ui *UI, err error) {
22+func New(config Config) (ui *UI, colors ConfigColors, err error) {
23 ui = &UI{
24 config: config,
25 clickEvents: make([]clickEvent, 0, 128),
26@@ -105,7 +107,8 @@ func New(config Config) (ui *UI, err error) {
27 window: vx.Window(),
28 }
29
30- if bg := ui.vx.QueryBackground().Params(); len(bg) == 3 {
31+ bg := ui.vx.QueryBackground().Params()
32+ if len(bg) == 3 {
33 if (int(bg[0])+int(bg[1])+int(bg[2]))/3 > 127 {
34 ui.colorThemeMode = vaxis.LightMode
35 } else {
36@@ -115,6 +118,32 @@ func New(config Config) (ui *UI, err error) {
37 ui.colorThemeMode = vaxis.DarkMode
38 }
39
40+ ui.config.Colors.Gray = vaxis.IndexColor(8)
41+ black := ui.vx.QueryColor(vaxis.IndexColor(uint8(0))).Params()
42+ gray := ui.vx.QueryColor(vaxis.IndexColor(uint8(8))).Params()
43+ white := ui.vx.QueryColor(vaxis.IndexColor(uint8(15))).Params()
44+ if len(bg) == 3 && len(gray) == 3 && reflect.DeepEqual(bg, gray) {
45+ // Color theme with background set to gray: gray would be invisible.
46+ if len(black) == 3 && !reflect.DeepEqual(bg, black) {
47+ // Black is distinct from background: use that as gray.
48+ ui.config.Colors.Gray = vaxis.IndexColor(0)
49+ } else if len(white) == 3 && !reflect.DeepEqual(bg, white) {
50+ // Black == gray == background color. (Can happen in 8-color themes.)
51+ // Use an RGB color as the gray color, interpolated from black and white.
52+ p := make([]uint8, 3)
53+ for i := range p {
54+ p[i] = uint8((int(black[i]) + int(white[i])) / 2)
55+ }
56+ ui.config.Colors.Gray = vaxis.RGBColor(p[0], p[1], p[2])
57+ } else {
58+ // Black == gray == background == white. Give up.
59+ ui.config.Colors.Gray = ColorDefault
60+ }
61+ }
62+ if ui.config.Colors.Status == ColorDefault {
63+ ui.config.Colors.Status = ui.config.Colors.Gray
64+ }
65+
66 ui.vx.SetTitle("senpai")
67 ui.vx.SetAppID("senpai")
68
69@@ -143,7 +172,7 @@ func New(config Config) (ui *UI, err error) {
70 ui.e = NewEditor(ui)
71 ui.Resize()
72
73- return
74+ return ui, ui.config.Colors, nil
75 }
76
77 func (ui *UI) ShouldExit() bool {
78@@ -760,6 +789,20 @@ func (ui *UI) ScrollToBuffer() {
79 }
80 }
81
82+func (ui *UI) drawHorizontalLine(vx *Vaxis, x0, y, width int) {
83+ for x := x0; x < x0+width; x++ {
84+ setCell(vx, x, y, '─', vaxis.Style{
85+ Foreground: ui.config.Colors.Gray,
86+ })
87+ }
88+}
89+
90+func (ui *UI) drawVerticalLine(vx *Vaxis, x, y0, height int) {
91+ for y := y0; y < y0+height; y++ {
92+ setCell(vx, x, y, '│', vaxis.Style{})
93+ }
94+}
95+
96 func (ui *UI) drawStatusBar(x0, y, width int) {
97 clearArea(ui.vx, x0, y, width, 1)
98
99@@ -769,7 +812,7 @@ func (ui *UI) drawStatusBar(x0, y, width int) {
100
101 var s StyledStringBuilder
102 s.SetStyle(vaxis.Style{
103- Foreground: ColorGray,
104+ Foreground: ui.config.Colors.Gray,
105 })
106 s.WriteString("--")
107
108@@ -779,7 +822,7 @@ func (ui *UI) drawStatusBar(x0, y, width int) {
109
110 s.Reset()
111 s.SetStyle(vaxis.Style{
112- Foreground: ColorGray,
113+ Foreground: ui.config.Colors.Gray,
114 })
115 s.WriteString(ui.status)
116
117@@ -787,7 +830,7 @@ func (ui *UI) drawStatusBar(x0, y, width int) {
118 }
119
120 func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *buffer, members []irc.Member, offset *int) {
121- drawVerticalLine(vx, x0, y0, height)
122+ ui.drawVerticalLine(vx, x0, y0, height)
123 x0++
124 width--
125 clearArea(vx, x0, y0, width, height)
126@@ -797,7 +840,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
127 printString(vx, &x, y0, Styled("Help", vaxis.Style{
128 Foreground: ui.config.Colors.Status,
129 }))
130- drawHorizontalLine(vx, x0, y0+1, width)
131+ ui.drawHorizontalLine(vx, x0, y0+1, width)
132 y0 += 2
133
134 lines := []string{
135@@ -812,7 +855,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
136 }
137 x := x0
138 printString(vx, &x, y0, Styled(line, st))
139- drawHorizontalLine(vx, x0, y0+1, width)
140+ ui.drawHorizontalLine(vx, x0, y0+1, width)
141 y0 += 2
142 }
143 return
144@@ -837,7 +880,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
145 }
146 y0++
147 height--
148- drawHorizontalLine(vx, x0, y0, width)
149+ ui.drawHorizontalLine(vx, x0, y0, width)
150 y0++
151 height--
152
153@@ -855,7 +898,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
154 actions = append(actions, "→ Leave")
155 for i, action := range actions {
156 x := x0
157- drawHorizontalLine(vx, x0, y0+height-(len(actions)-i)*2, width)
158+ ui.drawHorizontalLine(vx, x0, y0+height-(len(actions)-i)*2, width)
159 printString(vx, &x, y0+height-(len(actions)-i)*2+1, Styled(action, vaxis.Style{}))
160 }
161 height -= 2 * len(actions)
162@@ -895,7 +938,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
163 x += padding - 1
164 powerLevelText := m.PowerLevel[:1]
165 powerLevelSt := vaxis.Style{
166- Foreground: vaxis.IndexColor(2),
167+ Foreground: ColorGreen,
168 Attribute: attr,
169 }
170 printString(vx, &x, y, Styled(powerLevelText, powerLevelSt))
171@@ -907,7 +950,7 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
172 nameText := truncate(vx, m.Name.Name, width-1, "\u2026")
173 if m.Away {
174 name = Styled(nameText, vaxis.Style{
175- Foreground: ColorGray,
176+ Foreground: ui.config.Colors.Gray,
177 Attribute: attr,
178 })
179 } else {