styles.go RawHistoryFolder
1package theme
2
3import (
4 "math"
5
6 renderer "github.com/bnema/vev-vt"
7 "github.com/bnema/vev/internal/domain"
8)
9
10const accentContrastMin = 3.0
11
12// RelativeLuminance returns a color's WCAG 2.x relative luminance.
13func RelativeLuminance(color renderer.RGB) float64 {
14 linearize := func(channel uint8) float64 {
15 srgb := float64(channel) / 255
16 if srgb <= 0.04045 {
17 return srgb / 12.92
18 }
19 return math.Pow((srgb+0.055)/1.055, 2.4)
20 }
21
22 return 0.2126*linearize(color.R) +
23 0.7152*linearize(color.G) +
24 0.0722*linearize(color.B)
25}
26
27// ContrastRatio returns the WCAG 2.x contrast ratio between two colors.
28func ContrastRatio(a, b renderer.RGB) float64 {
29 aLuminance := RelativeLuminance(a)
30 bLuminance := RelativeLuminance(b)
31 lighter := math.Max(aLuminance, bLuminance)
32 darker := math.Min(aLuminance, bLuminance)
33 return (lighter + 0.05) / (darker + 0.05)
34}
35
36// PulseColor derives the attention pulse color at intensity while preserving
37// the base style's remaining attributes.
38func PulseColor(base renderer.Style, intensity float64) renderer.Style {
39 style := base
40 style.Bold = true
41 if base.HasForegroundRGB && base.HasBackgroundRGB {
42 style.ForegroundRGB = Blend(base.BackgroundRGB, base.ForegroundRGB, intensity)
43 } else {
44 style.HasForegroundRGB = false
45 style.Foreground = 244 + int(intensity*11)
46 }
47 return style
48}
49
50// PulseAttribute is PulseColor for reduced-color attachments: the glyph's
51// on/off beat already pulses, so the visible beat is just bold. Grey indexes
52// would quantize to arbitrary slots or vanish.
53func PulseAttribute(base renderer.Style) renderer.Style {
54 base.Bold = true
55 return base
56}
57
58// MRUFade fades an MRU style by position, reaching 60 percent of the terminal
59// background color at the oldest entry.
60func MRUFade(base renderer.Style, t Theme, i, count int) renderer.Style {
61 if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG {
62 return base
63 }
64 amount := (float64(i) / float64(count-1)) * 0.6
65 base.ForegroundRGB = Blend(base.ForegroundRGB, t.Background, amount)
66 base.BackgroundRGB = Blend(base.BackgroundRGB, t.Background, amount)
67 return base
68}
69
70// Styles is the complete semantic set of terminal chrome styles. The legacy
71// aliases remain until daemon renderers consume the semantic fields directly.
72type Styles struct {
73 // Reduced marks the ANSI16/monochrome policy: no RGB anywhere, and the
74 // attention pulse uses bold instead of a grey ramp (see ResolveForColor).
75 Reduced bool
76
77 // mruStyles are all position/count combinations that status composition
78 // can display. They keep OKLab interpolation out of render paths.
79 mruStyles [9][9]renderer.Style
80
81 SurfaceBar renderer.Style
82 SurfaceInactive renderer.Style
83 SurfaceRecent renderer.Style
84 SurfaceActive renderer.Style
85 BorderMuted renderer.Style
86 BorderActive renderer.Style
87 // BorderWarn is the amber-hued warn-severity border. Unlike every other
88 // role in this camaïeu palette it does not blend toward the resolved
89 // accent color: it keeps the accent's lightness/chroma band but shifts
90 // hue to a fixed amber target (see warnHueDegrees), so Warn reads as
91 // amber and stays distinct from Error regardless of the accent hue.
92 BorderWarn renderer.Style
93 // NeutralBorder is the non-accent structural border for pane dividers and
94 // unfocused pane title bars. It is resolved with the rest of the immutable
95 // chrome snapshot so composition never derives theme colors.
96 NeutralBorder renderer.Style
97
98 TabInactive renderer.Style
99 TabInactiveTitle renderer.Style
100 TabActive renderer.Style
101 TabActiveTitle renderer.Style
102 MRURecent renderer.Style
103
104 PickerBase renderer.Style
105 PickerSelection renderer.Style
106 // HintKey is the keycap badge style for key hints: an accent background
107 // over " key " padding, distinct from PickerSelection's full-line emphasis.
108 HintKey renderer.Style
109 PickerDescription renderer.Style
110 PickerSeparator renderer.Style
111 PromptBase renderer.Style
112 CopyStatus renderer.Style
113 SearchSelection renderer.Style
114
115 // Compatibility aliases for pre-cache render paths.
116 StatusBar renderer.Style
117 Accent renderer.Style
118 Border renderer.Style
119 Selection renderer.Style
120 PaletteDesc renderer.Style
121 TabName renderer.Style
122 TabNameActive renderer.Style
123 TabTitle renderer.Style
124 TabTitleActive renderer.Style
125 PickerName renderer.Style
126 PickerSelectionName renderer.Style
127 PickerSelectionMuted renderer.Style
128}
129
130// NewStyles applies the default automatic policy. New callers that own a
131// policy should use Resolve and retain its complete immutable result.
132func NewStyles(t Theme) Styles {
133 return Resolve(t, domain.ThemeAccent{Mode: domain.ThemeAccentAuto}).Styles
134}
135
136// MRUStyle returns the position-dependent recent-session surface. Color
137// interpolation stays owned by theme; renderers consume only this cached
138// semantic style snapshot.
139func (s Styles) MRUStyle(index, count int) renderer.Style {
140 if count <= 0 || count > len(s.mruStyles) {
141 return s.MRURecent
142 }
143 if index < 0 {
144 index = 0
145 }
146 if index >= count {
147 index = count - 1
148 }
149 return s.mruStyles[count-1][index]
150}