rae 0.1.13

Renderer-neutral Rust design catalog for desktop widgets, layouts, sanitized output, and GLSL shader primitives.
Documentation
use crate::{Palette, Rgba};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Built-in visual theme families exposed by `rae`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ThemeFamily {
    Midnight,
    Graphite,
    Aurora,
    Ember,
}

impl ThemeFamily {
    pub fn label(self) -> &'static str {
        match self {
            Self::Midnight => "midnight",
            Self::Graphite => "graphite",
            Self::Aurora => "aurora",
            Self::Ember => "ember",
        }
    }

    pub fn palette(self) -> Palette {
        match self {
            Self::Midnight => Palette::midnight(),
            Self::Graphite => Palette {
                name: "graphite".to_string(),
                background: Rgba::rgb8(13, 14, 18),
                panel: Rgba::rgba8(25, 27, 33, 232),
                panel_hot: Rgba::rgba8(42, 45, 54, 238),
                text: Rgba::rgb8(230, 231, 226),
                muted: Rgba::rgb8(141, 146, 154),
                accent: Rgba::rgb8(166, 190, 220),
                success: Rgba::rgb8(137, 197, 151),
                warning: Rgba::rgb8(221, 180, 109),
                error: Rgba::rgb8(226, 116, 108),
            },
            Self::Aurora => Palette {
                name: "aurora".to_string(),
                background: Rgba::rgb8(2, 12, 24),
                panel: Rgba::rgba8(4, 28, 46, 226),
                panel_hot: Rgba::rgba8(12, 54, 74, 238),
                text: Rgba::rgb8(220, 255, 246),
                muted: Rgba::rgb8(119, 181, 181),
                accent: Rgba::rgb8(80, 255, 209),
                success: Rgba::rgb8(123, 240, 150),
                warning: Rgba::rgb8(255, 207, 115),
                error: Rgba::rgb8(255, 117, 142),
            },
            Self::Ember => Palette {
                name: "ember".to_string(),
                background: Rgba::rgb8(18, 7, 5),
                panel: Rgba::rgba8(38, 16, 12, 228),
                panel_hot: Rgba::rgba8(72, 31, 19, 238),
                text: Rgba::rgb8(255, 233, 214),
                muted: Rgba::rgb8(182, 127, 104),
                accent: Rgba::rgb8(255, 143, 68),
                success: Rgba::rgb8(155, 228, 130),
                warning: Rgba::rgb8(255, 196, 91),
                error: Rgba::rgb8(255, 89, 71),
            },
        }
    }
}

impl ThemePreset {
    pub fn sanitized_shader_bias(&self) -> f32 {
        if self.shader_bias.is_finite() {
            self.shader_bias.clamp(0.0, 2.0)
        } else {
            1.0
        }
    }
}

/// Fully materialized theme preset with palette and shader intensity bias.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ThemePreset {
    pub family: ThemeFamily,
    pub palette: Palette,
    pub shader_bias: f32,
}

/// Returns the built-in theme presets in stable display order.
pub fn built_in_theme_presets() -> Vec<ThemePreset> {
    [
        (ThemeFamily::Midnight, 1.0),
        (ThemeFamily::Graphite, 0.55),
        (ThemeFamily::Aurora, 1.2),
        (ThemeFamily::Ember, 0.95),
    ]
    .into_iter()
    .map(|(family, shader_bias)| ThemePreset {
        family,
        palette: family.palette(),
        shader_bias,
    })
    .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn built_in_theme_presets_have_stable_labels_and_palettes() {
        let presets = built_in_theme_presets();
        let labels = presets
            .iter()
            .map(|preset| preset.family.label())
            .collect::<Vec<_>>();

        assert_eq!(labels, ["midnight", "graphite", "aurora", "ember"]);
        assert!(presets.iter().all(|preset| !preset.palette.name.is_empty()));
        assert!(presets.iter().all(|preset| preset.shader_bias > 0.0));
        assert!(presets
            .iter()
            .all(|preset| (0.0..=2.0).contains(&preset.sanitized_shader_bias())));
    }

    #[test]
    fn theme_preset_sanitizes_public_shader_bias() {
        let mut preset = built_in_theme_presets().remove(0);

        preset.shader_bias = f32::NAN;
        assert_eq!(preset.sanitized_shader_bias(), 1.0);
        preset.shader_bias = f32::INFINITY;
        assert_eq!(preset.sanitized_shader_bias(), 1.0);
        preset.shader_bias = -1.0;
        assert_eq!(preset.sanitized_shader_bias(), 0.0);
    }
}