1use crate::config::Config;
9use crate::types::{
10 CursorShaderConfig, CursorShaderMetadata, ResolvedCursorShaderConfig, ResolvedShaderConfig,
11 ShaderConfig, ShaderMetadata,
12};
13use std::path::PathBuf;
14
15#[allow(dead_code)]
30pub fn resolve_shader_config(
31 user_override: Option<&ShaderConfig>,
32 metadata: Option<&ShaderMetadata>,
33 config: &Config,
34) -> ResolvedShaderConfig {
35 let meta_defaults = metadata.map(|m| &m.defaults);
37
38 macro_rules! resolve {
40 ($field:ident, $global:expr) => {
41 user_override
42 .and_then(|o| o.$field.clone())
43 .or_else(|| meta_defaults.and_then(|m| m.$field.clone()))
44 .unwrap_or($global)
45 };
46 }
47
48 macro_rules! resolve_path {
51 ($field:ident, $global:expr) => {{
52 if let Some(override_val) = user_override.and_then(|o| o.$field.clone()) {
54 if override_val.is_empty() {
55 None } else {
57 Some(Config::resolve_texture_path(&override_val))
58 }
59 } else {
60 let path_str: Option<String> =
62 meta_defaults.and_then(|m| m.$field.clone()).or($global);
63 path_str
64 .filter(|p| !p.is_empty())
65 .map(|p| Config::resolve_texture_path(&p))
66 }
67 }};
68 }
69
70 ResolvedShaderConfig {
71 animation_speed: resolve!(animation_speed, config.custom_shader_animation_speed),
72 brightness: resolve!(brightness, config.custom_shader_brightness),
73 text_opacity: resolve!(text_opacity, config.custom_shader_text_opacity),
74 full_content: resolve!(full_content, config.custom_shader_full_content),
75 channel0: resolve_path!(channel0, config.custom_shader_channel0.clone()),
76 channel1: resolve_path!(channel1, config.custom_shader_channel1.clone()),
77 channel2: resolve_path!(channel2, config.custom_shader_channel2.clone()),
78 channel3: resolve_path!(channel3, config.custom_shader_channel3.clone()),
79 cubemap: resolve_path!(cubemap, config.custom_shader_cubemap.clone()),
80 cubemap_enabled: resolve!(cubemap_enabled, config.custom_shader_cubemap_enabled),
81 use_background_as_channel0: resolve!(
82 use_background_as_channel0,
83 config.custom_shader_use_background_as_channel0
84 ),
85 }
86}
87
88pub fn resolve_cursor_shader_config(
103 user_override: Option<&CursorShaderConfig>,
104 metadata: Option<&CursorShaderMetadata>,
105 config: &Config,
106) -> ResolvedCursorShaderConfig {
107 let meta_defaults = metadata.map(|m| &m.defaults);
109
110 macro_rules! resolve_cursor {
112 ($field:ident, $global:expr) => {
113 user_override
114 .and_then(|o| o.$field)
115 .or_else(|| meta_defaults.and_then(|m| m.$field))
116 .unwrap_or($global)
117 };
118 }
119
120 let animation_speed = user_override
122 .and_then(|o| o.base.animation_speed)
123 .or_else(|| meta_defaults.and_then(|m| m.base.animation_speed))
124 .unwrap_or(config.cursor_shader_animation_speed);
125
126 let base = ResolvedShaderConfig {
129 animation_speed,
130 brightness: 1.0,
131 text_opacity: 1.0,
132 full_content: true, channel0: None,
134 channel1: None,
135 channel2: None,
136 channel3: None,
137 cubemap: None,
138 cubemap_enabled: false,
139 use_background_as_channel0: false,
140 };
141
142 let hides_cursor = resolve_cursor!(hides_cursor, config.cursor_shader_hides_cursor);
144 let disable_in_alt_screen = resolve_cursor!(
145 disable_in_alt_screen,
146 config.cursor_shader_disable_in_alt_screen
147 );
148 let glow_radius = resolve_cursor!(glow_radius, config.cursor_shader_glow_radius);
149 let glow_intensity = resolve_cursor!(glow_intensity, config.cursor_shader_glow_intensity);
150 let trail_duration = resolve_cursor!(trail_duration, config.cursor_shader_trail_duration);
151 let cursor_color = user_override
152 .and_then(|o| o.cursor_color)
153 .or_else(|| meta_defaults.and_then(|m| m.cursor_color))
154 .unwrap_or(config.cursor_shader_color);
155
156 ResolvedCursorShaderConfig {
157 base,
158 hides_cursor,
159 disable_in_alt_screen,
160 glow_radius,
161 glow_intensity,
162 trail_duration,
163 cursor_color,
164 }
165}
166
167#[allow(dead_code)]
168impl ResolvedShaderConfig {
169 pub fn for_shader(
179 shader_name: &str,
180 metadata: Option<&ShaderMetadata>,
181 config: &Config,
182 ) -> Self {
183 let user_override = config.get_shader_override(shader_name);
184 resolve_shader_config(user_override, metadata, config)
185 }
186
187 pub fn channel_paths(&self) -> [Option<PathBuf>; 4] {
189 [
190 self.channel0.clone(),
191 self.channel1.clone(),
192 self.channel2.clone(),
193 self.channel3.clone(),
194 ]
195 }
196
197 pub fn cubemap_path(&self) -> Option<&PathBuf> {
199 if self.cubemap_enabled {
200 self.cubemap.as_ref()
201 } else {
202 None
203 }
204 }
205}
206
207#[allow(dead_code)]
208impl ResolvedCursorShaderConfig {
209 pub fn for_shader(
216 shader_name: &str,
217 metadata: Option<&CursorShaderMetadata>,
218 config: &Config,
219 ) -> Self {
220 let user_override = config.get_cursor_shader_override(shader_name);
221 resolve_cursor_shader_config(user_override, metadata, config)
222 }
223}
224
225#[allow(dead_code)]
229pub mod global_defaults {
230 pub const ANIMATION_SPEED: f32 = 1.0;
231 pub const BRIGHTNESS: f32 = 1.0;
232 pub const TEXT_OPACITY: f32 = 1.0;
233 pub const FULL_CONTENT: bool = false;
234 pub const CUBEMAP_ENABLED: bool = true;
235
236 pub const GLOW_RADIUS: f32 = 80.0;
238 pub const GLOW_INTENSITY: f32 = 0.3;
239 pub const TRAIL_DURATION: f32 = 0.5;
240 pub const CURSOR_COLOR: [u8; 3] = [255, 255, 255];
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::ShaderConfig;
247
248 fn make_test_config() -> Config {
249 Config::default()
250 }
251
252 #[test]
253 fn test_resolve_with_no_overrides() {
254 let config = make_test_config();
255 let resolved = resolve_shader_config(None, None, &config);
256
257 assert_eq!(
258 resolved.animation_speed,
259 config.custom_shader_animation_speed
260 );
261 assert_eq!(resolved.brightness, config.custom_shader_brightness);
262 assert_eq!(resolved.text_opacity, config.custom_shader_text_opacity);
263 assert_eq!(resolved.full_content, config.custom_shader_full_content);
264 }
265
266 #[test]
267 fn test_resolve_with_metadata_defaults() {
268 let config = make_test_config();
269 let shader_defaults = ShaderConfig {
270 animation_speed: Some(0.5),
271 brightness: Some(0.7),
272 ..Default::default()
273 };
274
275 let metadata = ShaderMetadata {
276 name: Some("Test".to_string()),
277 defaults: shader_defaults,
278 ..Default::default()
279 };
280
281 let resolved = resolve_shader_config(None, Some(&metadata), &config);
282
283 assert_eq!(resolved.animation_speed, 0.5);
284 assert_eq!(resolved.brightness, 0.7);
285 assert_eq!(resolved.text_opacity, config.custom_shader_text_opacity);
287 }
288
289 #[test]
290 fn test_resolve_with_user_override() {
291 let config = make_test_config();
292 let user_override = ShaderConfig {
293 animation_speed: Some(2.0),
294 brightness: Some(0.9),
295 ..Default::default()
296 };
297
298 let shader_defaults = ShaderConfig {
299 animation_speed: Some(0.5), text_opacity: Some(0.8), ..Default::default()
302 };
303
304 let metadata = ShaderMetadata {
305 name: Some("Test".to_string()),
306 defaults: shader_defaults,
307 ..Default::default()
308 };
309
310 let resolved = resolve_shader_config(Some(&user_override), Some(&metadata), &config);
311
312 assert_eq!(resolved.animation_speed, 2.0);
314 assert_eq!(resolved.brightness, 0.9);
315 assert_eq!(resolved.text_opacity, 0.8);
317 }
318
319 #[test]
320 fn test_channel_paths() {
321 let resolved = ResolvedShaderConfig {
322 channel0: Some(PathBuf::from("/path/to/tex0.png")),
323 channel1: None,
324 channel2: Some(PathBuf::from("/path/to/tex2.png")),
325 channel3: None,
326 ..Default::default()
327 };
328
329 let paths = resolved.channel_paths();
330 assert!(paths[0].is_some());
331 assert!(paths[1].is_none());
332 assert!(paths[2].is_some());
333 assert!(paths[3].is_none());
334 }
335
336 #[test]
337 fn test_cubemap_path_respects_enabled() {
338 let mut resolved = ResolvedShaderConfig {
339 cubemap: Some(PathBuf::from("/path/to/cubemap")),
340 cubemap_enabled: true,
341 ..Default::default()
342 };
343
344 assert!(resolved.cubemap_path().is_some());
345
346 resolved.cubemap_enabled = false;
347 assert!(resolved.cubemap_path().is_none());
348 }
349}