use std::ops::{Add, Sub};
use palette::{FromColor, Hsl, Hsv, LinSrgba, Srgba, WithAlpha};
pub use kurbo::Affine;
use kurbo::{Point, Vec2};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
}
impl Size {
pub const ZERO: Self = Self {
width: 0.0,
height: 0.0,
};
#[must_use]
pub fn new(width: f32, height: f32) -> Self {
assert!(
width.is_finite() && height.is_finite() && width >= 0.0 && height >= 0.0,
"sizes must be finite and non-negative"
);
Self { width, height }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Offset {
pub x: f32,
pub y: f32,
}
impl Offset {
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
#[must_use]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
#[must_use]
pub fn finite_or_zero(self) -> Self {
Self::new(finite_or_zero(self.x), finite_or_zero(self.y))
}
}
#[inline]
#[must_use]
pub fn finite_or_zero(value: f32) -> f32 {
if value.is_finite() { value } else { 0.0 }
}
#[inline]
#[must_use]
pub fn finite_non_negative(value: f32) -> f32 {
finite_or_zero(value).max(0.0)
}
impl Add for Offset {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl Sub for Offset {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Rect {
pub origin: Offset,
pub size: Size,
}
impl Rect {
#[must_use]
pub fn from_origin_size(origin: Offset, size: Size) -> Self {
Self { origin, size }
}
#[must_use]
pub fn contains(self, point: Offset) -> bool {
point.x >= self.origin.x
&& point.x <= self.origin.x + self.size.width
&& point.y >= self.origin.y
&& point.y <= self.origin.y + self.size.height
}
#[must_use]
pub fn intersects(self, other: Self) -> bool {
self.origin.x < other.origin.x + other.size.width
&& other.origin.x < self.origin.x + self.size.width
&& self.origin.y < other.origin.y + other.size.height
&& other.origin.y < self.origin.y + self.size.height
}
#[must_use]
pub fn intersection(self, other: Self) -> Option<Self> {
let left = self.origin.x.max(other.origin.x);
let top = self.origin.y.max(other.origin.y);
let right = (self.origin.x + self.size.width).min(other.origin.x + other.size.width);
let bottom = (self.origin.y + self.size.height).min(other.origin.y + other.size.height);
(right >= left && bottom >= top).then(|| {
Self::from_origin_size(
Offset::new(left, top),
Size::new(right - left, bottom - top),
)
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
impl Color {
pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
pub const WHITE: Self = Self::rgba(255, 255, 255, 255);
pub const BLACK: Self = Self::rgba(0, 0, 0, 255);
#[must_use]
pub const fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
Self {
red,
green,
blue,
alpha,
}
}
#[must_use]
pub fn to_linear_rgba(self) -> [f32; 4] {
let linear = Srgba::new(
self.red as f32 / 255.0,
self.green as f32 / 255.0,
self.blue as f32 / 255.0,
self.alpha as f32 / 255.0,
)
.into_linear();
[
linear.color.red,
linear.color.green,
linear.color.blue,
linear.alpha,
]
}
#[must_use]
pub fn from_linear_rgba([red, green, blue, alpha]: [f32; 4]) -> Self {
let encoded = Srgba::<u8>::from_linear(LinSrgba::new(
unit(red),
unit(green),
unit(blue),
unit(alpha),
));
Self::rgba(
encoded.color.red,
encoded.color.green,
encoded.color.blue,
encoded.alpha,
)
}
#[must_use]
pub fn to_hsl(self) -> HslColor {
HslColor::from_color(self)
}
#[must_use]
pub fn to_hsv(self) -> HsvColor {
HsvColor::from_color(self)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HslColor {
pub hue: f32,
pub saturation: f32,
pub lightness: f32,
pub alpha: f32,
}
impl HslColor {
#[must_use]
pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
Self {
hue: normalize_hue(hue),
saturation: unit(saturation),
lightness: unit(lightness),
alpha: unit(alpha),
}
}
#[must_use]
pub fn from_color(color: Color) -> Self {
let source = palette_color(color);
let hsl = Hsl::from_color(source.color);
Self::new(
hsl.hue.into_degrees(),
hsl.saturation,
hsl.lightness,
source.alpha,
)
}
#[must_use]
pub fn to_color(self) -> Color {
let hsl = Hsl::new_srgb(
normalize_hue(self.hue),
unit(self.saturation),
unit(self.lightness),
);
color_from_palette(Srgba::from_color(hsl).with_alpha(unit(self.alpha)))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HsvColor {
pub hue: f32,
pub saturation: f32,
pub value: f32,
pub alpha: f32,
}
impl HsvColor {
#[must_use]
pub fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
Self {
hue: normalize_hue(hue),
saturation: unit(saturation),
value: unit(value),
alpha: unit(alpha),
}
}
#[must_use]
pub fn from_color(color: Color) -> Self {
let source = palette_color(color);
let hsv = Hsv::from_color(source.color);
Self::new(
hsv.hue.into_degrees(),
hsv.saturation,
hsv.value,
source.alpha,
)
}
#[must_use]
pub fn to_color(self) -> Color {
let hsv = Hsv::new_srgb(
normalize_hue(self.hue),
unit(self.saturation),
unit(self.value),
);
color_from_palette(Srgba::from_color(hsv).with_alpha(unit(self.alpha)))
}
}
fn unit(value: f32) -> f32 {
if value.is_finite() {
value.clamp(0.0, 1.0)
} else {
0.0
}
}
fn normalize_hue(hue: f32) -> f32 {
if hue.is_finite() {
hue.rem_euclid(360.0)
} else {
0.0
}
}
fn palette_color(color: Color) -> Srgba {
Srgba::new(
color.red as f32 / 255.0,
color.green as f32 / 255.0,
color.blue as f32 / 255.0,
color.alpha as f32 / 255.0,
)
}
fn color_from_palette(color: Srgba) -> Color {
let byte = |value: f32| (unit(value) * 255.0).round() as u8;
Color::rgba(
byte(color.color.red),
byte(color.color.green),
byte(color.color.blue),
byte(color.alpha),
)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform(Affine);
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
impl Transform {
pub const IDENTITY: Self = Self(Affine::IDENTITY);
#[must_use]
pub fn translation(offset: Offset) -> Self {
Self(Affine::translate(Vec2::new(
f64::from(offset.x),
f64::from(offset.y),
)))
}
#[must_use]
pub const fn scale(scale: f32) -> Self {
Self(Affine::scale(scale as f64))
}
#[must_use]
pub const fn scale_non_uniform(x: f32, y: f32) -> Self {
Self(Affine::scale_non_uniform(x as f64, y as f64))
}
#[must_use]
pub fn rotation(radians: f32) -> Self {
Self(Affine::rotate(f64::from(radians)))
}
#[must_use]
pub const fn skew(x: f32, y: f32) -> Self {
Self(Affine::skew(x as f64, y as f64))
}
#[must_use]
pub const fn from_coefficients(coefficients: [f64; 6]) -> Self {
Self(Affine::new(coefficients))
}
#[must_use]
pub const fn from_kurbo(affine: Affine) -> Self {
Self(affine)
}
#[must_use]
pub const fn to_kurbo(self) -> Affine {
self.0
}
#[must_use]
pub fn then(self, other: Self) -> Self {
Self(self.0 * other.0)
}
#[must_use]
pub fn inverse(self) -> Option<Self> {
let inverse = self.0.inverse();
inverse.is_finite().then_some(Self(inverse))
}
#[must_use]
pub fn inverse_translation(self) -> Self {
self.inverse().unwrap_or(Self::IDENTITY)
}
#[must_use]
pub fn translation_offset(self) -> Offset {
let coefficients = self.0.as_coeffs();
Offset::new(coefficients[4] as f32, coefficients[5] as f32)
}
#[must_use]
pub fn is_translation(self) -> bool {
let [a, b, c, d, _, _] = self.0.as_coeffs();
a == 1. && b == 0. && c == 0. && d == 1.
}
#[must_use]
pub fn magnification_bound(self) -> f32 {
let [a, b, c, d, _, _] = self.0.as_coeffs();
let bound = a.hypot(b).hypot(c.hypot(d));
if !bound.is_finite() {
f32::INFINITY
} else if bound == 0.0 {
0.0
} else {
(bound as f32).next_up()
}
}
#[must_use]
pub fn transform_point(self, point: Offset) -> Offset {
let point = self.0 * Point::new(f64::from(point.x), f64::from(point.y));
Offset::new(point.x as f32, point.y as f32)
}
#[must_use]
pub fn inverse_transform_point(self, point: Offset) -> Option<Offset> {
self.inverse().map(|inverse| inverse.transform_point(point))
}
#[must_use]
pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
let rect = kurbo::Rect::new(
f64::from(rect.origin.x),
f64::from(rect.origin.y),
f64::from(rect.origin.x + rect.size.width),
f64::from(rect.origin.y + rect.size.height),
);
let rect = self.0.transform_rect_bbox(rect);
Rect::from_origin_size(
Offset::new(rect.x0 as f32, rect.y0 as f32),
Size::new((rect.x1 - rect.x0) as f32, (rect.y1 - rect.y0) as f32),
)
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Invalidation: u16 {
const NONE = 0;
const BUILD = 1 << 0;
const LAYOUT = 1 << 1;
const PAINT = 1 << 2;
const COMPOSITE = 1 << 3;
const SEMANTICS = 1 << 4;
const HIT_TEST = 1 << 5;
}
}
pub type DirtyFlags = Invalidation;
pub trait Lerp {
#[must_use]
fn lerp(&self, other: &Self, t: f32) -> Self;
}
impl Lerp for f32 {
fn lerp(&self, other: &Self, t: f32) -> Self {
self + (other - self) * t
}
}
impl Lerp for f64 {
fn lerp(&self, other: &Self, t: f32) -> Self {
self + (other - self) * f64::from(t)
}
}
impl Lerp for Offset {
fn lerp(&self, other: &Self, t: f32) -> Self {
Self {
x: self.x.lerp(&other.x, t),
y: self.y.lerp(&other.y, t),
}
}
}
impl Lerp for Size {
fn lerp(&self, other: &Self, t: f32) -> Self {
Self {
width: self.width.lerp(&other.width, t).max(0.0),
height: self.height.lerp(&other.height, t).max(0.0),
}
}
}
impl Lerp for Transform {
fn lerp(&self, other: &Self, t: f32) -> Self {
let t = f64::from(t);
let (begin_translation, begin_rotation, begin_scale) = decompose(self.0);
let (end_translation, end_rotation, end_scale) = decompose(other.0);
let mix = |a: f64, b: f64| a * (1.0 - t) + b * t;
let translation = [
mix(begin_translation[0], end_translation[0]),
mix(begin_translation[1], end_translation[1]),
];
let mut rotation = [0.0; 4];
for (index, value) in rotation.iter_mut().enumerate() {
*value = mix(begin_rotation[index], end_rotation[index]);
}
let length = rotation
.iter()
.map(|value| value * value)
.sum::<f64>()
.sqrt();
if length != 0.0 {
for value in &mut rotation {
*value /= length;
}
}
let scale = [
mix(begin_scale[0], end_scale[0]),
mix(begin_scale[1], end_scale[1]),
];
Self(compose(translation, rotation, scale))
}
}
fn decompose(affine: Affine) -> ([f64; 2], [f64; 4], [f64; 2]) {
let [a, b, c, d, e, f] = affine.as_coeffs();
let mut sx = a.hypot(b);
let sy = c.hypot(d);
if a * d - b * c < 0.0 {
sx = -sx;
}
let m = [
[a / sx, c / sy, 0.0],
[b / sx, d / sy, 0.0],
[0.0, 0.0, 1.0],
];
([e, f], quaternion_from_rotation(m), [sx, sy])
}
fn quaternion_from_rotation(m: [[f64; 3]; 3]) -> [f64; 4] {
let mut q = [0.0; 4];
let trace = m[0][0] + m[1][1] + m[2][2];
if trace > 0.0 {
let mut s = (trace + 1.0).sqrt();
q[3] = s * 0.5;
s = 0.5 / s;
q[0] = (m[2][1] - m[1][2]) * s;
q[1] = (m[0][2] - m[2][0]) * s;
q[2] = (m[1][0] - m[0][1]) * s;
} else {
let i = if m[0][0] < m[1][1] {
if m[1][1] < m[2][2] { 2 } else { 1 }
} else if m[0][0] < m[2][2] {
2
} else {
0
};
let j = (i + 1) % 3;
let k = (i + 2) % 3;
let mut s = (m[i][i] - m[j][j] - m[k][k] + 1.0).sqrt();
q[i] = s * 0.5;
s = 0.5 / s;
q[3] = (m[k][j] - m[j][k]) * s;
q[j] = (m[j][i] + m[i][j]) * s;
q[k] = (m[k][i] + m[i][k]) * s;
}
q
}
fn compose(translation: [f64; 2], rotation: [f64; 4], scale: [f64; 2]) -> Affine {
let [x, y, z, w] = rotation;
let (x2, y2, z2) = (x + x, y + y, z + z);
let (xx, xy) = (x * x2, x * y2);
let (yy, zz) = (y * y2, z * z2);
let wz = w * z2;
Affine::new([
(1.0 - (yy + zz)) * scale[0],
(xy + wz) * scale[0],
(xy - wz) * scale[1],
(1.0 - (xx + zz)) * scale[1],
translation[0],
translation[1],
])
}
impl Lerp for Rect {
fn lerp(&self, other: &Self, t: f32) -> Self {
Self {
origin: self.origin.lerp(&other.origin, t),
size: self.size.lerp(&other.size, t),
}
}
}
impl Lerp for Color {
fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
let mix = |a: u8, b: u8| -> u8 {
(f32::from(a) + (f32::from(b) - f32::from(a)) * t)
.round()
.clamp(0.0, 255.0) as u8
};
Self::rgba(
mix(self.red, other.red),
mix(self.green, other.green),
mix(self.blue, other.blue),
mix(self.alpha, other.alpha),
)
}
}
impl Lerp for HslColor {
fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
let mix = |a: f32, b: f32| a + (b - a) * t;
Self::new(
mix(self.hue, other.hue),
mix(self.saturation, other.saturation),
mix(self.lightness, other.lightness),
mix(self.alpha, other.alpha),
)
}
}
impl Lerp for HsvColor {
fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
let mix = |a: f32, b: f32| a + (b - a) * t;
Self::new(
mix(self.hue, other.hue),
mix(self.saturation, other.saturation),
mix(self.value, other.value),
mix(self.alpha, other.alpha),
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ChangeImpact {
#[default]
None,
Composite,
Paint,
Layout,
Build,
}