use crate::config::Config;
use crate::types::{
CursorShaderConfig, CursorShaderMetadata, ResolvedCursorShaderConfig, ResolvedShaderConfig,
ShaderConfig, ShaderMetadata,
};
use std::path::PathBuf;
pub fn resolve_shader_config(
user_override: Option<&ShaderConfig>,
metadata: Option<&ShaderMetadata>,
config: &Config,
) -> ResolvedShaderConfig {
let meta_defaults = metadata.map(|m| &m.defaults);
macro_rules! resolve {
($field:ident, $global:expr) => {
user_override
.and_then(|o| o.$field.clone())
.or_else(|| meta_defaults.and_then(|m| m.$field.clone()))
.unwrap_or($global)
};
}
macro_rules! resolve_path {
($field:ident, $global:expr) => {{
if let Some(override_val) = user_override.and_then(|o| o.$field.clone()) {
if override_val.is_empty() {
None } else {
Some(Config::resolve_texture_path(&override_val))
}
} else {
let path_str: Option<String> =
meta_defaults.and_then(|m| m.$field.clone()).or($global);
path_str
.filter(|p| !p.is_empty())
.map(|p| Config::resolve_texture_path(&p))
}
}};
}
ResolvedShaderConfig {
animation_speed: resolve!(animation_speed, config.shader.custom_shader_animation_speed),
brightness: resolve!(brightness, config.shader.custom_shader_brightness),
text_opacity: resolve!(text_opacity, config.shader.custom_shader_text_opacity),
full_content: resolve!(full_content, config.shader.custom_shader_full_content),
channel0: resolve_path!(channel0, config.shader.custom_shader_channel0.clone()),
channel1: resolve_path!(channel1, config.shader.custom_shader_channel1.clone()),
channel2: resolve_path!(channel2, config.shader.custom_shader_channel2.clone()),
channel3: resolve_path!(channel3, config.shader.custom_shader_channel3.clone()),
cubemap: resolve_path!(cubemap, config.shader.custom_shader_cubemap.clone()),
cubemap_enabled: resolve!(cubemap_enabled, config.shader.custom_shader_cubemap_enabled),
use_background_as_channel0: resolve!(
use_background_as_channel0,
config.shader.custom_shader_use_background_as_channel0
),
}
}
pub fn resolve_cursor_shader_config(
user_override: Option<&CursorShaderConfig>,
metadata: Option<&CursorShaderMetadata>,
config: &Config,
) -> ResolvedCursorShaderConfig {
let meta_defaults = metadata.map(|m| &m.defaults);
macro_rules! resolve_cursor {
($field:ident, $global:expr) => {
user_override
.and_then(|o| o.$field)
.or_else(|| meta_defaults.and_then(|m| m.$field))
.unwrap_or($global)
};
}
let animation_speed = user_override
.and_then(|o| o.base.animation_speed)
.or_else(|| meta_defaults.and_then(|m| m.base.animation_speed))
.unwrap_or(config.shader.cursor_shader_animation_speed);
let base = ResolvedShaderConfig {
animation_speed,
brightness: 1.0,
text_opacity: 1.0,
full_content: true, channel0: None,
channel1: None,
channel2: None,
channel3: None,
cubemap: None,
cubemap_enabled: false,
use_background_as_channel0: false,
};
let hides_cursor = resolve_cursor!(hides_cursor, config.shader.cursor_shader_hides_cursor);
let disable_in_alt_screen = resolve_cursor!(
disable_in_alt_screen,
config.shader.cursor_shader_disable_in_alt_screen
);
let glow_radius = resolve_cursor!(glow_radius, config.shader.cursor_shader_glow_radius);
let glow_intensity =
resolve_cursor!(glow_intensity, config.shader.cursor_shader_glow_intensity);
let trail_duration =
resolve_cursor!(trail_duration, config.shader.cursor_shader_trail_duration);
let cursor_color = user_override
.and_then(|o| o.cursor_color)
.or_else(|| meta_defaults.and_then(|m| m.cursor_color))
.unwrap_or(config.shader.cursor_shader_color);
ResolvedCursorShaderConfig {
base,
hides_cursor,
disable_in_alt_screen,
glow_radius,
glow_intensity,
trail_duration,
cursor_color,
}
}
impl ResolvedShaderConfig {
pub fn for_shader(
shader_name: &str,
metadata: Option<&ShaderMetadata>,
config: &Config,
) -> Self {
let user_override = config.get_shader_override(shader_name);
resolve_shader_config(user_override, metadata, config)
}
pub fn channel_paths(&self) -> [Option<PathBuf>; 4] {
[
self.channel0.clone(),
self.channel1.clone(),
self.channel2.clone(),
self.channel3.clone(),
]
}
pub fn cubemap_path(&self) -> Option<&PathBuf> {
if self.cubemap_enabled {
self.cubemap.as_ref()
} else {
None
}
}
}
impl ResolvedCursorShaderConfig {
pub fn for_shader(
shader_name: &str,
metadata: Option<&CursorShaderMetadata>,
config: &Config,
) -> Self {
let user_override = config.get_cursor_shader_override(shader_name);
resolve_cursor_shader_config(user_override, metadata, config)
}
}
pub mod global_defaults {
pub const ANIMATION_SPEED: f32 = 1.0;
pub const BRIGHTNESS: f32 = 1.0;
pub const TEXT_OPACITY: f32 = 1.0;
pub const FULL_CONTENT: bool = false;
pub const CUBEMAP_ENABLED: bool = true;
pub const GLOW_RADIUS: f32 = 80.0;
pub const GLOW_INTENSITY: f32 = 0.3;
pub const TRAIL_DURATION: f32 = 0.5;
pub const CURSOR_COLOR: [u8; 3] = [255, 255, 255];
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ShaderConfig;
fn make_test_config() -> Config {
Config::default()
}
#[test]
fn test_resolve_with_no_overrides() {
let config = make_test_config();
let resolved = resolve_shader_config(None, None, &config);
assert_eq!(
resolved.animation_speed,
config.shader.custom_shader_animation_speed
);
assert_eq!(resolved.brightness, config.shader.custom_shader_brightness);
assert_eq!(
resolved.text_opacity,
config.shader.custom_shader_text_opacity
);
assert_eq!(
resolved.full_content,
config.shader.custom_shader_full_content
);
}
#[test]
fn test_resolve_with_metadata_defaults() {
let config = make_test_config();
let shader_defaults = ShaderConfig {
animation_speed: Some(0.5),
brightness: Some(0.7),
..Default::default()
};
let metadata = ShaderMetadata {
name: Some("Test".to_string()),
defaults: shader_defaults,
..Default::default()
};
let resolved = resolve_shader_config(None, Some(&metadata), &config);
assert_eq!(resolved.animation_speed, 0.5);
assert_eq!(resolved.brightness, 0.7);
assert_eq!(
resolved.text_opacity,
config.shader.custom_shader_text_opacity
);
}
#[test]
fn test_resolve_with_user_override() {
let config = make_test_config();
let user_override = ShaderConfig {
animation_speed: Some(2.0),
brightness: Some(0.9),
..Default::default()
};
let shader_defaults = ShaderConfig {
animation_speed: Some(0.5), text_opacity: Some(0.8), ..Default::default()
};
let metadata = ShaderMetadata {
name: Some("Test".to_string()),
defaults: shader_defaults,
..Default::default()
};
let resolved = resolve_shader_config(Some(&user_override), Some(&metadata), &config);
assert_eq!(resolved.animation_speed, 2.0);
assert_eq!(resolved.brightness, 0.9);
assert_eq!(resolved.text_opacity, 0.8);
}
#[test]
fn test_channel_paths() {
let resolved = ResolvedShaderConfig {
channel0: Some(PathBuf::from("/path/to/tex0.png")),
channel1: None,
channel2: Some(PathBuf::from("/path/to/tex2.png")),
channel3: None,
..Default::default()
};
let paths = resolved.channel_paths();
assert!(paths[0].is_some());
assert!(paths[1].is_none());
assert!(paths[2].is_some());
assert!(paths[3].is_none());
}
#[test]
fn test_cubemap_path_respects_enabled() {
let mut resolved = ResolvedShaderConfig {
cubemap: Some(PathBuf::from("/path/to/cubemap")),
cubemap_enabled: true,
..Default::default()
};
assert!(resolved.cubemap_path().is_some());
resolved.cubemap_enabled = false;
assert!(resolved.cubemap_path().is_none());
}
}