use crate::view::View;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f64,
pub g: f64,
pub b: f64,
pub a: f64,
}
impl Color {
pub const RED: Color = Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const GREEN: Color = Color {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
pub const BLUE: Color = Color {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
pub const YELLOW: Color = Color {
r: 1.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
pub const ORANGE: Color = Color {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
};
pub const PURPLE: Color = Color {
r: 0.5,
g: 0.0,
b: 0.5,
a: 1.0,
};
pub const PINK: Color = Color {
r: 1.0,
g: 0.0,
b: 0.5,
a: 1.0,
};
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const BLACK: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const CLEAR: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
pub const GRAY: Color = Color {
r: 0.5,
g: 0.5,
b: 0.5,
a: 1.0,
};
pub fn new(r: f64, g: f64, b: f64, a: f64) -> Self {
Self { r, g, b, a }
}
pub fn rgb(r: f64, g: f64, b: f64) -> Self {
Self { r, g, b, a: 1.0 }
}
pub fn with_alpha(mut self, a: f64) -> Self {
self.a = a;
self
}
pub fn to_system_name(&self) -> Option<&'static str> {
if *self == Self::RED {
return Some("systemRed");
}
if *self == Self::GREEN {
return Some("systemGreen");
}
if *self == Self::BLUE {
return Some("systemBlue");
}
if *self == Self::YELLOW {
return Some("systemYellow");
}
if *self == Self::ORANGE {
return Some("systemOrange");
}
if *self == Self::PURPLE {
return Some("systemPurple");
}
if *self == Self::PINK {
return Some("systemPink");
}
if *self == Self::GRAY {
return Some("systemGray");
}
if *self == Self::WHITE {
return Some("whiteColor");
}
if *self == Self::BLACK {
return Some("blackColor");
}
if *self == Self::CLEAR {
return Some("clearColor");
}
None
}
}
impl Default for Color {
fn default() -> Self {
Self::BLACK
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "Style must be applied to a View with .apply() or .styled()"]
pub struct Style {
pub width: Option<f64>,
pub height: Option<f64>,
pub padding: Option<f64>,
pub margin: Option<f64>,
pub background: Option<String>,
pub foreground: Option<String>,
pub font_size: Option<f64>,
pub font_weight: Option<FontWeight>,
pub corner_radius: Option<f64>,
pub border_width: Option<f64>,
pub border_color: Option<String>,
pub opacity: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FontWeight {
Normal,
Bold,
Light,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
pub fn width(mut self, w: f64) -> Self {
self.width = Some(w);
self
}
pub fn height(mut self, h: f64) -> Self {
self.height = Some(h);
self
}
pub fn size(mut self, w: f64, h: f64) -> Self {
self.width = Some(w);
self.height = Some(h);
self
}
pub fn padding(mut self, p: f64) -> Self {
self.padding = Some(p);
self
}
pub fn margin(mut self, m: f64) -> Self {
self.margin = Some(m);
self
}
pub fn background(mut self, color: impl Into<String>) -> Self {
self.background = Some(color.into());
self
}
pub fn foreground(mut self, color: impl Into<String>) -> Self {
self.foreground = Some(color.into());
self
}
pub fn font_size(mut self, size: f64) -> Self {
self.font_size = Some(size);
self
}
pub fn bold(mut self) -> Self {
self.font_weight = Some(FontWeight::Bold);
self
}
pub fn corner_radius(mut self, r: f64) -> Self {
self.corner_radius = Some(r);
self
}
pub fn border(mut self, width: f64, color: impl Into<String>) -> Self {
self.border_width = Some(width);
self.border_color = Some(color.into());
self
}
pub fn opacity(mut self, o: f64) -> Self {
self.opacity = Some(o);
self
}
pub fn apply(&self, mut view: View) -> View {
if let Some(w) = self.width {
view.style.width = Some(w);
}
if let Some(h) = self.height {
view.style.height = Some(h);
}
if let Some(p) = self.padding {
view.style.padding = Some(p);
}
if let Some(m) = self.margin {
view.style.margin = Some(m);
}
if let Some(ref c) = self.background {
view.style.background = Some(c.clone());
}
if let Some(ref c) = self.foreground {
view.style.foreground = Some(c.clone());
}
if let Some(s) = self.font_size {
view.style.font_size = Some(s);
}
if let Some(FontWeight::Bold) = self.font_weight {
view.style.font_bold = true;
}
if let Some(r) = self.corner_radius {
view.style.corner_radius = Some(r);
}
if let Some(w) = self.border_width {
view.style.border_width = Some(w);
}
if let Some(ref c) = self.border_color {
view.style.border_color = Some(c.clone());
}
if let Some(o) = self.opacity {
view.style.opacity = Some(o);
}
view
}
}
pub fn style() -> Style {
Style::new()
}
pub fn color(name: &str) -> String {
match name.to_lowercase().as_str() {
"red" => "systemRed".to_string(),
"green" => "systemGreen".to_string(),
"blue" => "systemBlue".to_string(),
"yellow" => "systemYellow".to_string(),
"orange" => "systemOrange".to_string(),
"purple" => "systemPurple".to_string(),
"pink" => "systemPink".to_string(),
"gray" | "grey" => "systemGray".to_string(),
"white" => "whiteColor".to_string(),
"black" => "blackColor".to_string(),
"clear" => "clearColor".to_string(),
_ => name.to_string(),
}
}
pub fn font(name: &str) -> String {
match name.to_lowercase().as_str() {
"title" => "title".to_string(),
"headline" => "headline".to_string(),
"body" => "body".to_string(),
"caption" => "caption".to_string(),
"footnote" => "footnote".to_string(),
_ => name.to_string(),
}
}
pub trait Styled {
fn styled(self, style: &Style) -> Self;
}
impl Styled for View {
fn styled(self, style: &Style) -> Self {
style.apply(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_new_rgb_with_alpha() {
let c = Color::rgb(0.2, 0.4, 0.6).with_alpha(0.5);
assert!((c.r - 0.2).abs() < 1e-9);
assert!((c.a - 0.5).abs() < 1e-9);
}
#[test]
fn color_to_system_name_known_colors() {
assert_eq!(Color::RED.to_system_name(), Some("systemRed"));
assert_eq!(Color::GRAY.to_system_name(), Some("systemGray"));
assert_eq!(Color::CLEAR.to_system_name(), Some("clearColor"));
assert_eq!(Color::new(0.1, 0.2, 0.3, 1.0).to_system_name(), None);
}
#[test]
fn style_builder_apply_to_view() {
let s = style()
.size(120.0, 48.0)
.padding(8.0)
.margin(4.0)
.background("systemBlue")
.foreground("whiteColor")
.font_size(15.0)
.bold()
.corner_radius(6.0)
.border(1.0, "blackColor")
.opacity(0.9);
let v = View::button("OK").styled(&s);
assert_eq!(v.style.width, Some(120.0));
assert_eq!(v.style.height, Some(48.0));
assert_eq!(v.style.padding, Some(8.0));
assert_eq!(v.style.margin, Some(4.0));
assert_eq!(v.style.background.as_deref(), Some("systemBlue"));
assert_eq!(v.style.foreground.as_deref(), Some("whiteColor"));
assert_eq!(v.style.font_size, Some(15.0));
assert!(v.style.font_bold);
assert_eq!(v.style.corner_radius, Some(6.0));
assert_eq!(v.style.border_width, Some(1.0));
assert_eq!(v.style.border_color.as_deref(), Some("blackColor"));
assert_eq!(v.style.opacity, Some(0.9));
}
#[test]
fn color_and_font_helpers() {
assert_eq!(color("ReD"), "systemRed");
assert_eq!(color("grey"), "systemGray");
assert_eq!(color("custom"), "custom");
assert_eq!(font("TITLE"), "title");
assert_eq!(font("unknown"), "unknown");
}
}