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// MRUFade fades an MRU style by position, reaching 60 percent of the terminal
51// background color at the oldest entry.
52func MRUFade(base renderer.Style, t Theme, i, count int) renderer.Style {
53 if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG {
54 return base
55 }
56 amount := (float64(i) / float64(count-1)) * 0.6
57 base.ForegroundRGB = Blend(base.ForegroundRGB, t.Background, amount)
58 base.BackgroundRGB = Blend(base.BackgroundRGB, t.Background, amount)
59 return base
60}
61
62// Styles is the complete semantic set of terminal chrome styles. The legacy
63// aliases remain until daemon renderers consume the semantic fields directly.
64type Styles struct {
65 // mruStyles are all position/count combinations that status composition
66 // can display. They keep OKLab interpolation out of render paths.
67 mruStyles [9][9]renderer.Style
68
69 SurfaceBar renderer.Style
70 SurfaceInactive renderer.Style
71 SurfaceRecent renderer.Style
72 SurfaceActive renderer.Style
73 BorderMuted renderer.Style
74 BorderActive renderer.Style
75 // BorderWarn is the amber-hued warn-severity border. Unlike every other
76 // role in this camaïeu palette it does not blend toward the resolved
77 // accent color: it keeps the accent's lightness/chroma band but shifts
78 // hue to a fixed amber target (see warnHueDegrees), so Warn reads as
79 // amber and stays distinct from Error regardless of the accent hue.
80 BorderWarn renderer.Style
81 // NeutralBorder is the non-accent structural border for pane dividers and
82 // unfocused pane title bars. It is resolved with the rest of the immutable
83 // chrome snapshot so composition never derives theme colors.
84 NeutralBorder renderer.Style
85
86 TabInactive renderer.Style
87 TabInactiveTitle renderer.Style
88 TabActive renderer.Style
89 TabActiveTitle renderer.Style
90 MRURecent renderer.Style
91
92 PickerBase renderer.Style
93 PickerSelection renderer.Style
94 // HintKey is the keycap badge style for key hints: an accent background
95 // over " key " padding, distinct from PickerSelection's full-line emphasis.
96 HintKey renderer.Style
97 PickerDescription renderer.Style
98 PickerSeparator renderer.Style
99 PromptBase renderer.Style
100 CopyStatus renderer.Style
101 SearchSelection renderer.Style
102
103 // Compatibility aliases for pre-cache render paths.
104 StatusBar renderer.Style
105 Accent renderer.Style
106 Border renderer.Style
107 Selection renderer.Style
108 PaletteDesc renderer.Style
109 TabName renderer.Style
110 TabNameActive renderer.Style
111 TabTitle renderer.Style
112 TabTitleActive renderer.Style
113 PickerName renderer.Style
114 PickerSelectionName renderer.Style
115 PickerSelectionMuted renderer.Style
116}
117
118// NewStyles applies the default automatic policy. New callers that own a
119// policy should use Resolve and retain its complete immutable result.
120func NewStyles(t Theme) Styles {
121 return Resolve(t, domain.ThemeAccent{Mode: domain.ThemeAccentAuto}).Styles
122}
123
124// MRUStyle returns the position-dependent recent-session surface. Color
125// interpolation stays owned by theme; renderers consume only this cached
126// semantic style snapshot.
127func (s Styles) MRUStyle(index, count int) renderer.Style {
128 if count <= 0 || count > len(s.mruStyles) {
129 return s.MRURecent
130 }
131 if index < 0 {
132 index = 0
133 }
134 if index >= count {
135 index = count - 1
136 }
137 return s.mruStyles[count-1][index]
138}