use serde::{Deserialize, Serialize};
use tui_vfx_types::Color;
use super::{ShadowCompositeMode, ShadowEdges, ShadowGradeConfig, ShadowStyle};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct ShadowConfig {
pub style: ShadowStyle,
pub offset_x: i8,
pub offset_y: i8,
pub color: Color,
#[serde(skip_serializing_if = "Option::is_none")]
pub surface_color: Option<Color>,
pub edges: ShadowEdges,
pub soft_edges: bool,
pub composite_mode: ShadowCompositeMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub grade: Option<ShadowGradeConfig>,
}
impl Default for ShadowConfig {
fn default() -> Self {
Self {
style: ShadowStyle::HalfBlock,
offset_x: 1,
offset_y: 1,
color: Color::BLACK.with_alpha(128),
surface_color: None,
edges: ShadowEdges::BOTTOM_RIGHT,
soft_edges: true,
composite_mode: ShadowCompositeMode::GlyphOverlay,
grade: None,
}
}
}
impl ShadowConfig {
#[inline]
pub fn new(color: Color) -> Self {
Self {
color,
..Default::default()
}
}
#[inline]
pub fn with_offset(mut self, x: i8, y: i8) -> Self {
self.offset_x = x;
self.offset_y = y;
self
}
#[inline]
pub fn with_style(mut self, style: ShadowStyle) -> Self {
self.style = style;
self
}
#[inline]
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
#[inline]
pub fn with_surface_color(mut self, color: Color) -> Self {
self.surface_color = Some(color);
self
}
#[inline]
pub fn with_edges(mut self, edges: ShadowEdges) -> Self {
self.edges = edges;
self
}
#[inline]
pub fn with_soft_edges(mut self, enabled: bool) -> Self {
self.soft_edges = enabled;
self
}
#[inline]
pub fn with_composite_mode(mut self, mode: ShadowCompositeMode) -> Self {
self.composite_mode = mode;
self
}
#[inline]
pub fn with_grade(mut self, grade: ShadowGradeConfig) -> Self {
self.composite_mode = ShadowCompositeMode::GradeUnderlying;
self.grade = Some(grade);
self
}
#[inline]
pub fn with_dramatic_grade(self) -> Self {
self.with_grade(ShadowGradeConfig::dramatic())
}
#[inline]
pub fn color_at_progress(&self, progress: f64) -> Color {
let alpha = (self.color.a as f64 * progress).round() as u8;
self.color.with_alpha(alpha)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ShadowConfig::default();
assert_eq!(config.style, ShadowStyle::HalfBlock);
assert_eq!(config.offset_x, 1);
assert_eq!(config.offset_y, 1);
assert_eq!(config.edges, ShadowEdges::BOTTOM_RIGHT);
assert!(config.soft_edges);
}
#[test]
fn test_builder_pattern() {
let config = ShadowConfig::new(Color::RED)
.with_offset(2, 3)
.with_style(ShadowStyle::Solid)
.with_edges(ShadowEdges::ALL)
.with_soft_edges(false);
assert_eq!(config.color, Color::RED);
assert_eq!(config.offset_x, 2);
assert_eq!(config.offset_y, 3);
assert_eq!(config.style, ShadowStyle::Solid);
assert_eq!(config.edges, ShadowEdges::ALL);
assert!(!config.soft_edges);
}
#[test]
fn shadow_config_defaults_to_glyph_overlay() {
let config = ShadowConfig::default();
assert_eq!(config.composite_mode, ShadowCompositeMode::GlyphOverlay);
assert!(config.grade.is_none());
}
#[test]
fn shadow_config_grade_underlying_serde_round_trip() {
let config = ShadowConfig::new(Color::BLACK.with_alpha(180)).with_dramatic_grade();
let json = serde_json::to_string(&config).unwrap();
let restored: ShadowConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config, restored);
assert_eq!(
restored.composite_mode,
ShadowCompositeMode::GradeUnderlying
);
assert!(restored.grade.is_some());
}
#[test]
fn shadow_config_with_dramatic_grade_sets_mode_and_grade() {
let config = ShadowConfig::new(Color::BLACK.with_alpha(128)).with_dramatic_grade();
assert_eq!(config.composite_mode, ShadowCompositeMode::GradeUnderlying);
assert_eq!(config.grade, Some(ShadowGradeConfig::dramatic()));
}
#[test]
fn test_color_at_progress() {
let config = ShadowConfig::new(Color::BLACK.with_alpha(200));
let color = config.color_at_progress(0.5);
assert_eq!(color.a, 100);
let color = config.color_at_progress(0.0);
assert_eq!(color.a, 0);
let color = config.color_at_progress(1.0);
assert_eq!(color.a, 200);
}
}