use crate::scheme::ColorScheme;
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ThemeUniforms {
pub background: [f32; 4],
pub foreground: [f32; 4],
pub accent: [f32; 4],
pub error: [f32; 4],
pub is_dark: f32,
pub _pad: [f32; 3],
}
impl ThemeUniforms {
#[must_use]
pub fn from_scheme(scheme: &ColorScheme) -> Self {
Self {
background: scheme.base00.to_array(),
foreground: scheme.base05.to_array(),
accent: scheme.base0d.to_array(),
error: scheme.base08.to_array(),
is_dark: if scheme.is_dark() { 1.0 } else { 0.0 },
_pad: [0.0; 3],
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}
pub const THEME_UNIFORMS_WGSL: &str = r"
struct ThemeUniforms {
background: vec4<f32>,
foreground: vec4<f32>,
accent: vec4<f32>,
error_color: vec4<f32>,
is_dark: f32,
_pad: vec3<f32>,
}
";
#[cfg(test)]
mod tests {
use super::*;
use crate::scheme::ColorScheme;
#[test]
fn uniforms_from_default() {
let scheme = ColorScheme::default();
let u = ThemeUniforms::from_scheme(&scheme);
assert_eq!(u.is_dark, 1.0); assert_eq!(u.background, scheme.base00.to_array());
}
#[test]
fn uniforms_size_is_aligned() {
assert_eq!(std::mem::size_of::<ThemeUniforms>() % 16, 0);
}
#[test]
fn wgsl_snippet_contains_struct() {
assert!(THEME_UNIFORMS_WGSL.contains("ThemeUniforms"));
assert!(THEME_UNIFORMS_WGSL.contains("background"));
assert!(THEME_UNIFORMS_WGSL.contains("is_dark"));
}
#[test]
fn uniforms_non_dark_scheme_zero_flag() {
let light = ColorScheme {
base00: crate::scheme::Color::new(1.0, 1.0, 1.0, 1.0),
..ColorScheme::default()
};
let u = ThemeUniforms::from_scheme(&light);
assert_eq!(u.is_dark, 0.0);
}
#[test]
fn uniforms_as_bytes_length_matches_size() {
let u = ThemeUniforms::from_scheme(&ColorScheme::default());
assert_eq!(u.as_bytes().len(), std::mem::size_of::<ThemeUniforms>());
}
#[test]
fn uniforms_pod_bytes_roundtrip() {
let u = ThemeUniforms::from_scheme(&ColorScheme::default());
let bytes = bytemuck::bytes_of(&u);
let u2: &ThemeUniforms = bytemuck::from_bytes(bytes);
assert_eq!(u2.background, u.background);
assert_eq!(u2.foreground, u.foreground);
assert_eq!(u2.accent, u.accent);
assert_eq!(u2.error, u.error);
assert_eq!(u2.is_dark, u.is_dark);
assert_eq!(u2._pad, u._pad);
}
#[test]
fn uniforms_zeroable() {
let z = <ThemeUniforms as bytemuck::Zeroable>::zeroed();
assert_eq!(z.background, [0.0; 4]);
assert_eq!(z.is_dark, 0.0);
}
#[test]
fn wgsl_snippet_declares_every_cpu_field() {
for name in ["background", "foreground", "accent", "is_dark", "_pad"] {
assert!(
THEME_UNIFORMS_WGSL.contains(name),
"WGSL snippet missing field `{name}`"
);
}
}
#[test]
fn uniforms_size_is_at_least_four_vec4s_plus_scalar() {
assert!(std::mem::size_of::<ThemeUniforms>() >= 80);
}
}