use bevy_ecs::prelude::*;
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
#[derive(Clone, Debug, PartialEq, Default)]
pub enum Brush {
#[default]
Inherit,
Solid(D2D1_COLOR_F),
}
impl Brush {
pub const TRANSPARENT: Self = Self::Solid(D2D1_COLOR_F {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
});
pub const BLACK: Self = Self::Solid(D2D1_COLOR_F {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
pub const WHITE: Self = Self::Solid(D2D1_COLOR_F {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
});
pub const RED: Self = Self::Solid(D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
pub const GREEN: Self = Self::Solid(D2D1_COLOR_F {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
});
pub const BLUE: Self = Self::Solid(D2D1_COLOR_F {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
});
pub fn as_color(&self) -> Option<D2D1_COLOR_F> {
match self {
Brush::Inherit => None,
Brush::Solid(color) => Some(*color),
}
}
pub fn is_inherit(&self) -> bool {
matches!(self, Brush::Inherit)
}
}
#[derive(Component, Clone, Debug, PartialEq)]
#[component(storage = "SparseSet")]
pub struct Brushes {
pub foreground: Brush,
pub background: Brush,
}
impl Default for Brushes {
fn default() -> Self {
Self {
foreground: Brush::Inherit,
background: Brush::Inherit,
}
}
}
impl Brushes {
pub fn with_foreground(color: D2D1_COLOR_F) -> Self {
Self {
foreground: Brush::Solid(color),
background: Brush::Inherit,
}
}
pub fn with_background(color: D2D1_COLOR_F) -> Self {
Self {
foreground: Brush::Inherit,
background: Brush::Solid(color),
}
}
pub fn with_colors(foreground: D2D1_COLOR_F, background: D2D1_COLOR_F) -> Self {
Self {
foreground: Brush::Solid(foreground),
background: Brush::Solid(background),
}
}
}
#[derive(Component, Default, Debug, Clone, Copy, PartialEq, Eq)]
#[component(storage = "SparseSet")]
pub struct BrushInherit;
pub const DEFAULT_FOREGROUND: Brush = Brush::BLACK;
pub const DEFAULT_BACKGROUND: Brush = Brush::TRANSPARENT;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_brush_as_color_inherit() {
let brush = Brush::Inherit;
assert!(brush.as_color().is_none());
}
#[test]
fn test_brush_as_color_solid() {
let brush = Brush::Solid(D2D1_COLOR_F {
r: 1.0,
g: 0.5,
b: 0.25,
a: 0.75,
});
let color = brush.as_color().unwrap();
assert!((color.r - 1.0).abs() < f32::EPSILON);
assert!((color.g - 0.5).abs() < f32::EPSILON);
assert!((color.b - 0.25).abs() < f32::EPSILON);
assert!((color.a - 0.75).abs() < f32::EPSILON);
}
#[test]
fn test_brush_is_inherit() {
assert!(Brush::Inherit.is_inherit());
assert!(!Brush::BLACK.is_inherit());
assert!(!Brush::TRANSPARENT.is_inherit());
}
#[test]
fn test_brush_constants() {
let color = Brush::TRANSPARENT.as_color().unwrap();
assert!((color.a - 0.0).abs() < f32::EPSILON);
let color = Brush::BLACK.as_color().unwrap();
assert!((color.r - 0.0).abs() < f32::EPSILON);
assert!((color.g - 0.0).abs() < f32::EPSILON);
assert!((color.b - 0.0).abs() < f32::EPSILON);
assert!((color.a - 1.0).abs() < f32::EPSILON);
let color = Brush::WHITE.as_color().unwrap();
assert!((color.r - 1.0).abs() < f32::EPSILON);
assert!((color.g - 1.0).abs() < f32::EPSILON);
assert!((color.b - 1.0).abs() < f32::EPSILON);
let color = Brush::RED.as_color().unwrap();
assert!((color.r - 1.0).abs() < f32::EPSILON);
assert!((color.g - 0.0).abs() < f32::EPSILON);
assert!((color.b - 0.0).abs() < f32::EPSILON);
let color = Brush::GREEN.as_color().unwrap();
assert!((color.r - 0.0).abs() < f32::EPSILON);
assert!((color.g - 1.0).abs() < f32::EPSILON);
assert!((color.b - 0.0).abs() < f32::EPSILON);
let color = Brush::BLUE.as_color().unwrap();
assert!((color.r - 0.0).abs() < f32::EPSILON);
assert!((color.g - 0.0).abs() < f32::EPSILON);
assert!((color.b - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_brushes_default() {
let brushes = Brushes::default();
assert!(brushes.foreground.is_inherit());
assert!(brushes.background.is_inherit());
}
#[test]
fn test_brushes_with_foreground() {
let color = D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let brushes = Brushes::with_foreground(color);
assert_eq!(brushes.foreground, Brush::Solid(color));
assert!(brushes.background.is_inherit());
}
#[test]
fn test_brushes_with_background() {
let color = D2D1_COLOR_F {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
let brushes = Brushes::with_background(color);
assert!(brushes.foreground.is_inherit());
assert_eq!(brushes.background, Brush::Solid(color));
}
#[test]
fn test_brushes_with_colors() {
let fg = D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let bg = D2D1_COLOR_F {
r: 0.0,
g: 0.0,
b: 1.0,
a: 0.5,
};
let brushes = Brushes::with_colors(fg, bg);
assert_eq!(brushes.foreground, Brush::Solid(fg));
assert_eq!(brushes.background, Brush::Solid(bg));
}
#[test]
fn test_brush_default_is_inherit() {
let brush = Brush::default();
assert_eq!(brush, Brush::Inherit);
assert!(brush.is_inherit());
assert!(brush.as_color().is_none());
}
#[test]
fn test_brush_partial_eq_distinguishes_color_and_variant() {
let opaque = Brush::Solid(D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
});
let transparent_red = Brush::Solid(D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 0.0,
});
assert_eq!(opaque, opaque.clone());
assert_ne!(opaque, transparent_red);
assert_ne!(Brush::Inherit, opaque);
}
#[test]
fn test_default_color_constants() {
assert_eq!(DEFAULT_FOREGROUND, Brush::BLACK);
let fg = DEFAULT_FOREGROUND.as_color().expect("foreground is Solid");
assert!((fg.r - 0.0).abs() < f32::EPSILON);
assert!((fg.g - 0.0).abs() < f32::EPSILON);
assert!((fg.b - 0.0).abs() < f32::EPSILON);
assert!((fg.a - 1.0).abs() < f32::EPSILON);
assert_eq!(DEFAULT_BACKGROUND, Brush::TRANSPARENT);
let bg = DEFAULT_BACKGROUND.as_color().expect("background is Solid");
assert!((bg.a - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_brushes_clone_and_eq() {
let brushes = Brushes::with_colors(
D2D1_COLOR_F {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
},
D2D1_COLOR_F {
r: 0.4,
g: 0.5,
b: 0.6,
a: 0.5,
},
);
let cloned = brushes.clone();
assert_eq!(brushes, cloned);
let mut modified = brushes.clone();
modified.foreground = Brush::Inherit;
assert_ne!(brushes, modified);
}
#[test]
fn test_brush_inherit_marker_default() {
let a = BrushInherit;
let b = BrushInherit::default();
assert_eq!(a, b);
}
#[test]
fn test_foreground_color_resolution_fallback() {
let resolve = |fg: &Brush| -> D2D1_COLOR_F {
fg.as_color()
.unwrap_or_else(|| DEFAULT_FOREGROUND.as_color().unwrap())
};
let red = D2D1_COLOR_F {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let resolved = resolve(&Brush::Solid(red));
assert_eq!(resolved, red);
let fallback = resolve(&Brush::Inherit);
assert!((fallback.r - 0.0).abs() < f32::EPSILON);
assert!((fallback.g - 0.0).abs() < f32::EPSILON);
assert!((fallback.b - 0.0).abs() < f32::EPSILON);
assert!((fallback.a - 1.0).abs() < f32::EPSILON);
}
}