use crate::animation::{Animate, Transition};
use crate::geometry::{Affine, Point, Rect};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
Color { r, g, b, a: 255 }
}
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
Color { r, g, b, a }
}
pub const fn hex(hex: &str) -> Color {
let bytes = hex.as_bytes();
if bytes.is_empty() || bytes[0] != b'#' || (bytes.len() != 7 && bytes.len() != 9) {
panic!("Color::hex takes \"#rrggbb\" or \"#rrggbbaa\"");
}
let a = if bytes.len() == 9 {
hex_byte(bytes[7], bytes[8])
} else {
255
};
Color {
r: hex_byte(bytes[1], bytes[2]),
g: hex_byte(bytes[3], bytes[4]),
b: hex_byte(bytes[5], bytes[6]),
a,
}
}
}
const fn hex_byte(high: u8, low: u8) -> u8 {
hex_digit(high) * 16 + hex_digit(low)
}
const fn hex_digit(digit: u8) -> u8 {
match digit {
b'0'..=b'9' => digit - b'0',
b'a'..=b'f' => digit - b'a' + 10,
b'A'..=b'F' => digit - b'A' + 10,
_ => panic!("Color::hex found a character that isn't a hex digit"),
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Font {
pub family: &'static str,
pub size: f32,
pub weight: u16,
}
impl Font {
pub const DEFAULT: Font = Font::new("sans-serif", 13.0);
pub const fn new(family: &'static str, size: f32) -> Font {
Font {
family,
size,
weight: 400,
}
}
pub const fn weight(self, weight: u16) -> Font {
Font { weight, ..self }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Border {
pub width: f32,
pub color: Color,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Gradient {
pub shape: GradientShape,
stops: [(f32, Color); 4],
count: u8,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GradientShape {
Linear(f32),
Radial,
}
impl Gradient {
pub const fn linear(angle: f32, from: Color, to: Color) -> Gradient {
Gradient::new(GradientShape::Linear(angle), from, to)
}
pub const fn radial(from: Color, to: Color) -> Gradient {
Gradient::new(GradientShape::Radial, from, to)
}
const fn new(shape: GradientShape, from: Color, to: Color) -> Gradient {
Gradient {
shape,
stops: [(0.0, from), (1.0, to), (1.0, to), (1.0, to)],
count: 2,
}
}
pub const fn stop(self, at: f32, color: Color) -> Gradient {
assert!(self.count < 4, "a gradient has at most four colors");
let mut stops = self.stops;
let last = self.count as usize - 1;
stops[last + 1] = stops[last];
stops[last] = (at, color);
Gradient {
stops,
count: self.count + 1,
..self
}
}
pub fn stops(&self) -> &[(f32, Color)] {
&self.stops[..usize::from(self.count)]
}
fn lerp(&self, to: &Gradient, t: f32) -> Option<Gradient> {
let shape = match (self.shape, to.shape) {
(GradientShape::Linear(from), GradientShape::Linear(to)) => {
GradientShape::Linear(from.lerp(&to, t))
}
(GradientShape::Radial, GradientShape::Radial) => GradientShape::Radial,
_ => return None,
};
if self.count != to.count {
return None;
}
let mut stops = to.stops;
for (stop, (from, to)) in stops.iter_mut().zip(self.stops.iter().zip(to.stops)) {
*stop = (from.0.lerp(&to.0, t), from.1.lerp(&to.1, t));
}
Some(Gradient {
shape,
stops,
count: to.count,
})
}
fn clear(mut self) -> Gradient {
for (_, color) in &mut self.stops {
color.a = 0;
}
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Shadow {
pub x: f32,
pub y: f32,
pub blur: f32,
pub spread: f32,
pub color: Color,
pub cut_out: bool,
}
impl Shadow {
pub const fn new(x: f32, y: f32, blur: f32, color: Color) -> Shadow {
Shadow {
x,
y,
blur,
spread: 0.0,
color,
cut_out: false,
}
}
pub const fn spread(self, spread: f32) -> Shadow {
Shadow { spread, ..self }
}
pub const fn cut_out(self) -> Shadow {
Shadow {
cut_out: true,
..self
}
}
pub(crate) fn reach(&self, rect: Rect) -> Rect {
let grow = self.spread + 1.5 * self.blur.max(0.0);
if ![self.x, self.y, grow].iter().all(|n| n.is_finite()) {
return rect;
}
let (left, right) = (rect.x + self.x - grow, rect.right() + self.x + grow);
let (top, bottom) = (rect.y + self.y - grow, rect.bottom() + self.y + grow);
Rect::new(
left.min(right),
top.min(bottom),
(right - left).abs(),
(bottom - top).abs(),
)
}
fn lerp(&self, to: &Shadow, t: f32) -> Shadow {
Shadow {
x: self.x.lerp(&to.x, t),
y: self.y.lerp(&to.y, t),
blur: self.blur.lerp(&to.blur, t),
spread: self.spread.lerp(&to.spread, t),
color: self.color.lerp(&to.color, t),
cut_out: to.cut_out,
}
}
fn clear(self) -> Shadow {
Shadow {
color: Color::rgba(self.color.r, self.color.g, self.color.b, 0),
..self
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Padding {
pub top: u32,
pub right: u32,
pub bottom: u32,
pub left: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Style {
pub background: Option<Color>,
pub gradient: Option<Gradient>,
pub shadow: Option<Shadow>,
pub color: Option<Color>,
pub font: Option<Font>,
pub padding: Option<Padding>,
pub radius: Option<f32>,
pub border: Option<Border>,
pub opacity: Option<f32>,
pub clip: Option<bool>,
pub width: Option<f32>,
pub height: Option<f32>,
pub translate: Option<Point>,
pub scale: Option<f32>,
pub rotate: Option<f32>,
pub origin: Option<Point>,
pub transition: Option<Transition>,
}
impl Style {
pub const fn new() -> Style {
Style {
background: None,
gradient: None,
shadow: None,
color: None,
font: None,
padding: None,
radius: None,
border: None,
opacity: None,
clip: None,
width: None,
height: None,
translate: None,
scale: None,
rotate: None,
origin: None,
transition: None,
}
}
pub const fn background(self, color: Color) -> Style {
Style {
background: Some(color),
..self
}
}
pub const fn gradient(self, gradient: Gradient) -> Style {
Style {
gradient: Some(gradient),
..self
}
}
pub const fn shadow(self, shadow: Shadow) -> Style {
Style {
shadow: Some(shadow),
..self
}
}
pub const fn color(self, color: Color) -> Style {
Style {
color: Some(color),
..self
}
}
pub const fn font(self, font: Font) -> Style {
Style {
font: Some(font),
..self
}
}
pub const fn padding(self, vertical: u32, horizontal: u32) -> Style {
Style {
padding: Some(Padding {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}),
..self
}
}
pub const fn padding_top(self, top: u32) -> Style {
let mut padding = self.sides();
padding.top = top;
Style {
padding: Some(padding),
..self
}
}
pub const fn padding_right(self, right: u32) -> Style {
let mut padding = self.sides();
padding.right = right;
Style {
padding: Some(padding),
..self
}
}
pub const fn padding_bottom(self, bottom: u32) -> Style {
let mut padding = self.sides();
padding.bottom = bottom;
Style {
padding: Some(padding),
..self
}
}
pub const fn padding_left(self, left: u32) -> Style {
let mut padding = self.sides();
padding.left = left;
Style {
padding: Some(padding),
..self
}
}
const fn sides(self) -> Padding {
match self.padding {
Some(padding) => padding,
None => Padding {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
}
}
pub const fn radius(self, radius: f32) -> Style {
Style {
radius: Some(radius),
..self
}
}
pub const fn border(self, width: f32, color: Color) -> Style {
Style {
border: Some(Border { width, color }),
..self
}
}
pub const fn opacity(self, opacity: f32) -> Style {
Style {
opacity: Some(opacity),
..self
}
}
pub const fn clip(self, clip: bool) -> Style {
Style {
clip: Some(clip),
..self
}
}
pub const fn width(self, width: f32) -> Style {
Style {
width: Some(width),
..self
}
}
pub const fn height(self, height: f32) -> Style {
Style {
height: Some(height),
..self
}
}
pub const fn translate(self, x: f32, y: f32) -> Style {
Style {
translate: Some(Point::new(x, y)),
..self
}
}
pub const fn scale(self, scale: f32) -> Style {
Style {
scale: Some(scale),
..self
}
}
pub const fn rotate(self, degrees: f32) -> Style {
Style {
rotate: Some(degrees),
..self
}
}
pub const fn origin(self, x: f32, y: f32) -> Style {
Style {
origin: Some(Point::new(x, y)),
..self
}
}
pub const fn transition(self, transition: Transition) -> Style {
Style {
transition: Some(transition),
..self
}
}
pub const fn merge(self, over: Style) -> Style {
Style {
background: or(over.background, self.background),
gradient: or(over.gradient, self.gradient),
shadow: or(over.shadow, self.shadow),
color: or(over.color, self.color),
font: or(over.font, self.font),
padding: or(over.padding, self.padding),
radius: or(over.radius, self.radius),
border: or(over.border, self.border),
opacity: or(over.opacity, self.opacity),
clip: or(over.clip, self.clip),
width: or(over.width, self.width),
height: or(over.height, self.height),
translate: or(over.translate, self.translate),
scale: or(over.scale, self.scale),
rotate: or(over.rotate, self.rotate),
origin: or(over.origin, self.origin),
transition: or(over.transition, self.transition),
}
}
pub fn clips(&self) -> bool {
self.clip == Some(true)
}
pub(crate) fn same_layout(&self, other: &Style) -> bool {
self.font == other.font
&& self.padding == other.padding
&& self.width == other.width
&& self.height == other.height
&& self.clips() == other.clips()
}
pub(crate) fn same_transform(&self, other: &Style) -> bool {
self.translate == other.translate
&& self.scale == other.scale
&& self.rotate == other.rotate
&& self.origin == other.origin
}
pub(crate) fn transform(&self, rect: Rect) -> Option<Affine> {
let finite = |value: f32, default: f32| if value.is_finite() { value } else { default };
let translate = self.translate.unwrap_or_default();
let (x, y) = (finite(translate.x, 0.0), finite(translate.y, 0.0));
let scale = finite(self.scale.unwrap_or(1.0), 1.0);
let degrees = finite(self.rotate.unwrap_or(0.0), 0.0).rem_euclid(360.0);
if x == 0.0 && y == 0.0 && scale == 1.0 && degrees == 0.0 {
return None;
}
let (sin, cos) = match degrees {
90.0 => (1.0, 0.0),
180.0 => (0.0, -1.0),
270.0 => (-1.0, 0.0),
_ => degrees.to_radians().sin_cos(),
};
let origin = self.origin.unwrap_or(Point::new(0.5, 0.5));
let ox = rect.x + finite(origin.x, 0.5) * rect.width;
let oy = rect.y + finite(origin.y, 0.5) * rect.height;
let (sx, ky, kx, sy) = (cos * scale, sin * scale, -sin * scale, cos * scale);
Some(Affine {
sx,
ky,
kx,
sy,
tx: ox + x - (sx * ox + kx * oy),
ty: oy + y - (ky * ox + sy * oy),
})
}
}
const fn or<T: Copy>(first: Option<T>, second: Option<T>) -> Option<T> {
match first {
Some(value) => Some(value),
None => second,
}
}
impl Style {
pub(crate) fn lerp(&self, to: &Style, t: f32) -> Style {
let color = |from: Option<Color>, to: Option<Color>| match (from, to) {
(Some(from), Some(to)) => Some(from.lerp(&to, t)),
(None, Some(to)) => Some(Color::rgba(to.r, to.g, to.b, 0).lerp(&to, t)),
(Some(from), None) => Some(from.lerp(&Color::rgba(from.r, from.g, from.b, 0), t)),
(None, None) => None,
};
let border = match (self.border, to.border) {
(None, None) => None,
(from, to) => {
let from = from.or(to.map(|b| Border {
width: 0.0,
color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
}));
let to = to.or(from.map(|b| Border {
width: 0.0,
color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
}));
match (from, to) {
(Some(from), Some(to)) => Some(Border {
width: from.width.lerp(&to.width, t),
color: from.color.lerp(&to.color, t),
}),
_ => None,
}
}
};
let number = |from: Option<f32>, to: Option<f32>, none: f32| match (from, to) {
(None, None) => None,
(from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
};
let point = |from: Option<Point>, to: Option<Point>, none: Point| match (from, to) {
(None, None) => None,
(from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
};
let gradient = match (self.gradient, to.gradient) {
(Some(from), Some(end)) => from.lerp(&end, t).or(to.gradient),
(None, Some(end)) => end.clear().lerp(&end, t),
(Some(from), None) => from.lerp(&from.clear(), t),
(None, None) => None,
};
let shadow = match (self.shadow, to.shadow) {
(Some(from), Some(end)) => Some(from.lerp(&end, t)),
(None, Some(end)) => Some(end.clear().lerp(&end, t)),
(Some(from), None) => Some(from.lerp(&from.clear(), t)),
(None, None) => None,
};
Style {
background: color(self.background, to.background),
gradient,
shadow,
color: match (self.color, to.color) {
(Some(from), Some(to)) => Some(from.lerp(&to, t)),
_ => to.color,
},
border,
radius: number(self.radius, to.radius, 0.0),
opacity: number(self.opacity, to.opacity, 1.0),
translate: point(self.translate, to.translate, Point::default()),
scale: number(self.scale, to.scale, 1.0),
rotate: number(self.rotate, to.rotate, 0.0),
origin: point(self.origin, to.origin, Point::new(0.5, 0.5)),
..*to
}
}
}