1package theme
2
3import (
4 renderer "github.com/bnema/vev-vt"
5 "github.com/bnema/vev/internal/domain"
6 "github.com/bnema/vev/internal/domain/terminalcap"
7)
8
9// reducedColor reports whether chrome for c must be built from attributes and
10// terminal palette slots instead of RGB surfaces: 16-color and monochrome
11// attachments.
12func reducedColor(c terminalcap.ColorCapabilities) bool {
13 return c.Mode == terminalcap.ANSI16 || c.Mode == terminalcap.Monochrome
14}
15
16// ResolveForColor is Resolve for one attachment's color capability. TrueColor
17// and ANSI256 return exactly what Resolve returns. ANSI16 and Monochrome
18// replace the chrome styles with the reduced-color policy (see reducedStyles)
19// and mark the returned Theme so dimming uses the faint attribute instead of
20// RGB blends. Color is per attachment, so callers resolve once per attachment.
21func ResolveForColor(t Theme, policy domain.ThemeAccent, color terminalcap.ColorCapabilities) ResolvedTheme {
22 if !reducedColor(color) {
23 return Resolve(t, policy)
24 }
25 t.DimByAttribute = true
26 resolved := Resolve(t, policy) // resolved.Theme carries DimByAttribute
27 accent := resolved.Accent
28 slot := -1
29 if (accent.Known || accent.IndexedOnly) && accent.Slot < 16 {
30 slot = int(accent.Slot)
31 }
32 resolved.Styles = reducedStyles(slot, color.Mode == terminalcap.Monochrome)
33 return resolved
34}
35
36// reducedStyles builds the chrome for terminals that cannot show tinted RGB
37// surfaces. Surfaces keep the terminal's default background; hierarchy comes
38// from attributes, and the accent (when known) is only ever a palette slot
39// 0-15 so the terminal's own palette renders it:
40//
41// - active tab, session name, selected rows, hint keys, search selection:
42// bold reverse, so contrast is always the terminal's own fg/bg pair;
43// - copy status, copy selection and the focused pane title: plain reverse
44// (a selection keeps the text's own colors);
45// - inactive tabs and bars: plain; secondary text, muted borders and
46// unfocused titles: faint;
47// - focused borders: bold, in the accent palette slot when there is one;
48// warn borders: bold yellow (slot 3); errors share the focused border.
49//
50// Monochrome uses the same attributes with no color at all. The accent never
51// fills a background: a palette slot is not guaranteed to contrast with the
52// terminal background that reverse video would put on top of it.
53func reducedStyles(accentSlot int, mono bool) Styles {
54 plain := renderer.DefaultStyle()
55 withFG := func(s renderer.Style, slot int) renderer.Style {
56 if !mono && slot >= 0 {
57 s.Foreground = slot
58 }
59 return s
60 }
61 with := func(base renderer.Style, bold, italic, inverse, faint bool) renderer.Style {
62 base.Bold, base.Italic, base.Inverse = bold, italic, inverse
63 if faint {
64 base.Attrs |= renderer.AttrDim
65 }
66 return base
67 }
68 active := with(plain, true, false, true, false)
69 activeTitle := with(plain, false, false, true, false)
70 selectionMuted := with(plain, false, true, true, false)
71 faint := with(plain, false, false, false, true)
72 description := with(plain, false, true, false, true)
73 focusedBorder := with(withFG(plain, accentSlot), true, false, false, false)
74 warn := with(plain, true, true, false, false)
75 if !mono {
76 warn = with(withFG(plain, 3), true, false, false, false)
77 }
78 copySelection := with(plain, false, false, true, false)
79
80 s := Styles{
81 Reduced: true,
82
83 SurfaceBar: plain,
84 SurfaceInactive: plain,
85 SurfaceRecent: plain,
86 SurfaceActive: active,
87 BorderMuted: faint,
88 BorderActive: focusedBorder,
89 BorderWarn: warn,
90 NeutralBorder: faint,
91
92 TabInactive: plain,
93 TabInactiveTitle: faint,
94 TabActive: active,
95 TabActiveTitle: activeTitle,
96 MRURecent: plain,
97
98 PickerBase: plain,
99 PickerSelection: active,
100 HintKey: active,
101 PickerDescription: description,
102 PickerSeparator: faint,
103 PromptBase: plain,
104 CopyStatus: with(plain, false, false, true, false),
105 SearchSelection: active,
106 }
107 // Aliases consumed by render paths that have not migrated yet. Selection
108 // is the copy-mode selection role and StatusBar the focused pane title
109 // bar; both are plain reverse.
110 s.StatusBar = copySelection
111 s.Accent = s.SurfaceActive
112 s.Border = s.BorderMuted
113 s.Selection = copySelection
114 s.PaletteDesc = description
115 s.TabName = s.TabInactive
116 s.TabNameActive = s.TabActive
117 s.TabTitle = s.TabInactiveTitle
118 s.TabTitleActive = s.TabActiveTitle
119 s.PickerName = plain
120 s.PickerSelectionName = active
121 s.PickerSelectionMuted = selectionMuted
122 for count := range s.mruStyles {
123 for index := 0; index <= count; index++ {
124 s.mruStyles[count][index] = plain
125 }
126 }
127 return s
128}