urushi/theme/
definition.rs1use std::fmt;
4
5use urushi_terminal::TerminalBackground;
6
7use crate::{BlockStyle, List, Scrollbar, Table, TextStyle, Tree, View};
8
9use super::{BlockThemeRole, ComponentTheme, SemanticTokens, TextThemeRole};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ColorScheme {
14 Light,
15 Dark,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ThemeMode {
43 Light,
44 Dark,
45 Auto { fallback: ColorScheme },
46}
47
48impl ThemeMode {
49 pub fn resolve(self, background: Option<TerminalBackground>) -> ColorScheme {
50 match self {
51 Self::Light => ColorScheme::Light,
52 Self::Dark => ColorScheme::Dark,
53 Self::Auto { fallback } => background.map_or(fallback, classify_background),
54 }
55 }
56}
57
58fn classify_background(background: TerminalBackground) -> ColorScheme {
59 let red = linear_srgb(background.red());
60 let green = linear_srgb(background.green());
61 let blue = linear_srgb(background.blue());
62 let luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
63 if luminance < 0.5 {
64 ColorScheme::Dark
65 } else {
66 ColorScheme::Light
67 }
68}
69
70fn linear_srgb(channel: u16) -> f64 {
71 let encoded = f64::from(channel) / f64::from(u16::MAX);
72 if encoded <= 0.04045 {
73 encoded / 12.92
74 } else {
75 ((encoded + 0.055) / 1.055).powf(2.4)
76 }
77}
78
79#[derive(Debug, Clone, PartialEq)]
86pub struct Theme {
87 tokens: SemanticTokens,
88 components: ComponentTheme,
89}
90
91impl Theme {
92 pub fn from_tokens(tokens: SemanticTokens) -> Self {
93 let components = ComponentTheme::from_tokens(&tokens);
94 Self::new(tokens, components)
95 }
96
97 pub fn new(tokens: SemanticTokens, components: ComponentTheme) -> Self {
98 Self { tokens, components }
99 }
100
101 pub fn tokens(&self) -> &SemanticTokens {
102 &self.tokens
103 }
104
105 pub fn components(&self) -> &ComponentTheme {
106 &self.components
107 }
108
109 pub fn list<T>(&self, list: &List<T>) -> View
111 where
112 T: fmt::Display,
113 {
114 self.components.list().compose(list)
115 }
116
117 pub fn tree<T>(&self, tree: &Tree<T>) -> View
119 where
120 T: fmt::Display,
121 {
122 self.components.tree().compose(tree)
123 }
124
125 pub fn table<T>(&self, table: &Table<T>) -> View
127 where
128 T: crate::TableRow,
129 {
130 self.components.table().compose(table)
131 }
132
133 pub fn scrollbar(&self, scrollbar: &Scrollbar) -> View {
135 self.components.scrollbar().compose(scrollbar)
136 }
137
138 pub fn text_style<R>(&self, role: R) -> TextStyle
146 where
147 R: TextThemeRole,
148 {
149 role.resolve(self)
150 }
151
152 pub fn block_style<R>(&self, role: R) -> BlockStyle
154 where
155 R: BlockThemeRole,
156 {
157 role.resolve(self)
158 }
159}
160
161#[derive(Debug, Clone, PartialEq)]
163pub struct ThemeSet {
164 light: Theme,
165 dark: Theme,
166}
167
168impl ThemeSet {
169 pub fn new(light: Theme, dark: Theme) -> Self {
170 Self { light, dark }
171 }
172
173 pub fn light(&self) -> &Theme {
174 &self.light
175 }
176
177 pub fn dark(&self) -> &Theme {
178 &self.dark
179 }
180
181 pub fn select(&self, scheme: ColorScheme) -> &Theme {
182 match scheme {
183 ColorScheme::Light => self.light(),
184 ColorScheme::Dark => self.dark(),
185 }
186 }
187}