use objc2_core_foundation::CFRetained;
use objc2_core_graphics::CGColor;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f64,
pub g: f64,
pub b: f64,
pub a: f64,
}
impl Color {
pub const fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
Self { r, g, b, a }
}
pub const fn rgb(r: f64, g: f64, b: f64) -> Self {
Self { r, g, b, a: 1.0 }
}
pub fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
r: r as f64 / 255.0,
g: g as f64 / 255.0,
b: b as f64 / 255.0,
a: a as f64 / 255.0,
}
}
pub fn rgb8(r: u8, g: u8, b: u8) -> Self {
Self::rgba8(r, g, b, 255)
}
pub fn from_hex(hex: &str) -> Option<Self> {
let hex = hex.trim_start_matches('#');
let len = hex.len();
match len {
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Self::rgb8(r, g, b))
}
8 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
Some(Self::rgba8(r, g, b, a))
}
_ => None,
}
}
pub const fn gray(intensity: f64) -> Self {
Self::rgb(intensity, intensity, intensity)
}
pub const fn gray_alpha(intensity: f64, alpha: f64) -> Self {
Self::rgba(intensity, intensity, intensity, alpha)
}
pub const fn with_alpha(self, alpha: f64) -> Self {
Self {
r: self.r,
g: self.g,
b: self.b,
a: alpha,
}
}
pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
pub const RED: Self = Self::rgb(1.0, 0.0, 0.0);
pub const GREEN: Self = Self::rgb(0.0, 1.0, 0.0);
pub const BLUE: Self = Self::rgb(0.0, 0.0, 1.0);
pub const YELLOW: Self = Self::rgb(1.0, 1.0, 0.0);
pub const CYAN: Self = Self::rgb(0.0, 1.0, 1.0);
pub const MAGENTA: Self = Self::rgb(1.0, 0.0, 1.0);
pub const ORANGE: Self = Self::rgb(1.0, 0.5, 0.0);
pub const PINK: Self = Self::rgb(1.0, 0.4, 0.6);
pub const PURPLE: Self = Self::rgb(0.5, 0.0, 0.5);
pub const DARK_GRAY: Self = Self::gray(0.25);
pub const GRAY: Self = Self::gray(0.5);
pub const LIGHT_GRAY: Self = Self::gray(0.75);
}
impl Default for Color {
fn default() -> Self {
Self::BLACK
}
}
impl From<Color> for CFRetained<CGColor> {
fn from(c: Color) -> Self {
CGColor::new_srgb(c.r, c.g, c.b, c.a)
}
}
impl From<&Color> for CFRetained<CGColor> {
fn from(c: &Color) -> Self {
CGColor::new_srgb(c.r, c.g, c.b, c.a)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgba() {
let c = Color::rgba(0.5, 0.25, 0.75, 0.5);
assert!((c.r - 0.5).abs() < f64::EPSILON);
assert!((c.g - 0.25).abs() < f64::EPSILON);
assert!((c.b - 0.75).abs() < f64::EPSILON);
assert!((c.a - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_rgb8() {
let c = Color::rgb8(255, 128, 0);
assert!((c.r - 1.0).abs() < 0.01);
assert!((c.g - 0.5).abs() < 0.01);
assert!((c.b - 0.0).abs() < 0.01);
assert!((c.a - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_from_hex() {
let c = Color::from_hex("#FF8000").unwrap();
assert!((c.r - 1.0).abs() < 0.01);
assert!((c.g - 0.5).abs() < 0.01);
assert!((c.b - 0.0).abs() < 0.01);
let c = Color::from_hex("FF800080").unwrap();
assert!((c.a - 0.5).abs() < 0.01);
}
#[test]
fn test_from_hex_invalid() {
assert!(Color::from_hex("invalid").is_none());
assert!(Color::from_hex("#FFF").is_none());
}
#[test]
fn test_gray() {
let c = Color::gray(0.5);
assert_eq!(c.r, c.g);
assert_eq!(c.g, c.b);
assert!((c.r - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_with_alpha() {
let c = Color::WHITE.with_alpha(0.5);
assert!((c.a - 0.5).abs() < f64::EPSILON);
assert!((c.r - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_preset_colors() {
assert_eq!(Color::BLACK, Color::rgb(0.0, 0.0, 0.0));
assert_eq!(Color::WHITE, Color::rgb(1.0, 1.0, 1.0));
assert_eq!(Color::RED, Color::rgb(1.0, 0.0, 0.0));
}
#[test]
fn test_cgcolor_conversion() {
let color = Color::rgb(0.5, 0.25, 0.75);
let _cgcolor: CFRetained<CGColor> = color.into();
}
}